vinext 0.0.0 → 0.0.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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/build/static-export.d.ts +78 -0
- package/dist/build/static-export.d.ts.map +1 -0
- package/dist/build/static-export.js +553 -0
- package/dist/build/static-export.js.map +1 -0
- package/dist/check.d.ts +52 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +483 -0
- package/dist/check.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +565 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/entry.d.ts +2 -0
- package/dist/client/entry.d.ts.map +1 -0
- package/dist/client/entry.js +85 -0
- package/dist/client/entry.js.map +1 -0
- package/dist/cloudflare/index.d.ts +8 -0
- package/dist/cloudflare/index.d.ts.map +1 -0
- package/dist/cloudflare/index.js +8 -0
- package/dist/cloudflare/index.js.map +1 -0
- package/dist/cloudflare/kv-cache-handler.d.ts +68 -0
- package/dist/cloudflare/kv-cache-handler.d.ts.map +1 -0
- package/dist/cloudflare/kv-cache-handler.js +304 -0
- package/dist/cloudflare/kv-cache-handler.js.map +1 -0
- package/dist/cloudflare/tpr.d.ts +78 -0
- package/dist/cloudflare/tpr.d.ts.map +1 -0
- package/dist/cloudflare/tpr.js +672 -0
- package/dist/cloudflare/tpr.js.map +1 -0
- package/dist/config/config-matchers.d.ts +106 -0
- package/dist/config/config-matchers.d.ts.map +1 -0
- package/dist/config/config-matchers.js +499 -0
- package/dist/config/config-matchers.js.map +1 -0
- package/dist/config/next-config.d.ts +153 -0
- package/dist/config/next-config.d.ts.map +1 -0
- package/dist/config/next-config.js +274 -0
- package/dist/config/next-config.js.map +1 -0
- package/dist/deploy.d.ts +87 -0
- package/dist/deploy.d.ts.map +1 -0
- package/dist/deploy.js +644 -0
- package/dist/deploy.js.map +1 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3296 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +55 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +201 -0
- package/dist/init.js.map +1 -0
- package/dist/routing/app-router.d.ts +96 -0
- package/dist/routing/app-router.d.ts.map +1 -0
- package/dist/routing/app-router.js +815 -0
- package/dist/routing/app-router.js.map +1 -0
- package/dist/routing/pages-router.d.ts +52 -0
- package/dist/routing/pages-router.d.ts.map +1 -0
- package/dist/routing/pages-router.js +239 -0
- package/dist/routing/pages-router.js.map +1 -0
- package/dist/server/api-handler.d.ts +18 -0
- package/dist/server/api-handler.d.ts.map +1 -0
- package/dist/server/api-handler.js +169 -0
- package/dist/server/api-handler.js.map +1 -0
- package/dist/server/app-dev-server.d.ts +42 -0
- package/dist/server/app-dev-server.d.ts.map +1 -0
- package/dist/server/app-dev-server.js +2718 -0
- package/dist/server/app-dev-server.js.map +1 -0
- package/dist/server/app-router-entry.d.ts +18 -0
- package/dist/server/app-router-entry.d.ts.map +1 -0
- package/dist/server/app-router-entry.js +34 -0
- package/dist/server/app-router-entry.js.map +1 -0
- package/dist/server/dev-server.d.ts +40 -0
- package/dist/server/dev-server.d.ts.map +1 -0
- package/dist/server/dev-server.js +758 -0
- package/dist/server/dev-server.js.map +1 -0
- package/dist/server/html.d.ts +22 -0
- package/dist/server/html.d.ts.map +1 -0
- package/dist/server/html.js +29 -0
- package/dist/server/html.js.map +1 -0
- package/dist/server/image-optimization.d.ts +56 -0
- package/dist/server/image-optimization.d.ts.map +1 -0
- package/dist/server/image-optimization.js +103 -0
- package/dist/server/image-optimization.js.map +1 -0
- package/dist/server/instrumentation.d.ts +68 -0
- package/dist/server/instrumentation.d.ts.map +1 -0
- package/dist/server/instrumentation.js +90 -0
- package/dist/server/instrumentation.js.map +1 -0
- package/dist/server/isr-cache.d.ts +61 -0
- package/dist/server/isr-cache.d.ts.map +1 -0
- package/dist/server/isr-cache.js +134 -0
- package/dist/server/isr-cache.js.map +1 -0
- package/dist/server/metadata-routes.d.ts +103 -0
- package/dist/server/metadata-routes.d.ts.map +1 -0
- package/dist/server/metadata-routes.js +270 -0
- package/dist/server/metadata-routes.js.map +1 -0
- package/dist/server/middleware.d.ts +77 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +228 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/prod-server.d.ts +78 -0
- package/dist/server/prod-server.d.ts.map +1 -0
- package/dist/server/prod-server.js +712 -0
- package/dist/server/prod-server.js.map +1 -0
- package/dist/shims/amp.d.ts +17 -0
- package/dist/shims/amp.d.ts.map +1 -0
- package/dist/shims/amp.js +21 -0
- package/dist/shims/amp.js.map +1 -0
- package/dist/shims/app.d.ts +12 -0
- package/dist/shims/app.d.ts.map +1 -0
- package/dist/shims/app.js +2 -0
- package/dist/shims/app.js.map +1 -0
- package/dist/shims/cache-runtime.d.ts +68 -0
- package/dist/shims/cache-runtime.d.ts.map +1 -0
- package/dist/shims/cache-runtime.js +437 -0
- package/dist/shims/cache-runtime.js.map +1 -0
- package/dist/shims/cache.d.ts +243 -0
- package/dist/shims/cache.d.ts.map +1 -0
- package/dist/shims/cache.js +415 -0
- package/dist/shims/cache.js.map +1 -0
- package/dist/shims/client-only.d.ts +18 -0
- package/dist/shims/client-only.d.ts.map +1 -0
- package/dist/shims/client-only.js +18 -0
- package/dist/shims/client-only.js.map +1 -0
- package/dist/shims/config.d.ts +27 -0
- package/dist/shims/config.d.ts.map +1 -0
- package/dist/shims/config.js +30 -0
- package/dist/shims/config.js.map +1 -0
- package/dist/shims/constants.d.ts +13 -0
- package/dist/shims/constants.d.ts.map +1 -0
- package/dist/shims/constants.js +13 -0
- package/dist/shims/constants.js.map +1 -0
- package/dist/shims/document.d.ts +33 -0
- package/dist/shims/document.d.ts.map +1 -0
- package/dist/shims/document.js +32 -0
- package/dist/shims/document.js.map +1 -0
- package/dist/shims/dynamic.d.ts +33 -0
- package/dist/shims/dynamic.d.ts.map +1 -0
- package/dist/shims/dynamic.js +149 -0
- package/dist/shims/dynamic.js.map +1 -0
- package/dist/shims/error-boundary.d.ts +33 -0
- package/dist/shims/error-boundary.d.ts.map +1 -0
- package/dist/shims/error-boundary.js +88 -0
- package/dist/shims/error-boundary.js.map +1 -0
- package/dist/shims/error.d.ts +16 -0
- package/dist/shims/error.d.ts.map +1 -0
- package/dist/shims/error.js +45 -0
- package/dist/shims/error.js.map +1 -0
- package/dist/shims/fetch-cache.d.ts +61 -0
- package/dist/shims/fetch-cache.d.ts.map +1 -0
- package/dist/shims/fetch-cache.js +307 -0
- package/dist/shims/fetch-cache.js.map +1 -0
- package/dist/shims/font-google.d.ts +122 -0
- package/dist/shims/font-google.d.ts.map +1 -0
- package/dist/shims/font-google.js +387 -0
- package/dist/shims/font-google.js.map +1 -0
- package/dist/shims/font-local.d.ts +61 -0
- package/dist/shims/font-local.d.ts.map +1 -0
- package/dist/shims/font-local.js +303 -0
- package/dist/shims/font-local.js.map +1 -0
- package/dist/shims/form.d.ts +30 -0
- package/dist/shims/form.d.ts.map +1 -0
- package/dist/shims/form.js +78 -0
- package/dist/shims/form.js.map +1 -0
- package/dist/shims/head-state.d.ts +11 -0
- package/dist/shims/head-state.d.ts.map +1 -0
- package/dist/shims/head-state.js +47 -0
- package/dist/shims/head-state.js.map +1 -0
- package/dist/shims/head.d.ts +28 -0
- package/dist/shims/head.d.ts.map +1 -0
- package/dist/shims/head.js +148 -0
- package/dist/shims/head.js.map +1 -0
- package/dist/shims/headers.d.ts +150 -0
- package/dist/shims/headers.d.ts.map +1 -0
- package/dist/shims/headers.js +412 -0
- package/dist/shims/headers.js.map +1 -0
- package/dist/shims/image-config.d.ts +30 -0
- package/dist/shims/image-config.d.ts.map +1 -0
- package/dist/shims/image-config.js +91 -0
- package/dist/shims/image-config.js.map +1 -0
- package/dist/shims/image.d.ts +63 -0
- package/dist/shims/image.d.ts.map +1 -0
- package/dist/shims/image.js +284 -0
- package/dist/shims/image.js.map +1 -0
- package/dist/shims/internal/api-utils.d.ts +12 -0
- package/dist/shims/internal/api-utils.d.ts.map +1 -0
- package/dist/shims/internal/api-utils.js +7 -0
- package/dist/shims/internal/api-utils.js.map +1 -0
- package/dist/shims/internal/app-router-context.d.ts +21 -0
- package/dist/shims/internal/app-router-context.d.ts.map +1 -0
- package/dist/shims/internal/app-router-context.js +15 -0
- package/dist/shims/internal/app-router-context.js.map +1 -0
- package/dist/shims/internal/cookies.d.ts +9 -0
- package/dist/shims/internal/cookies.d.ts.map +1 -0
- package/dist/shims/internal/cookies.js +9 -0
- package/dist/shims/internal/cookies.js.map +1 -0
- package/dist/shims/internal/router-context.d.ts +2 -0
- package/dist/shims/internal/router-context.d.ts.map +1 -0
- package/dist/shims/internal/router-context.js +9 -0
- package/dist/shims/internal/router-context.js.map +1 -0
- package/dist/shims/internal/utils.d.ts +48 -0
- package/dist/shims/internal/utils.d.ts.map +1 -0
- package/dist/shims/internal/utils.js +35 -0
- package/dist/shims/internal/utils.js.map +1 -0
- package/dist/shims/internal/work-unit-async-storage.d.ts +12 -0
- package/dist/shims/internal/work-unit-async-storage.d.ts.map +1 -0
- package/dist/shims/internal/work-unit-async-storage.js +13 -0
- package/dist/shims/internal/work-unit-async-storage.js.map +1 -0
- package/dist/shims/layout-segment-context.d.ts +21 -0
- package/dist/shims/layout-segment-context.d.ts.map +1 -0
- package/dist/shims/layout-segment-context.js +27 -0
- package/dist/shims/layout-segment-context.js.map +1 -0
- package/dist/shims/legacy-image.d.ts +52 -0
- package/dist/shims/legacy-image.d.ts.map +1 -0
- package/dist/shims/legacy-image.js +46 -0
- package/dist/shims/legacy-image.js.map +1 -0
- package/dist/shims/link.d.ts +48 -0
- package/dist/shims/link.d.ts.map +1 -0
- package/dist/shims/link.js +395 -0
- package/dist/shims/link.js.map +1 -0
- package/dist/shims/metadata.d.ts +184 -0
- package/dist/shims/metadata.d.ts.map +1 -0
- package/dist/shims/metadata.js +472 -0
- package/dist/shims/metadata.js.map +1 -0
- package/dist/shims/navigation-state.d.ts +14 -0
- package/dist/shims/navigation-state.d.ts.map +1 -0
- package/dist/shims/navigation-state.js +77 -0
- package/dist/shims/navigation-state.js.map +1 -0
- package/dist/shims/navigation.d.ts +201 -0
- package/dist/shims/navigation.d.ts.map +1 -0
- package/dist/shims/navigation.js +672 -0
- package/dist/shims/navigation.js.map +1 -0
- package/dist/shims/og.d.ts +20 -0
- package/dist/shims/og.d.ts.map +1 -0
- package/dist/shims/og.js +19 -0
- package/dist/shims/og.js.map +1 -0
- package/dist/shims/router-state.d.ts +11 -0
- package/dist/shims/router-state.d.ts.map +1 -0
- package/dist/shims/router-state.js +56 -0
- package/dist/shims/router-state.js.map +1 -0
- package/dist/shims/router.d.ts +103 -0
- package/dist/shims/router.d.ts.map +1 -0
- package/dist/shims/router.js +536 -0
- package/dist/shims/router.js.map +1 -0
- package/dist/shims/script.d.ts +58 -0
- package/dist/shims/script.d.ts.map +1 -0
- package/dist/shims/script.js +163 -0
- package/dist/shims/script.js.map +1 -0
- package/dist/shims/server-only.d.ts +19 -0
- package/dist/shims/server-only.d.ts.map +1 -0
- package/dist/shims/server-only.js +19 -0
- package/dist/shims/server-only.js.map +1 -0
- package/dist/shims/server.d.ts +178 -0
- package/dist/shims/server.d.ts.map +1 -0
- package/dist/shims/server.js +377 -0
- package/dist/shims/server.js.map +1 -0
- package/dist/shims/web-vitals.d.ts +24 -0
- package/dist/shims/web-vitals.d.ts.map +1 -0
- package/dist/shims/web-vitals.js +17 -0
- package/dist/shims/web-vitals.js.map +1 -0
- package/dist/utils/hash.d.ts +6 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +20 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/project.d.ts +36 -0
- package/dist/utils/project.d.ts.map +1 -0
- package/dist/utils/project.js +112 -0
- package/dist/utils/project.js.map +1 -0
- package/dist/utils/query.d.ts +10 -0
- package/dist/utils/query.d.ts.map +1 -0
- package/dist/utils/query.js +27 -0
- package/dist/utils/query.js.map +1 -0
- package/package.json +65 -7
- package/index.js +0 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISR (Incremental Static Regeneration) cache layer.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the pluggable CacheHandler with stale-while-revalidate semantics:
|
|
5
|
+
* - Fresh hit: serve immediately
|
|
6
|
+
* - Stale hit: serve immediately + trigger background regeneration
|
|
7
|
+
* - Miss: render synchronously, cache, serve
|
|
8
|
+
*
|
|
9
|
+
* Background regeneration is deduped — only one regeneration per cache key
|
|
10
|
+
* runs at a time, preventing thundering herd on popular pages.
|
|
11
|
+
*
|
|
12
|
+
* This layer works with any CacheHandler backend (memory, Redis, KV, etc.)
|
|
13
|
+
* because it only uses the standard get/set interface.
|
|
14
|
+
*/
|
|
15
|
+
import { getCacheHandler, } from "../shims/cache.js";
|
|
16
|
+
import { fnv1a64 } from "../utils/hash.js";
|
|
17
|
+
/**
|
|
18
|
+
* Get a cache entry with staleness information.
|
|
19
|
+
*
|
|
20
|
+
* Returns { value, isStale: false } for fresh entries,
|
|
21
|
+
* { value, isStale: true } for expired-but-usable entries,
|
|
22
|
+
* or null for cache misses.
|
|
23
|
+
*/
|
|
24
|
+
export async function isrGet(key) {
|
|
25
|
+
const handler = getCacheHandler();
|
|
26
|
+
const result = await handler.get(key);
|
|
27
|
+
if (!result || !result.value)
|
|
28
|
+
return null;
|
|
29
|
+
return {
|
|
30
|
+
value: result,
|
|
31
|
+
isStale: result.cacheState === "stale",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Store a value in the ISR cache with a revalidation period.
|
|
36
|
+
*/
|
|
37
|
+
export async function isrSet(key, data, revalidateSeconds, tags) {
|
|
38
|
+
const handler = getCacheHandler();
|
|
39
|
+
await handler.set(key, data, {
|
|
40
|
+
revalidate: revalidateSeconds,
|
|
41
|
+
tags: tags ?? [],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Background regeneration dedup
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
const pendingRegenerations = new Map();
|
|
48
|
+
/**
|
|
49
|
+
* Trigger a background regeneration for a cache key.
|
|
50
|
+
*
|
|
51
|
+
* If a regeneration for this key is already in progress, this is a no-op.
|
|
52
|
+
* The renderFn should produce the new cache value and call isrSet internally.
|
|
53
|
+
*/
|
|
54
|
+
export function triggerBackgroundRegeneration(key, renderFn) {
|
|
55
|
+
if (pendingRegenerations.has(key))
|
|
56
|
+
return;
|
|
57
|
+
const promise = renderFn()
|
|
58
|
+
.catch((err) => {
|
|
59
|
+
console.error(`[vinext] ISR background regeneration failed for ${key}:`, err);
|
|
60
|
+
})
|
|
61
|
+
.finally(() => {
|
|
62
|
+
pendingRegenerations.delete(key);
|
|
63
|
+
});
|
|
64
|
+
pendingRegenerations.set(key, promise);
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Helpers for building ISR cache values
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
/**
|
|
70
|
+
* Build a CachedPagesValue for the Pages Router ISR cache.
|
|
71
|
+
*/
|
|
72
|
+
export function buildPagesCacheValue(html, pageData, status) {
|
|
73
|
+
return {
|
|
74
|
+
kind: "PAGES",
|
|
75
|
+
html,
|
|
76
|
+
pageData,
|
|
77
|
+
headers: undefined,
|
|
78
|
+
status,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Build a CachedAppPageValue for the App Router ISR cache.
|
|
83
|
+
*/
|
|
84
|
+
export function buildAppPageCacheValue(html, rscData, status) {
|
|
85
|
+
return {
|
|
86
|
+
kind: "APP_PAGE",
|
|
87
|
+
html,
|
|
88
|
+
rscData,
|
|
89
|
+
headers: undefined,
|
|
90
|
+
postponed: undefined,
|
|
91
|
+
status,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Compute an ISR cache key for a given router type and pathname.
|
|
96
|
+
* Long pathnames are hashed to stay within KV key-length limits (512 bytes).
|
|
97
|
+
*/
|
|
98
|
+
export function isrCacheKey(router, pathname) {
|
|
99
|
+
const normalized = pathname === "/" ? "/" : pathname.replace(/\/$/, "");
|
|
100
|
+
const key = `${router}:${normalized}`;
|
|
101
|
+
if (key.length <= 200)
|
|
102
|
+
return key;
|
|
103
|
+
return `${router}:__hash:${fnv1a64(normalized)}`;
|
|
104
|
+
}
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Revalidate duration tracking — remembers how long each ISR key's TTL is
|
|
107
|
+
// so we can emit correct Cache-Control headers on cache hits.
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
const MAX_REVALIDATE_ENTRIES = 10_000;
|
|
110
|
+
const revalidateDurations = new Map();
|
|
111
|
+
/**
|
|
112
|
+
* Store the revalidate duration for a cache key.
|
|
113
|
+
* Uses insertion-order LRU eviction to prevent unbounded growth.
|
|
114
|
+
*/
|
|
115
|
+
export function setRevalidateDuration(key, seconds) {
|
|
116
|
+
// Simple LRU: delete and re-insert to move to end (most recent)
|
|
117
|
+
revalidateDurations.delete(key);
|
|
118
|
+
revalidateDurations.set(key, seconds);
|
|
119
|
+
// Evict oldest entries if over limit
|
|
120
|
+
while (revalidateDurations.size > MAX_REVALIDATE_ENTRIES) {
|
|
121
|
+
const first = revalidateDurations.keys().next().value;
|
|
122
|
+
if (first !== undefined)
|
|
123
|
+
revalidateDurations.delete(first);
|
|
124
|
+
else
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get the revalidate duration for a cache key.
|
|
130
|
+
*/
|
|
131
|
+
export function getRevalidateDuration(key) {
|
|
132
|
+
return revalidateDurations.get(key);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=isr-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isr-cache.js","sourceRoot":"","sources":["../../src/server/isr-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,eAAe,GAKhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAO3C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,GAAW;IACtC,MAAM,OAAO,GAAG,eAAe,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAE1C,OAAO;QACL,KAAK,EAAE,MAAM;QACb,OAAO,EAAE,MAAM,CAAC,UAAU,KAAK,OAAO;KACvC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,GAAW,EACX,IAA2B,EAC3B,iBAAyB,EACzB,IAAe;IAEf,MAAM,OAAO,GAAG,eAAe,EAAE,CAAC;IAClC,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE;QAC3B,UAAU,EAAE,iBAAiB;QAC7B,IAAI,EAAE,IAAI,IAAI,EAAE;KACjB,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,gCAAgC;AAChC,8EAA8E;AAE9E,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAyB,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,UAAU,6BAA6B,CAC3C,GAAW,EACX,QAA6B;IAE7B,IAAI,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO;IAE1C,MAAM,OAAO,GAAG,QAAQ,EAAE;SACvB,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACb,OAAO,CAAC,KAAK,CAAC,mDAAmD,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC;IAChF,CAAC,CAAC;SACD,OAAO,CAAC,GAAG,EAAE;QACZ,oBAAoB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEL,oBAAoB,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC;AAED,8EAA8E;AAC9E,wCAAwC;AACxC,8EAA8E;AAE9E;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,QAAgB,EAChB,MAAe;IAEf,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI;QACJ,QAAQ;QACR,OAAO,EAAE,SAAS;QAClB,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAY,EACZ,OAAqB,EACrB,MAAe;IAEf,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,IAAI;QACJ,OAAO;QACP,OAAO,EAAE,SAAS;QAClB,SAAS,EAAE,SAAS;QACpB,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAuB,EAAE,QAAgB;IACnE,MAAM,UAAU,GAAG,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACxE,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,UAAU,EAAE,CAAC;IACtC,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC;IAClC,OAAO,GAAG,MAAM,WAAW,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;AACnD,CAAC;AAED,8EAA8E;AAC9E,0EAA0E;AAC1E,8DAA8D;AAC9D,8EAA8E;AAE9E,MAAM,sBAAsB,GAAG,MAAM,CAAC;AACtC,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEtD;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW,EAAE,OAAe;IAChE,gEAAgE;IAChE,mBAAmB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChC,mBAAmB,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACtC,qCAAqC;IACrC,OAAO,mBAAmB,CAAC,IAAI,GAAG,sBAAsB,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QACtD,IAAI,KAAK,KAAK,SAAS;YAAE,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;;YACtD,MAAM;IACb,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,OAAO,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACtC,CAAC","sourcesContent":["/**\n * ISR (Incremental Static Regeneration) cache layer.\n *\n * Wraps the pluggable CacheHandler with stale-while-revalidate semantics:\n * - Fresh hit: serve immediately\n * - Stale hit: serve immediately + trigger background regeneration\n * - Miss: render synchronously, cache, serve\n *\n * Background regeneration is deduped — only one regeneration per cache key\n * runs at a time, preventing thundering herd on popular pages.\n *\n * This layer works with any CacheHandler backend (memory, Redis, KV, etc.)\n * because it only uses the standard get/set interface.\n */\n\nimport {\n getCacheHandler,\n type CacheHandlerValue,\n type IncrementalCacheValue,\n type CachedPagesValue,\n type CachedAppPageValue,\n} from \"../shims/cache.js\";\nimport { fnv1a64 } from \"../utils/hash.js\";\n\nexport interface ISRCacheEntry {\n value: CacheHandlerValue;\n isStale: boolean;\n}\n\n/**\n * Get a cache entry with staleness information.\n *\n * Returns { value, isStale: false } for fresh entries,\n * { value, isStale: true } for expired-but-usable entries,\n * or null for cache misses.\n */\nexport async function isrGet(key: string): Promise<ISRCacheEntry | null> {\n const handler = getCacheHandler();\n const result = await handler.get(key);\n if (!result || !result.value) return null;\n\n return {\n value: result,\n isStale: result.cacheState === \"stale\",\n };\n}\n\n/**\n * Store a value in the ISR cache with a revalidation period.\n */\nexport async function isrSet(\n key: string,\n data: IncrementalCacheValue,\n revalidateSeconds: number,\n tags?: string[],\n): Promise<void> {\n const handler = getCacheHandler();\n await handler.set(key, data, {\n revalidate: revalidateSeconds,\n tags: tags ?? [],\n });\n}\n\n// ---------------------------------------------------------------------------\n// Background regeneration dedup\n// ---------------------------------------------------------------------------\n\nconst pendingRegenerations = new Map<string, Promise<void>>();\n\n/**\n * Trigger a background regeneration for a cache key.\n *\n * If a regeneration for this key is already in progress, this is a no-op.\n * The renderFn should produce the new cache value and call isrSet internally.\n */\nexport function triggerBackgroundRegeneration(\n key: string,\n renderFn: () => Promise<void>,\n): void {\n if (pendingRegenerations.has(key)) return;\n\n const promise = renderFn()\n .catch((err) => {\n console.error(`[vinext] ISR background regeneration failed for ${key}:`, err);\n })\n .finally(() => {\n pendingRegenerations.delete(key);\n });\n\n pendingRegenerations.set(key, promise);\n}\n\n// ---------------------------------------------------------------------------\n// Helpers for building ISR cache values\n// ---------------------------------------------------------------------------\n\n/**\n * Build a CachedPagesValue for the Pages Router ISR cache.\n */\nexport function buildPagesCacheValue(\n html: string,\n pageData: object,\n status?: number,\n): CachedPagesValue {\n return {\n kind: \"PAGES\",\n html,\n pageData,\n headers: undefined,\n status,\n };\n}\n\n/**\n * Build a CachedAppPageValue for the App Router ISR cache.\n */\nexport function buildAppPageCacheValue(\n html: string,\n rscData?: ArrayBuffer,\n status?: number,\n): CachedAppPageValue {\n return {\n kind: \"APP_PAGE\",\n html,\n rscData,\n headers: undefined,\n postponed: undefined,\n status,\n };\n}\n\n/**\n * Compute an ISR cache key for a given router type and pathname.\n * Long pathnames are hashed to stay within KV key-length limits (512 bytes).\n */\nexport function isrCacheKey(router: \"pages\" | \"app\", pathname: string): string {\n const normalized = pathname === \"/\" ? \"/\" : pathname.replace(/\\/$/, \"\");\n const key = `${router}:${normalized}`;\n if (key.length <= 200) return key;\n return `${router}:__hash:${fnv1a64(normalized)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Revalidate duration tracking — remembers how long each ISR key's TTL is\n// so we can emit correct Cache-Control headers on cache hits.\n// ---------------------------------------------------------------------------\n\nconst MAX_REVALIDATE_ENTRIES = 10_000;\nconst revalidateDurations = new Map<string, number>();\n\n/**\n * Store the revalidate duration for a cache key.\n * Uses insertion-order LRU eviction to prevent unbounded growth.\n */\nexport function setRevalidateDuration(key: string, seconds: number): void {\n // Simple LRU: delete and re-insert to move to end (most recent)\n revalidateDurations.delete(key);\n revalidateDurations.set(key, seconds);\n // Evict oldest entries if over limit\n while (revalidateDurations.size > MAX_REVALIDATE_ENTRIES) {\n const first = revalidateDurations.keys().next().value;\n if (first !== undefined) revalidateDurations.delete(first);\n else break;\n }\n}\n\n/**\n * Get the revalidate duration for a cache key.\n */\nexport function getRevalidateDuration(key: string): number | undefined {\n return revalidateDurations.get(key);\n}\n"]}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export interface SitemapEntry {
|
|
2
|
+
url: string;
|
|
3
|
+
lastModified?: string | Date;
|
|
4
|
+
changeFrequency?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
|
5
|
+
priority?: number;
|
|
6
|
+
alternates?: {
|
|
7
|
+
languages?: Record<string, string>;
|
|
8
|
+
};
|
|
9
|
+
images?: string[];
|
|
10
|
+
videos?: Array<{
|
|
11
|
+
title: string;
|
|
12
|
+
thumbnail_loc: string;
|
|
13
|
+
description: string;
|
|
14
|
+
content_loc?: string;
|
|
15
|
+
player_loc?: string;
|
|
16
|
+
duration?: number;
|
|
17
|
+
expiration_date?: string;
|
|
18
|
+
rating?: number;
|
|
19
|
+
view_count?: number;
|
|
20
|
+
publication_date?: string;
|
|
21
|
+
family_friendly?: "yes" | "no";
|
|
22
|
+
restriction?: {
|
|
23
|
+
relationship: "allow" | "deny";
|
|
24
|
+
content: string;
|
|
25
|
+
};
|
|
26
|
+
platform?: {
|
|
27
|
+
relationship: "allow" | "deny";
|
|
28
|
+
content: string;
|
|
29
|
+
};
|
|
30
|
+
live?: "yes" | "no";
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
export interface RobotsRule {
|
|
34
|
+
userAgent?: string | string[];
|
|
35
|
+
allow?: string | string[];
|
|
36
|
+
disallow?: string | string[];
|
|
37
|
+
crawlDelay?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface RobotsConfig {
|
|
40
|
+
rules: RobotsRule | RobotsRule[];
|
|
41
|
+
sitemap?: string | string[];
|
|
42
|
+
host?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface ManifestConfig {
|
|
45
|
+
name?: string;
|
|
46
|
+
short_name?: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
start_url?: string;
|
|
49
|
+
display?: "fullscreen" | "standalone" | "minimal-ui" | "browser";
|
|
50
|
+
background_color?: string;
|
|
51
|
+
theme_color?: string;
|
|
52
|
+
icons?: Array<{
|
|
53
|
+
src: string;
|
|
54
|
+
sizes?: string;
|
|
55
|
+
type?: string;
|
|
56
|
+
purpose?: string;
|
|
57
|
+
}>;
|
|
58
|
+
[key: string]: unknown;
|
|
59
|
+
}
|
|
60
|
+
/** Map of metadata file base names to their URL path and content type. */
|
|
61
|
+
export declare const METADATA_FILE_MAP: Record<string, {
|
|
62
|
+
/** URL path this file is served at */
|
|
63
|
+
urlPath: string;
|
|
64
|
+
/** Content type for the response */
|
|
65
|
+
contentType: string;
|
|
66
|
+
/** Whether this can be dynamic (.ts/.tsx/.js) */
|
|
67
|
+
canBeDynamic: boolean;
|
|
68
|
+
/** File extensions for static variants */
|
|
69
|
+
staticExtensions: string[];
|
|
70
|
+
/** File extensions for dynamic variants */
|
|
71
|
+
dynamicExtensions: string[];
|
|
72
|
+
/** Whether this can be nested in sub-segments */
|
|
73
|
+
nestable: boolean;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Convert a sitemap array to XML string.
|
|
77
|
+
*/
|
|
78
|
+
export declare function sitemapToXml(entries: SitemapEntry[]): string;
|
|
79
|
+
/**
|
|
80
|
+
* Convert a robots config to text format.
|
|
81
|
+
*/
|
|
82
|
+
export declare function robotsToText(config: RobotsConfig): string;
|
|
83
|
+
/**
|
|
84
|
+
* Convert a manifest config to JSON string.
|
|
85
|
+
*/
|
|
86
|
+
export declare function manifestToJson(config: ManifestConfig): string;
|
|
87
|
+
export interface MetadataFileRoute {
|
|
88
|
+
/** Type of metadata file */
|
|
89
|
+
type: string;
|
|
90
|
+
/** Whether this is a dynamic (code-generated) route */
|
|
91
|
+
isDynamic: boolean;
|
|
92
|
+
/** Absolute file path */
|
|
93
|
+
filePath: string;
|
|
94
|
+
/** URL path this file is served at */
|
|
95
|
+
servedUrl: string;
|
|
96
|
+
/** Content type for the response */
|
|
97
|
+
contentType: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Scan an app directory for metadata files.
|
|
101
|
+
*/
|
|
102
|
+
export declare function scanMetadataFiles(appDir: string): MetadataFileRoute[];
|
|
103
|
+
//# sourceMappingURL=metadata-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata-routes.d.ts","sourceRoot":"","sources":["../../src/server/metadata-routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-based metadata route handling.
|
|
3
|
+
*
|
|
4
|
+
* Next.js supports special files in the app/ directory that auto-generate
|
|
5
|
+
* metadata routes:
|
|
6
|
+
* - sitemap.ts/.xml → /sitemap.xml (application/xml)
|
|
7
|
+
* - robots.ts/.txt → /robots.txt (text/plain)
|
|
8
|
+
* - manifest.ts/.json/.webmanifest → /manifest.webmanifest (application/manifest+json)
|
|
9
|
+
* - icon.tsx/.png → /icon (image/*)
|
|
10
|
+
* - opengraph-image.tsx/.png → /opengraph-image (image/*)
|
|
11
|
+
* - twitter-image.tsx/.png → /twitter-image (image/*)
|
|
12
|
+
* - apple-icon.tsx/.png → /apple-icon (image/*)
|
|
13
|
+
* - favicon.ico → /favicon.ico (image/x-icon)
|
|
14
|
+
*
|
|
15
|
+
* Dynamic versions (ts/tsx/js) export a default function that returns the data.
|
|
16
|
+
* Static versions (xml/txt/json/png/etc.) are served as-is.
|
|
17
|
+
*/
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
// -------------------------------------------------------------------
|
|
21
|
+
// Known metadata file patterns
|
|
22
|
+
// -------------------------------------------------------------------
|
|
23
|
+
/** Map of metadata file base names to their URL path and content type. */
|
|
24
|
+
export const METADATA_FILE_MAP = {
|
|
25
|
+
sitemap: {
|
|
26
|
+
urlPath: "/sitemap.xml",
|
|
27
|
+
contentType: "application/xml",
|
|
28
|
+
canBeDynamic: true,
|
|
29
|
+
staticExtensions: [".xml"],
|
|
30
|
+
dynamicExtensions: [".ts", ".js"],
|
|
31
|
+
nestable: true,
|
|
32
|
+
},
|
|
33
|
+
robots: {
|
|
34
|
+
urlPath: "/robots.txt",
|
|
35
|
+
contentType: "text/plain",
|
|
36
|
+
canBeDynamic: true,
|
|
37
|
+
staticExtensions: [".txt"],
|
|
38
|
+
dynamicExtensions: [".ts", ".js"],
|
|
39
|
+
nestable: false,
|
|
40
|
+
},
|
|
41
|
+
manifest: {
|
|
42
|
+
urlPath: "/manifest.webmanifest",
|
|
43
|
+
contentType: "application/manifest+json",
|
|
44
|
+
canBeDynamic: true,
|
|
45
|
+
staticExtensions: [".json", ".webmanifest"],
|
|
46
|
+
dynamicExtensions: [".ts", ".js"],
|
|
47
|
+
nestable: false,
|
|
48
|
+
},
|
|
49
|
+
favicon: {
|
|
50
|
+
urlPath: "/favicon.ico",
|
|
51
|
+
contentType: "image/x-icon",
|
|
52
|
+
canBeDynamic: false,
|
|
53
|
+
staticExtensions: [".ico"],
|
|
54
|
+
dynamicExtensions: [],
|
|
55
|
+
nestable: false,
|
|
56
|
+
},
|
|
57
|
+
icon: {
|
|
58
|
+
urlPath: "/icon",
|
|
59
|
+
contentType: "image/png",
|
|
60
|
+
canBeDynamic: true,
|
|
61
|
+
staticExtensions: [".ico", ".jpg", ".jpeg", ".png", ".svg"],
|
|
62
|
+
dynamicExtensions: [".ts", ".tsx", ".js"],
|
|
63
|
+
nestable: true,
|
|
64
|
+
},
|
|
65
|
+
"opengraph-image": {
|
|
66
|
+
urlPath: "/opengraph-image",
|
|
67
|
+
contentType: "image/png",
|
|
68
|
+
canBeDynamic: true,
|
|
69
|
+
staticExtensions: [".jpg", ".jpeg", ".png", ".gif"],
|
|
70
|
+
dynamicExtensions: [".ts", ".tsx", ".js"],
|
|
71
|
+
nestable: true,
|
|
72
|
+
},
|
|
73
|
+
"twitter-image": {
|
|
74
|
+
urlPath: "/twitter-image",
|
|
75
|
+
contentType: "image/png",
|
|
76
|
+
canBeDynamic: true,
|
|
77
|
+
staticExtensions: [".jpg", ".jpeg", ".png", ".gif"],
|
|
78
|
+
dynamicExtensions: [".ts", ".tsx", ".js"],
|
|
79
|
+
nestable: true,
|
|
80
|
+
},
|
|
81
|
+
"apple-icon": {
|
|
82
|
+
urlPath: "/apple-icon",
|
|
83
|
+
contentType: "image/png",
|
|
84
|
+
canBeDynamic: true,
|
|
85
|
+
staticExtensions: [".jpg", ".jpeg", ".png"],
|
|
86
|
+
dynamicExtensions: [".ts", ".tsx", ".js"],
|
|
87
|
+
nestable: true,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
// -------------------------------------------------------------------
|
|
91
|
+
// Serializers
|
|
92
|
+
// -------------------------------------------------------------------
|
|
93
|
+
/**
|
|
94
|
+
* Convert a sitemap array to XML string.
|
|
95
|
+
*/
|
|
96
|
+
export function sitemapToXml(entries) {
|
|
97
|
+
const lines = [
|
|
98
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
99
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
100
|
+
];
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
lines.push(" <url>");
|
|
103
|
+
lines.push(` <loc>${escapeXml(entry.url)}</loc>`);
|
|
104
|
+
if (entry.lastModified) {
|
|
105
|
+
const date = entry.lastModified instanceof Date
|
|
106
|
+
? entry.lastModified.toISOString()
|
|
107
|
+
: entry.lastModified;
|
|
108
|
+
lines.push(` <lastmod>${escapeXml(date)}</lastmod>`);
|
|
109
|
+
}
|
|
110
|
+
if (entry.changeFrequency) {
|
|
111
|
+
lines.push(` <changefreq>${escapeXml(entry.changeFrequency)}</changefreq>`);
|
|
112
|
+
}
|
|
113
|
+
if (entry.priority !== undefined) {
|
|
114
|
+
lines.push(` <priority>${entry.priority}</priority>`);
|
|
115
|
+
}
|
|
116
|
+
if (entry.images) {
|
|
117
|
+
for (const image of entry.images) {
|
|
118
|
+
lines.push(" <image:image>");
|
|
119
|
+
lines.push(` <image:loc>${escapeXml(image)}</image:loc>`);
|
|
120
|
+
lines.push(" </image:image>");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
lines.push(" </url>");
|
|
124
|
+
}
|
|
125
|
+
lines.push("</urlset>");
|
|
126
|
+
return lines.join("\n");
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Convert a robots config to text format.
|
|
130
|
+
*/
|
|
131
|
+
export function robotsToText(config) {
|
|
132
|
+
const lines = [];
|
|
133
|
+
const rules = Array.isArray(config.rules) ? config.rules : [config.rules];
|
|
134
|
+
for (const rule of rules) {
|
|
135
|
+
const agents = Array.isArray(rule.userAgent)
|
|
136
|
+
? rule.userAgent
|
|
137
|
+
: [rule.userAgent ?? "*"];
|
|
138
|
+
for (const agent of agents) {
|
|
139
|
+
lines.push(`User-Agent: ${agent}`);
|
|
140
|
+
}
|
|
141
|
+
if (rule.allow) {
|
|
142
|
+
const allows = Array.isArray(rule.allow) ? rule.allow : [rule.allow];
|
|
143
|
+
for (const allow of allows) {
|
|
144
|
+
lines.push(`Allow: ${allow}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (rule.disallow) {
|
|
148
|
+
const disallows = Array.isArray(rule.disallow)
|
|
149
|
+
? rule.disallow
|
|
150
|
+
: [rule.disallow];
|
|
151
|
+
for (const disallow of disallows) {
|
|
152
|
+
lines.push(`Disallow: ${disallow}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (rule.crawlDelay !== undefined) {
|
|
156
|
+
lines.push(`Crawl-delay: ${rule.crawlDelay}`);
|
|
157
|
+
}
|
|
158
|
+
lines.push("");
|
|
159
|
+
}
|
|
160
|
+
if (config.sitemap) {
|
|
161
|
+
const sitemaps = Array.isArray(config.sitemap)
|
|
162
|
+
? config.sitemap
|
|
163
|
+
: [config.sitemap];
|
|
164
|
+
for (const sitemap of sitemaps) {
|
|
165
|
+
lines.push(`Sitemap: ${sitemap}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (config.host) {
|
|
169
|
+
lines.push(`Host: ${config.host}`);
|
|
170
|
+
}
|
|
171
|
+
return lines.join("\n").trim() + "\n";
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Convert a manifest config to JSON string.
|
|
175
|
+
*/
|
|
176
|
+
export function manifestToJson(config) {
|
|
177
|
+
return JSON.stringify(config, null, 2);
|
|
178
|
+
}
|
|
179
|
+
function escapeXml(s) {
|
|
180
|
+
return s
|
|
181
|
+
.replace(/&/g, "&")
|
|
182
|
+
.replace(/</g, "<")
|
|
183
|
+
.replace(/>/g, ">")
|
|
184
|
+
.replace(/"/g, """)
|
|
185
|
+
.replace(/'/g, "'");
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Scan an app directory for metadata files.
|
|
189
|
+
*/
|
|
190
|
+
export function scanMetadataFiles(appDir) {
|
|
191
|
+
const routes = [];
|
|
192
|
+
// Scan the app directory recursively
|
|
193
|
+
function scan(dir, urlPrefix) {
|
|
194
|
+
if (!fs.existsSync(dir))
|
|
195
|
+
return;
|
|
196
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
197
|
+
for (const entry of entries) {
|
|
198
|
+
if (entry.isDirectory()) {
|
|
199
|
+
// Skip route group parentheses from URL
|
|
200
|
+
const dirName = entry.name;
|
|
201
|
+
const isRouteGroup = dirName.startsWith("(") && dirName.endsWith(")");
|
|
202
|
+
const nextUrlPrefix = isRouteGroup
|
|
203
|
+
? urlPrefix
|
|
204
|
+
: `${urlPrefix}/${dirName}`;
|
|
205
|
+
scan(path.join(dir, dirName), nextUrlPrefix);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
// Check each metadata file pattern
|
|
209
|
+
const fileName = entry.name;
|
|
210
|
+
const baseName = fileName.replace(/\.[^.]+$/, "");
|
|
211
|
+
const ext = fileName.slice(baseName.length);
|
|
212
|
+
for (const [metaType, config] of Object.entries(METADATA_FILE_MAP)) {
|
|
213
|
+
// Check if the base name matches
|
|
214
|
+
if (baseName !== metaType)
|
|
215
|
+
continue;
|
|
216
|
+
// Check nestability — non-nestable types only at root
|
|
217
|
+
if (!config.nestable && urlPrefix !== "")
|
|
218
|
+
continue;
|
|
219
|
+
// Check if this is a static or dynamic variant
|
|
220
|
+
const isStatic = config.staticExtensions.includes(ext);
|
|
221
|
+
const isDynamic = config.dynamicExtensions.includes(ext);
|
|
222
|
+
if (!isStatic && !isDynamic)
|
|
223
|
+
continue;
|
|
224
|
+
routes.push({
|
|
225
|
+
type: metaType,
|
|
226
|
+
isDynamic,
|
|
227
|
+
filePath: path.join(dir, fileName),
|
|
228
|
+
servedUrl: urlPrefix === ""
|
|
229
|
+
? config.urlPath
|
|
230
|
+
: `${urlPrefix}${config.urlPath}`,
|
|
231
|
+
contentType: isStatic
|
|
232
|
+
? getStaticContentType(ext, config.contentType)
|
|
233
|
+
: config.contentType,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
scan(appDir, "");
|
|
239
|
+
// Deduplicate: if both dynamic and static variants exist at the same URL,
|
|
240
|
+
// keep only the dynamic one (matches Next.js behavior).
|
|
241
|
+
const byUrl = new Map();
|
|
242
|
+
for (const route of routes) {
|
|
243
|
+
const existing = byUrl.get(route.servedUrl);
|
|
244
|
+
if (!existing) {
|
|
245
|
+
byUrl.set(route.servedUrl, route);
|
|
246
|
+
}
|
|
247
|
+
else if (route.isDynamic && !existing.isDynamic) {
|
|
248
|
+
// Dynamic takes priority over static
|
|
249
|
+
byUrl.set(route.servedUrl, route);
|
|
250
|
+
}
|
|
251
|
+
// If both are static or both dynamic, keep the first one found
|
|
252
|
+
}
|
|
253
|
+
return Array.from(byUrl.values());
|
|
254
|
+
}
|
|
255
|
+
function getStaticContentType(ext, fallback) {
|
|
256
|
+
const map = {
|
|
257
|
+
".xml": "application/xml",
|
|
258
|
+
".txt": "text/plain",
|
|
259
|
+
".json": "application/json",
|
|
260
|
+
".webmanifest": "application/manifest+json",
|
|
261
|
+
".ico": "image/x-icon",
|
|
262
|
+
".png": "image/png",
|
|
263
|
+
".jpg": "image/jpeg",
|
|
264
|
+
".jpeg": "image/jpeg",
|
|
265
|
+
".gif": "image/gif",
|
|
266
|
+
".svg": "image/svg+xml",
|
|
267
|
+
};
|
|
268
|
+
return map[ext] ?? fallback;
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=metadata-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata-routes.js","sourceRoot":"","sources":["../../src/server/metadata-routes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * File-based metadata route handling.\n *\n * Next.js supports special files in the app/ directory that auto-generate\n * metadata routes:\n * - sitemap.ts/.xml → /sitemap.xml (application/xml)\n * - robots.ts/.txt → /robots.txt (text/plain)\n * - manifest.ts/.json/.webmanifest → /manifest.webmanifest (application/manifest+json)\n * - icon.tsx/.png → /icon (image/*)\n * - opengraph-image.tsx/.png → /opengraph-image (image/*)\n * - twitter-image.tsx/.png → /twitter-image (image/*)\n * - apple-icon.tsx/.png → /apple-icon (image/*)\n * - favicon.ico → /favicon.ico (image/x-icon)\n *\n * Dynamic versions (ts/tsx/js) export a default function that returns the data.\n * Static versions (xml/txt/json/png/etc.) are served as-is.\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n// -------------------------------------------------------------------\n// Types matching Next.js MetadataRoute\n// -------------------------------------------------------------------\n\nexport interface SitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?:\n | \"always\"\n | \"hourly\"\n | \"daily\"\n | \"weekly\"\n | \"monthly\"\n | \"yearly\"\n | \"never\";\n priority?: number;\n alternates?: {\n languages?: Record<string, string>;\n };\n images?: string[];\n videos?: Array<{\n title: string;\n thumbnail_loc: string;\n description: string;\n content_loc?: string;\n player_loc?: string;\n duration?: number;\n expiration_date?: string;\n rating?: number;\n view_count?: number;\n publication_date?: string;\n family_friendly?: \"yes\" | \"no\";\n restriction?: { relationship: \"allow\" | \"deny\"; content: string };\n platform?: { relationship: \"allow\" | \"deny\"; content: string };\n live?: \"yes\" | \"no\";\n }>;\n}\n\nexport interface RobotsRule {\n userAgent?: string | string[];\n allow?: string | string[];\n disallow?: string | string[];\n crawlDelay?: number;\n}\n\nexport interface RobotsConfig {\n rules: RobotsRule | RobotsRule[];\n sitemap?: string | string[];\n host?: string;\n}\n\nexport interface ManifestConfig {\n name?: string;\n short_name?: string;\n description?: string;\n start_url?: string;\n display?: \"fullscreen\" | \"standalone\" | \"minimal-ui\" | \"browser\";\n background_color?: string;\n theme_color?: string;\n icons?: Array<{\n src: string;\n sizes?: string;\n type?: string;\n purpose?: string;\n }>;\n [key: string]: unknown;\n}\n\n// -------------------------------------------------------------------\n// Known metadata file patterns\n// -------------------------------------------------------------------\n\n/** Map of metadata file base names to their URL path and content type. */\nexport const METADATA_FILE_MAP: Record<\n string,\n {\n /** URL path this file is served at */\n urlPath: string;\n /** Content type for the response */\n contentType: string;\n /** Whether this can be dynamic (.ts/.tsx/.js) */\n canBeDynamic: boolean;\n /** File extensions for static variants */\n staticExtensions: string[];\n /** File extensions for dynamic variants */\n dynamicExtensions: string[];\n /** Whether this can be nested in sub-segments */\n nestable: boolean;\n }\n> = {\n sitemap: {\n urlPath: \"/sitemap.xml\",\n contentType: \"application/xml\",\n canBeDynamic: true,\n staticExtensions: [\".xml\"],\n dynamicExtensions: [\".ts\", \".js\"],\n nestable: true,\n },\n robots: {\n urlPath: \"/robots.txt\",\n contentType: \"text/plain\",\n canBeDynamic: true,\n staticExtensions: [\".txt\"],\n dynamicExtensions: [\".ts\", \".js\"],\n nestable: false,\n },\n manifest: {\n urlPath: \"/manifest.webmanifest\",\n contentType: \"application/manifest+json\",\n canBeDynamic: true,\n staticExtensions: [\".json\", \".webmanifest\"],\n dynamicExtensions: [\".ts\", \".js\"],\n nestable: false,\n },\n favicon: {\n urlPath: \"/favicon.ico\",\n contentType: \"image/x-icon\",\n canBeDynamic: false,\n staticExtensions: [\".ico\"],\n dynamicExtensions: [],\n nestable: false,\n },\n icon: {\n urlPath: \"/icon\",\n contentType: \"image/png\",\n canBeDynamic: true,\n staticExtensions: [\".ico\", \".jpg\", \".jpeg\", \".png\", \".svg\"],\n dynamicExtensions: [\".ts\", \".tsx\", \".js\"],\n nestable: true,\n },\n \"opengraph-image\": {\n urlPath: \"/opengraph-image\",\n contentType: \"image/png\",\n canBeDynamic: true,\n staticExtensions: [\".jpg\", \".jpeg\", \".png\", \".gif\"],\n dynamicExtensions: [\".ts\", \".tsx\", \".js\"],\n nestable: true,\n },\n \"twitter-image\": {\n urlPath: \"/twitter-image\",\n contentType: \"image/png\",\n canBeDynamic: true,\n staticExtensions: [\".jpg\", \".jpeg\", \".png\", \".gif\"],\n dynamicExtensions: [\".ts\", \".tsx\", \".js\"],\n nestable: true,\n },\n \"apple-icon\": {\n urlPath: \"/apple-icon\",\n contentType: \"image/png\",\n canBeDynamic: true,\n staticExtensions: [\".jpg\", \".jpeg\", \".png\"],\n dynamicExtensions: [\".ts\", \".tsx\", \".js\"],\n nestable: true,\n },\n};\n\n// -------------------------------------------------------------------\n// Serializers\n// -------------------------------------------------------------------\n\n/**\n * Convert a sitemap array to XML string.\n */\nexport function sitemapToXml(entries: SitemapEntry[]): string {\n const lines = [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">',\n ];\n\n for (const entry of entries) {\n lines.push(\" <url>\");\n lines.push(` <loc>${escapeXml(entry.url)}</loc>`);\n\n if (entry.lastModified) {\n const date =\n entry.lastModified instanceof Date\n ? entry.lastModified.toISOString()\n : entry.lastModified;\n lines.push(` <lastmod>${escapeXml(date)}</lastmod>`);\n }\n\n if (entry.changeFrequency) {\n lines.push(\n ` <changefreq>${escapeXml(entry.changeFrequency)}</changefreq>`,\n );\n }\n\n if (entry.priority !== undefined) {\n lines.push(` <priority>${entry.priority}</priority>`);\n }\n\n if (entry.images) {\n for (const image of entry.images) {\n lines.push(\" <image:image>\");\n lines.push(` <image:loc>${escapeXml(image)}</image:loc>`);\n lines.push(\" </image:image>\");\n }\n }\n\n lines.push(\" </url>\");\n }\n\n lines.push(\"</urlset>\");\n return lines.join(\"\\n\");\n}\n\n/**\n * Convert a robots config to text format.\n */\nexport function robotsToText(config: RobotsConfig): string {\n const lines: string[] = [];\n const rules = Array.isArray(config.rules) ? config.rules : [config.rules];\n\n for (const rule of rules) {\n const agents = Array.isArray(rule.userAgent)\n ? rule.userAgent\n : [rule.userAgent ?? \"*\"];\n\n for (const agent of agents) {\n lines.push(`User-Agent: ${agent}`);\n }\n\n if (rule.allow) {\n const allows = Array.isArray(rule.allow) ? rule.allow : [rule.allow];\n for (const allow of allows) {\n lines.push(`Allow: ${allow}`);\n }\n }\n\n if (rule.disallow) {\n const disallows = Array.isArray(rule.disallow)\n ? rule.disallow\n : [rule.disallow];\n for (const disallow of disallows) {\n lines.push(`Disallow: ${disallow}`);\n }\n }\n\n if (rule.crawlDelay !== undefined) {\n lines.push(`Crawl-delay: ${rule.crawlDelay}`);\n }\n\n lines.push(\"\");\n }\n\n if (config.sitemap) {\n const sitemaps = Array.isArray(config.sitemap)\n ? config.sitemap\n : [config.sitemap];\n for (const sitemap of sitemaps) {\n lines.push(`Sitemap: ${sitemap}`);\n }\n }\n\n if (config.host) {\n lines.push(`Host: ${config.host}`);\n }\n\n return lines.join(\"\\n\").trim() + \"\\n\";\n}\n\n/**\n * Convert a manifest config to JSON string.\n */\nexport function manifestToJson(config: ManifestConfig): string {\n return JSON.stringify(config, null, 2);\n}\n\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n// -------------------------------------------------------------------\n// Metadata route discovery\n// -------------------------------------------------------------------\n\nexport interface MetadataFileRoute {\n /** Type of metadata file */\n type: string;\n /** Whether this is a dynamic (code-generated) route */\n isDynamic: boolean;\n /** Absolute file path */\n filePath: string;\n /** URL path this file is served at */\n servedUrl: string;\n /** Content type for the response */\n contentType: string;\n}\n\n/**\n * Scan an app directory for metadata files.\n */\nexport function scanMetadataFiles(appDir: string): MetadataFileRoute[] {\n const routes: MetadataFileRoute[] = [];\n\n // Scan the app directory recursively\n function scan(dir: string, urlPrefix: string): void {\n if (!fs.existsSync(dir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n // Skip route group parentheses from URL\n const dirName = entry.name;\n const isRouteGroup = dirName.startsWith(\"(\") && dirName.endsWith(\")\");\n const nextUrlPrefix = isRouteGroup\n ? urlPrefix\n : `${urlPrefix}/${dirName}`;\n scan(path.join(dir, dirName), nextUrlPrefix);\n continue;\n }\n\n // Check each metadata file pattern\n const fileName = entry.name;\n const baseName = fileName.replace(/\\.[^.]+$/, \"\");\n const ext = fileName.slice(baseName.length);\n\n for (const [metaType, config] of Object.entries(METADATA_FILE_MAP)) {\n // Check if the base name matches\n if (baseName !== metaType) continue;\n\n // Check nestability — non-nestable types only at root\n if (!config.nestable && urlPrefix !== \"\") continue;\n\n // Check if this is a static or dynamic variant\n const isStatic = config.staticExtensions.includes(ext);\n const isDynamic = config.dynamicExtensions.includes(ext);\n\n if (!isStatic && !isDynamic) continue;\n\n routes.push({\n type: metaType,\n isDynamic,\n filePath: path.join(dir, fileName),\n servedUrl:\n urlPrefix === \"\"\n ? config.urlPath\n : `${urlPrefix}${config.urlPath}`,\n contentType: isStatic\n ? getStaticContentType(ext, config.contentType)\n : config.contentType,\n });\n }\n }\n }\n\n scan(appDir, \"\");\n\n // Deduplicate: if both dynamic and static variants exist at the same URL,\n // keep only the dynamic one (matches Next.js behavior).\n const byUrl = new Map<string, MetadataFileRoute>();\n for (const route of routes) {\n const existing = byUrl.get(route.servedUrl);\n if (!existing) {\n byUrl.set(route.servedUrl, route);\n } else if (route.isDynamic && !existing.isDynamic) {\n // Dynamic takes priority over static\n byUrl.set(route.servedUrl, route);\n }\n // If both are static or both dynamic, keep the first one found\n }\n return Array.from(byUrl.values());\n}\n\nfunction getStaticContentType(ext: string, fallback: string): string {\n const map: Record<string, string> = {\n \".xml\": \"application/xml\",\n \".txt\": \"text/plain\",\n \".json\": \"application/json\",\n \".webmanifest\": \"application/manifest+json\",\n \".ico\": \"image/x-icon\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n };\n return map[ext] ?? fallback;\n}\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* proxy.ts / middleware.ts runner
|
|
3
|
+
*
|
|
4
|
+
* Loads and executes the user's proxy.ts (Next.js 16) or middleware.ts file
|
|
5
|
+
* before routing. Runs in Node (not Edge Runtime), per the vinext design.
|
|
6
|
+
*
|
|
7
|
+
* In Next.js 16, proxy.ts replaces middleware.ts:
|
|
8
|
+
* - proxy.ts: default export function, runs on Node.js runtime
|
|
9
|
+
* - middleware.ts: deprecated but still supported for Edge runtime use cases
|
|
10
|
+
*
|
|
11
|
+
* The proxy/middleware receives a NextRequest and can:
|
|
12
|
+
* - Return NextResponse.next() to continue to the route
|
|
13
|
+
* - Return NextResponse.redirect() to redirect
|
|
14
|
+
* - Return NextResponse.rewrite() to rewrite the URL
|
|
15
|
+
* - Set/modify headers and cookies
|
|
16
|
+
* - Return a Response directly (e.g., for auth guards)
|
|
17
|
+
*
|
|
18
|
+
* Supports the `config.matcher` export for path filtering.
|
|
19
|
+
*/
|
|
20
|
+
import type { ViteDevServer } from "vite";
|
|
21
|
+
/**
|
|
22
|
+
* Find the proxy or middleware file in the project root.
|
|
23
|
+
* Checks for proxy.ts (Next.js 16) first, then falls back to middleware.ts.
|
|
24
|
+
* If middleware.ts is found, logs a deprecation warning.
|
|
25
|
+
*/
|
|
26
|
+
export declare function findMiddlewareFile(root: string): string | null;
|
|
27
|
+
/** Matcher pattern from middleware config export. */
|
|
28
|
+
type MatcherConfig = string | string[] | {
|
|
29
|
+
source: string;
|
|
30
|
+
regexp?: string;
|
|
31
|
+
locale?: boolean;
|
|
32
|
+
has?: any[];
|
|
33
|
+
missing?: any[];
|
|
34
|
+
}[];
|
|
35
|
+
/**
|
|
36
|
+
* Check if a pathname matches the middleware matcher config.
|
|
37
|
+
* If no matcher is configured, middleware runs on all paths
|
|
38
|
+
* except static files and internal Next.js paths.
|
|
39
|
+
*/
|
|
40
|
+
export declare function matchesMiddleware(pathname: string, matcher: MatcherConfig | undefined): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Match a single pattern against a pathname.
|
|
43
|
+
* Supports Next.js matcher patterns:
|
|
44
|
+
* /about -> exact match
|
|
45
|
+
* /dashboard/:path* -> prefix match with params
|
|
46
|
+
* /api/:path+ -> one or more segments
|
|
47
|
+
* /((?!api|_next).*) -> regex patterns
|
|
48
|
+
*/
|
|
49
|
+
export declare function matchPattern(pathname: string, pattern: string): boolean;
|
|
50
|
+
/** Result of running middleware. */
|
|
51
|
+
export interface MiddlewareResult {
|
|
52
|
+
/** Whether to continue to the route handler. */
|
|
53
|
+
continue: boolean;
|
|
54
|
+
/** If set, redirect to this URL. */
|
|
55
|
+
redirectUrl?: string;
|
|
56
|
+
/** HTTP status for redirect (default 307). */
|
|
57
|
+
redirectStatus?: number;
|
|
58
|
+
/** If set, rewrite to this URL (internal). */
|
|
59
|
+
rewriteUrl?: string;
|
|
60
|
+
/** HTTP status for rewrite (e.g. 403 from NextResponse.rewrite(url, { status: 403 })). */
|
|
61
|
+
rewriteStatus?: number;
|
|
62
|
+
/** Headers to set on the response. */
|
|
63
|
+
responseHeaders?: Headers;
|
|
64
|
+
/** If the middleware returned a full Response, use it directly. */
|
|
65
|
+
response?: Response;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Load and execute middleware for a given request.
|
|
69
|
+
*
|
|
70
|
+
* @param server - Vite dev server (for SSR module loading)
|
|
71
|
+
* @param middlewarePath - Absolute path to the middleware file
|
|
72
|
+
* @param request - The incoming Request object
|
|
73
|
+
* @returns Middleware result describing what action to take
|
|
74
|
+
*/
|
|
75
|
+
export declare function runMiddleware(server: ViteDevServer, middlewarePath: string, request: Request): Promise<MiddlewareResult>;
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AA8B1C;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB9D;AAED,qDAAqD;AACrD,KAAK,aAAa,GACd,MAAM,GACN,MAAM,EAAE,GACR;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,EAAE,CAAC;AAE1F;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,GAAG,SAAS,GACjC,OAAO,CAyBT;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAwBvE;AAED,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAC;IAClB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,aAAa,EACrB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,gBAAgB,CAAC,CAiG3B"}
|