Skip to content

v0.5.0 — @dualmark/nextjs

Choose a tag to compare

@aagarwal1012 aagarwal1012 released this 07 May 11:03
· 90 commits to main since this release

New: `@dualmark/nextjs` — first-class Next.js 15 App Router adapter

Closes #4. Same one-line install as `@dualmark/astro`, with a small surface area:

  • `withDualmark(nextConfig, options)` — wraps `next.config.mjs`
  • `createDualmarkMiddleware(options)` — drop-in `middleware.ts`
  • `createDualmarkRouteHandler(options)` — catch-all markdown twin route handler with `generateStaticParams`
  • `createLlmsTxtHandler(options)` — `/llms.txt` route handler

The `collections` / `staticPages` / `parameterizedRoutes` config shape mirrors `@dualmark/astro` so users can copy their config across frameworks. All 12 built-in converters (`blog`, `case-study`, `changelog`, `compare`, `docs`, `feature`, `glossary`, `legal`, `pricing`, `pseo`, `tool`, `video`) work identically. Tree-shakeable, ESM + CJS, zero runtime deps beyond `@dualmark/core` + `@dualmark/converters`. 47 vitest tests cover config validation, middleware negotiation, route dispatch, and `generateStaticParams`.

Install

```bash
bun add @dualmark/nextjs @dualmark/core @dualmark/converters
```

Minimal setup

```ts
// middleware.ts
import { createDualmarkMiddleware } from "@dualmark/nextjs";

export default createDualmarkMiddleware({ siteUrl: "https://example.com\" });

export const config = {
matcher: [
{
source: "/((?!_next/|favicon.ico|md/).*)",
missing: [{ type: "header", key: "next-router-prefetch" }],
},
],
};
```

```ts
// app/md/[...path]/route.ts
import { createDualmarkRouteHandler } from "@dualmark/nextjs";
import { POSTS } from "@/lib/posts";

const handler = createDualmarkRouteHandler({
siteUrl: "https://example.com\",
collections: {
blog: { converter: "blog", getEntries: () => POSTS.map(toEntry) },
},
});

export const dynamic = "force-static";
export const GET = handler.GET;
export const generateStaticParams = handler.generateStaticParams;
```

That's it. Bot UAs get markdown, browsers get HTML with `Link rel="alternate"`, direct `.md` URLs serve markdown.

Migration from manual `@dualmark/core` setup

If you wired `@dualmark/core` into Next.js by hand before this release, the migration is mechanical:

Before (`@dualmark/core` only) After (`@dualmark/nextjs`)
Hand-rolled `middleware.ts` with `detectAIBot` + `negotiateFormat` + manual rewrite `createDualmarkMiddleware({ siteUrl })`
Hand-rolled `app/md/[...path]/route.ts` with `if`-chains `createDualmarkRouteHandler({ siteUrl, collections, staticPages, parameterizedRoutes })`
Hand-rolled `app/llms.txt/route.ts` calling `renderLlmsTxt` `createLlmsTxtHandler({ brandName, sections })`
Manual `transpilePackages: [...]` `withDualmark(nextConfig, options)`

The internal namespace, response headers, and conformance score are unchanged. Full guide: docs.dualmark.dev/docs/integrations/nextjs.

Reference example

`examples/nextjs-app-router` is now built on the new package — ~50 lines instead of ~120 hand-rolled, same 120/125 conformance score under both `next dev` and `next start`. The CI `nextjs-app-router` job verifies this on every PR.

Coordinated patch bumps

The linked `@dualmark/*` changeset group means every package gets a coordinated version bump:

  • `@dualmark/nextjs` 0.5.0 (new)
  • `@dualmark/core` 0.3.1 → 0.5.0
  • `@dualmark/converters` 0.3.1 → 0.5.0
  • `@dualmark/astro` 0.3.1 → 0.5.0
  • `@dualmark/cloudflare` 0.3.1 → 0.5.0
  • `@dualmark/cli` 0.3.1 → 0.5.0

No source-level changes to the other five packages — just version metadata bumps so internal `workspace:*` deps resolve cleanly when consumers install `@dualmark/nextjs`.

Verified

  • Full monorepo: 313 tests across 6 packages, build + test + typecheck green on CI
  • `examples/nextjs-app-router` E2E in CI: `next dev` + `dualmark verify` → 120/125
  • Local verification: `next dev` and `next start` both score 120/125
  • Docs site dogfood (`apps/docs`): every doc page ≥ 105/125

Full PR · Issue #4