next-md-negotiate 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kacper Siniło
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # next-md-negotiate
2
+
3
+ Content negotiation for Next.js — serve Markdown to LLMs and HTML to browsers from the same URL.
4
+
5
+ ## The problem
6
+
7
+ Your Next.js app serves HTML. LLMs, crawlers, and AI agents want Markdown. Today you're stuck choosing between:
8
+
9
+ - **Separate endpoints** (`/api/products/123.md`) — duplicates your routing, goes stale, and clients have to know about a non-standard URL scheme.
10
+ - **Markdown-only pages** — breaks the browser experience for human visitors.
11
+
12
+ The HTTP `Accept` header already solves this. A browser sends `Accept: text/html`. An LLM client sends `Accept: text/markdown`. Your server should respond accordingly — but Next.js has no built-in way to do this.
13
+
14
+ ## The solution
15
+
16
+ `next-md-negotiate` intercepts requests that ask for Markdown, rewrites them to an internal API route, and returns the Markdown version you define — all transparently, from the same URL.
17
+
18
+ ```
19
+ Browser → GET /products/42 Accept: text/html → your normal Next.js page
20
+ LLM agent → GET /products/42 Accept: text/markdown → your Markdown version
21
+ ```
22
+
23
+ No new URLs. No duplicate routing. The client just sets an `Accept` header.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install next-md-negotiate
29
+ ```
30
+
31
+ ## Quick start
32
+
33
+ ### 1. Scaffold the handler
34
+
35
+ ```bash
36
+ npx next-md-negotiate init
37
+ ```
38
+
39
+ This creates:
40
+
41
+ - **App Router:** `app/md-api/[...path]/route.ts`
42
+ - **Pages Router:** `pages/api/md-api/[...path].ts`
43
+ - **Config:** `md.config.ts`
44
+
45
+ ### 2. Define your Markdown versions
46
+
47
+ ```ts
48
+ // md.config.ts
49
+ import { createMdVersion } from 'next-md-negotiate';
50
+
51
+ export default [
52
+ createMdVersion('/products/[productId]', async ({ productId }) => {
53
+ const product = await db.products.find(productId);
54
+ return `# ${product.name}\n\nPrice: $${product.price}\n\n${product.description}`;
55
+ }),
56
+
57
+ createMdVersion('/blog/[slug]', async ({ slug }) => {
58
+ const post = await db.posts.find(slug);
59
+ return `# ${post.title}\n\n${post.content}`;
60
+ }),
61
+ ];
62
+ ```
63
+
64
+ Parameters are type-safe — `{ productId }` is inferred from the `[productId]` in the pattern.
65
+
66
+ ### 3. Add rewrites to `next.config`
67
+
68
+ This works with both App Router and Pages Router, on any supported Next.js version.
69
+
70
+ ```ts
71
+ // next.config.ts
72
+ import { createMarkdownRewrites } from 'next-md-negotiate';
73
+
74
+ export default {
75
+ async rewrites() {
76
+ return {
77
+ beforeFiles: createMarkdownRewrites({
78
+ routes: ['/products/[productId]', '/blog/[slug]'],
79
+ }),
80
+ };
81
+ },
82
+ };
83
+ ```
84
+
85
+ That's it. Requests with `Accept: text/markdown` get your Markdown. Everything else is untouched.
86
+
87
+ ## How it works
88
+
89
+ ```
90
+ Accept: text/markdown?
91
+
92
+ ┌─────────┴─────────┐
93
+ │ yes │ no
94
+ ▼ ▼
95
+ Route matches? Normal Next.js
96
+ │ page renders
97
+ ┌──────┴──────┐
98
+ │ yes │ no
99
+ ▼ ▼
100
+ Rewrite to Pass through
101
+ /md-api/...
102
+
103
+
104
+ Catch-all handler
105
+ runs your function
106
+
107
+
108
+ 200 text/markdown
109
+ ```
110
+
111
+ 1. The rewrite/middleware/proxy layer checks the `Accept` header for `text/markdown`, `application/markdown`, or `text/x-markdown`.
112
+ 2. If the request matches a configured route, it internally rewrites to `/md-api/...`.
113
+ 3. The catch-all route handler matches the path against your registry and calls your function.
114
+ 4. Your function returns a Markdown string. The handler sends it back as `text/markdown; charset=utf-8`.
115
+
116
+ ## Alternative: middleware or proxy
117
+
118
+ The `next.config` rewrites approach covers most use cases. If you need content negotiation to live in your request-handling layer instead — for example, you already have a `middleware.ts` handling auth/i18n/redirects, or you're on Next.js 16+ using `proxy.ts` — use `createMarkdownNegotiator`:
119
+
120
+ ```ts
121
+ // middleware.ts or proxy.ts
122
+ import { createMarkdownNegotiator } from 'next-md-negotiate';
123
+
124
+ const md = createMarkdownNegotiator({
125
+ routes: ['/products/[productId]', '/blog/[slug]'],
126
+ });
127
+
128
+ export function middleware(request: Request) {
129
+ // Check markdown negotiation first
130
+ const mdResponse = md(request);
131
+ if (mdResponse) return mdResponse;
132
+
133
+ // ...your other middleware logic (auth, i18n, etc.)
134
+ }
135
+ ```
136
+
137
+ ### When to use what
138
+
139
+ | Method | Best for |
140
+ |---|---|
141
+ | **`next.config` rewrites** | Most projects. Zero runtime overhead — Next.js handles the routing natively. Works with both App Router and Pages Router. |
142
+ | **`middleware.ts` / `proxy.ts`** | Projects that already have a middleware or proxy and want all request interception in one place. |
143
+
144
+ ## Testing it
145
+
146
+ ```bash
147
+ # Normal HTML response
148
+ curl http://localhost:3000/products/42
149
+
150
+ # Markdown response
151
+ curl -H "Accept: text/markdown" http://localhost:3000/products/42
152
+ ```
153
+
154
+ ## API
155
+
156
+ ### `createMdVersion(pattern, handler)`
157
+
158
+ Defines a Markdown version for a route.
159
+
160
+ ```ts
161
+ createMdVersion('/products/[productId]', async ({ productId }) => {
162
+ return `# Product ${productId}`;
163
+ });
164
+ ```
165
+
166
+ **Supported patterns:**
167
+ - Named params: `/products/[productId]`
168
+ - Catch-all params: `/docs/[...slug]`
169
+ - Multiple params: `/[org]/[repo]`
170
+ - Static routes: `/about`
171
+
172
+ ### `createMdHandler(registry)`
173
+
174
+ Creates an App Router handler for the catch-all route. Assign it to `GET`.
175
+
176
+ ```ts
177
+ export const GET = createMdHandler(registry);
178
+ ```
179
+
180
+ ### `createMdApiHandler(registry)`
181
+
182
+ Creates a Pages Router API handler for the catch-all route.
183
+
184
+ ```ts
185
+ export default createMdApiHandler(registry);
186
+ ```
187
+
188
+ ### `createMarkdownRewrites(options)`
189
+
190
+ Generates rewrite rules for `next.config.js`. The recommended approach for most projects.
191
+
192
+ ### `createMarkdownNegotiator(options)`
193
+
194
+ Creates a handler for `middleware.ts` or `proxy.ts`. Returns a `Response` for markdown requests, or `undefined` to pass through.
195
+
196
+ **Options (shared by both):**
197
+
198
+ | Option | Type | Default | Description |
199
+ |---|---|---|---|
200
+ | `routes` | `string[]` | required | Route patterns to negotiate |
201
+ | `internalPrefix` | `string` | `'/md-api'` | Internal rewrite destination prefix |
202
+
203
+ ## License
204
+
205
+ MIT
package/dist/cli.js CHANGED
@@ -86,67 +86,22 @@ function main() {
86
86
  console.error(`Error: Failed to write files \u2014 ${message}`);
87
87
  process.exit(1);
88
88
  }
89
- let nextMajor = null;
90
- if (existsSync(pkgPath)) {
91
- try {
92
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
93
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
94
- const nextVersion = deps["next"];
95
- if (nextVersion) {
96
- const match = nextVersion.match(/(\d+)/);
97
- if (match) nextMajor = parseInt(match[1], 10);
98
- }
99
- } catch {
100
- }
101
- }
102
- console.log("\nNext step \u2014 add content negotiation:\n");
103
- if (nextMajor !== null && nextMajor >= 16) {
104
- console.log(` Your project uses Next.js ${nextMajor}, so add a proxy.ts in your project root:
105
- `);
106
- console.log(" // proxy.ts");
107
- console.log(" import { createMarkdownProxy } from 'next-md-negotiate';");
108
- console.log("");
109
- console.log(" const md = createMarkdownProxy({");
110
- console.log(" routes: ['/products/[productId]', '/blog/[slug]'],");
111
- console.log(" });");
112
- console.log("");
113
- console.log(" export function proxy(request: Request) {");
114
- console.log(" return md(request);");
115
- console.log(" }");
116
- } else if (nextMajor !== null && nextMajor >= 14 && hasAppDir) {
117
- console.log(` Your project uses Next.js ${nextMajor} with App Router, so add a middleware.ts in your project root:
118
- `);
119
- console.log(" // middleware.ts");
120
- console.log(" import { createMarkdownMiddleware } from 'next-md-negotiate';");
121
- console.log("");
122
- console.log(" const md = createMarkdownMiddleware({");
123
- console.log(" routes: ['/products/[productId]', '/blog/[slug]'],");
124
- console.log(" });");
125
- console.log("");
126
- console.log(" export function middleware(request: Request) {");
127
- console.log(" return md(request);");
128
- console.log(" }");
129
- } else if (nextMajor !== null && hasPagesDir && !hasAppDir) {
130
- console.log(` Your project uses Next.js ${nextMajor} with Pages Router, so add rewrites to next.config.js:
131
- `);
132
- console.log(" // next.config.js");
133
- console.log(" import { createMarkdownRewrites } from 'next-md-negotiate';");
134
- console.log("");
135
- console.log(" export default {");
136
- console.log(" async rewrites() {");
137
- console.log(" return {");
138
- console.log(" beforeFiles: createMarkdownRewrites({");
139
- console.log(" routes: ['/products/[productId]', '/blog/[slug]'],");
140
- console.log(" }),");
141
- console.log(" };");
142
- console.log(" },");
143
- console.log(" };");
144
- } else {
145
- console.log(" Choose the integration method that fits your setup:\n");
146
- console.log(" Next.js 16+ \u2192 proxy.ts with createMarkdownProxy");
147
- console.log(" Next.js 14-15 (App Router) \u2192 middleware.ts with createMarkdownMiddleware");
148
- console.log(" Pages Router \u2192 next.config.js with createMarkdownRewrites");
149
- }
150
- console.log("\n Then define your routes in md.config.ts");
89
+ console.log("\nNext step \u2014 add rewrites to next.config:\n");
90
+ console.log(" // next.config.ts");
91
+ console.log(" import { createMarkdownRewrites } from 'next-md-negotiate';");
92
+ console.log("");
93
+ console.log(" export default {");
94
+ console.log(" async rewrites() {");
95
+ console.log(" return {");
96
+ console.log(" beforeFiles: createMarkdownRewrites({");
97
+ console.log(" routes: ['/products/[productId]', '/blog/[slug]'],");
98
+ console.log(" }),");
99
+ console.log(" };");
100
+ console.log(" },");
101
+ console.log(" };");
102
+ console.log("");
103
+ console.log(" Then define your routes in md.config.ts");
104
+ console.log("");
105
+ console.log(" Alternative: use createMarkdownNegotiator in middleware.ts or proxy.ts");
151
106
  }
152
107
  main();
package/dist/index.cjs CHANGED
@@ -20,8 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- createMarkdownMiddleware: () => createMarkdownMiddleware,
24
- createMarkdownProxy: () => createMarkdownProxy,
23
+ createMarkdownNegotiator: () => createMarkdownNegotiator,
25
24
  createMarkdownRewrites: () => createMarkdownRewrites,
26
25
  createMdApiHandler: () => createMdApiHandler,
27
26
  createMdHandler: () => createMdHandler,
@@ -171,8 +170,8 @@ function negotiateRequest(request, options) {
171
170
  return null;
172
171
  }
173
172
 
174
- // src/createRewriteHandler.ts
175
- function createRewriteHandler(options) {
173
+ // src/createMarkdownNegotiator.ts
174
+ function createMarkdownNegotiator(options) {
176
175
  for (const route of options.routes) {
177
176
  validatePattern(route);
178
177
  }
@@ -186,20 +185,9 @@ function createRewriteHandler(options) {
186
185
  });
187
186
  };
188
187
  }
189
-
190
- // src/createMarkdownProxy.ts
191
- function createMarkdownProxy(options) {
192
- return createRewriteHandler(options);
193
- }
194
-
195
- // src/createMarkdownMiddleware.ts
196
- function createMarkdownMiddleware(options) {
197
- return createRewriteHandler(options);
198
- }
199
188
  // Annotate the CommonJS export names for ESM import in node:
200
189
  0 && (module.exports = {
201
- createMarkdownMiddleware,
202
- createMarkdownProxy,
190
+ createMarkdownNegotiator,
203
191
  createMarkdownRewrites,
204
192
  createMdApiHandler,
205
193
  createMdHandler,
package/dist/index.d.cts CHANGED
@@ -104,43 +104,24 @@ interface NegotiateOptions {
104
104
  }
105
105
 
106
106
  /**
107
- * Creates a proxy handler for Next.js 16+ `proxy.ts`.
108
- *
109
- * Returns a rewrite response for markdown requests matching
110
- * configured routes, or `undefined` to pass through.
111
- *
112
- * @example
113
- * // proxy.ts
114
- * import { createMarkdownProxy } from 'next-md-negotiate';
115
- *
116
- * const markdownProxy = createMarkdownProxy({
117
- * routes: ['/products/[productId]', '/blog/[slug]'],
118
- * });
119
- *
120
- * export function proxy(request: Request) {
121
- * return markdownProxy(request);
122
- * }
123
- */
124
- declare function createMarkdownProxy(options: NegotiateOptions): (request: Request) => Response | undefined;
125
-
126
- /**
127
- * Creates a middleware handler for Next.js 14-15 `middleware.ts`.
107
+ * Creates a handler that negotiates markdown content.
128
108
  *
109
+ * Use this in `middleware.ts` (Next.js 14–15) or `proxy.ts` (Next.js 16+).
129
110
  * Returns a rewrite response for markdown requests matching configured
130
111
  * routes, or `undefined` to pass through.
131
112
  *
132
113
  * @example
133
114
  * // middleware.ts
134
- * import { createMarkdownMiddleware } from 'next-md-negotiate';
115
+ * import { createMarkdownNegotiator } from 'next-md-negotiate';
135
116
  *
136
- * const markdownMiddleware = createMarkdownMiddleware({
117
+ * const md = createMarkdownNegotiator({
137
118
  * routes: ['/products/[productId]', '/blog/[slug]'],
138
119
  * });
139
120
  *
140
121
  * export function middleware(request: Request) {
141
- * return markdownMiddleware(request);
122
+ * return md(request);
142
123
  * }
143
124
  */
144
- declare function createMarkdownMiddleware(options: NegotiateOptions): (request: Request) => Response | undefined;
125
+ declare function createMarkdownNegotiator(options: NegotiateOptions): (request: Request) => Response | undefined;
145
126
 
146
- export { type ExtractParams, type MarkdownRewriteOptions, type MdVersionHandler, type NegotiateOptions, createMarkdownMiddleware, createMarkdownProxy, createMarkdownRewrites, createMdApiHandler, createMdHandler, createMdVersion };
127
+ export { type ExtractParams, type MarkdownRewriteOptions, type MdVersionHandler, type NegotiateOptions, createMarkdownNegotiator, createMarkdownRewrites, createMdApiHandler, createMdHandler, createMdVersion };
package/dist/index.d.ts CHANGED
@@ -104,43 +104,24 @@ interface NegotiateOptions {
104
104
  }
105
105
 
106
106
  /**
107
- * Creates a proxy handler for Next.js 16+ `proxy.ts`.
108
- *
109
- * Returns a rewrite response for markdown requests matching
110
- * configured routes, or `undefined` to pass through.
111
- *
112
- * @example
113
- * // proxy.ts
114
- * import { createMarkdownProxy } from 'next-md-negotiate';
115
- *
116
- * const markdownProxy = createMarkdownProxy({
117
- * routes: ['/products/[productId]', '/blog/[slug]'],
118
- * });
119
- *
120
- * export function proxy(request: Request) {
121
- * return markdownProxy(request);
122
- * }
123
- */
124
- declare function createMarkdownProxy(options: NegotiateOptions): (request: Request) => Response | undefined;
125
-
126
- /**
127
- * Creates a middleware handler for Next.js 14-15 `middleware.ts`.
107
+ * Creates a handler that negotiates markdown content.
128
108
  *
109
+ * Use this in `middleware.ts` (Next.js 14–15) or `proxy.ts` (Next.js 16+).
129
110
  * Returns a rewrite response for markdown requests matching configured
130
111
  * routes, or `undefined` to pass through.
131
112
  *
132
113
  * @example
133
114
  * // middleware.ts
134
- * import { createMarkdownMiddleware } from 'next-md-negotiate';
115
+ * import { createMarkdownNegotiator } from 'next-md-negotiate';
135
116
  *
136
- * const markdownMiddleware = createMarkdownMiddleware({
117
+ * const md = createMarkdownNegotiator({
137
118
  * routes: ['/products/[productId]', '/blog/[slug]'],
138
119
  * });
139
120
  *
140
121
  * export function middleware(request: Request) {
141
- * return markdownMiddleware(request);
122
+ * return md(request);
142
123
  * }
143
124
  */
144
- declare function createMarkdownMiddleware(options: NegotiateOptions): (request: Request) => Response | undefined;
125
+ declare function createMarkdownNegotiator(options: NegotiateOptions): (request: Request) => Response | undefined;
145
126
 
146
- export { type ExtractParams, type MarkdownRewriteOptions, type MdVersionHandler, type NegotiateOptions, createMarkdownMiddleware, createMarkdownProxy, createMarkdownRewrites, createMdApiHandler, createMdHandler, createMdVersion };
127
+ export { type ExtractParams, type MarkdownRewriteOptions, type MdVersionHandler, type NegotiateOptions, createMarkdownNegotiator, createMarkdownRewrites, createMdApiHandler, createMdHandler, createMdVersion };
package/dist/index.js CHANGED
@@ -140,8 +140,8 @@ function negotiateRequest(request, options) {
140
140
  return null;
141
141
  }
142
142
 
143
- // src/createRewriteHandler.ts
144
- function createRewriteHandler(options) {
143
+ // src/createMarkdownNegotiator.ts
144
+ function createMarkdownNegotiator(options) {
145
145
  for (const route of options.routes) {
146
146
  validatePattern(route);
147
147
  }
@@ -155,19 +155,8 @@ function createRewriteHandler(options) {
155
155
  });
156
156
  };
157
157
  }
158
-
159
- // src/createMarkdownProxy.ts
160
- function createMarkdownProxy(options) {
161
- return createRewriteHandler(options);
162
- }
163
-
164
- // src/createMarkdownMiddleware.ts
165
- function createMarkdownMiddleware(options) {
166
- return createRewriteHandler(options);
167
- }
168
158
  export {
169
- createMarkdownMiddleware,
170
- createMarkdownProxy,
159
+ createMarkdownNegotiator,
171
160
  createMarkdownRewrites,
172
161
  createMdApiHandler,
173
162
  createMdHandler,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-md-negotiate",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Content negotiation for Next.js - serve Markdown to LLMs, HTML to browsers, from a single URL",
5
5
  "type": "module",
6
6
  "exports": {