vinext 0.0.21 → 0.0.23
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/dist/build/static-export.d.ts.map +1 -1
- package/dist/build/static-export.js +9 -7
- package/dist/build/static-export.js.map +1 -1
- package/dist/config/next-config.d.ts +4 -1
- package/dist/config/next-config.d.ts.map +1 -1
- package/dist/config/next-config.js +10 -5
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +17 -4
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +381 -38
- package/dist/index.js.map +1 -1
- package/dist/routing/app-router.d.ts +2 -1
- package/dist/routing/app-router.d.ts.map +1 -1
- package/dist/routing/app-router.js +74 -107
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/file-matcher.d.ts +24 -0
- package/dist/routing/file-matcher.d.ts.map +1 -0
- package/dist/routing/file-matcher.js +75 -0
- package/dist/routing/file-matcher.js.map +1 -0
- package/dist/routing/pages-router.d.ts +3 -2
- package/dist/routing/pages-router.d.ts.map +1 -1
- package/dist/routing/pages-router.js +25 -44
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/routing/utils.d.ts +25 -0
- package/dist/routing/utils.d.ts.map +1 -0
- package/dist/routing/utils.js +70 -0
- package/dist/routing/utils.js.map +1 -0
- package/dist/server/app-dev-server.d.ts.map +1 -1
- package/dist/server/app-dev-server.js +123 -47
- package/dist/server/app-dev-server.js.map +1 -1
- package/dist/server/dev-server.d.ts +2 -1
- package/dist/server/dev-server.d.ts.map +1 -1
- package/dist/server/dev-server.js +93 -18
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/prod-server.d.ts.map +1 -1
- package/dist/server/prod-server.js +33 -2
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/request-log.d.ts +34 -0
- package/dist/server/request-log.d.ts.map +1 -0
- package/dist/server/request-log.js +65 -0
- package/dist/server/request-log.js.map +1 -0
- package/dist/shims/cache-runtime.d.ts.map +1 -1
- package/dist/shims/cache-runtime.js +5 -1
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +7 -1
- package/dist/shims/cache.d.ts.map +1 -1
- package/dist/shims/cache.js +30 -5
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/head.d.ts +11 -0
- package/dist/shims/head.d.ts.map +1 -1
- package/dist/shims/head.js +21 -0
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts +8 -0
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/headers.js +41 -0
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/metadata.d.ts +1 -0
- package/dist/shims/metadata.d.ts.map +1 -1
- package/dist/shims/metadata.js +5 -1
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/script.d.ts.map +1 -1
- package/dist/shims/script.js +7 -1
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server.d.ts.map +1 -1
- package/dist/shims/server.js +2 -1
- package/dist/shims/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages-router.js","sourceRoot":"","sources":["../../src/routing/pages-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { glob } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport interface Route {\n /** URL pattern, e.g. \"/\" or \"/about\" or \"/posts/:id\" */\n pattern: string;\n /** Absolute file path to the page component */\n filePath: string;\n /** Whether this is a dynamic route */\n isDynamic: boolean;\n /** Parameter names for dynamic segments */\n params: string[];\n}\n\n// Route cache — invalidated when pages directory changes\nconst routeCache = new Map<string, { routes: Route[]; promise: Promise<Route[]> }>();\n\n/**\n * Invalidate cached routes for a given pages directory.\n * Called by the file watcher when pages are added/removed.\n */\nexport function invalidateRouteCache(pagesDir: string): void {\n routeCache.delete(`pages:${pagesDir}`);\n routeCache.delete(`api:${pagesDir}`);\n}\n\n/**\n * Scan the pages/ directory and return a list of routes.\n * Results are cached — call invalidateRouteCache() when files change.\n *\n * Follows Next.js Pages Router conventions:\n * - pages/index.tsx -> /\n * - pages/about.tsx -> /about\n * - pages/posts/[id].tsx -> /posts/:id\n * - pages/[...slug].tsx -> /:slug+\n * - Ignores _app.tsx, _document.tsx, _error.tsx, files starting with _\n * - Ignores pages/api/ (handled separately later)\n */\nexport async function pagesRouter(pagesDir: string): Promise<Route[]> {\n const cacheKey = `pages:${pagesDir}`;\n const cached = routeCache.get(cacheKey);\n if (cached) return cached.promise;\n\n const promise = scanPageRoutes(pagesDir);\n routeCache.set(cacheKey, { routes: [], promise });\n const routes = await promise;\n routeCache.set(cacheKey, { routes, promise });\n return routes;\n}\n\nasync function scanPageRoutes(pagesDir: string): Promise<Route[]> {\n const routes: Route[] = [];\n\n // Use function form of exclude for Node < 22.14 compatibility (string arrays require >= 22.14)\n for await (const file of glob(\"**/*.{tsx,ts,jsx,js}\", { cwd: pagesDir, exclude: (name: string) => name === \"api\" || name.startsWith(\"_\") })) {\n const route = fileToRoute(file, pagesDir);\n if (route) routes.push(route);\n }\n\n // Sort: static routes first, then dynamic, then catch-all\n routes.sort((a, b) => {\n const diff = routePrecedence(a.pattern) - routePrecedence(b.pattern);\n return diff !== 0 ? diff : a.pattern.localeCompare(b.pattern);\n });\n\n return routes;\n}\n\n/**\n * Convert a file path relative to pages/ into a Route.\n */\nfunction fileToRoute(file: string, pagesDir: string): Route | null {\n // Remove extension\n const withoutExt = file.replace(/\\.(tsx?|jsx?)$/, \"\");\n\n // Convert to URL segments\n const segments = withoutExt.split(path.sep);\n\n // Handle index files: pages/index.tsx -> /\n const lastSegment = segments[segments.length - 1];\n if (lastSegment === \"index\") {\n segments.pop();\n }\n\n const params: string[] = [];\n let isDynamic = false;\n\n // Convert Next.js dynamic segments to URL patterns\n const urlSegments = segments.map((segment) => {\n // Catch-all: [...slug] -> :slug+ (param names may contain hyphens)\n const catchAllMatch = segment.match(/^\\[\\.\\.\\.([\\w-]+)\\]$/);\n if (catchAllMatch) {\n isDynamic = true;\n params.push(catchAllMatch[1]);\n return `:${catchAllMatch[1]}+`;\n }\n\n // Optional catch-all: [[...slug]] -> :slug* (param names may contain hyphens)\n const optionalCatchAllMatch = segment.match(/^\\[\\[\\.\\.\\.([\\w-]+)\\]\\]$/);\n if (optionalCatchAllMatch) {\n isDynamic = true;\n params.push(optionalCatchAllMatch[1]);\n return `:${optionalCatchAllMatch[1]}*`;\n }\n\n // Dynamic segment: [id] -> :id (param names may contain hyphens)\n const dynamicMatch = segment.match(/^\\[([\\w-]+)\\]$/);\n if (dynamicMatch) {\n isDynamic = true;\n params.push(dynamicMatch[1]);\n return `:${dynamicMatch[1]}`;\n }\n\n return segment;\n });\n\n const pattern = \"/\" + urlSegments.join(\"/\");\n\n return {\n pattern: pattern === \"/\" ? \"/\" : pattern,\n filePath: path.join(pagesDir, file),\n isDynamic,\n params,\n };\n}\n\n/**\n * Route precedence — lower score is higher priority.\n * Matches Next.js specificity rules:\n * 1. Static routes first (scored by segment count, more = more specific)\n * 2. Dynamic segments penalized by position\n * 3. Catch-all comes after dynamic\n * 4. Optional catch-all last\n * 5. Lexicographic tiebreaker for determinism\n */\nfunction routePrecedence(pattern: string): number {\n const parts = pattern.split(\"/\").filter(Boolean);\n let score = 0;\n for (let i = 0; i < parts.length; i++) {\n const p = parts[i];\n if (p.endsWith(\"+\")) {\n score += 10000 + i; // catch-all: high penalty\n } else if (p.endsWith(\"*\")) {\n score += 20000 + i; // optional catch-all: highest penalty\n } else if (p.startsWith(\":\")) {\n score += 100 + i; // dynamic: moderate penalty by position\n }\n // static segments contribute nothing (better specificity)\n }\n return score;\n}\n\n/**\n * Match a URL path against a route pattern.\n * Returns the matched params or null if no match.\n */\nexport function matchRoute(\n url: string,\n routes: Route[],\n): { route: Route; params: Record<string, string | string[]> } | null {\n // Normalize: strip query string and trailing slash\n const pathname = url.split(\"?\")[0];\n let normalizedUrl =\n pathname === \"/\" ? \"/\" : pathname.replace(/\\/$/, \"\");\n try { normalizedUrl = decodeURIComponent(normalizedUrl); } catch { /* malformed percent-encoding — match as-is */ }\n\n for (const route of routes) {\n const params = matchPattern(normalizedUrl, route.pattern);\n if (params !== null) {\n return { route, params };\n }\n }\n\n return null;\n}\n\n/**\n * Scan the pages/api/ directory and return API routes.\n * Results are cached — call invalidateRouteCache() when files change.\n *\n * Follows Next.js conventions:\n * - pages/api/hello.ts -> /api/hello\n * - pages/api/users/[id].ts -> /api/users/:id\n */\nexport async function apiRouter(pagesDir: string): Promise<Route[]> {\n const cacheKey = `api:${pagesDir}`;\n const cached = routeCache.get(cacheKey);\n if (cached) return cached.promise;\n\n const promise = scanApiRoutes(pagesDir);\n routeCache.set(cacheKey, { routes: [], promise });\n const routes = await promise;\n routeCache.set(cacheKey, { routes, promise });\n return routes;\n}\n\nasync function scanApiRoutes(pagesDir: string): Promise<Route[]> {\n const apiDir = path.join(pagesDir, \"api\");\n let files: string[];\n try {\n files = [];\n for await (const file of glob(\"**/*.{ts,tsx,js,jsx}\", { cwd: apiDir })) {\n files.push(file);\n }\n } catch {\n files = [];\n }\n\n const routes: Route[] = [];\n\n for (const file of files) {\n // Reuse fileToRoute but pretend the file is under a virtual \"api/\" prefix\n const route = fileToRoute(path.join(\"api\", file), pagesDir);\n if (route) {\n routes.push(route);\n }\n }\n\n // Sort same as page routes\n routes.sort((a, b) => {\n const diff = routePrecedence(a.pattern) - routePrecedence(b.pattern);\n return diff !== 0 ? diff : a.pattern.localeCompare(b.pattern);\n });\n\n return routes;\n}\n\nfunction matchPattern(\n url: string,\n pattern: string,\n): Record<string, string | string[]> | null {\n const urlParts = url.split(\"/\").filter(Boolean);\n const patternParts = pattern.split(\"/\").filter(Boolean);\n\n const params: Record<string, string | string[]> = Object.create(null);\n\n for (let i = 0; i < patternParts.length; i++) {\n const pp = patternParts[i];\n\n // Catch-all: :slug+\n if (pp.endsWith(\"+\")) {\n const paramName = pp.slice(1, -1);\n const remaining = urlParts.slice(i);\n if (remaining.length === 0) return null;\n params[paramName] = remaining;\n return params;\n }\n\n // Optional catch-all: :slug*\n if (pp.endsWith(\"*\")) {\n const paramName = pp.slice(1, -1);\n const remaining = urlParts.slice(i);\n params[paramName] = remaining;\n return params;\n }\n\n // Dynamic segment: :id\n if (pp.startsWith(\":\")) {\n const paramName = pp.slice(1);\n if (i >= urlParts.length) return null;\n params[paramName] = urlParts[i];\n continue;\n }\n\n // Static segment\n if (i >= urlParts.length || urlParts[i] !== pp) return null;\n }\n\n // All pattern parts matched - check url doesn't have extra segments\n if (urlParts.length !== patternParts.length) return null;\n\n return params;\n}\n\n/**\n * Convert internal route pattern (e.g., \"/posts/:id\", \"/docs/:slug+\")\n * to Next.js bracket format (e.g., \"/posts/[id]\", \"/docs/[...slug]\").\n * Used for __NEXT_DATA__.page which apps expect in Next.js format.\n */\nexport function patternToNextFormat(pattern: string): string {\n return pattern\n .replace(/:([\\w-]+)\\*/g, \"[[...$1]]\") // optional catch-all :slug* -> [[...slug]]\n .replace(/:([\\w-]+)\\+/g, \"[...$1]\") // catch-all :slug+ -> [...slug]\n .replace(/:([\\w-]+)/g, \"[$1]\"); // dynamic :id -> [id]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pages-router.js","sourceRoot":"","sources":["../../src/routing/pages-router.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport { routePrecedence } from \"./utils.js\";\nimport {\n createValidFileMatcher,\n scanWithExtensions,\n type ValidFileMatcher,\n} from \"./file-matcher.js\";\n\nexport interface Route {\n /** URL pattern, e.g. \"/\" or \"/about\" or \"/posts/:id\" */\n pattern: string;\n /** Absolute file path to the page component */\n filePath: string;\n /** Whether this is a dynamic route */\n isDynamic: boolean;\n /** Parameter names for dynamic segments */\n params: string[];\n}\n\n// Route cache — invalidated when pages directory changes\nconst routeCache = new Map<string, { routes: Route[]; promise: Promise<Route[]> }>();\n\n/**\n * Invalidate cached routes for a given pages directory.\n * Called by the file watcher when pages are added/removed.\n */\nexport function invalidateRouteCache(pagesDir: string): void {\n for (const key of routeCache.keys()) {\n if (key.startsWith(`pages:${pagesDir}:`) || key.startsWith(`api:${pagesDir}:`)) {\n routeCache.delete(key);\n }\n }\n}\n\n/**\n * Scan the pages/ directory and return a list of routes.\n * Results are cached — call invalidateRouteCache() when files change.\n *\n * Follows Next.js Pages Router conventions:\n * - pages/index.tsx -> /\n * - pages/about.tsx -> /about\n * - pages/posts/[id].tsx -> /posts/:id\n * - pages/[...slug].tsx -> /:slug+\n * - Ignores _app.tsx, _document.tsx, _error.tsx, files starting with _\n * - Ignores pages/api/ (handled separately later)\n */\nexport async function pagesRouter(\n pagesDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<Route[]> {\n matcher ??= createValidFileMatcher(pageExtensions);\n const cacheKey = `pages:${pagesDir}:${JSON.stringify(matcher.extensions)}`;\n const cached = routeCache.get(cacheKey);\n if (cached) return cached.promise;\n\n const promise = scanPageRoutes(pagesDir, matcher);\n routeCache.set(cacheKey, { routes: [], promise });\n const routes = await promise;\n routeCache.set(cacheKey, { routes, promise });\n return routes;\n}\n\nasync function scanPageRoutes(\n pagesDir: string,\n matcher: ValidFileMatcher,\n): Promise<Route[]> {\n const routes: Route[] = [];\n\n // Use function form of exclude for Node < 22.14 compatibility (string arrays require >= 22.14)\n for await (const file of scanWithExtensions(\n \"**/*\",\n pagesDir,\n matcher.extensions,\n (name: string) => name === \"api\" || name.startsWith(\"_\"),\n )) {\n const route = fileToRoute(file, pagesDir, matcher);\n if (route) routes.push(route);\n }\n\n // Sort: static routes first, then dynamic, then catch-all\n routes.sort((a, b) => {\n const diff = routePrecedence(a.pattern) - routePrecedence(b.pattern);\n return diff !== 0 ? diff : a.pattern.localeCompare(b.pattern);\n });\n\n return routes;\n}\n\n/**\n * Convert a file path relative to pages/ into a Route.\n */\nfunction fileToRoute(\n file: string,\n pagesDir: string,\n matcher: ValidFileMatcher,\n): Route | null {\n // Remove extension\n const withoutExt = matcher.stripExtension(file);\n if (withoutExt === file) return null;\n\n // Convert to URL segments\n const segments = withoutExt.split(path.sep);\n\n // Handle index files: pages/index.tsx -> /\n const lastSegment = segments[segments.length - 1];\n if (lastSegment === \"index\") {\n segments.pop();\n }\n\n const params: string[] = [];\n let isDynamic = false;\n\n // Convert Next.js dynamic segments to URL patterns\n const urlSegments = segments.map((segment) => {\n // Catch-all: [...slug] -> :slug+ (param names may contain hyphens)\n const catchAllMatch = segment.match(/^\\[\\.\\.\\.([\\w-]+)\\]$/);\n if (catchAllMatch) {\n isDynamic = true;\n params.push(catchAllMatch[1]);\n return `:${catchAllMatch[1]}+`;\n }\n\n // Optional catch-all: [[...slug]] -> :slug* (param names may contain hyphens)\n const optionalCatchAllMatch = segment.match(/^\\[\\[\\.\\.\\.([\\w-]+)\\]\\]$/);\n if (optionalCatchAllMatch) {\n isDynamic = true;\n params.push(optionalCatchAllMatch[1]);\n return `:${optionalCatchAllMatch[1]}*`;\n }\n\n // Dynamic segment: [id] -> :id (param names may contain hyphens)\n const dynamicMatch = segment.match(/^\\[([\\w-]+)\\]$/);\n if (dynamicMatch) {\n isDynamic = true;\n params.push(dynamicMatch[1]);\n return `:${dynamicMatch[1]}`;\n }\n\n return segment;\n });\n\n const pattern = \"/\" + urlSegments.join(\"/\");\n\n return {\n pattern: pattern === \"/\" ? \"/\" : pattern,\n filePath: path.join(pagesDir, file),\n isDynamic,\n params,\n };\n}\n\n/**\n * Match a URL path against a route pattern.\n * Returns the matched params or null if no match.\n */\nexport function matchRoute(\n url: string,\n routes: Route[],\n): { route: Route; params: Record<string, string | string[]> } | null {\n // Normalize: strip query string and trailing slash\n const pathname = url.split(\"?\")[0];\n let normalizedUrl =\n pathname === \"/\" ? \"/\" : pathname.replace(/\\/$/, \"\");\n try { normalizedUrl = decodeURIComponent(normalizedUrl); } catch { /* malformed percent-encoding — match as-is */ }\n\n for (const route of routes) {\n const params = matchPattern(normalizedUrl, route.pattern);\n if (params !== null) {\n return { route, params };\n }\n }\n\n return null;\n}\n\n/**\n * Scan the pages/api/ directory and return API routes.\n * Results are cached — call invalidateRouteCache() when files change.\n *\n * Follows Next.js conventions:\n * - pages/api/hello.ts -> /api/hello\n * - pages/api/users/[id].ts -> /api/users/:id\n */\nexport async function apiRouter(\n pagesDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<Route[]> {\n matcher ??= createValidFileMatcher(pageExtensions);\n const cacheKey = `api:${pagesDir}:${JSON.stringify(matcher.extensions)}`;\n const cached = routeCache.get(cacheKey);\n if (cached) return cached.promise;\n\n const promise = scanApiRoutes(pagesDir, matcher);\n routeCache.set(cacheKey, { routes: [], promise });\n const routes = await promise;\n routeCache.set(cacheKey, { routes, promise });\n return routes;\n}\n\nasync function scanApiRoutes(\n pagesDir: string,\n matcher: ValidFileMatcher,\n): Promise<Route[]> {\n const apiDir = path.join(pagesDir, \"api\");\n let files: string[];\n try {\n files = [];\n for await (const file of scanWithExtensions(\n \"**/*\",\n apiDir,\n matcher.extensions,\n )) {\n files.push(file);\n }\n } catch {\n files = [];\n }\n\n const routes: Route[] = [];\n\n for (const file of files) {\n // Reuse fileToRoute but pretend the file is under a virtual \"api/\" prefix\n const route = fileToRoute(path.join(\"api\", file), pagesDir, matcher);\n if (route) {\n routes.push(route);\n }\n }\n\n // Sort same as page routes\n routes.sort((a, b) => {\n const diff = routePrecedence(a.pattern) - routePrecedence(b.pattern);\n return diff !== 0 ? diff : a.pattern.localeCompare(b.pattern);\n });\n\n return routes;\n}\n\nfunction matchPattern(\n url: string,\n pattern: string,\n): Record<string, string | string[]> | null {\n const urlParts = url.split(\"/\").filter(Boolean);\n const patternParts = pattern.split(\"/\").filter(Boolean);\n\n const params: Record<string, string | string[]> = Object.create(null);\n\n for (let i = 0; i < patternParts.length; i++) {\n const pp = patternParts[i];\n\n // Catch-all: :slug+\n if (pp.endsWith(\"+\")) {\n const paramName = pp.slice(1, -1);\n const remaining = urlParts.slice(i);\n if (remaining.length === 0) return null;\n params[paramName] = remaining;\n return params;\n }\n\n // Optional catch-all: :slug*\n if (pp.endsWith(\"*\")) {\n const paramName = pp.slice(1, -1);\n const remaining = urlParts.slice(i);\n params[paramName] = remaining;\n return params;\n }\n\n // Dynamic segment: :id\n if (pp.startsWith(\":\")) {\n const paramName = pp.slice(1);\n if (i >= urlParts.length) return null;\n params[paramName] = urlParts[i];\n continue;\n }\n\n // Static segment\n if (i >= urlParts.length || urlParts[i] !== pp) return null;\n }\n\n // All pattern parts matched - check url doesn't have extra segments\n if (urlParts.length !== patternParts.length) return null;\n\n return params;\n}\n\n/**\n * Convert internal route pattern (e.g., \"/posts/:id\", \"/docs/:slug+\")\n * to Next.js bracket format (e.g., \"/posts/[id]\", \"/docs/[...slug]\").\n * Used for __NEXT_DATA__.page which apps expect in Next.js format.\n */\nexport function patternToNextFormat(pattern: string): string {\n return pattern\n .replace(/:([\\w-]+)\\*/g, \"[[...$1]]\") // optional catch-all :slug* -> [[...slug]]\n .replace(/:([\\w-]+)\\+/g, \"[...$1]\") // catch-all :slug+ -> [...slug]\n .replace(/:([\\w-]+)/g, \"[$1]\"); // dynamic :id -> [id]\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route precedence — lower score is higher priority.
|
|
3
|
+
* Matches Next.js specificity rules:
|
|
4
|
+
* 1. Static routes first (scored by segment count, more = more specific)
|
|
5
|
+
* 2. Dynamic segments penalized by position
|
|
6
|
+
* 3. Catch-all comes after dynamic
|
|
7
|
+
* 4. Optional catch-all last
|
|
8
|
+
* 5. Lexicographic tiebreaker for determinism
|
|
9
|
+
*
|
|
10
|
+
* Key insight: routes with a static prefix before a dynamic/catch-all segment
|
|
11
|
+
* should have higher priority than bare dynamic/catch-all routes at the same
|
|
12
|
+
* depth. E.g., /_sites/:subdomain should match before /:subdomain, and
|
|
13
|
+
* /_sites/:subdomain/:slug* should match before /:slug*.
|
|
14
|
+
*
|
|
15
|
+
* The static-prefix reduction uses a small value (-50 per segment) so that:
|
|
16
|
+
* - It beats the per-dynamic-segment penalty (100), placing prefix routes
|
|
17
|
+
* above their no-prefix equivalents.
|
|
18
|
+
* - It never goes negative, so purely-static routes (score 0) always win.
|
|
19
|
+
* - It is small enough that infix-static bonuses (-500) and catch-all
|
|
20
|
+
* penalties (1000+) are not swamped, preserving their relative ordering.
|
|
21
|
+
* E.g. /:locale/blog/:path+ (with infix "blog") correctly beats /:locale/:path+
|
|
22
|
+
* even when both share the same "locale-test" static prefix.
|
|
23
|
+
*/
|
|
24
|
+
export declare function routePrecedence(pattern: string): number;
|
|
25
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/routing/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA+CvD"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route precedence — lower score is higher priority.
|
|
3
|
+
* Matches Next.js specificity rules:
|
|
4
|
+
* 1. Static routes first (scored by segment count, more = more specific)
|
|
5
|
+
* 2. Dynamic segments penalized by position
|
|
6
|
+
* 3. Catch-all comes after dynamic
|
|
7
|
+
* 4. Optional catch-all last
|
|
8
|
+
* 5. Lexicographic tiebreaker for determinism
|
|
9
|
+
*
|
|
10
|
+
* Key insight: routes with a static prefix before a dynamic/catch-all segment
|
|
11
|
+
* should have higher priority than bare dynamic/catch-all routes at the same
|
|
12
|
+
* depth. E.g., /_sites/:subdomain should match before /:subdomain, and
|
|
13
|
+
* /_sites/:subdomain/:slug* should match before /:slug*.
|
|
14
|
+
*
|
|
15
|
+
* The static-prefix reduction uses a small value (-50 per segment) so that:
|
|
16
|
+
* - It beats the per-dynamic-segment penalty (100), placing prefix routes
|
|
17
|
+
* above their no-prefix equivalents.
|
|
18
|
+
* - It never goes negative, so purely-static routes (score 0) always win.
|
|
19
|
+
* - It is small enough that infix-static bonuses (-500) and catch-all
|
|
20
|
+
* penalties (1000+) are not swamped, preserving their relative ordering.
|
|
21
|
+
* E.g. /:locale/blog/:path+ (with infix "blog") correctly beats /:locale/:path+
|
|
22
|
+
* even when both share the same "locale-test" static prefix.
|
|
23
|
+
*/
|
|
24
|
+
export function routePrecedence(pattern) {
|
|
25
|
+
const parts = pattern.split("/").filter(Boolean);
|
|
26
|
+
let score = 0;
|
|
27
|
+
let staticPrefixCount = 0;
|
|
28
|
+
for (const p of parts) {
|
|
29
|
+
if (p.startsWith(":") || p.endsWith("+") || p.endsWith("*"))
|
|
30
|
+
break;
|
|
31
|
+
staticPrefixCount++;
|
|
32
|
+
}
|
|
33
|
+
for (let i = 0; i < parts.length; i++) {
|
|
34
|
+
const p = parts[i];
|
|
35
|
+
if (p.endsWith("+")) {
|
|
36
|
+
score += 1000 + i; // catch-all: moderate penalty
|
|
37
|
+
}
|
|
38
|
+
else if (p.endsWith("*")) {
|
|
39
|
+
score += 2000 + i; // optional catch-all: high penalty
|
|
40
|
+
}
|
|
41
|
+
else if (p.startsWith(":")) {
|
|
42
|
+
score += 100 + i; // dynamic: small penalty by position
|
|
43
|
+
}
|
|
44
|
+
else if (i >= staticPrefixCount) {
|
|
45
|
+
// Static segment interleaved after a dynamic segment (infix static).
|
|
46
|
+
// Boost priority — more specific than a bare catch-all.
|
|
47
|
+
// The -500 compounds for each infix static segment, so routes with more
|
|
48
|
+
// static infixes score lower (higher priority) than those with fewer.
|
|
49
|
+
// E.g. /:a/x/y/:b+ (-1000) beats /:a/x/:b+ (-500) beats /:a/:b+ (0).
|
|
50
|
+
// This is intentional: more static constraints = more specific route.
|
|
51
|
+
score -= 500;
|
|
52
|
+
}
|
|
53
|
+
// Static prefix segments (i < staticPrefixCount) are handled below.
|
|
54
|
+
}
|
|
55
|
+
// Apply a small reduction per static-prefix segment for routes that also
|
|
56
|
+
// contain dynamic segments. This ensures /_sites/:subdomain sorts above
|
|
57
|
+
// /:subdomain, and /_sites/:slug* sorts above /:slug*, while keeping the
|
|
58
|
+
// final score positive (so purely-static routes at score=0 always win).
|
|
59
|
+
//
|
|
60
|
+
// 50 is deliberately smaller than the dynamic-segment penalty (100) so
|
|
61
|
+
// one static prefix segment is enough to beat one bare dynamic segment,
|
|
62
|
+
// and smaller than the infix-static bonus (500) so that infix ordering is
|
|
63
|
+
// not disturbed between two routes that share the same prefix.
|
|
64
|
+
const isDynamic = parts.some((p) => p.startsWith(":") || p.endsWith("+") || p.endsWith("*"));
|
|
65
|
+
if (isDynamic && staticPrefixCount > 0) {
|
|
66
|
+
score -= staticPrefixCount * 50;
|
|
67
|
+
}
|
|
68
|
+
return score;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/routing/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjD,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,IAAI,iBAAiB,GAAG,CAAC,CAAC;IAC1B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,MAAM;QACnE,iBAAiB,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACnB,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,8BAA8B;QACnD,CAAC;aAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,mCAAmC;QACxD,CAAC;aAAM,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,KAAK,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,qCAAqC;QACzD,CAAC;aAAM,IAAI,CAAC,IAAI,iBAAiB,EAAE,CAAC;YAClC,qEAAqE;YACrE,wDAAwD;YACxD,wEAAwE;YACxE,sEAAsE;YACtE,qEAAqE;YACrE,sEAAsE;YACtE,KAAK,IAAI,GAAG,CAAC;QACf,CAAC;QACD,oEAAoE;IACtE,CAAC;IAED,yEAAyE;IACzE,wEAAwE;IACxE,yEAAyE;IACzE,wEAAwE;IACxE,EAAE;IACF,uEAAuE;IACvE,wEAAwE;IACxE,0EAA0E;IAC1E,+DAA+D;IAC/D,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAC1B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAC/D,CAAC;IACF,IAAI,SAAS,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;QACvC,KAAK,IAAI,iBAAiB,GAAG,EAAE,CAAC;IAClC,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["/**\n * Route precedence — lower score is higher priority.\n * Matches Next.js specificity rules:\n * 1. Static routes first (scored by segment count, more = more specific)\n * 2. Dynamic segments penalized by position\n * 3. Catch-all comes after dynamic\n * 4. Optional catch-all last\n * 5. Lexicographic tiebreaker for determinism\n *\n * Key insight: routes with a static prefix before a dynamic/catch-all segment\n * should have higher priority than bare dynamic/catch-all routes at the same\n * depth. E.g., /_sites/:subdomain should match before /:subdomain, and\n * /_sites/:subdomain/:slug* should match before /:slug*.\n *\n * The static-prefix reduction uses a small value (-50 per segment) so that:\n * - It beats the per-dynamic-segment penalty (100), placing prefix routes\n * above their no-prefix equivalents.\n * - It never goes negative, so purely-static routes (score 0) always win.\n * - It is small enough that infix-static bonuses (-500) and catch-all\n * penalties (1000+) are not swamped, preserving their relative ordering.\n * E.g. /:locale/blog/:path+ (with infix \"blog\") correctly beats /:locale/:path+\n * even when both share the same \"locale-test\" static prefix.\n */\nexport function routePrecedence(pattern: string): number {\n const parts = pattern.split(\"/\").filter(Boolean);\n let score = 0;\n\n let staticPrefixCount = 0;\n for (const p of parts) {\n if (p.startsWith(\":\") || p.endsWith(\"+\") || p.endsWith(\"*\")) break;\n staticPrefixCount++;\n }\n\n for (let i = 0; i < parts.length; i++) {\n const p = parts[i];\n if (p.endsWith(\"+\")) {\n score += 1000 + i; // catch-all: moderate penalty\n } else if (p.endsWith(\"*\")) {\n score += 2000 + i; // optional catch-all: high penalty\n } else if (p.startsWith(\":\")) {\n score += 100 + i; // dynamic: small penalty by position\n } else if (i >= staticPrefixCount) {\n // Static segment interleaved after a dynamic segment (infix static).\n // Boost priority — more specific than a bare catch-all.\n // The -500 compounds for each infix static segment, so routes with more\n // static infixes score lower (higher priority) than those with fewer.\n // E.g. /:a/x/y/:b+ (-1000) beats /:a/x/:b+ (-500) beats /:a/:b+ (0).\n // This is intentional: more static constraints = more specific route.\n score -= 500;\n }\n // Static prefix segments (i < staticPrefixCount) are handled below.\n }\n\n // Apply a small reduction per static-prefix segment for routes that also\n // contain dynamic segments. This ensures /_sites/:subdomain sorts above\n // /:subdomain, and /_sites/:slug* sorts above /:slug*, while keeping the\n // final score positive (so purely-static routes at score=0 always win).\n //\n // 50 is deliberately smaller than the dynamic-segment penalty (100) so\n // one static prefix segment is enough to beat one bare dynamic segment,\n // and smaller than the infix-static bonus (500) so that infix ordering is\n // not disturbed between two routes that share the same prefix.\n const isDynamic = parts.some(\n (p) => p.startsWith(\":\") || p.endsWith(\"+\") || p.endsWith(\"*\"),\n );\n if (isDynamic && staticPrefixCount > 0) {\n score -= staticPrefixCount * 50;\n }\n\n return score;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-dev-server.d.ts","sourceRoot":"","sources":["../../src/server/app-dev-server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAKtF;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE;QACT,WAAW,EAAE,WAAW,EAAE,CAAC;QAC3B,UAAU,EAAE,WAAW,EAAE,CAAC;QAC1B,QAAQ,EAAE,WAAW,EAAE,CAAC;KACzB,CAAC;IACF,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,4GAA4G;IAC5G,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,uGAAuG;IACvG,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,EAAE,EAClB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,EAC9B,cAAc,CAAC,EAAE,iBAAiB,EAAE,EACpC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,EAC/B,QAAQ,CAAC,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,OAAO,EACvB,MAAM,CAAC,EAAE,eAAe,GACvB,MAAM,
|
|
1
|
+
{"version":3,"file":"app-dev-server.d.ts","sourceRoot":"","sources":["../../src/server/app-dev-server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAKtF;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE;QACT,WAAW,EAAE,WAAW,EAAE,CAAC;QAC3B,UAAU,EAAE,WAAW,EAAE,CAAC;QAC1B,QAAQ,EAAE,WAAW,EAAE,CAAC;KACzB,CAAC;IACF,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,4GAA4G;IAC5G,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,uGAAuG;IACvG,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,EAAE,EAClB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,EAC9B,cAAc,CAAC,EAAE,iBAAiB,EAAE,EACpC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,EAC/B,QAAQ,CAAC,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,OAAO,EACvB,MAAM,CAAC,EAAE,eAAe,GACvB,MAAM,CAgvER;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAibzC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CA+T7C"}
|
|
@@ -397,7 +397,7 @@ async function renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, req
|
|
|
397
397
|
}
|
|
398
398
|
}
|
|
399
399
|
const resolvedMetadata = metadataList.length > 0 ? mergeMetadata(metadataList) : null;
|
|
400
|
-
const resolvedViewport =
|
|
400
|
+
const resolvedViewport = mergeViewport(viewportList);
|
|
401
401
|
|
|
402
402
|
// Build element: metadata head + noindex meta + boundary component wrapped in layouts
|
|
403
403
|
// Always include charset and default viewport for parity with Next.js.
|
|
@@ -405,8 +405,7 @@ async function renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, req
|
|
|
405
405
|
const noindexMeta = createElement("meta", { name: "robots", content: "noindex" });
|
|
406
406
|
const headElements = [charsetMeta, noindexMeta];
|
|
407
407
|
if (resolvedMetadata) headElements.push(createElement(MetadataHead, { metadata: resolvedMetadata }));
|
|
408
|
-
|
|
409
|
-
headElements.push(createElement(ViewportHead, { viewport: effectiveViewport }));
|
|
408
|
+
headElements.push(createElement(ViewportHead, { viewport: resolvedViewport }));
|
|
410
409
|
let element = createElement(Fragment, null, ...headElements, createElement(BoundaryComponent));
|
|
411
410
|
if (isRscRequest) {
|
|
412
411
|
// For RSC requests (client-side navigation), wrap the element with the same
|
|
@@ -671,7 +670,7 @@ async function buildPageElement(route, params, opts, searchParams) {
|
|
|
671
670
|
if (pageVp) viewportList.push(pageVp);
|
|
672
671
|
}
|
|
673
672
|
const resolvedMetadata = metadataList.length > 0 ? mergeMetadata(metadataList) : null;
|
|
674
|
-
const resolvedViewport =
|
|
673
|
+
const resolvedViewport = mergeViewport(viewportList);
|
|
675
674
|
|
|
676
675
|
// Build nested layout tree from outermost to innermost.
|
|
677
676
|
// Next.js 16 passes params/searchParams as Promises (async pattern)
|
|
@@ -712,9 +711,7 @@ async function buildPageElement(route, params, opts, searchParams) {
|
|
|
712
711
|
// Always emit <meta charset="utf-8"> — Next.js includes this on every page
|
|
713
712
|
headElements.push(createElement("meta", { charSet: "utf-8" }));
|
|
714
713
|
if (resolvedMetadata) headElements.push(createElement(MetadataHead, { metadata: resolvedMetadata }));
|
|
715
|
-
|
|
716
|
-
const effectiveViewport = resolvedViewport ?? { width: "device-width", initialScale: 1 };
|
|
717
|
-
headElements.push(createElement(ViewportHead, { viewport: effectiveViewport }));
|
|
714
|
+
headElements.push(createElement(ViewportHead, { viewport: resolvedViewport }));
|
|
718
715
|
element = createElement(Fragment, null, ...headElements, element);
|
|
719
716
|
}
|
|
720
717
|
|
|
@@ -1231,18 +1228,41 @@ export default async function handler(request) {
|
|
|
1231
1228
|
_runWithPrivateCache(() =>
|
|
1232
1229
|
runWithFetchCache(async () => {
|
|
1233
1230
|
const __reqCtx = __buildRequestContext(request);
|
|
1234
|
-
|
|
1231
|
+
// Per-request container for middleware state. Passed into
|
|
1232
|
+
// _handleRequest which fills in .headers and .status;
|
|
1233
|
+
// avoids module-level variables that race on Workers.
|
|
1234
|
+
const _mwCtx = { headers: null, status: null };
|
|
1235
|
+
const response = await _handleRequest(request, __reqCtx, _mwCtx);
|
|
1235
1236
|
// Apply custom headers from next.config.js to non-redirect responses.
|
|
1236
1237
|
// Skip redirects (3xx) because Response.redirect() creates immutable headers,
|
|
1237
1238
|
// and Next.js doesn't apply custom headers to redirects anyway.
|
|
1238
|
-
if (
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1239
|
+
if (response && response.headers && !(response.status >= 300 && response.status < 400)) {
|
|
1240
|
+
if (__configHeaders.length) {
|
|
1241
|
+
const url = new URL(request.url);
|
|
1242
|
+
let pathname;
|
|
1243
|
+
try { pathname = __normalizePath(decodeURIComponent(url.pathname)); } catch { pathname = url.pathname; }
|
|
1244
|
+
${bp ? `if (pathname.startsWith(${JSON.stringify(bp)})) pathname = pathname.slice(${JSON.stringify(bp)}.length) || "/";` : ""}
|
|
1245
|
+
const extraHeaders = __applyConfigHeaders(pathname, __reqCtx);
|
|
1246
|
+
for (const h of extraHeaders) {
|
|
1247
|
+
// Use append() for headers where multiple values must coexist
|
|
1248
|
+
// (Vary, Set-Cookie). Using set() on these would destroy
|
|
1249
|
+
// existing values like "Vary: RSC, Accept" which are critical
|
|
1250
|
+
// for correct CDN caching behavior.
|
|
1251
|
+
const lk = h.key.toLowerCase();
|
|
1252
|
+
if (lk === "vary" || lk === "set-cookie") {
|
|
1253
|
+
response.headers.append(h.key, h.value);
|
|
1254
|
+
} else {
|
|
1255
|
+
response.headers.set(h.key, h.value);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
// Merge middleware response headers into the final response.
|
|
1260
|
+
// This runs at the top level so every response path (route
|
|
1261
|
+
// handlers, server actions, metadata, errors, etc.) gets them.
|
|
1262
|
+
if (_mwCtx.headers) {
|
|
1263
|
+
for (const [key, value] of _mwCtx.headers) {
|
|
1264
|
+
response.headers.append(key, value);
|
|
1265
|
+
}
|
|
1246
1266
|
}
|
|
1247
1267
|
}
|
|
1248
1268
|
return response;
|
|
@@ -1253,7 +1273,13 @@ export default async function handler(request) {
|
|
|
1253
1273
|
);
|
|
1254
1274
|
}
|
|
1255
1275
|
|
|
1256
|
-
async function _handleRequest(request, __reqCtx) {
|
|
1276
|
+
async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
1277
|
+
const __reqStart = process.env.NODE_ENV !== "production" ? performance.now() : 0;
|
|
1278
|
+
let __compileEnd;
|
|
1279
|
+
let __renderEnd;
|
|
1280
|
+
// __reqStart is included in the timing header so the Node logging middleware
|
|
1281
|
+
// can compute true compile time as: handlerStart - middlewareStart.
|
|
1282
|
+
// Format: "handlerStart,compileMs,renderMs" - all as integers (ms). Dev-only.
|
|
1257
1283
|
const url = new URL(request.url);
|
|
1258
1284
|
|
|
1259
1285
|
// ── Cross-origin request protection ─────────────────────────────────
|
|
@@ -1300,7 +1326,11 @@ async function _handleRequest(request, __reqCtx) {
|
|
|
1300
1326
|
|
|
1301
1327
|
// ── Apply redirects from next.config.js ───────────────────────────────
|
|
1302
1328
|
if (__configRedirects.length) {
|
|
1303
|
-
|
|
1329
|
+
// Strip .rsc suffix before matching redirect rules - RSC (client-side nav) requests
|
|
1330
|
+
// arrive as /some/path.rsc but redirect patterns are defined without it (e.g.
|
|
1331
|
+
// /some/path). Without this, soft-nav fetches bypass all config redirects.
|
|
1332
|
+
const __redirPathname = pathname.endsWith(".rsc") ? pathname.slice(0, -4) : pathname;
|
|
1333
|
+
const __redir = __applyConfigRedirects(__redirPathname, __reqCtx);
|
|
1304
1334
|
if (__redir) {
|
|
1305
1335
|
const __redirDest = __sanitizeDestination(
|
|
1306
1336
|
__basePath && !__redir.destination.startsWith(__basePath)
|
|
@@ -1316,7 +1346,9 @@ async function _handleRequest(request, __reqCtx) {
|
|
|
1316
1346
|
|
|
1317
1347
|
// ── Apply beforeFiles rewrites from next.config.js ────────────────────
|
|
1318
1348
|
if (__configRewrites.beforeFiles && __configRewrites.beforeFiles.length) {
|
|
1319
|
-
|
|
1349
|
+
// Strip .rsc suffix before matching rewrite rules — same reason as redirects above.
|
|
1350
|
+
const __rewritePathname = pathname.endsWith(".rsc") ? pathname.slice(0, -4) : pathname;
|
|
1351
|
+
const __rewritten = __applyConfigRewrites(__rewritePathname, __configRewrites.beforeFiles, __reqCtx);
|
|
1320
1352
|
if (__rewritten) {
|
|
1321
1353
|
if (__isExternalUrl(__rewritten)) {
|
|
1322
1354
|
setHeadersContext(null);
|
|
@@ -1330,10 +1362,9 @@ async function _handleRequest(request, __reqCtx) {
|
|
|
1330
1362
|
const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("accept")?.includes("text/x-component");
|
|
1331
1363
|
let cleanPathname = pathname.replace(/\\.rsc$/, "");
|
|
1332
1364
|
|
|
1333
|
-
// Middleware response headers
|
|
1334
|
-
|
|
1335
|
-
//
|
|
1336
|
-
let _middlewareRewriteStatus = null;
|
|
1365
|
+
// Middleware response headers and custom rewrite status are stored in
|
|
1366
|
+
// _mwCtx (per-request container) so handler() can merge them into
|
|
1367
|
+
// every response path without module-level state that races on Workers.
|
|
1337
1368
|
|
|
1338
1369
|
${middlewarePath ? `
|
|
1339
1370
|
// Run proxy/middleware if present and path matches.
|
|
@@ -1367,10 +1398,10 @@ async function _handleRequest(request, __reqCtx) {
|
|
|
1367
1398
|
// headers are kept so applyMiddlewareRequestHeaders() can unpack them;
|
|
1368
1399
|
// the blanket strip loop after that call removes every remaining
|
|
1369
1400
|
// x-middleware-* header before the set is merged into the response.
|
|
1370
|
-
|
|
1401
|
+
_mwCtx.headers = new Headers();
|
|
1371
1402
|
for (const [key, value] of mwResponse.headers) {
|
|
1372
1403
|
if (key !== "x-middleware-next" && key !== "x-middleware-rewrite") {
|
|
1373
|
-
|
|
1404
|
+
_mwCtx.headers.append(key, value);
|
|
1374
1405
|
}
|
|
1375
1406
|
}
|
|
1376
1407
|
} else {
|
|
@@ -1385,13 +1416,13 @@ async function _handleRequest(request, __reqCtx) {
|
|
|
1385
1416
|
cleanPathname = rewriteParsed.pathname;
|
|
1386
1417
|
// Capture custom status code from rewrite (e.g. NextResponse.rewrite(url, { status: 403 }))
|
|
1387
1418
|
if (mwResponse.status !== 200) {
|
|
1388
|
-
|
|
1419
|
+
_mwCtx.status = mwResponse.status;
|
|
1389
1420
|
}
|
|
1390
1421
|
// Also save any other headers from the rewrite response
|
|
1391
|
-
|
|
1422
|
+
_mwCtx.headers = new Headers();
|
|
1392
1423
|
for (const [key, value] of mwResponse.headers) {
|
|
1393
1424
|
if (key !== "x-middleware-next" && key !== "x-middleware-rewrite") {
|
|
1394
|
-
|
|
1425
|
+
_mwCtx.headers.append(key, value);
|
|
1395
1426
|
}
|
|
1396
1427
|
}
|
|
1397
1428
|
} else {
|
|
@@ -1411,11 +1442,11 @@ async function _handleRequest(request, __reqCtx) {
|
|
|
1411
1442
|
// request headers. Strip ALL x-middleware-* headers from the set that will
|
|
1412
1443
|
// be merged into the outgoing HTTP response — this prefix is reserved for
|
|
1413
1444
|
// internal routing signals and must never reach clients.
|
|
1414
|
-
if (
|
|
1415
|
-
applyMiddlewareRequestHeaders(
|
|
1416
|
-
for (const key of [...
|
|
1445
|
+
if (_mwCtx.headers) {
|
|
1446
|
+
applyMiddlewareRequestHeaders(_mwCtx.headers);
|
|
1447
|
+
for (const key of [..._mwCtx.headers.keys()]) {
|
|
1417
1448
|
if (key.startsWith("x-middleware-")) {
|
|
1418
|
-
|
|
1449
|
+
_mwCtx.headers.delete(key);
|
|
1419
1450
|
}
|
|
1420
1451
|
}
|
|
1421
1452
|
}
|
|
@@ -2098,6 +2129,9 @@ async function _handleRequest(request, __reqCtx) {
|
|
|
2098
2129
|
console.error = _origConsoleError;
|
|
2099
2130
|
}
|
|
2100
2131
|
|
|
2132
|
+
// Mark end of compile phase: route matching, middleware, tree building are done.
|
|
2133
|
+
if (process.env.NODE_ENV !== "production") __compileEnd = performance.now();
|
|
2134
|
+
|
|
2101
2135
|
// Render to RSC stream
|
|
2102
2136
|
const rscStream = renderToReadableStream(element, { onError: rscOnError });
|
|
2103
2137
|
|
|
@@ -2120,13 +2154,23 @@ async function _handleRequest(request, __reqCtx) {
|
|
|
2120
2154
|
} else if (revalidateSeconds) {
|
|
2121
2155
|
responseHeaders["Cache-Control"] = "s-maxage=" + revalidateSeconds + ", stale-while-revalidate";
|
|
2122
2156
|
}
|
|
2123
|
-
//
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2157
|
+
// Middleware response headers are merged by the handler() wrapper.
|
|
2158
|
+
// Attach internal timing header so the dev server middleware can log it.
|
|
2159
|
+
// Format: "handlerStart,compileMs,renderMs"
|
|
2160
|
+
// handlerStart - absolute performance.now() when _handleRequest began,
|
|
2161
|
+
// used by the logging middleware to compute true compile
|
|
2162
|
+
// time as (handlerStart - middlewareReqStart).
|
|
2163
|
+
// compileMs - time inside the handler before renderToReadableStream.
|
|
2164
|
+
// -1 sentinel means compile time is not measured.
|
|
2165
|
+
// renderMs - -1 sentinel for RSC-only (soft-nav) responses, since
|
|
2166
|
+
// rendering is handled asynchronously by the client. The
|
|
2167
|
+
// logging middleware computes render time as totalMs - compileMs.
|
|
2168
|
+
if (process.env.NODE_ENV !== "production") {
|
|
2169
|
+
const handlerStart = Math.round(__reqStart);
|
|
2170
|
+
const compileMs = __compileEnd !== undefined ? Math.round(__compileEnd - __reqStart) : -1;
|
|
2171
|
+
responseHeaders["x-vinext-timing"] = handlerStart + "," + compileMs + ",-1";
|
|
2172
|
+
}
|
|
2173
|
+
return new Response(rscStream, { status: _mwCtx.status || 200, headers: responseHeaders });
|
|
2130
2174
|
}
|
|
2131
2175
|
|
|
2132
2176
|
// Collect font data from RSC environment before passing to SSR
|
|
@@ -2152,6 +2196,8 @@ async function _handleRequest(request, __reqCtx) {
|
|
|
2152
2196
|
try {
|
|
2153
2197
|
const ssrEntry = await import.meta.viteRsc.loadModule("ssr", "index");
|
|
2154
2198
|
htmlStream = await ssrEntry.handleSsr(rscStream, _getNavigationContext(), fontData);
|
|
2199
|
+
// Shell render complete; Suspense boundaries stream asynchronously
|
|
2200
|
+
if (process.env.NODE_ENV !== "production") __renderEnd = performance.now();
|
|
2155
2201
|
} catch (ssrErr) {
|
|
2156
2202
|
const specialResponse = await handleRenderError(ssrErr);
|
|
2157
2203
|
if (specialResponse) return specialResponse;
|
|
@@ -2176,16 +2222,27 @@ async function _handleRequest(request, __reqCtx) {
|
|
|
2176
2222
|
if (fontLinkHeader) {
|
|
2177
2223
|
response.headers.set("Link", fontLinkHeader);
|
|
2178
2224
|
}
|
|
2179
|
-
//
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2225
|
+
// Middleware response headers are merged by the handler() wrapper.
|
|
2226
|
+
// Attach internal timing header so the dev server middleware can log it.
|
|
2227
|
+
// Format: "handlerStart,compileMs,renderMs"
|
|
2228
|
+
// handlerStart - absolute performance.now() when _handleRequest began,
|
|
2229
|
+
// used by the logging middleware to compute true compile
|
|
2230
|
+
// time as (handlerStart - middlewareReqStart).
|
|
2231
|
+
// compileMs - time inside the handler before renderToReadableStream.
|
|
2232
|
+
// renderMs - time from renderToReadableStream to handleSsr completion,
|
|
2233
|
+
// or -1 sentinel if not measured (falls back to totalMs - compileMs).
|
|
2234
|
+
if (process.env.NODE_ENV !== "production") {
|
|
2235
|
+
const handlerStart = Math.round(__reqStart);
|
|
2236
|
+
const compileMs = __compileEnd !== undefined ? Math.round(__compileEnd - __reqStart) : -1;
|
|
2237
|
+
const renderMs = __renderEnd !== undefined && __compileEnd !== undefined
|
|
2238
|
+
? Math.round(__renderEnd - __compileEnd)
|
|
2239
|
+
: -1;
|
|
2240
|
+
response.headers.set("x-vinext-timing", handlerStart + "," + compileMs + "," + renderMs);
|
|
2184
2241
|
}
|
|
2185
2242
|
// Apply custom status code from middleware rewrite
|
|
2186
|
-
if (
|
|
2243
|
+
if (_mwCtx.status) {
|
|
2187
2244
|
return new Response(response.body, {
|
|
2188
|
-
status:
|
|
2245
|
+
status: _mwCtx.status,
|
|
2189
2246
|
headers: response.headers,
|
|
2190
2247
|
});
|
|
2191
2248
|
}
|
|
@@ -2932,7 +2989,12 @@ async function main() {
|
|
|
2932
2989
|
// Checks the prefetch cache (populated by <Link> IntersectionObserver and
|
|
2933
2990
|
// router.prefetch()) before making a network request. This makes navigation
|
|
2934
2991
|
// near-instant for prefetched routes.
|
|
2935
|
-
window.__VINEXT_RSC_NAVIGATE__ = async function navigateRsc(href) {
|
|
2992
|
+
window.__VINEXT_RSC_NAVIGATE__ = async function navigateRsc(href, __redirectDepth) {
|
|
2993
|
+
if ((__redirectDepth || 0) > 10) {
|
|
2994
|
+
console.error("[vinext] Too many RSC redirects — aborting navigation to prevent infinite loop.");
|
|
2995
|
+
window.location.href = href;
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2936
2998
|
try {
|
|
2937
2999
|
const url = new URL(href, window.location.origin);
|
|
2938
3000
|
const rscUrl = toRscUrl(url.pathname + url.search);
|
|
@@ -2958,6 +3020,20 @@ async function main() {
|
|
|
2958
3020
|
});
|
|
2959
3021
|
}
|
|
2960
3022
|
|
|
3023
|
+
// Detect if fetch followed a redirect: compare the final response URL to
|
|
3024
|
+
// what we requested. If they differ, the server issued a 3xx — push the
|
|
3025
|
+
// canonical destination URL into history before rendering.
|
|
3026
|
+
const __finalUrl = new URL(navResponse.url);
|
|
3027
|
+
const __requestedUrl = new URL(rscUrl, window.location.origin);
|
|
3028
|
+
if (__finalUrl.pathname !== __requestedUrl.pathname) {
|
|
3029
|
+
// Strip .rsc suffix from the final URL to get the page path for history.
|
|
3030
|
+
// Use replaceState instead of pushState: the caller (navigateImpl) already
|
|
3031
|
+
// pushed the pre-redirect URL; replacing it avoids a stale history entry.
|
|
3032
|
+
const __destPath = __finalUrl.pathname.replace(/\\.rsc$/, "") + __finalUrl.search;
|
|
3033
|
+
window.history.replaceState(null, "", __destPath);
|
|
3034
|
+
return window.__VINEXT_RSC_NAVIGATE__(__destPath, (__redirectDepth || 0) + 1);
|
|
3035
|
+
}
|
|
3036
|
+
|
|
2961
3037
|
// Update useParams() with route params from the server before re-rendering
|
|
2962
3038
|
const navParamsHeader = navResponse.headers.get("X-Vinext-Params");
|
|
2963
3039
|
if (navParamsHeader) {
|