@fullstackdatasolutions/articles 0.8.0 → 0.8.2
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/README.md +211 -11
- package/dist/nextjs-middleware.cjs +61 -0
- package/dist/nextjs-middleware.cjs.map +1 -0
- package/dist/nextjs-middleware.d.cts +25 -0
- package/dist/nextjs-middleware.d.ts +25 -0
- package/dist/nextjs-middleware.js +33 -0
- package/dist/nextjs-middleware.js.map +1 -0
- package/dist/nextjs.cjs +652 -0
- package/dist/nextjs.cjs.map +1 -0
- package/dist/nextjs.d.cts +112 -0
- package/dist/nextjs.d.ts +112 -0
- package/dist/nextjs.js +618 -0
- package/dist/nextjs.js.map +1 -0
- package/package.json +26 -3
- package/src/__tests__/nextjs-middleware.test.ts +183 -0
- package/src/__tests__/nextjs.test.ts +137 -0
- package/src/nextjs-middleware.ts +48 -0
- package/src/nextjs.ts +27 -0
package/README.md
CHANGED
|
@@ -175,7 +175,57 @@ export default async function Page({ params }: CategoryPageProps) {
|
|
|
175
175
|
}
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
### 6.
|
|
178
|
+
### 6. Set up markdown handler (Next.js 16+)
|
|
179
|
+
|
|
180
|
+
Create an API route to serve article markdown at `/api/articles-markdown/[...slug]`:
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
// app/api/articles-markdown/[...slug]/route.ts
|
|
184
|
+
import { createArticleMarkdownHandler } from '@fullstackdatasolutions/articles/nextjs'
|
|
185
|
+
import { siteConfig } from '@/config/articles'
|
|
186
|
+
|
|
187
|
+
export const { GET } = createArticleMarkdownHandler(siteConfig)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Then wire the markdown rewrite into your proxy.ts (Next.js 16+ edge proxy):
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
// proxy.ts
|
|
194
|
+
import { NextResponse, NextRequest } from 'next/server'
|
|
195
|
+
import { rewriteArticleMarkdown } from '@fullstackdatasolutions/articles/middleware'
|
|
196
|
+
|
|
197
|
+
export async function proxy(request: NextRequest) {
|
|
198
|
+
const mdRewrite = rewriteArticleMarkdown(request)
|
|
199
|
+
if (mdRewrite) return mdRewrite
|
|
200
|
+
|
|
201
|
+
return NextResponse.next()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const config = {
|
|
205
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This pattern rewrites requests from `/articles/slug.md` to `/api/articles-markdown/slug` where the handler serves the markdown. See the AI markdown twins section below for details on how to expose markdown and configure AI crawling rules.
|
|
210
|
+
|
|
211
|
+
For **Next.js 14/15**, use the middleware.ts pattern below instead.
|
|
212
|
+
|
|
213
|
+
### 6b. Set up middleware (Next.js 14/15 only)
|
|
214
|
+
|
|
215
|
+
If using Next.js 14 or 15 and cannot use a proxy, create middleware.ts to rewrite markdown requests:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
// middleware.ts
|
|
219
|
+
import { NextRequest } from 'next/server'
|
|
220
|
+
import { middleware, config } from '@fullstackdatasolutions/articles/middleware'
|
|
221
|
+
|
|
222
|
+
export { config }
|
|
223
|
+
export default middleware
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
This pattern does NOT work in Next.js 16 (use the proxy.ts pattern instead). You still need the handler route above to actually serve the markdown.
|
|
227
|
+
|
|
228
|
+
### 6c. Copy the comments API routes
|
|
179
229
|
|
|
180
230
|
These cannot live inside the package — Next.js file-based routing requires them in your app. Copy these files from the reference implementation:
|
|
181
231
|
|
|
@@ -207,6 +257,87 @@ Article directories can be nested to any depth below `public/articles`. For exam
|
|
|
207
257
|
|
|
208
258
|
---
|
|
209
259
|
|
|
260
|
+
## Markdown handler exports
|
|
261
|
+
|
|
262
|
+
### `./nextjs` — Article markdown handler factory
|
|
263
|
+
|
|
264
|
+
Import from `@fullstackdatasolutions/articles/nextjs`. Node.js runtime only (use in API routes, not Edge middleware).
|
|
265
|
+
|
|
266
|
+
#### `createArticleMarkdownHandler(config)`
|
|
267
|
+
|
|
268
|
+
Factory function that returns a `{ GET }` handler for serving article markdown.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { createArticleMarkdownHandler } from '@fullstackdatasolutions/articles/nextjs'
|
|
272
|
+
import { siteConfig } from '@/config/articles'
|
|
273
|
+
|
|
274
|
+
export const { GET } = createArticleMarkdownHandler(siteConfig)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
- **config** - `ArticlesConfig` instance (required). Must include `siteUrl` and `siteName` at minimum.
|
|
278
|
+
- **returns** - Object with `GET` async function matching Next.js Route Handler signature.
|
|
279
|
+
- The handler extracts slug from `params['slug']` and strips `.md` extension if present.
|
|
280
|
+
- Uses `getArticleMarkdownResponse` from the server utilities to fetch and return markdown.
|
|
281
|
+
- Only returns markdown for articles with `aiCrawl: true` in frontmatter (returns 404 otherwise).
|
|
282
|
+
|
|
283
|
+
### `./middleware` — Article markdown rewrite helpers
|
|
284
|
+
|
|
285
|
+
Import from `@fullstackdatasolutions/articles/middleware`. Edge runtime safe (no Node.js built-ins).
|
|
286
|
+
|
|
287
|
+
#### `rewriteArticleMarkdown(request, apiBase?)`
|
|
288
|
+
|
|
289
|
+
Rewrites `/articles/*.md` requests to your markdown handler route.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { rewriteArticleMarkdown } from '@fullstackdatasolutions/articles/middleware'
|
|
293
|
+
|
|
294
|
+
export async function proxy(request: NextRequest) {
|
|
295
|
+
const mdRewrite = rewriteArticleMarkdown(request)
|
|
296
|
+
if (mdRewrite) return mdRewrite
|
|
297
|
+
|
|
298
|
+
return NextResponse.next()
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
- **request** - `NextRequest` object (required).
|
|
303
|
+
- **apiBase** - Base path to your markdown handler. Default: `'/api/articles-markdown'`
|
|
304
|
+
- **returns** - `NextResponse` if request matches `/articles/...md`, `undefined` otherwise.
|
|
305
|
+
- Intended use: call in proxy.ts (Next.js 16+) or middleware.ts. If result is truthy, return it; otherwise continue processing.
|
|
306
|
+
- Example: request to `/articles/my-post.md` rewrites to `/api/articles-markdown/my-post`.
|
|
307
|
+
|
|
308
|
+
#### `createArticleMarkdownMiddleware(apiBase?)`
|
|
309
|
+
|
|
310
|
+
Factory for building custom middleware/proxy with a custom API base path.
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import { createArticleMarkdownMiddleware } from '@fullstackdatasolutions/articles/middleware'
|
|
314
|
+
|
|
315
|
+
const { middleware, config } = createArticleMarkdownMiddleware('/api/my-custom-path')
|
|
316
|
+
|
|
317
|
+
export { config }
|
|
318
|
+
export default middleware
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
- **apiBase** - Base path to your markdown handler. Default: `'/api/articles-markdown'`
|
|
322
|
+
- **returns** - Object with `{ middleware, config }` ready to export from middleware.ts.
|
|
323
|
+
- `config.matcher` automatically targets `/articles/:path*.md`.
|
|
324
|
+
|
|
325
|
+
#### `middleware` (default export)
|
|
326
|
+
|
|
327
|
+
Pre-built middleware using the default API base (`'/api/articles-markdown'`).
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
// middleware.ts
|
|
331
|
+
import { middleware, config } from '@fullstackdatasolutions/articles/middleware'
|
|
332
|
+
|
|
333
|
+
export { config }
|
|
334
|
+
export default middleware
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Use this if you don't need a custom API base path. For custom paths, use `createArticleMarkdownMiddleware()` instead.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
210
341
|
## `ArticlesConfig` reference
|
|
211
342
|
|
|
212
343
|
| Field | Type | Default | Description |
|
|
@@ -548,24 +679,82 @@ aiCrawl: true
|
|
|
548
679
|
Markdown body...
|
|
549
680
|
```
|
|
550
681
|
|
|
551
|
-
|
|
682
|
+
To expose markdown at public URLs like `/articles/my-post.md`, use the markdown handler factories above:
|
|
683
|
+
|
|
684
|
+
- **Next.js 16+** — Use `createArticleMarkdownHandler()` + `rewriteArticleMarkdown()` in proxy.ts (see Step 6 above).
|
|
685
|
+
- **Next.js 14/15** — Use `createArticleMarkdownHandler()` + middleware.ts with `middleware` export from the package (see Step 6b above).
|
|
686
|
+
|
|
687
|
+
For static generation, call `getArticleMarkdown(slug)` during your build and write the returned content beside your generated HTML; it returns `null` unless `aiCrawl: true`.
|
|
688
|
+
|
|
689
|
+
**robots.txt - block AI crawlers from non-aiCrawl articles**
|
|
690
|
+
|
|
691
|
+
Wire `getAiRobotsTxtRules()` into your robots.txt route. It returns disallow rules for known AI crawler agents (GPTBot, Claude-Web, etc.) targeting any article that does not have `aiCrawl: true`. Without this step, AI crawlers can still reach all articles regardless of the frontmatter flag.
|
|
552
692
|
|
|
553
693
|
```ts
|
|
554
|
-
// app/
|
|
555
|
-
import {
|
|
556
|
-
import {
|
|
694
|
+
// app/robots.txt/route.ts
|
|
695
|
+
import { NextResponse } from 'next/server'
|
|
696
|
+
import { getAiRobotsTxtRules } from '@fullstackdatasolutions/articles/server'
|
|
557
697
|
|
|
558
|
-
|
|
698
|
+
export async function GET() {
|
|
699
|
+
const siteUrl = 'https://yoursite.com'
|
|
700
|
+
const aiRules = await getAiRobotsTxtRules()
|
|
559
701
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
702
|
+
const robotsTxt = `User-agent: *
|
|
703
|
+
Allow: /
|
|
704
|
+
|
|
705
|
+
Sitemap: ${siteUrl}/sitemap.xml${aiRules ? `\n\n${aiRules}` : ''}`
|
|
706
|
+
|
|
707
|
+
return new NextResponse(robotsTxt, {
|
|
708
|
+
headers: {
|
|
709
|
+
'Content-Type': 'text/plain',
|
|
710
|
+
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
|
|
711
|
+
},
|
|
712
|
+
})
|
|
563
713
|
}
|
|
564
714
|
```
|
|
565
715
|
|
|
566
|
-
|
|
716
|
+
`getAiRobotsTxtRules()` returns a string of `User-agent` / `Disallow` blocks, one per known AI crawler, or `null` if every article is opted in. The template literal appends it only when non-null so no blank lines appear when it is unused.
|
|
717
|
+
|
|
718
|
+
**article page metadata - emit alternate link and noai directive**
|
|
567
719
|
|
|
568
|
-
|
|
720
|
+
`generateArticleMetadata` handles the `text/markdown` alternate link automatically when `aiCrawl: true`. If you override `generateMetadata` and build the metadata object yourself, cast the return value as `Metadata` before spreading so TypeScript accepts it:
|
|
721
|
+
|
|
722
|
+
```ts
|
|
723
|
+
// app/articles/[...slug]/page.tsx
|
|
724
|
+
import type { Metadata } from 'next'
|
|
725
|
+
import {
|
|
726
|
+
generateArticleMetadata,
|
|
727
|
+
getArticleMetadata,
|
|
728
|
+
getArticleMarkdownUrl,
|
|
729
|
+
} from '@fullstackdatasolutions/articles/server'
|
|
730
|
+
import { siteConfig } from '@/config/articles'
|
|
731
|
+
|
|
732
|
+
export async function generateMetadata({ params }): Promise<Metadata> {
|
|
733
|
+
const slug = (await params).slug.join('/')
|
|
734
|
+
const base = (await generateArticleMetadata(slug, siteConfig))
|
|
735
|
+
const article = await getArticleMetadata(slug)
|
|
736
|
+
if (!article) return base
|
|
737
|
+
|
|
738
|
+
const markdownUrl = getArticleMarkdownUrl(article, siteConfig)
|
|
739
|
+
if (markdownUrl) {
|
|
740
|
+
return {
|
|
741
|
+
...base,
|
|
742
|
+
alternates: {
|
|
743
|
+
...(base.alternates as Record<string, unknown>),
|
|
744
|
+
types: { 'text/markdown': markdownUrl },
|
|
745
|
+
},
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
...base,
|
|
750
|
+
robots: 'index, follow, noai, noimageai',
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
Do **not** use `headers()` from `next/headers` to set response headers in a Server Component page - it is read-only in the App Router (reads request headers). Use `generateMetadata` instead.
|
|
756
|
+
|
|
757
|
+
Use `getArticleAiHeaders(article, siteConfig)` only in a Route Handler (not a page), where you control the `Response` object directly. Opted-in articles receive a markdown `Link` header; all others receive `X-Robots-Tag: noai, noimageai`.
|
|
569
758
|
|
|
570
759
|
### Sitemaps
|
|
571
760
|
|
|
@@ -739,6 +928,7 @@ For sitemaps and metadata: `import { getArticleSitemapEntries, generateArticlesI
|
|
|
739
928
|
- `hero.title` and `hero.description` in `ArticlesConfig` control the hero heading — pass them to customize per app.
|
|
740
929
|
- `showToc?: boolean` (default `true`) controls TOC visibility on all article detail pages.
|
|
741
930
|
- `description?: string` sets the RSS feed channel description (falls back to `siteName`).
|
|
931
|
+
- `aiCrawl: true` in article frontmatter opts that article into AI indexing. Wire up three things: (1) `getAiRobotsTxtRules()` in `app/robots.txt/route.ts` appended to the robots.txt string; (2) a `app/articles/[slug].md/route.ts` Route Handler calling `getArticleMarkdownResponse`; (3) `generateMetadata` in the article page casting the base metadata as `Metadata` and spreading it before adding `alternates.types` or `robots`. Do NOT use `next/headers` `headers()` to set response headers in a page - it is read-only.
|
|
742
932
|
- Full library docs: `node_modules/@fullstackdatasolutions/articles/README.md`
|
|
743
933
|
```
|
|
744
934
|
|
|
@@ -757,6 +947,16 @@ GitHub Actions runs typecheck → test → build → `npm publish --access publi
|
|
|
757
947
|
|
|
758
948
|
---
|
|
759
949
|
|
|
950
|
+
## Examples
|
|
951
|
+
|
|
952
|
+
See this package in use at:
|
|
953
|
+
|
|
954
|
+
- [voxpopulus.us/articles](https://voxpopulus.us/articles/)
|
|
955
|
+
- [loopvolt.io/articles](https://loopvolt.io/articles)
|
|
956
|
+
- [theroleplayersguild.com/articles](https://theroleplayersguild.com/articles)
|
|
957
|
+
|
|
958
|
+
---
|
|
959
|
+
|
|
760
960
|
## Upgrading
|
|
761
961
|
|
|
762
962
|
Follow semver. Breaking changes (major) include a migration note in the changelog.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/nextjs-middleware.ts
|
|
21
|
+
var nextjs_middleware_exports = {};
|
|
22
|
+
__export(nextjs_middleware_exports, {
|
|
23
|
+
config: () => config,
|
|
24
|
+
createArticleMarkdownMiddleware: () => createArticleMarkdownMiddleware,
|
|
25
|
+
middleware: () => middleware,
|
|
26
|
+
rewriteArticleMarkdown: () => rewriteArticleMarkdown
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(nextjs_middleware_exports);
|
|
29
|
+
var import_server = require("next/server");
|
|
30
|
+
var DEFAULT_API_BASE = "/api/articles-markdown";
|
|
31
|
+
function rewriteArticleMarkdown(request, apiBase = DEFAULT_API_BASE) {
|
|
32
|
+
const { pathname } = request.nextUrl;
|
|
33
|
+
if (pathname.startsWith("/articles/") && pathname.endsWith(".md")) {
|
|
34
|
+
const slug = pathname.slice("/articles/".length);
|
|
35
|
+
return import_server.NextResponse.rewrite(new URL(`${apiBase}/${slug}`, request.url));
|
|
36
|
+
}
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
function buildMiddleware(apiBase) {
|
|
40
|
+
return function middleware2(request) {
|
|
41
|
+
return rewriteArticleMarkdown(request, apiBase);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
var middleware = buildMiddleware(DEFAULT_API_BASE);
|
|
45
|
+
var config = {
|
|
46
|
+
matcher: "/articles/:path*.md"
|
|
47
|
+
};
|
|
48
|
+
function createArticleMarkdownMiddleware(apiBase = DEFAULT_API_BASE) {
|
|
49
|
+
return {
|
|
50
|
+
middleware: buildMiddleware(apiBase),
|
|
51
|
+
config: { matcher: "/articles/:path*.md" }
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
55
|
+
0 && (module.exports = {
|
|
56
|
+
config,
|
|
57
|
+
createArticleMarkdownMiddleware,
|
|
58
|
+
middleware,
|
|
59
|
+
rewriteArticleMarkdown
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=nextjs-middleware.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/nextjs-middleware.ts"],"sourcesContent":["// Next.js proxy/middleware helper — Edge runtime safe (no Node.js built-ins)\n// Import from '@fullstackdatasolutions/articles/middleware'\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nconst DEFAULT_API_BASE = '/api/articles-markdown'\n\n/**\n * Rewrite /articles/*.md requests to the markdown API route.\n * Returns a NextResponse rewrite if the request matches, undefined otherwise.\n *\n * Usage in proxy.ts (Next.js 16+):\n * import { rewriteArticleMarkdown } from '@fullstackdatasolutions/articles/middleware'\n * export async function proxy(request: NextRequest) {\n * return rewriteArticleMarkdown(request) ?? NextResponse.next()\n * }\n */\nexport function rewriteArticleMarkdown(\n request: NextRequest,\n apiBase = DEFAULT_API_BASE\n): NextResponse | undefined {\n const { pathname } = request.nextUrl\n if (pathname.startsWith('/articles/') && pathname.endsWith('.md')) {\n const slug = pathname.slice('/articles/'.length)\n return NextResponse.rewrite(new URL(`${apiBase}/${slug}`, request.url))\n }\n return undefined\n}\n\n// Legacy middleware.ts usage (Next.js 14/15)\nfunction buildMiddleware(apiBase: string) {\n return function middleware(request: NextRequest) {\n return rewriteArticleMarkdown(request, apiBase)\n }\n}\n\nexport const middleware = buildMiddleware(DEFAULT_API_BASE)\n\nexport const config = {\n matcher: '/articles/:path*.md',\n}\n\nexport function createArticleMarkdownMiddleware(apiBase = DEFAULT_API_BASE) {\n return {\n middleware: buildMiddleware(apiBase),\n config: { matcher: '/articles/:path*.md' },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,oBAA6B;AAG7B,IAAM,mBAAmB;AAYlB,SAAS,uBACd,SACA,UAAU,kBACgB;AAC1B,QAAM,EAAE,SAAS,IAAI,QAAQ;AAC7B,MAAI,SAAS,WAAW,YAAY,KAAK,SAAS,SAAS,KAAK,GAAG;AACjE,UAAM,OAAO,SAAS,MAAM,aAAa,MAAM;AAC/C,WAAO,2BAAa,QAAQ,IAAI,IAAI,GAAG,OAAO,IAAI,IAAI,IAAI,QAAQ,GAAG,CAAC;AAAA,EACxE;AACA,SAAO;AACT;AAGA,SAAS,gBAAgB,SAAiB;AACxC,SAAO,SAASA,YAAW,SAAsB;AAC/C,WAAO,uBAAuB,SAAS,OAAO;AAAA,EAChD;AACF;AAEO,IAAM,aAAa,gBAAgB,gBAAgB;AAEnD,IAAM,SAAS;AAAA,EACpB,SAAS;AACX;AAEO,SAAS,gCAAgC,UAAU,kBAAkB;AAC1E,SAAO;AAAA,IACL,YAAY,gBAAgB,OAAO;AAAA,IACnC,QAAQ,EAAE,SAAS,sBAAsB;AAAA,EAC3C;AACF;","names":["middleware"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rewrite /articles/*.md requests to the markdown API route.
|
|
5
|
+
* Returns a NextResponse rewrite if the request matches, undefined otherwise.
|
|
6
|
+
*
|
|
7
|
+
* Usage in proxy.ts (Next.js 16+):
|
|
8
|
+
* import { rewriteArticleMarkdown } from '@fullstackdatasolutions/articles/middleware'
|
|
9
|
+
* export async function proxy(request: NextRequest) {
|
|
10
|
+
* return rewriteArticleMarkdown(request) ?? NextResponse.next()
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
13
|
+
declare function rewriteArticleMarkdown(request: NextRequest, apiBase?: string): NextResponse | undefined;
|
|
14
|
+
declare const middleware: (request: NextRequest) => NextResponse<unknown> | undefined;
|
|
15
|
+
declare const config: {
|
|
16
|
+
matcher: string;
|
|
17
|
+
};
|
|
18
|
+
declare function createArticleMarkdownMiddleware(apiBase?: string): {
|
|
19
|
+
middleware: (request: NextRequest) => NextResponse<unknown> | undefined;
|
|
20
|
+
config: {
|
|
21
|
+
matcher: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export { config, createArticleMarkdownMiddleware, middleware, rewriteArticleMarkdown };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rewrite /articles/*.md requests to the markdown API route.
|
|
5
|
+
* Returns a NextResponse rewrite if the request matches, undefined otherwise.
|
|
6
|
+
*
|
|
7
|
+
* Usage in proxy.ts (Next.js 16+):
|
|
8
|
+
* import { rewriteArticleMarkdown } from '@fullstackdatasolutions/articles/middleware'
|
|
9
|
+
* export async function proxy(request: NextRequest) {
|
|
10
|
+
* return rewriteArticleMarkdown(request) ?? NextResponse.next()
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
13
|
+
declare function rewriteArticleMarkdown(request: NextRequest, apiBase?: string): NextResponse | undefined;
|
|
14
|
+
declare const middleware: (request: NextRequest) => NextResponse<unknown> | undefined;
|
|
15
|
+
declare const config: {
|
|
16
|
+
matcher: string;
|
|
17
|
+
};
|
|
18
|
+
declare function createArticleMarkdownMiddleware(apiBase?: string): {
|
|
19
|
+
middleware: (request: NextRequest) => NextResponse<unknown> | undefined;
|
|
20
|
+
config: {
|
|
21
|
+
matcher: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export { config, createArticleMarkdownMiddleware, middleware, rewriteArticleMarkdown };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/nextjs-middleware.ts
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
var DEFAULT_API_BASE = "/api/articles-markdown";
|
|
4
|
+
function rewriteArticleMarkdown(request, apiBase = DEFAULT_API_BASE) {
|
|
5
|
+
const { pathname } = request.nextUrl;
|
|
6
|
+
if (pathname.startsWith("/articles/") && pathname.endsWith(".md")) {
|
|
7
|
+
const slug = pathname.slice("/articles/".length);
|
|
8
|
+
return NextResponse.rewrite(new URL(`${apiBase}/${slug}`, request.url));
|
|
9
|
+
}
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
function buildMiddleware(apiBase) {
|
|
13
|
+
return function middleware2(request) {
|
|
14
|
+
return rewriteArticleMarkdown(request, apiBase);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
var middleware = buildMiddleware(DEFAULT_API_BASE);
|
|
18
|
+
var config = {
|
|
19
|
+
matcher: "/articles/:path*.md"
|
|
20
|
+
};
|
|
21
|
+
function createArticleMarkdownMiddleware(apiBase = DEFAULT_API_BASE) {
|
|
22
|
+
return {
|
|
23
|
+
middleware: buildMiddleware(apiBase),
|
|
24
|
+
config: { matcher: "/articles/:path*.md" }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
config,
|
|
29
|
+
createArticleMarkdownMiddleware,
|
|
30
|
+
middleware,
|
|
31
|
+
rewriteArticleMarkdown
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=nextjs-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/nextjs-middleware.ts"],"sourcesContent":["// Next.js proxy/middleware helper — Edge runtime safe (no Node.js built-ins)\n// Import from '@fullstackdatasolutions/articles/middleware'\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nconst DEFAULT_API_BASE = '/api/articles-markdown'\n\n/**\n * Rewrite /articles/*.md requests to the markdown API route.\n * Returns a NextResponse rewrite if the request matches, undefined otherwise.\n *\n * Usage in proxy.ts (Next.js 16+):\n * import { rewriteArticleMarkdown } from '@fullstackdatasolutions/articles/middleware'\n * export async function proxy(request: NextRequest) {\n * return rewriteArticleMarkdown(request) ?? NextResponse.next()\n * }\n */\nexport function rewriteArticleMarkdown(\n request: NextRequest,\n apiBase = DEFAULT_API_BASE\n): NextResponse | undefined {\n const { pathname } = request.nextUrl\n if (pathname.startsWith('/articles/') && pathname.endsWith('.md')) {\n const slug = pathname.slice('/articles/'.length)\n return NextResponse.rewrite(new URL(`${apiBase}/${slug}`, request.url))\n }\n return undefined\n}\n\n// Legacy middleware.ts usage (Next.js 14/15)\nfunction buildMiddleware(apiBase: string) {\n return function middleware(request: NextRequest) {\n return rewriteArticleMarkdown(request, apiBase)\n }\n}\n\nexport const middleware = buildMiddleware(DEFAULT_API_BASE)\n\nexport const config = {\n matcher: '/articles/:path*.md',\n}\n\nexport function createArticleMarkdownMiddleware(apiBase = DEFAULT_API_BASE) {\n return {\n middleware: buildMiddleware(apiBase),\n config: { matcher: '/articles/:path*.md' },\n }\n}\n"],"mappings":";AAEA,SAAS,oBAAoB;AAG7B,IAAM,mBAAmB;AAYlB,SAAS,uBACd,SACA,UAAU,kBACgB;AAC1B,QAAM,EAAE,SAAS,IAAI,QAAQ;AAC7B,MAAI,SAAS,WAAW,YAAY,KAAK,SAAS,SAAS,KAAK,GAAG;AACjE,UAAM,OAAO,SAAS,MAAM,aAAa,MAAM;AAC/C,WAAO,aAAa,QAAQ,IAAI,IAAI,GAAG,OAAO,IAAI,IAAI,IAAI,QAAQ,GAAG,CAAC;AAAA,EACxE;AACA,SAAO;AACT;AAGA,SAAS,gBAAgB,SAAiB;AACxC,SAAO,SAASA,YAAW,SAAsB;AAC/C,WAAO,uBAAuB,SAAS,OAAO;AAAA,EAChD;AACF;AAEO,IAAM,aAAa,gBAAgB,gBAAgB;AAEnD,IAAM,SAAS;AAAA,EACpB,SAAS;AACX;AAEO,SAAS,gCAAgC,UAAU,kBAAkB;AAC1E,SAAO;AAAA,IACL,YAAY,gBAAgB,OAAO;AAAA,IACnC,QAAQ,EAAE,SAAS,sBAAsB;AAAA,EAC3C;AACF;","names":["middleware"]}
|