vinext 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -1
- package/dist/build/app-page-static-eligibility.d.ts +15 -0
- package/dist/build/app-page-static-eligibility.js +82 -0
- package/dist/build/prerender-paths.js +3 -78
- package/dist/build/prerender.js +33 -8
- package/dist/build/server-manifest.d.ts +7 -0
- package/dist/build/server-manifest.js +11 -1
- package/dist/cache/cache-adapters-virtual.d.ts +7 -1
- package/dist/cache/cache-adapters-virtual.js +3 -3
- package/dist/client/navigation-runtime.d.ts +11 -0
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/config/config-matchers.js +3 -1
- package/dist/config/next-config.d.ts +7 -0
- package/dist/config/next-config.js +18 -3
- package/dist/entries/app-rsc-entry.d.ts +2 -0
- package/dist/entries/app-rsc-entry.js +64 -43
- package/dist/index.d.ts +4 -1
- package/dist/index.js +59 -30
- package/dist/plugins/cache-flight-codec.d.ts +19 -0
- package/dist/plugins/cache-flight-codec.js +68 -0
- package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
- package/dist/plugins/commonjs-esm-facade.js +80 -0
- package/dist/plugins/fonts.d.ts +8 -11
- package/dist/plugins/fonts.js +71 -34
- package/dist/routing/file-matcher.d.ts +1 -0
- package/dist/routing/file-matcher.js +1 -1
- package/dist/server/api-handler.js +17 -7
- package/dist/server/app-browser-entry.js +11 -7
- package/dist/server/app-browser-history-controller.d.ts +2 -0
- package/dist/server/app-browser-history-controller.js +8 -0
- package/dist/server/app-browser-interception-context.js +26 -2
- package/dist/server/app-browser-navigation-controller.d.ts +7 -1
- package/dist/server/app-browser-navigation-controller.js +93 -10
- package/dist/server/app-fallback-renderer.d.ts +2 -0
- package/dist/server/app-fallback-renderer.js +1 -0
- package/dist/server/app-history-state.d.ts +3 -0
- package/dist/server/app-history-state.js +7 -0
- package/dist/server/app-middleware.js +2 -1
- package/dist/server/app-optimistic-routing.js +1 -1
- package/dist/server/app-page-boundary-render.d.ts +5 -0
- package/dist/server/app-page-boundary-render.js +6 -4
- package/dist/server/app-page-cache-finalizer.d.ts +11 -0
- package/dist/server/app-page-cache-finalizer.js +41 -7
- package/dist/server/app-page-cache-render.d.ts +18 -0
- package/dist/server/app-page-cache-render.js +63 -43
- package/dist/server/app-page-cache.d.ts +5 -0
- package/dist/server/app-page-cache.js +27 -7
- package/dist/server/app-page-dispatch.d.ts +9 -1
- package/dist/server/app-page-dispatch.js +47 -30
- package/dist/server/app-page-element-builder.d.ts +7 -0
- package/dist/server/app-page-element-builder.js +27 -42
- package/dist/server/app-page-execution.d.ts +15 -12
- package/dist/server/app-page-execution.js +30 -40
- package/dist/server/app-page-head.d.ts +5 -0
- package/dist/server/app-page-head.js +9 -3
- package/dist/server/app-page-probe.d.ts +0 -109
- package/dist/server/app-page-probe.js +3 -136
- package/dist/server/app-page-render-identity.d.ts +0 -1
- package/dist/server/app-page-render-identity.js +1 -1
- package/dist/server/app-page-render-observation.js +2 -2
- package/dist/server/app-page-render.d.ts +13 -5
- package/dist/server/app-page-render.js +219 -119
- package/dist/server/app-page-request.d.ts +0 -1
- package/dist/server/app-page-request.js +1 -2
- package/dist/server/app-page-response.d.ts +4 -3
- package/dist/server/app-page-response.js +4 -3
- package/dist/server/app-page-route-wiring.d.ts +17 -5
- package/dist/server/app-page-route-wiring.js +55 -33
- package/dist/server/app-page-rsc-render-status.d.ts +48 -0
- package/dist/server/app-page-rsc-render-status.js +54 -0
- package/dist/server/app-page-stream.d.ts +15 -8
- package/dist/server/app-page-stream.js +6 -0
- package/dist/server/app-request-stage-dispatch.js +1 -1
- package/dist/server/app-request-stage-independent-entry.js +4 -4
- package/dist/server/app-response-header-provenance.d.ts +2 -0
- package/dist/server/app-response-header-provenance.js +10 -4
- package/dist/server/app-route-handler-dispatch.js +1 -1
- package/dist/server/app-router-entry.js +3 -3
- package/dist/server/app-rsc-handler.d.ts +4 -0
- package/dist/server/app-rsc-handler.js +212 -96
- package/dist/server/app-rsc-request-normalization.d.ts +3 -1
- package/dist/server/app-rsc-request-normalization.js +4 -2
- package/dist/server/app-rsc-response-stage.d.ts +1 -1
- package/dist/server/app-rsc-response-stage.js +6 -4
- package/dist/server/app-rsc-route-matching.d.ts +9 -1
- package/dist/server/app-rsc-route-matching.js +36 -19
- package/dist/server/app-server-action-execution.js +1 -1
- package/dist/server/app-ssr-entry.d.ts +3 -2
- package/dist/server/app-ssr-entry.js +6 -4
- package/dist/server/app-ssr-stream.d.ts +6 -1
- package/dist/server/app-ssr-stream.js +34 -15
- package/dist/server/app-worker-stages.d.ts +2 -2
- package/dist/server/app-worker-stages.js +3 -3
- package/dist/server/cacheability-request.js +9 -5
- package/dist/server/config-headers.js +2 -0
- package/dist/server/dev-server.js +12 -2
- package/dist/server/headers.d.ts +14 -0
- package/dist/server/headers.js +15 -1
- package/dist/server/image-optimization.d.ts +16 -0
- package/dist/server/image-optimization.js +26 -6
- package/dist/server/isr-cache.js +2 -2
- package/dist/server/middleware-matcher-pattern.js +8 -4
- package/dist/server/middleware-response-headers.js +2 -0
- package/dist/server/multi-stage.d.ts +8 -0
- package/dist/server/navigation-planner.js +1 -1
- package/dist/server/node-response-signal.d.ts +16 -0
- package/dist/server/node-response-signal.js +26 -0
- package/dist/server/open-redirect.d.ts +47 -0
- package/dist/server/open-redirect.js +74 -1
- package/dist/server/pages-page-handler.js +21 -4
- package/dist/server/pages-request-stage-entry.js +8 -4
- package/dist/server/prod-server.d.ts +8 -7
- package/dist/server/prod-server.js +153 -69
- package/dist/server/request-pipeline.d.ts +20 -8
- package/dist/server/request-pipeline.js +21 -7
- package/dist/server/response-compression.d.ts +43 -0
- package/dist/server/response-compression.js +111 -0
- package/dist/server/seed-cache.js +10 -7
- package/dist/server/socket-error-backstop.d.ts +14 -0
- package/dist/server/socket-error-backstop.js +27 -4
- package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
- package/dist/shims/cache-adapter-instantiate.js +118 -0
- package/dist/shims/cache-callable-runtime.d.ts +2 -40
- package/dist/shims/cache-callable-runtime.js +50 -168
- package/dist/shims/cache-flight-arguments.d.ts +22 -0
- package/dist/shims/cache-flight-arguments.js +56 -0
- package/dist/shims/cache-request-state.d.ts +15 -0
- package/dist/shims/cache-request-state.js +31 -8
- package/dist/shims/cache-runtime.d.ts +6 -19
- package/dist/shims/cache-runtime.js +165 -225
- package/dist/shims/cache.d.ts +2 -2
- package/dist/shims/cache.js +5 -4
- package/dist/shims/cacheability-classification.d.ts +7 -0
- package/dist/shims/error-boundary-navigation.d.ts +7 -0
- package/dist/shims/error-boundary-navigation.js +19 -1
- package/dist/shims/error-boundary.d.ts +10 -10
- package/dist/shims/error-boundary.js +26 -7
- package/dist/shims/fetch-cache.d.ts +6 -0
- package/dist/shims/fetch-cache.js +19 -1
- package/dist/shims/form.js +1 -1
- package/dist/shims/headers.d.ts +13 -0
- package/dist/shims/headers.js +47 -4
- package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
- package/dist/shims/internal/app-page-props-cache-key.js +24 -15
- package/dist/shims/internal/headers-state.d.ts +0 -1
- package/dist/shims/metadata.js +3 -3
- package/dist/shims/navigation-errors.d.ts +6 -0
- package/dist/shims/navigation-errors.js +11 -1
- package/dist/shims/navigation.d.ts +7 -0
- package/dist/shims/navigation.js +14 -4
- package/dist/shims/request-context.d.ts +9 -0
- package/dist/shims/thenable-params.d.ts +4 -0
- package/dist/shims/thenable-params.js +25 -3
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/mdx-scan.d.ts +5 -0
- package/dist/utils/mdx-scan.js +8 -1
- package/dist/utils/regex-safety.d.ts +10 -0
- package/dist/utils/regex-safety.js +70 -18
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -158,6 +158,42 @@ Environment variables: `PORT` (default `3000`), `HOST` (default `0.0.0.0`).
|
|
|
158
158
|
|
|
159
159
|
> **Note:** Next.js standalone uses `HOSTNAME` for the bind address, but vinext uses `HOST` to avoid collision with the system-set `HOSTNAME` variable on Linux. Update your deployment config accordingly.
|
|
160
160
|
|
|
161
|
+
#### Custom standalone entries
|
|
162
|
+
|
|
163
|
+
The generated `server.js` is a small wrapper around `startProdServer` from `vinext/server/prod-server`. To control where the server loads the build output from, write your own entry and call `startProdServer` directly. All options are optional:
|
|
164
|
+
|
|
165
|
+
| Option | Default | Description |
|
|
166
|
+
| ----------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
167
|
+
| `port` | `PORT` env var, otherwise `3000` | Port to listen on. |
|
|
168
|
+
| `host` | `"0.0.0.0"` | Address to bind to. Unlike the generated `server.js`, `startProdServer` doesn't read `HOST`, so pass `process.env.HOST` yourself if you need it. |
|
|
169
|
+
| `outDir` | `dist`, resolved against the current directory | Build output directory containing `client/` and `server/`. Relative paths resolve against the current directory. In standalone output, this is `dist/standalone/dist`. |
|
|
170
|
+
| `serverDir` | `<outDir>/server` | Directory containing the server bundles, manifests, and prerendered output. |
|
|
171
|
+
| `rscEntryPath` | `<serverDir>/index.js` | App Router server entry. If this file exists, the server runs in App Router mode. |
|
|
172
|
+
| `serverEntryPath` | `<serverDir>/entry.js` | Pages Router server entry. Used when there's no App Router entry. |
|
|
173
|
+
| `noCompression` | `false` | Disable response compression. |
|
|
174
|
+
|
|
175
|
+
`ProdServerOptions` also has `purpose` and `silent`, which vinext uses for its own build-time servers. Leave them unset. The returned promise resolves to `{ server, port }` once the server is listening.
|
|
176
|
+
|
|
177
|
+
**Compiled single-file binaries.** A `bun build --compile` binary can't use the generated `server.js`. Bun fixes `import.meta.dirname` when it compiles the binary, so `join(import.meta.dirname, "dist")` doesn't point at the directory the binary runs from. Depending on the Bun version, it points at the build machine's path or at Bun's embedded `/$bunfs/root`. Resolve `outDir` from the executable's location instead:
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
// server-bin.mjs, in your project root
|
|
181
|
+
import path from "node:path";
|
|
182
|
+
import { startProdServer } from "vinext/server/prod-server";
|
|
183
|
+
|
|
184
|
+
await startProdServer({
|
|
185
|
+
port: Number.parseInt(process.env.PORT ?? "3000", 10),
|
|
186
|
+
host: process.env.HOST ?? "0.0.0.0",
|
|
187
|
+
outDir: path.join(path.dirname(process.execPath), "dist"),
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
bun build --compile --compile-autoload-package-json server-bin.mjs --outfile dist/standalone/server
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Keep the entry outside `dist/standalone`: each build deletes and recreates that directory. Compile after `vite build`, then ship the whole `dist/standalone` directory, because the `server` binary needs the `dist/` and `node_modules/` directories next to it. The server bundles in `dist/server` still import their dependencies from `node_modules` at runtime. `--compile-autoload-package-json` lets the binary read package `exports` and `main` fields when it resolves them. Under plain Node, `process.execPath` is the Node binary, so use this entry only for compiled builds.
|
|
196
|
+
|
|
161
197
|
### Starting a new vinext project
|
|
162
198
|
|
|
163
199
|
Use `create-vinext-app` for new projects. It creates a TypeScript App Router project
|
|
@@ -792,7 +828,7 @@ their `env` binding lookups) are instantiated lazily on the first request.
|
|
|
792
828
|
|
|
793
829
|
Registration is wired into **every router and runtime** — App Router and Pages Router, on Cloudflare Workers as well as the Node.js server (`vinext start`) and dev. It self-guards (instantiated once per isolate) and is resilient: if an adapter can't initialize on a given runtime (e.g. a KV binding doesn't exist on the Node server), vinext logs a warning and falls back to the default handler instead of failing requests.
|
|
794
830
|
|
|
795
|
-
To write your own adapter, point a slot at any module by path
|
|
831
|
+
To write your own adapter, point a slot at any module by path. Its default export receives one `{ env, options }` argument at runtime and produces a data-cache `CacheHandler` (or a CDN adapter):
|
|
796
832
|
|
|
797
833
|
```ts
|
|
798
834
|
vinext({
|
|
@@ -805,6 +841,41 @@ vinext({
|
|
|
805
841
|
});
|
|
806
842
|
```
|
|
807
843
|
|
|
844
|
+
The default export can be a factory or a class:
|
|
845
|
+
|
|
846
|
+
```ts
|
|
847
|
+
// my-adapter.js: a factory
|
|
848
|
+
export default ({ env, options }) => ({
|
|
849
|
+
async get(key) {
|
|
850
|
+
/* … */
|
|
851
|
+
},
|
|
852
|
+
async set(key, data) {
|
|
853
|
+
/* … */
|
|
854
|
+
},
|
|
855
|
+
async revalidateTag(tags) {
|
|
856
|
+
/* … */
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
// my-adapter.js: or a class
|
|
861
|
+
export default class MyAdapter {
|
|
862
|
+
constructor({ env, options }) {
|
|
863
|
+
/* … */
|
|
864
|
+
}
|
|
865
|
+
async get(key) {
|
|
866
|
+
/* … */
|
|
867
|
+
}
|
|
868
|
+
async set(key, data) {
|
|
869
|
+
/* … */
|
|
870
|
+
}
|
|
871
|
+
async revalidateTag(tags) {
|
|
872
|
+
/* … */
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
vinext invokes class syntax with `new` and calls any other function, so existing factories keep plain-call semantics. A constructor that isn't class syntax (an ES5-compiled, bound, or Proxy-wrapped class) can't be told apart from a factory, so export a factory that constructs it: `export default (args) => new MyAdapter(args)`. The result must be the adapter itself, not a Promise. A data adapter implements `get`, `set`, and `revalidateTag`; a CDN adapter also implements `buildResponseHeaders` and sets `ownsBackgroundRevalidation` to a boolean. If the export isn't a function or the result is missing a member, vinext logs a warning naming the slot and what's wrong, then keeps the default handler.
|
|
878
|
+
|
|
808
879
|
## What's NOT supported (and won't be)
|
|
809
880
|
|
|
810
881
|
These are intentional exclusions. For things that are missing today but on the roadmap, see [Known gaps we're working on](#known-gaps-were-working-on) above.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AppRoute } from "../routing/app-route-graph.js";
|
|
2
|
+
//#region src/build/app-page-static-eligibility.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Whether Next.js classifies an App page route as static or SSG, read from its
|
|
5
|
+
* layout, page and parallel-slot sources with the helpers dispatch applies to
|
|
6
|
+
* the loaded modules. Only such a route has listed paths.
|
|
7
|
+
*
|
|
8
|
+
* An MDX source read without the MDX parser has unknown exports, so its route
|
|
9
|
+
* is "unreadable". Its paths are still listed: the built runtime reads the real
|
|
10
|
+
* exports and never stores a route that isn't static, while an unlisted path's
|
|
11
|
+
* render failure would be dropped instead of failing. But no probe renders an
|
|
12
|
+
* unreadable route's fallback, so it never certifies one.
|
|
13
|
+
*/
|
|
14
|
+
export declare function classifyAppPageRouteStaticEligibility(route: AppRoute, readMdxEsm: ((source: string) => string) | null): "eligible" | "ineligible" | "unreadable";
|
|
15
|
+
//#endregion
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { extractExportConstNumber, extractExportConstString, hasRuntimeExportedName } from "./report.js";
|
|
2
|
+
import { collectAppPageStaticGenerationRuntimes, hasAppPageGenerateStaticParamsAtLastDynamicSegment, isAppPageStaticEligible, isEdgeRuntime, resolveAppPageSegmentConfig, resolveAppPageStaticGenerationRuntime } from "../server/app-segment-config.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
//#region src/build/app-page-static-eligibility.ts
|
|
5
|
+
/**
|
|
6
|
+
* Whether Next.js classifies an App page route as static or SSG, read from its
|
|
7
|
+
* layout, page and parallel-slot sources with the helpers dispatch applies to
|
|
8
|
+
* the loaded modules. Only such a route has listed paths.
|
|
9
|
+
*
|
|
10
|
+
* An MDX source read without the MDX parser has unknown exports, so its route
|
|
11
|
+
* is "unreadable". Its paths are still listed: the built runtime reads the real
|
|
12
|
+
* exports and never stores a route that isn't static, while an unlisted path's
|
|
13
|
+
* render failure would be dropped instead of failing. But no probe renders an
|
|
14
|
+
* unreadable route's fallback, so it never certifies one.
|
|
15
|
+
*/
|
|
16
|
+
function classifyAppPageRouteStaticEligibility(route, readMdxEsm) {
|
|
17
|
+
let unreadable = false;
|
|
18
|
+
const readSegmentConfig = (filePath) => {
|
|
19
|
+
if (!filePath) return null;
|
|
20
|
+
let code = fs.readFileSync(filePath, "utf8");
|
|
21
|
+
if (filePath.toLowerCase().endsWith(".mdx")) {
|
|
22
|
+
let esm = null;
|
|
23
|
+
try {
|
|
24
|
+
esm = readMdxEsm?.(code) ?? null;
|
|
25
|
+
} catch {}
|
|
26
|
+
unreadable ||= esm === null;
|
|
27
|
+
code = esm ?? "";
|
|
28
|
+
}
|
|
29
|
+
const dynamic = extractExportConstString(code, "dynamic");
|
|
30
|
+
const revalidate = extractExportConstNumber(code, "revalidate");
|
|
31
|
+
const runtime = extractExportConstString(code, "runtime");
|
|
32
|
+
return {
|
|
33
|
+
...dynamic === null ? {} : { dynamic },
|
|
34
|
+
...hasRuntimeExportedName(code, "generateStaticParams") ? { generateStaticParams() {} } : {},
|
|
35
|
+
...revalidate === null ? {} : { revalidate },
|
|
36
|
+
...runtime === null ? {} : { runtime }
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const layouts = route.layouts.map(readSegmentConfig);
|
|
40
|
+
const page = readSegmentConfig(route.pagePath);
|
|
41
|
+
const parallelBranches = route.parallelSlots.map((slot) => ({
|
|
42
|
+
configLayouts: (slot.configLayoutPaths ?? []).map(readSegmentConfig),
|
|
43
|
+
configLayoutTreePositions: slot.configLayoutTreePositions ?? [],
|
|
44
|
+
isDefault: !slot.pagePath,
|
|
45
|
+
layout: readSegmentConfig(slot.layoutPath),
|
|
46
|
+
name: slot.name,
|
|
47
|
+
ownerTreePosition: slot.ownerTreePosition ?? null,
|
|
48
|
+
page: readSegmentConfig(slot.pagePath ?? slot.defaultPath),
|
|
49
|
+
routeSegments: slot.routeSegments
|
|
50
|
+
}));
|
|
51
|
+
if (unreadable) return "unreadable";
|
|
52
|
+
const segmentConfig = resolveAppPageSegmentConfig({
|
|
53
|
+
layouts,
|
|
54
|
+
layoutTreePositions: route.layoutTreePositions,
|
|
55
|
+
page,
|
|
56
|
+
parallelBranches,
|
|
57
|
+
routeSegments: route.routeSegments
|
|
58
|
+
});
|
|
59
|
+
return isAppPageStaticEligible({
|
|
60
|
+
dynamicConfig: segmentConfig.dynamicConfig,
|
|
61
|
+
hasGenerateStaticParams: hasAppPageGenerateStaticParamsAtLastDynamicSegment({
|
|
62
|
+
childrenSlot: route.childrenSlot ?? null,
|
|
63
|
+
layouts,
|
|
64
|
+
layoutTreePositions: route.layoutTreePositions,
|
|
65
|
+
page,
|
|
66
|
+
parallelBranches,
|
|
67
|
+
routeSegments: route.routeSegments
|
|
68
|
+
}),
|
|
69
|
+
isDynamicRoute: route.isDynamic,
|
|
70
|
+
isStaticGenerationEdgeRuntime: isEdgeRuntime(resolveAppPageStaticGenerationRuntime(collectAppPageStaticGenerationRuntimes({
|
|
71
|
+
childrenSlot: route.childrenSlot ?? null,
|
|
72
|
+
layouts,
|
|
73
|
+
layoutTreePositions: route.layoutTreePositions,
|
|
74
|
+
page,
|
|
75
|
+
parallelBranches,
|
|
76
|
+
routeSegments: route.routeSegments
|
|
77
|
+
}))),
|
|
78
|
+
revalidateSeconds: segmentConfig.revalidateSeconds
|
|
79
|
+
}) ? "eligible" : "ineligible";
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { classifyAppPageRouteStaticEligibility };
|
|
@@ -10,17 +10,18 @@ import { apiRouter, matchRoute, pagesRouter } from "../routing/pages-router.js";
|
|
|
10
10
|
import "../server/headers.js";
|
|
11
11
|
import { matchesMiddlewarePathname } from "../server/middleware-matcher.js";
|
|
12
12
|
import { extractLocaleFromUrl, normalizeDefaultLocalePathname } from "../server/pages-i18n.js";
|
|
13
|
-
import { classifyAppRoute, classifyAppRouteHandler, classifyPagesRoute,
|
|
13
|
+
import { classifyAppRoute, classifyAppRouteHandler, classifyPagesRoute, extractExportConstString, extractMiddlewareMatcherConfig, getAppRouteRenderEntryPath } from "./report.js";
|
|
14
14
|
import { normalizeStaticPathname, normalizeStaticPathsEntry } from "../routing/route-pattern.js";
|
|
15
15
|
import { buildPagesDataHref } from "../shims/internal/pages-data-url.js";
|
|
16
16
|
import { normalizePathTrailingSlash } from "../shims/url-utils.js";
|
|
17
|
+
import { resolveAppPageDynamicConfig } from "../server/app-segment-config.js";
|
|
18
|
+
import { classifyAppPageRouteStaticEligibility } from "./app-page-static-eligibility.js";
|
|
17
19
|
import { pagesRouteHasPriorityOverAppRoute } from "../server/hybrid-route-priority.js";
|
|
18
20
|
import { readPrerenderSecret } from "./server-manifest.js";
|
|
19
21
|
import { loadMdxEsmReader } from "../utils/mdx-scan.js";
|
|
20
22
|
import { startProdServer } from "../server/prod-server.js";
|
|
21
23
|
import { enterPrerenderPhase } from "./prerender-phase.js";
|
|
22
24
|
import { buildUrlFromParams, layoutOnlyParamSets, localizePagesPath, resolveParentParams, routeStaticParamSets, validateDiscoveredParams } from "./prerender.js";
|
|
23
|
-
import { collectAppPageStaticGenerationRuntimes, hasAppPageGenerateStaticParamsAtLastDynamicSegment, isAppPageStaticEligible, isEdgeRuntime, resolveAppPageDynamicConfig, resolveAppPageSegmentConfig, resolveAppPageStaticGenerationRuntime } from "../server/app-segment-config.js";
|
|
24
25
|
import { resolveBuiltPagesEntryPath, resolveBuiltRscEntryPath } from "./server-entry.js";
|
|
25
26
|
import fs from "node:fs";
|
|
26
27
|
//#region src/build/prerender-paths.ts
|
|
@@ -351,82 +352,6 @@ function extractPagesStaticPathLocale(url, i18n) {
|
|
|
351
352
|
url: `${rest || "/"}${query}`
|
|
352
353
|
};
|
|
353
354
|
}
|
|
354
|
-
/**
|
|
355
|
-
* Whether Next.js classifies an App page route as static or SSG, read from its
|
|
356
|
-
* layout, page and parallel-slot sources with the helpers dispatch applies to
|
|
357
|
-
* the loaded modules. Only such a route has listed paths.
|
|
358
|
-
*
|
|
359
|
-
* An MDX source read without the MDX parser has unknown exports, so its route
|
|
360
|
-
* is "unreadable". Its paths are still listed: the built runtime reads the real
|
|
361
|
-
* exports and never stores a route that isn't static, while an unlisted path's
|
|
362
|
-
* render failure would be dropped instead of failing. But no probe renders an
|
|
363
|
-
* unreadable route's fallback, so it never certifies one.
|
|
364
|
-
*/
|
|
365
|
-
function classifyAppPageRouteStaticEligibility(route, readMdxEsm) {
|
|
366
|
-
let unreadable = false;
|
|
367
|
-
const readSegmentConfig = (filePath) => {
|
|
368
|
-
if (!filePath) return null;
|
|
369
|
-
let code = fs.readFileSync(filePath, "utf8");
|
|
370
|
-
if (filePath.toLowerCase().endsWith(".mdx")) {
|
|
371
|
-
let esm = null;
|
|
372
|
-
try {
|
|
373
|
-
esm = readMdxEsm?.(code) ?? null;
|
|
374
|
-
} catch {}
|
|
375
|
-
unreadable ||= esm === null;
|
|
376
|
-
code = esm ?? "";
|
|
377
|
-
}
|
|
378
|
-
const dynamic = extractExportConstString(code, "dynamic");
|
|
379
|
-
const revalidate = extractExportConstNumber(code, "revalidate");
|
|
380
|
-
const runtime = extractExportConstString(code, "runtime");
|
|
381
|
-
return {
|
|
382
|
-
...dynamic === null ? {} : { dynamic },
|
|
383
|
-
...hasRuntimeExportedName(code, "generateStaticParams") ? { generateStaticParams() {} } : {},
|
|
384
|
-
...revalidate === null ? {} : { revalidate },
|
|
385
|
-
...runtime === null ? {} : { runtime }
|
|
386
|
-
};
|
|
387
|
-
};
|
|
388
|
-
const layouts = route.layouts.map(readSegmentConfig);
|
|
389
|
-
const page = readSegmentConfig(route.pagePath);
|
|
390
|
-
const parallelBranches = route.parallelSlots.map((slot) => ({
|
|
391
|
-
configLayouts: (slot.configLayoutPaths ?? []).map(readSegmentConfig),
|
|
392
|
-
configLayoutTreePositions: slot.configLayoutTreePositions ?? [],
|
|
393
|
-
isDefault: !slot.pagePath,
|
|
394
|
-
layout: readSegmentConfig(slot.layoutPath),
|
|
395
|
-
name: slot.name,
|
|
396
|
-
ownerTreePosition: slot.ownerTreePosition ?? null,
|
|
397
|
-
page: readSegmentConfig(slot.pagePath ?? slot.defaultPath),
|
|
398
|
-
routeSegments: slot.routeSegments
|
|
399
|
-
}));
|
|
400
|
-
if (unreadable) return "unreadable";
|
|
401
|
-
const segmentConfig = resolveAppPageSegmentConfig({
|
|
402
|
-
layouts,
|
|
403
|
-
layoutTreePositions: route.layoutTreePositions,
|
|
404
|
-
page,
|
|
405
|
-
parallelBranches,
|
|
406
|
-
routeSegments: route.routeSegments
|
|
407
|
-
});
|
|
408
|
-
return isAppPageStaticEligible({
|
|
409
|
-
dynamicConfig: segmentConfig.dynamicConfig,
|
|
410
|
-
hasGenerateStaticParams: hasAppPageGenerateStaticParamsAtLastDynamicSegment({
|
|
411
|
-
childrenSlot: route.childrenSlot ?? null,
|
|
412
|
-
layouts,
|
|
413
|
-
layoutTreePositions: route.layoutTreePositions,
|
|
414
|
-
page,
|
|
415
|
-
parallelBranches,
|
|
416
|
-
routeSegments: route.routeSegments
|
|
417
|
-
}),
|
|
418
|
-
isDynamicRoute: route.isDynamic,
|
|
419
|
-
isStaticGenerationEdgeRuntime: isEdgeRuntime(resolveAppPageStaticGenerationRuntime(collectAppPageStaticGenerationRuntimes({
|
|
420
|
-
childrenSlot: route.childrenSlot ?? null,
|
|
421
|
-
layouts,
|
|
422
|
-
layoutTreePositions: route.layoutTreePositions,
|
|
423
|
-
page,
|
|
424
|
-
parallelBranches,
|
|
425
|
-
routeSegments: route.routeSegments
|
|
426
|
-
}))),
|
|
427
|
-
revalidateSeconds: segmentConfig.revalidateSeconds
|
|
428
|
-
}) ? "eligible" : "ineligible";
|
|
429
|
-
}
|
|
430
355
|
/** Keys a path an App page route lists by the route and the pathname together. */
|
|
431
356
|
function appPageListingKey(routePattern, pathname) {
|
|
432
357
|
return `${routePattern}\0${pathname}`;
|
package/dist/build/prerender.js
CHANGED
|
@@ -4,7 +4,7 @@ import { isUnknownRecord } from "../utils/record.js";
|
|
|
4
4
|
import { BLOCKED_PAGES } from "../shims/constants.js";
|
|
5
5
|
import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER } from "../utils/protocol-headers.js";
|
|
6
6
|
import { appRouteLayoutStaticParamsGroups } from "../routing/app-router.js";
|
|
7
|
-
import { NEXT_CACHE_TAGS_HEADER, VINEXT_METADATA_ROUTE_CACHE_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_METADATA_ROUTES_PATH, VINEXT_PRERENDER_RENDER_ERROR_HEADER } from "../server/headers.js";
|
|
7
|
+
import { NEXT_CACHE_TAGS_HEADER, VINEXT_METADATA_ROUTE_CACHE_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_METADATA_ROUTES_PATH, VINEXT_PRERENDER_RENDER_ERROR_HEADER, VINEXT_PRERENDER_SPECIAL_ERROR_HEADER } from "../server/headers.js";
|
|
8
8
|
import { extractLocaleFromUrl } from "../server/pages-i18n.js";
|
|
9
9
|
import { classifyAppRoute, classifyPagesRoute, getAppRouteRenderEntryPath } from "./report.js";
|
|
10
10
|
import { resolveClientStaleTimeSeconds } from "../utils/cache-control-metadata.js";
|
|
@@ -12,6 +12,7 @@ import { NoOpCacheHandler, getCacheHandler, setCacheHandler } from "../shims/cac
|
|
|
12
12
|
import { headersContextFromRequest, runWithHeadersContext } from "../shims/headers.js";
|
|
13
13
|
import { _consumeRequestScopedCacheLife } from "../shims/cache-request-state.js";
|
|
14
14
|
import { normalizeStaticPathsEntry } from "../routing/route-pattern.js";
|
|
15
|
+
import { createRscRequestUrl } from "../server/app-rsc-cache-busting.js";
|
|
15
16
|
import { readPrerenderSecret } from "./server-manifest.js";
|
|
16
17
|
import { buildPregeneratedConcretePathTable } from "../server/prerender-manifest.js";
|
|
17
18
|
import { concatUint8Arrays, decodeRscEmbeddedChunk } from "../server/app-rsc-embedded-chunks.js";
|
|
@@ -1041,17 +1042,22 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
1041
1042
|
const responseCacheLife = readPrerenderCacheLifeHeader(response.headers);
|
|
1042
1043
|
const cacheTags = readPrerenderCacheTagsHeader(response.headers);
|
|
1043
1044
|
const fatal = response.headers.get(VINEXT_PRERENDER_RENDER_ERROR_HEADER) === "1";
|
|
1044
|
-
|
|
1045
|
+
const specialError = mode === "export" ? null : readPrerenderSpecialErrorHeader(response.headers);
|
|
1046
|
+
const isSpecialError = specialError !== null;
|
|
1047
|
+
const ok = response.ok || isSpecialError;
|
|
1048
|
+
if (!ok || cacheControl.includes("no-store")) {
|
|
1045
1049
|
await response.body?.cancel();
|
|
1046
1050
|
return {
|
|
1047
1051
|
cacheControl,
|
|
1048
1052
|
linkHeader,
|
|
1049
1053
|
html: null,
|
|
1050
|
-
ok
|
|
1054
|
+
ok,
|
|
1051
1055
|
requestCacheLife: null,
|
|
1052
1056
|
tags: [],
|
|
1053
1057
|
status: response.status,
|
|
1054
|
-
fatal
|
|
1058
|
+
fatal,
|
|
1059
|
+
specialErrorLocation: null,
|
|
1060
|
+
isSpecialError
|
|
1055
1061
|
};
|
|
1056
1062
|
}
|
|
1057
1063
|
const html = await response.text();
|
|
@@ -1064,7 +1070,9 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
1064
1070
|
requestCacheLife: responseCacheLife ?? processCacheLife,
|
|
1065
1071
|
status: response.status,
|
|
1066
1072
|
tags: cacheTags,
|
|
1067
|
-
fatal: false
|
|
1073
|
+
fatal: false,
|
|
1074
|
+
specialErrorLocation: specialError?.location ?? null,
|
|
1075
|
+
isSpecialError
|
|
1068
1076
|
};
|
|
1069
1077
|
});
|
|
1070
1078
|
const htmlCacheControl = htmlRender.cacheControl;
|
|
@@ -1092,7 +1100,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
1092
1100
|
error: "RSC handler returned no prerender HTML"
|
|
1093
1101
|
};
|
|
1094
1102
|
const html = isFallback ? markAppPprDynamicFallbackShellHtml(htmlRender.html) : htmlRender.html;
|
|
1095
|
-
let rscData = extractRscPayloadFromPrerenderedHtml(html);
|
|
1103
|
+
let rscData = htmlRender.isSpecialError ? null : extractRscPayloadFromPrerenderedHtml(html);
|
|
1096
1104
|
if (rscData === null) {
|
|
1097
1105
|
const rscHeaders = new Headers({
|
|
1098
1106
|
Accept: "text/x-component",
|
|
@@ -1100,7 +1108,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
1100
1108
|
});
|
|
1101
1109
|
if (prerenderRouteParamsHeader !== null) rscHeaders.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);
|
|
1102
1110
|
if (isSpeculative) rscHeaders.set(VINEXT_PRERENDER_SPECULATIVE_HEADER, "1");
|
|
1103
|
-
const rscRequest = new Request(`http://localhost${requestPath}`, { headers: rscHeaders });
|
|
1111
|
+
const rscRequest = new Request(`http://localhost${await createRscRequestUrl(requestPath, rscHeaders)}`, { headers: rscHeaders });
|
|
1104
1112
|
const rscRes = await runWithHeadersContext(headersContextFromRequest(rscRequest), () => rscHandler(rscRequest));
|
|
1105
1113
|
if (!rscRes.ok) {
|
|
1106
1114
|
await rscRes.body?.cancel();
|
|
@@ -1135,7 +1143,11 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
1135
1143
|
...renderedStale === void 0 ? {} : { stale: renderedStale },
|
|
1136
1144
|
router: "app",
|
|
1137
1145
|
...htmlRender.tags.length > 0 ? { tags: htmlRender.tags } : {},
|
|
1138
|
-
...htmlRender.linkHeader
|
|
1146
|
+
...htmlRender.linkHeader || htmlRender.specialErrorLocation !== null ? { headers: {
|
|
1147
|
+
...htmlRender.linkHeader ? { link: htmlRender.linkHeader } : {},
|
|
1148
|
+
...htmlRender.specialErrorLocation !== null ? { location: htmlRender.specialErrorLocation } : {}
|
|
1149
|
+
} } : {},
|
|
1150
|
+
...htmlRender.isSpecialError ? { responseStatus: htmlRender.status } : {},
|
|
1139
1151
|
...urlPath !== routePattern ? { path: urlPath } : {},
|
|
1140
1152
|
...isFallback ? { fallback: true } : {}
|
|
1141
1153
|
};
|
|
@@ -1239,6 +1251,19 @@ function readPrerenderCacheLifeHeader(headers) {
|
|
|
1239
1251
|
return null;
|
|
1240
1252
|
}
|
|
1241
1253
|
}
|
|
1254
|
+
/** Reads the special-error marker: the page's stored headers, or null. */
|
|
1255
|
+
function readPrerenderSpecialErrorHeader(headers) {
|
|
1256
|
+
const value = headers.get(VINEXT_PRERENDER_SPECIAL_ERROR_HEADER);
|
|
1257
|
+
if (!value) return null;
|
|
1258
|
+
try {
|
|
1259
|
+
const parsed = JSON.parse(value);
|
|
1260
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1261
|
+
const location = parsed.location;
|
|
1262
|
+
return { location: typeof location === "string" ? location : null };
|
|
1263
|
+
} catch {
|
|
1264
|
+
return null;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1242
1267
|
function readPrerenderCacheTagsHeader(headers) {
|
|
1243
1268
|
const value = headers.get(NEXT_CACHE_TAGS_HEADER);
|
|
1244
1269
|
if (!value) return [];
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
* warn when this returns `undefined`.
|
|
15
15
|
*/
|
|
16
16
|
export declare function readPrerenderSecret(serverDir: string): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Read next.config `compress` from `vinext-server.json` in `serverDir`.
|
|
19
|
+
*
|
|
20
|
+
* Like `next start`, compression stays on unless the build recorded an
|
|
21
|
+
* explicit `compress: false`, so builds without the field keep compressing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function readServerCompress(serverDir: string): boolean;
|
|
17
24
|
/**
|
|
18
25
|
* Read every server output root that contains a deployable response-stage
|
|
19
26
|
* graph. Paths are stored relative to the project root so build artifacts stay
|
|
@@ -20,6 +20,16 @@ function readPrerenderSecret(serverDir) {
|
|
|
20
20
|
return readJsonFile(manifestPath)?.prerenderSecret;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
+
* Read next.config `compress` from `vinext-server.json` in `serverDir`.
|
|
24
|
+
*
|
|
25
|
+
* Like `next start`, compression stays on unless the build recorded an
|
|
26
|
+
* explicit `compress: false`, so builds without the field keep compressing.
|
|
27
|
+
*/
|
|
28
|
+
function readServerCompress(serverDir) {
|
|
29
|
+
const manifestPath = path.join(serverDir, "vinext-server.json");
|
|
30
|
+
return readJsonFile(manifestPath)?.compress !== false;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
23
33
|
* Read every server output root that contains a deployable response-stage
|
|
24
34
|
* graph. Paths are stored relative to the project root so build artifacts stay
|
|
25
35
|
* relocatable, then resolved for post-build sidecar updates.
|
|
@@ -31,4 +41,4 @@ function readServerRuntimeOutputDirs(serverDir, root) {
|
|
|
31
41
|
return manifest.runtimeOutputDirs.filter((outputDir) => typeof outputDir === "string").map((outputDir) => path.resolve(root, outputDir));
|
|
32
42
|
}
|
|
33
43
|
//#endregion
|
|
34
|
-
export { readPrerenderSecret, readServerRuntimeOutputDirs };
|
|
44
|
+
export { readPrerenderSecret, readServerCompress, readServerRuntimeOutputDirs };
|
|
@@ -74,7 +74,13 @@ export type CacheAdapterBuildOutput = {
|
|
|
74
74
|
export type CacheAdapterDescriptor<O extends Record<string, unknown> = Record<string, unknown>> = {
|
|
75
75
|
/**
|
|
76
76
|
* Module specifier (or absolute path, e.g. from `require.resolve(...)`) whose
|
|
77
|
-
* default export
|
|
77
|
+
* default export creates the adapter from one `{ env, options }` argument:
|
|
78
|
+
* either a factory `({ env, options }) => adapter` or a class
|
|
79
|
+
* `new Adapter({ env, options })`. Class syntax is invoked with `new`; any
|
|
80
|
+
* other function is called. For a constructor that is not class syntax (an
|
|
81
|
+
* ES5-compiled, bound or Proxy-wrapped class), export
|
|
82
|
+
* `(args) => new Adapter(args)`. The result must be the adapter object
|
|
83
|
+
* itself, not a Promise.
|
|
78
84
|
*/
|
|
79
85
|
adapter: string;
|
|
80
86
|
/** JSON-serializable options forwarded to the factory at runtime. */
|
|
@@ -103,7 +103,7 @@ function generateCacheAdaptersModule(cache) {
|
|
|
103
103
|
"export function registerConfiguredCacheAdapters() {}",
|
|
104
104
|
""
|
|
105
105
|
].join("\n");
|
|
106
|
-
const lines = ["// vinext: generated from the `cache` option in your vinext() plugin config."];
|
|
106
|
+
const lines = ["// vinext: generated from the `cache` option in your vinext() plugin config.", `import { instantiateCacheAdapter } from "vinext/shims/cache-adapter-instantiate";`];
|
|
107
107
|
if (data?.adapter) {
|
|
108
108
|
lines.push(`import __vinextDataAdapterFactory from ${JSON.stringify(data.adapter)};`);
|
|
109
109
|
lines.push(`import { registerDataCacheHandler } from "vinext/shims/cache-handler";`);
|
|
@@ -114,11 +114,11 @@ function generateCacheAdaptersModule(cache) {
|
|
|
114
114
|
} else if (dataProvidesBuildIdentity) lines.push(`import { DefaultCdnCacheAdapter } from "vinext/shims/cdn-cache";`, `import { registerCdnCacheAdapter } from "vinext/shims/cdn-cache-state";`);
|
|
115
115
|
lines.push("", "// A factory that throws (e.g. a missing binding on an incompatible runtime)", "// is logged and skipped so the default handler stays in place.", "function __vinextFormatAdapterError(error) {", " if (error instanceof Error && error.message) return error.message;", " try {", " return String(error);", " } catch {", " return '<unknown error>';", " }", "}", "", "let __vinextCacheAdaptersRegistered = false;", "", "export function registerConfiguredCacheAdapters(env) {", " if (typeof process !== 'undefined' && process.env?.__VINEXT_PRERENDER_PATH_DISCOVERY === '1') return;", " if (__vinextCacheAdaptersRegistered) return;", " __vinextCacheAdaptersRegistered = true;");
|
|
116
116
|
if (data?.adapter) {
|
|
117
|
-
lines.push(" try {", ` registerDataCacheHandler(() => __vinextDataAdapterFactory
|
|
117
|
+
lines.push(" try {", ` registerDataCacheHandler(() => instantiateCacheAdapter(__vinextDataAdapterFactory, { env, options: ${inlineOptions(data.adapter, data.options)} }, "data"));`);
|
|
118
118
|
if (dataProvidesBuildIdentity) lines.push(" registerCdnCacheAdapter(() => new DefaultCdnCacheAdapter(", " process.env.__VINEXT_RSC_BUILD_IDENTITY || process.env.__VINEXT_BUILD_ID,", " ));");
|
|
119
119
|
lines.push(" } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured data cache adapter; using the default handler.\\n\" + __vinextFormatAdapterError(error));", " }");
|
|
120
120
|
}
|
|
121
|
-
if (cdn?.adapter) lines.push(" try {", ` registerCdnCacheAdapter(() => __vinextCdnAdapterFactory
|
|
121
|
+
if (cdn?.adapter) lines.push(" try {", ` registerCdnCacheAdapter(() => instantiateCacheAdapter(__vinextCdnAdapterFactory, { env, options: ${inlineOptions(cdn.adapter, cdn.options)} }, "cdn"));`, " } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured CDN cache adapter; using the default adapter.\\n\" + __vinextFormatAdapterError(error));", " }");
|
|
122
122
|
lines.push("}", "");
|
|
123
123
|
return lines.join("\n");
|
|
124
124
|
}
|
|
@@ -57,6 +57,17 @@ export type NavigationRuntimeFunctions = {
|
|
|
57
57
|
preparePrefetchResponse?: (response: Response) => Promise<unknown>;
|
|
58
58
|
claimCurrentHistoryTreeSnapshot?: (historyUpdateMode: NavigationRuntimeHistoryUpdateMode, previousHistoryState: unknown) => void;
|
|
59
59
|
commitAppOwnedHistoryStateWrite?: (historyUpdateMode: NavigationRuntimeHistoryUpdateMode, previousHistoryState: unknown) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Called after a raw (non-app-owned) history.pushState/replaceState with a
|
|
62
|
+
* URL so an App Router navigation that has not committed yet is discarded,
|
|
63
|
+
* matching Next.js' ACTION_RESTORE.
|
|
64
|
+
*/
|
|
65
|
+
discardPendingNavigation?: () => void;
|
|
66
|
+
/**
|
|
67
|
+
* Called before any history.pushState/replaceState so a navigation React is
|
|
68
|
+
* committing writes its URL first, as Next.js does from an insertion effect.
|
|
69
|
+
*/
|
|
70
|
+
flushCommittingNavigationUrl?: () => void;
|
|
60
71
|
};
|
|
61
72
|
export type NavigationRuntimeBootstrap = {
|
|
62
73
|
routeManifest: RouteManifest | null;
|
|
@@ -31,7 +31,7 @@ function readRuntimeWindow() {
|
|
|
31
31
|
}
|
|
32
32
|
function isNavigationRuntimeFunctions(value) {
|
|
33
33
|
if (!isUnknownRecord(value)) return false;
|
|
34
|
-
return isOptionalRuntimeFunction(Reflect.get(value, "clearNavigationCaches")) && isOptionalRuntimeFunction(Reflect.get(value, "commitHashNavigation")) && isOptionalRuntimeFunction(Reflect.get(value, "navigateExternal")) && isOptionalRuntimeFunction(Reflect.get(value, "navigate")) && isOptionalRuntimeFunction(Reflect.get(value, "getPrefetchRouterState")) && isOptionalRuntimeFunction(Reflect.get(value, "notifyLinkNavigationStart")) && isOptionalRuntimeFunction(Reflect.get(value, "pingVisibleLinks")) && isOptionalRuntimeFunction(Reflect.get(value, "preparePrefetchResponse")) && isOptionalRuntimeFunction(Reflect.get(value, "claimCurrentHistoryTreeSnapshot")) && isOptionalRuntimeFunction(Reflect.get(value, "commitAppOwnedHistoryStateWrite"));
|
|
34
|
+
return isOptionalRuntimeFunction(Reflect.get(value, "clearNavigationCaches")) && isOptionalRuntimeFunction(Reflect.get(value, "commitHashNavigation")) && isOptionalRuntimeFunction(Reflect.get(value, "navigateExternal")) && isOptionalRuntimeFunction(Reflect.get(value, "navigate")) && isOptionalRuntimeFunction(Reflect.get(value, "getPrefetchRouterState")) && isOptionalRuntimeFunction(Reflect.get(value, "notifyLinkNavigationStart")) && isOptionalRuntimeFunction(Reflect.get(value, "pingVisibleLinks")) && isOptionalRuntimeFunction(Reflect.get(value, "preparePrefetchResponse")) && isOptionalRuntimeFunction(Reflect.get(value, "claimCurrentHistoryTreeSnapshot")) && isOptionalRuntimeFunction(Reflect.get(value, "commitAppOwnedHistoryStateWrite")) && isOptionalRuntimeFunction(Reflect.get(value, "discardPendingNavigation")) && isOptionalRuntimeFunction(Reflect.get(value, "flushCommittingNavigationUrl"));
|
|
35
35
|
}
|
|
36
36
|
function isNavigationRuntimeRscChunk(value) {
|
|
37
37
|
if (typeof value === "string") return true;
|
|
@@ -307,6 +307,7 @@ function applyMiddlewareRequestHeaders(middlewareHeaders, request) {
|
|
|
307
307
|
method: request.method,
|
|
308
308
|
headers: nextHeaders,
|
|
309
309
|
body: request.body,
|
|
310
|
+
signal: request.signal,
|
|
310
311
|
duplex: request.body ? "half" : void 0
|
|
311
312
|
});
|
|
312
313
|
return {
|
|
@@ -859,9 +860,10 @@ async function proxyExternalRequest(request, externalUrl) {
|
|
|
859
860
|
try {
|
|
860
861
|
upstreamResponse = await fetch(targetUrl.href, {
|
|
861
862
|
...init,
|
|
862
|
-
signal: controller.signal
|
|
863
|
+
signal: AbortSignal.any([request.signal, controller.signal])
|
|
863
864
|
});
|
|
864
865
|
} catch (e) {
|
|
866
|
+
if (request.signal.aborted && !controller.signal.aborted) return new Response(null, { status: 499 });
|
|
865
867
|
if (e instanceof Error && e.name === "AbortError") {
|
|
866
868
|
console.error("[vinext] External rewrite proxy timeout:", targetUrl.href);
|
|
867
869
|
return new Response("Gateway Timeout", { status: 504 });
|
|
@@ -111,6 +111,11 @@ export type NextConfig = {
|
|
|
111
111
|
crossOrigin?: "anonymous" | "use-credentials";
|
|
112
112
|
/** Whether to add trailing slashes */
|
|
113
113
|
trailingSlash?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Compress responses from the Node production server. Defaults to true.
|
|
116
|
+
* @see https://nextjs.org/docs/app/api-reference/config/next-config-js/compress
|
|
117
|
+
*/
|
|
118
|
+
compress?: boolean;
|
|
114
119
|
/** Keep the original request URL visible to middleware/proxy. */
|
|
115
120
|
skipProxyUrlNormalize?: boolean;
|
|
116
121
|
/** @deprecated Use `skipProxyUrlNormalize` instead. */
|
|
@@ -312,6 +317,8 @@ export type ResolvedNextConfig = {
|
|
|
312
317
|
*/
|
|
313
318
|
assetPrefix: string;
|
|
314
319
|
trailingSlash: boolean;
|
|
320
|
+
/** Whether the Node production server compresses responses (`compress`, default true). */
|
|
321
|
+
compress: boolean;
|
|
315
322
|
skipProxyUrlNormalize: boolean;
|
|
316
323
|
typescript: {
|
|
317
324
|
tsconfigPath?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path, { toSlash } from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
|
|
2
|
-
import { normalizePageExtensions } from "../routing/file-matcher.js";
|
|
2
|
+
import { DEFAULT_PAGE_EXTENSIONS, normalizePageExtensions } from "../routing/file-matcher.js";
|
|
3
3
|
import { loadCommonJsModule, shouldRetryAsCommonJs } from "../utils/commonjs-loader.js";
|
|
4
4
|
import { isUnknownRecord } from "../utils/record.js";
|
|
5
5
|
import { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
|
|
@@ -184,11 +184,24 @@ function warnConfigLoadFailure(filename, err) {
|
|
|
184
184
|
if (isNextIntlPlugin) console.warn("[vinext] Hint: createNextIntlPlugin() is not needed with vinext. Remove the next-intl/plugin wrapper from your next.config — vinext auto-detects next-intl and registers the i18n config alias automatically.");
|
|
185
185
|
}
|
|
186
186
|
/**
|
|
187
|
+
* The `defaultConfig` handed to a function-form next.config. Next.js passes
|
|
188
|
+
* its full `defaultConfig` (packages/next/src/server/config-shared.ts); vinext
|
|
189
|
+
* passes the subset configs are known to read, with values matching Next.js,
|
|
190
|
+
* so `[...defaultConfig.pageExtensions, "page.js"]` works. Fresh per call so a
|
|
191
|
+
* config that mutates it cannot leak into later loads.
|
|
192
|
+
*/
|
|
193
|
+
function createFunctionConfigDefaults() {
|
|
194
|
+
return {
|
|
195
|
+
pageExtensions: [...DEFAULT_PAGE_EXTENSIONS],
|
|
196
|
+
compress: true
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
187
200
|
* Resolve a Next-style config value, calling it if it's a function-form config
|
|
188
201
|
* (Next.js supports `module.exports = (phase, opts) => config`).
|
|
189
202
|
*/
|
|
190
203
|
async function resolveConfigValue(config, phase = DEFAULT_PHASE) {
|
|
191
|
-
if (typeof config === "function") return await config(phase, { defaultConfig:
|
|
204
|
+
if (typeof config === "function") return await config(phase, { defaultConfig: createFunctionConfigDefaults() });
|
|
192
205
|
return config;
|
|
193
206
|
}
|
|
194
207
|
/**
|
|
@@ -436,7 +449,7 @@ async function loadNextConfigWithPackageIdentity(root, phase) {
|
|
|
436
449
|
},
|
|
437
450
|
load(id) {
|
|
438
451
|
if (id !== resolvedVirtualConfigId) return;
|
|
439
|
-
return `import * as configModule from ${configPathLiteral};\nconst cjsModule = configModule[${JSON.stringify(VINEXT_CJS_EXPORTS_KEY)}];\nconst cjsInitial = configModule[${JSON.stringify(VINEXT_CJS_INITIAL_KEY)}];\nconst cjsExports = cjsModule && cjsModule.exports;\nconst cjsValue = cjsExports != null && (cjsExports !== cjsInitial || (typeof cjsExports === "object" && Object.keys(cjsExports).length > 0)) ? cjsExports : undefined;\nconst value = cjsValue ?? configModule.default ?? configModule;\nexport default typeof value === "function" ? await value(${phaseLiteral}, { defaultConfig: {} }) : value;\n`;
|
|
452
|
+
return `import * as configModule from ${configPathLiteral};\nconst cjsModule = configModule[${JSON.stringify(VINEXT_CJS_EXPORTS_KEY)}];\nconst cjsInitial = configModule[${JSON.stringify(VINEXT_CJS_INITIAL_KEY)}];\nconst cjsExports = cjsModule && cjsModule.exports;\nconst cjsValue = cjsExports != null && (cjsExports !== cjsInitial || (typeof cjsExports === "object" && Object.keys(cjsExports).length > 0)) ? cjsExports : undefined;\nconst value = cjsValue ?? configModule.default ?? configModule;\nexport default typeof value === "function" ? await value(${phaseLiteral}, { defaultConfig: ${JSON.stringify(createFunctionConfigDefaults())} }) : value;\n`;
|
|
440
453
|
}
|
|
441
454
|
},
|
|
442
455
|
...isTypeScriptConfig ? [cjsGlobalsInjectorPlugin(configPath)] : [],
|
|
@@ -748,6 +761,7 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
|
|
|
748
761
|
basePath: "",
|
|
749
762
|
assetPrefix: "",
|
|
750
763
|
trailingSlash: false,
|
|
764
|
+
compress: true,
|
|
751
765
|
skipProxyUrlNormalize: false,
|
|
752
766
|
typescript: {},
|
|
753
767
|
output: "",
|
|
@@ -933,6 +947,7 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
|
|
|
933
947
|
basePath: config.basePath ?? "",
|
|
934
948
|
assetPrefix: normalizeAssetPrefix(config.assetPrefix),
|
|
935
949
|
trailingSlash: config.trailingSlash ?? false,
|
|
950
|
+
compress: config.compress !== false,
|
|
936
951
|
skipProxyUrlNormalize: config.skipProxyUrlNormalize ?? config.skipMiddlewareUrlNormalize ?? false,
|
|
937
952
|
typescript: typeof config.typescript?.tsconfigPath === "string" ? { tsconfigPath: config.typescript.tsconfigPath } : {},
|
|
938
953
|
output: output === "export" || output === "standalone" ? output : "",
|
|
@@ -60,6 +60,8 @@ type AppRouterConfig = {
|
|
|
60
60
|
globalNotFound?: boolean;
|
|
61
61
|
/** Enables Next.js Cache Components semantics for App Router document HTML. */
|
|
62
62
|
cacheComponents?: boolean;
|
|
63
|
+
/** Reads an MDX source's ESM exports, as the prerender listing does. */
|
|
64
|
+
readMdxEsm?: ((source: string) => string) | null;
|
|
63
65
|
/** Resolved `experimental.prefetchInlining` thresholds. */
|
|
64
66
|
prefetchInlining?: PrefetchInliningConfig;
|
|
65
67
|
/** Whether the RSC build discovered any server references. Defaults to true. */
|