vinext 0.1.8 → 0.2.1
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 +51 -30
- package/dist/build/css-url-assets.d.ts +3 -1
- package/dist/build/css-url-assets.js +18 -1
- package/dist/build/inject-pregenerated-paths.d.ts +7 -0
- package/dist/build/inject-pregenerated-paths.js +21 -0
- package/dist/build/layout-classification.js +1 -1
- package/dist/build/nitro-route-rules.js +1 -1
- package/dist/build/pages-client-assets-module.d.ts +11 -0
- package/dist/build/pages-client-assets-module.js +27 -0
- package/dist/build/prerender-server-entry.d.ts +1 -0
- package/dist/build/prerender-server-entry.js +49 -0
- package/dist/build/prerender-server-pool.d.ts +44 -0
- package/dist/build/prerender-server-pool.js +194 -0
- package/dist/build/prerender.d.ts +2 -1
- package/dist/build/prerender.js +98 -27
- package/dist/build/report.d.ts +13 -2
- package/dist/build/report.js +99 -2
- package/dist/build/run-prerender.d.ts +7 -0
- package/dist/build/run-prerender.js +12 -2
- package/dist/build/standalone.js +2 -0
- package/dist/cache/cache-adapters-virtual.js +3 -3
- package/dist/check.d.ts +4 -6
- package/dist/check.js +48 -11
- package/dist/cli-args.d.ts +1 -2
- package/dist/cli-args.js +1 -1
- package/dist/cli-dev-config.d.ts +12 -0
- package/dist/cli-dev-config.js +23 -0
- package/dist/cli.js +112 -96
- package/dist/{server → client}/dev-error-overlay-store.d.ts +1 -1
- package/dist/{server → client}/dev-error-overlay-store.js +1 -1
- package/dist/{server → client}/dev-error-overlay.d.ts +1 -1
- package/dist/{server → client}/dev-error-overlay.js +2 -2
- package/dist/client/navigation-runtime.d.ts +2 -0
- package/dist/client/navigation-runtime.js +3 -1
- package/dist/client/vinext-next-data.d.ts +4 -1
- package/dist/client/window-next.d.ts +2 -1
- package/dist/cloudflare/index.d.ts +2 -2
- package/dist/cloudflare/index.js +2 -2
- package/dist/config/config-matchers.js +45 -19
- package/dist/config/next-config.d.ts +41 -9
- package/dist/config/next-config.js +60 -3
- package/dist/config/prerender.d.ts +34 -0
- package/dist/config/prerender.js +55 -0
- package/dist/entries/app-browser-entry.js +5 -1
- package/dist/entries/app-rsc-entry.d.ts +5 -3
- package/dist/entries/app-rsc-entry.js +44 -10
- package/dist/entries/app-rsc-manifest.js +2 -0
- package/dist/entries/pages-client-entry.d.ts +3 -0
- package/dist/entries/pages-client-entry.js +28 -3
- package/dist/entries/pages-server-entry.js +23 -6
- package/dist/image/image-adapters-virtual.d.ts +59 -0
- package/dist/image/image-adapters-virtual.js +59 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +482 -120
- package/dist/init-cloudflare.d.ts +43 -0
- package/dist/init-cloudflare.js +839 -0
- package/dist/init-platform.d.ts +40 -0
- package/dist/init-platform.js +195 -0
- package/dist/init.d.ts +16 -38
- package/dist/init.js +209 -99
- package/dist/node_modules/.pnpm/am-i-vibing@0.5.0/node_modules/am-i-vibing/dist/detector-1yx2Hoe0.js +294 -0
- package/dist/node_modules/.pnpm/process-ancestry@0.1.0/node_modules/process-ancestry/dist/index.js +94 -0
- package/dist/{cloudflare → packages/cloudflare}/src/cache/cdn-adapter.runtime.js +1 -1
- package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.d.ts +2 -2
- package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.js +9 -2
- package/dist/packages/cloudflare/src/deploy-config.js +150 -0
- package/dist/packages/cloudflare/src/deploy-help.js +55 -0
- package/dist/packages/cloudflare/src/deploy.js +272 -0
- package/dist/{cloudflare → packages/cloudflare/src}/tpr.d.ts +4 -41
- package/dist/{cloudflare → packages/cloudflare/src}/tpr.js +44 -20
- package/dist/plugins/ast-scope.d.ts +16 -0
- package/dist/plugins/ast-scope.js +62 -0
- package/dist/plugins/ast-utils.d.ts +27 -1
- package/dist/plugins/ast-utils.js +32 -1
- package/dist/plugins/css-module-imports.d.ts +14 -0
- package/dist/plugins/css-module-imports.js +59 -0
- package/dist/plugins/extensionless-dynamic-import.js +2 -2
- package/dist/plugins/fonts.js +1 -1
- package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
- package/dist/plugins/ignore-dynamic-requests.js +530 -0
- package/dist/plugins/og-assets.js +2 -1
- package/dist/plugins/optimize-imports.js +1 -1
- package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
- package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
- package/dist/plugins/sass.d.ts +17 -1
- package/dist/plugins/sass.js +74 -1
- package/dist/plugins/styled-jsx.d.ts +16 -0
- package/dist/plugins/styled-jsx.js +149 -0
- package/dist/plugins/typeof-window.d.ts +1 -1
- package/dist/plugins/typeof-window.js +28 -56
- package/dist/routing/app-route-graph.d.ts +23 -2
- package/dist/routing/app-route-graph.js +138 -38
- package/dist/routing/file-matcher.d.ts +11 -1
- package/dist/routing/file-matcher.js +18 -3
- package/dist/routing/pages-router.js +2 -2
- package/dist/server/api-handler.js +9 -14
- package/dist/server/app-browser-action-result.d.ts +4 -1
- package/dist/server/app-browser-action-result.js +8 -1
- package/dist/server/app-browser-entry.js +198 -57
- package/dist/server/app-browser-interception-context.d.ts +1 -0
- package/dist/server/app-browser-interception-context.js +4 -2
- package/dist/server/app-browser-navigation-controller.d.ts +2 -0
- package/dist/server/app-browser-navigation-controller.js +60 -31
- package/dist/server/app-browser-server-action-client.d.ts +1 -1
- package/dist/server/app-browser-server-action-client.js +8 -7
- package/dist/server/app-browser-state.d.ts +12 -1
- package/dist/server/app-browser-state.js +25 -8
- package/dist/server/app-browser-visible-commit.d.ts +6 -1
- package/dist/server/app-browser-visible-commit.js +42 -24
- package/dist/server/app-elements-wire.d.ts +9 -2
- package/dist/server/app-elements-wire.js +8 -1
- package/dist/server/app-elements.d.ts +2 -2
- package/dist/server/app-elements.js +4 -3
- package/dist/server/app-fallback-renderer.d.ts +2 -1
- package/dist/server/app-fallback-renderer.js +10 -6
- package/dist/server/app-hydration-cache-publication.d.ts +11 -0
- package/dist/server/app-hydration-cache-publication.js +43 -0
- package/dist/server/app-middleware.d.ts +4 -3
- package/dist/server/app-middleware.js +8 -3
- package/dist/server/app-optimistic-routing.js +11 -4
- package/dist/server/app-page-boundary.js +2 -1
- package/dist/server/app-page-cache-finalizer.d.ts +1 -0
- package/dist/server/app-page-cache-finalizer.js +5 -4
- package/dist/server/app-page-cache-render.d.ts +1 -0
- package/dist/server/app-page-cache-render.js +8 -4
- package/dist/server/app-page-cache.d.ts +1 -0
- package/dist/server/app-page-cache.js +27 -7
- package/dist/server/app-page-dispatch.d.ts +1 -0
- package/dist/server/app-page-dispatch.js +26 -8
- package/dist/server/app-page-element-builder.d.ts +2 -1
- package/dist/server/app-page-element-builder.js +59 -4
- package/dist/server/app-page-execution.js +2 -1
- package/dist/server/app-page-probe.js +5 -0
- package/dist/server/app-page-render-identity.d.ts +2 -0
- package/dist/server/app-page-render-identity.js +3 -2
- package/dist/server/app-page-render.d.ts +2 -0
- package/dist/server/app-page-render.js +85 -16
- package/dist/server/app-page-request.d.ts +14 -0
- package/dist/server/app-page-request.js +69 -4
- package/dist/server/app-page-response.d.ts +7 -0
- package/dist/server/app-page-response.js +16 -4
- package/dist/server/app-page-route-wiring.d.ts +8 -1
- package/dist/server/app-page-route-wiring.js +32 -18
- package/dist/server/app-page-segment-state.d.ts +2 -1
- package/dist/server/app-page-segment-state.js +3 -1
- package/dist/server/app-page-stream.d.ts +7 -1
- package/dist/server/app-page-stream.js +3 -1
- package/dist/server/app-route-handler-execution.js +5 -1
- package/dist/server/app-route-handler-response.js +1 -0
- package/dist/server/app-route-handler-runtime.js +1 -1
- package/dist/server/app-route-tree-prefetch.d.ts +43 -0
- package/dist/server/app-route-tree-prefetch.js +187 -0
- package/dist/server/app-router-entry.js +9 -2
- package/dist/server/app-rsc-cache-busting.d.ts +2 -1
- package/dist/server/app-rsc-cache-busting.js +9 -4
- package/dist/server/app-rsc-handler.d.ts +7 -0
- package/dist/server/app-rsc-handler.js +148 -39
- package/dist/server/app-rsc-render-mode.d.ts +3 -2
- package/dist/server/app-rsc-render-mode.js +4 -1
- package/dist/server/app-rsc-request-normalization.d.ts +6 -6
- package/dist/server/app-rsc-request-normalization.js +10 -8
- package/dist/server/app-segment-config.d.ts +11 -0
- package/dist/server/app-segment-config.js +62 -6
- package/dist/server/app-server-action-execution.d.ts +1 -0
- package/dist/server/app-server-action-execution.js +61 -30
- package/dist/server/app-ssr-entry.d.ts +3 -0
- package/dist/server/app-ssr-entry.js +13 -5
- package/dist/server/app-ssr-stream.d.ts +7 -3
- package/dist/server/app-ssr-stream.js +10 -6
- package/dist/server/app-visited-response-cache.d.ts +6 -0
- package/dist/server/app-visited-response-cache.js +3 -1
- package/dist/server/cache-control.d.ts +3 -1
- package/dist/server/cache-control.js +13 -1
- package/dist/server/client-trace-metadata.js +26 -0
- package/dist/server/default-global-not-found-module.d.ts +14 -0
- package/dist/server/default-global-not-found-module.js +14 -0
- package/dist/server/dev-route-files.js +4 -0
- package/dist/server/dev-server.d.ts +11 -1
- package/dist/server/dev-server.js +94 -35
- package/dist/server/dev-stack-sourcemap.d.ts +1 -1
- package/dist/server/dev-stack-sourcemap.js +1 -1
- package/dist/server/fetch-handler.d.ts +2 -0
- package/dist/server/fetch-handler.js +18 -0
- package/dist/server/headers.d.ts +7 -15
- package/dist/server/headers.js +11 -18
- package/dist/server/image-optimization.d.ts +51 -1
- package/dist/server/image-optimization.js +52 -2
- package/dist/server/implicit-tags.js +1 -1
- package/dist/server/isr-cache.d.ts +3 -2
- package/dist/server/isr-cache.js +9 -5
- package/dist/server/metadata-routes.d.ts +3 -1
- package/dist/server/metadata-routes.js +12 -2
- package/dist/server/middleware-runtime.js +6 -1
- package/dist/server/navigation-planner.d.ts +1 -0
- package/dist/server/navigation-planner.js +14 -3
- package/dist/server/pages-api-route.js +2 -2
- package/dist/server/pages-asset-tags.d.ts +4 -6
- package/dist/server/pages-asset-tags.js +12 -12
- package/dist/server/pages-client-assets.d.ts +12 -0
- package/dist/server/pages-client-assets.js +10 -0
- package/dist/server/pages-data-route.d.ts +4 -2
- package/dist/server/pages-data-route.js +18 -4
- package/dist/server/pages-dev-module-url.d.ts +2 -1
- package/dist/server/pages-dev-module-url.js +6 -3
- package/dist/server/pages-node-compat.d.ts +19 -1
- package/dist/server/pages-node-compat.js +81 -4
- package/dist/server/pages-page-data.d.ts +33 -1
- package/dist/server/pages-page-data.js +62 -33
- package/dist/server/pages-page-handler.d.ts +2 -1
- package/dist/server/pages-page-handler.js +38 -19
- package/dist/server/pages-page-response.d.ts +8 -0
- package/dist/server/pages-page-response.js +7 -3
- package/dist/server/pages-request-pipeline.d.ts +9 -5
- package/dist/server/pages-request-pipeline.js +55 -7
- package/dist/server/pages-router-entry.d.ts +30 -0
- package/dist/server/pages-router-entry.js +108 -0
- package/dist/server/prerender-manifest.d.ts +3 -1
- package/dist/server/prerender-route-params.js +1 -1
- package/dist/server/prod-server.d.ts +5 -3
- package/dist/server/prod-server.js +68 -39
- package/dist/server/request-pipeline.js +2 -1
- package/dist/server/seed-cache.js +4 -4
- package/dist/shims/app-router-scroll-state.d.ts +1 -0
- package/dist/shims/app-router-scroll-state.js +1 -0
- package/dist/shims/app-router-scroll.js +2 -1
- package/dist/shims/cache-handler.js +8 -1
- package/dist/shims/cache.js +22 -15
- package/dist/shims/cdn-cache.js +1 -1
- package/dist/shims/dynamic-preload-chunks.js +2 -1
- package/dist/shims/fetch-cache.d.ts +3 -1
- package/dist/shims/fetch-cache.js +77 -52
- package/dist/shims/form.d.ts +4 -2
- package/dist/shims/form.js +37 -43
- package/dist/shims/headers.d.ts +7 -1
- package/dist/shims/headers.js +17 -3
- package/dist/shims/image.js +75 -14
- package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
- package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
- package/dist/shims/internal/app-route-detection.d.ts +2 -17
- package/dist/shims/internal/app-route-detection.js +4 -17
- package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +23 -0
- package/dist/shims/internal/hybrid-client-route-owner-direct.js +51 -0
- package/dist/shims/internal/hybrid-client-route-owner.d.ts +3 -15
- package/dist/shims/internal/hybrid-client-route-owner.js +40 -58
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +10 -5
- package/dist/shims/internal/pages-data-fetch-dedup.js +84 -14
- package/dist/shims/internal/pages-data-target.d.ts +18 -12
- package/dist/shims/internal/pages-data-target.js +97 -13
- package/dist/shims/internal/pages-router-components.d.ts +7 -0
- package/dist/shims/internal/pages-router-components.js +13 -0
- package/dist/shims/layout-segment-context.d.ts +4 -1
- package/dist/shims/layout-segment-context.js +18 -4
- package/dist/shims/link.d.ts +1 -1
- package/dist/shims/link.js +224 -58
- package/dist/shims/navigation.d.ts +19 -3
- package/dist/shims/navigation.js +245 -47
- package/dist/shims/request-context.js +18 -0
- package/dist/shims/root-params.d.ts +15 -1
- package/dist/shims/root-params.js +21 -1
- package/dist/shims/router.d.ts +5 -7
- package/dist/shims/router.js +387 -102
- package/dist/shims/server.js +3 -2
- package/dist/shims/slot.js +3 -6
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/typegen.js +1 -1
- package/dist/utils/client-runtime-metadata.d.ts +2 -18
- package/dist/utils/client-runtime-metadata.js +31 -22
- package/dist/utils/dev-stack-sourcemap-endpoint.d.ts +4 -0
- package/dist/{server → utils}/dev-stack-sourcemap-endpoint.js +1 -1
- package/dist/utils/domain-locale.d.ts +6 -3
- package/dist/{server → utils}/middleware-request-headers.d.ts +1 -1
- package/dist/{server → utils}/middleware-request-headers.js +3 -3
- package/dist/utils/project.d.ts +48 -2
- package/dist/utils/project.js +225 -4
- package/dist/utils/protocol-headers.d.ts +23 -0
- package/dist/utils/protocol-headers.js +23 -0
- package/dist/utils/react-version.d.ts +4 -0
- package/dist/utils/react-version.js +44 -0
- package/dist/utils/vite-version.d.ts +2 -0
- package/dist/utils/vite-version.js +11 -1
- package/package.json +47 -2
- package/dist/deploy.d.ts +0 -197
- package/dist/deploy.js +0 -1040
- package/dist/server/dev-stack-sourcemap-endpoint.d.ts +0 -4
- /package/dist/{cloudflare → packages/cloudflare}/src/utils/cache-control-metadata.js +0 -0
package/README.md
CHANGED
|
@@ -49,30 +49,37 @@ Replace `next` with `vinext` in your scripts:
|
|
|
49
49
|
```bash
|
|
50
50
|
vinext dev # Development server with HMR
|
|
51
51
|
vinext build # Production build
|
|
52
|
-
vinext deploy
|
|
52
|
+
npx @vinext/cloudflare deploy # Build and deploy to Cloudflare Workers
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
With Vite+, use `vpx @vinext/cloudflare deploy`, or
|
|
56
|
+
`vp exec vinext-cloudflare deploy` when running the locally installed bin.
|
|
57
|
+
|
|
55
58
|
vinext auto-detects your `app/` or `pages/` directory, loads `next.config.js`, and configures Vite automatically. No `vite.config.ts` required for basic usage.
|
|
56
59
|
|
|
57
60
|
Your existing `pages/`, `app/`, `next.config.js`, and `public/` directories work as-is. Run `vinext check` first to scan for known compatibility issues, or use `vinext init` to [automate the full migration](#migrating-an-existing-nextjs-project).
|
|
58
61
|
|
|
59
62
|
### CLI reference
|
|
60
63
|
|
|
61
|
-
| Command
|
|
62
|
-
|
|
|
63
|
-
| `vinext dev`
|
|
64
|
-
| `vinext build`
|
|
65
|
-
| `vinext start`
|
|
66
|
-
| `vinext deploy`
|
|
67
|
-
| `vinext
|
|
68
|
-
| `vinext
|
|
69
|
-
| `vinext
|
|
64
|
+
| Command | Description |
|
|
65
|
+
| ---------------------------------- | ----------------------------------------------------------------------- |
|
|
66
|
+
| `vinext dev` | Start dev server with HMR |
|
|
67
|
+
| `vinext build` | Production build (multi-environment for App Router: RSC + SSR + client) |
|
|
68
|
+
| `vinext start` | Start local production server for testing |
|
|
69
|
+
| `npx @vinext/cloudflare deploy` | Build and deploy to Cloudflare Workers |
|
|
70
|
+
| `vp exec vinext-cloudflare deploy` | Build and deploy to Cloudflare Workers with Vite+ |
|
|
71
|
+
| `vinext init` | Migrate a Next.js project to run under vinext |
|
|
72
|
+
| `vinext check` | Scan your Next.js app for compatibility issues before migrating |
|
|
73
|
+
| `vinext lint` | Delegate to eslint or oxlint |
|
|
70
74
|
|
|
71
75
|
Options: `-p / --port <port>`, `-H / --hostname <host>`, `--turbopack` (accepted, no-op).
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
`@vinext/cloudflare deploy` options: `--preview`, `--env <name>`, `--name <name>`, `--skip-build`, `--dry-run`, `--experimental-tpr`.
|
|
78
|
+
|
|
79
|
+
`vinext init` prompts for a deployment target, defaulting to Cloudflare. Agents must ask the
|
|
80
|
+
user which target they want, then pass `--platform=cloudflare` or `--platform=node`.
|
|
74
81
|
|
|
75
|
-
|
|
82
|
+
Other options: `--port <port>` (default: 3001), `--skip-check`, `--force`.
|
|
76
83
|
|
|
77
84
|
If your `next.config.*` sets `output: "standalone"`, `vinext build` emits a self-hosting bundle at `dist/standalone/`. Start it with:
|
|
78
85
|
|
|
@@ -105,7 +112,9 @@ This will:
|
|
|
105
112
|
3. Rename CJS config files (e.g. `postcss.config.js` -> `.cjs`) to avoid ESM conflicts
|
|
106
113
|
4. Add `"type": "module"` to `package.json`
|
|
107
114
|
5. Add `dev:vinext`, `build:vinext`, and `start:vinext` scripts to `package.json`
|
|
108
|
-
6.
|
|
115
|
+
6. Prompt for a deployment platform (Cloudflare by default, or Node)
|
|
116
|
+
7. Generate the matching `vite.config.ts`
|
|
117
|
+
8. For Cloudflare, generate `wrangler.jsonc`
|
|
109
118
|
|
|
110
119
|
The migration is non-destructive -- your existing Next.js setup continues to work alongside vinext. It does not modify `next.config`, `tsconfig.json`, or any source files, and it does not remove Next.js dependencies.
|
|
111
120
|
|
|
@@ -118,7 +127,10 @@ npm run start:vinext # Start vinext production server
|
|
|
118
127
|
npm run dev # Still runs Next.js as before
|
|
119
128
|
```
|
|
120
129
|
|
|
121
|
-
Use `--
|
|
130
|
+
Use `--platform=cloudflare` or `--platform=node` to skip the platform prompt. Cloudflare init
|
|
131
|
+
updates an existing JavaScript or TypeScript Vite config using its AST, preserving unrelated
|
|
132
|
+
settings. Use `--force` to replace an existing Node-target Vite config, or `--skip-check` to skip
|
|
133
|
+
the compatibility report.
|
|
122
134
|
|
|
123
135
|
## Why
|
|
124
136
|
|
|
@@ -126,7 +138,7 @@ Vite has become the default build tool for modern web frameworks — fast HMR, a
|
|
|
126
138
|
|
|
127
139
|
vinext is an experiment: can we reimplement the Next.js API surface on Vite, so that existing Next.js applications can run on a completely different toolchain? The answer, so far, is mostly yes.
|
|
128
140
|
|
|
129
|
-
vinext works everywhere. It natively supports Cloudflare Workers (with `vinext deploy`, bindings, KV caching), and can be deployed to Vercel, Netlify, AWS, Deno Deploy, and more via the [Nitro](https://v3.nitro.build/) Vite plugin. Native support for additional platforms is [planned](https://github.com/cloudflare/vinext/issues/80).
|
|
141
|
+
vinext works everywhere. It natively supports Cloudflare Workers (with `npx @vinext/cloudflare deploy` or `vp exec vinext-cloudflare deploy`, bindings, KV caching), and can be deployed to Vercel, Netlify, AWS, Deno Deploy, and more via the [Nitro](https://v3.nitro.build/) Vite plugin. Native support for additional platforms is [planned](https://github.com/cloudflare/vinext/issues/80).
|
|
130
142
|
|
|
131
143
|
**Alternatives worth knowing about:**
|
|
132
144
|
|
|
@@ -173,7 +185,7 @@ Both. File-system routing, SSR, client hydration, and deployment to Cloudflare W
|
|
|
173
185
|
Next.js 16.x. No support for deprecated APIs from older versions.
|
|
174
186
|
|
|
175
187
|
**Can I deploy to AWS/Netlify/other platforms?**
|
|
176
|
-
Yes. Add the [Nitro](https://v3.nitro.build/) Vite plugin alongside vinext, and you can deploy to Vercel, Netlify, AWS Amplify, Deno Deploy, Azure, and [many more](https://v3.nitro.build/deploy). See [Other platforms (via Nitro)](#other-platforms-via-nitro) for setup. For Cloudflare Workers, the native integration (`vinext deploy`) gives you the smoothest experience. Native adapters for more platforms are [planned](https://github.com/cloudflare/vinext/issues/80).
|
|
188
|
+
Yes. Add the [Nitro](https://v3.nitro.build/) Vite plugin alongside vinext, and you can deploy to Vercel, Netlify, AWS Amplify, Deno Deploy, Azure, and [many more](https://v3.nitro.build/deploy). See [Other platforms (via Nitro)](#other-platforms-via-nitro) for setup. For Cloudflare Workers, the native integration (`npx @vinext/cloudflare deploy` or `vp exec vinext-cloudflare deploy`) gives you the smoothest experience. Native adapters for more platforms are [planned](https://github.com/cloudflare/vinext/issues/80).
|
|
177
189
|
|
|
178
190
|
**What happens when Next.js releases a new feature?**
|
|
179
191
|
We track the public Next.js API surface and add support for new stable features. Experimental or unstable Next.js features are lower priority. The plan is to add commit-level tracking of the Next.js repo so we can stay current as new versions are released.
|
|
@@ -182,16 +194,16 @@ We track the public Next.js API surface and add support for new stable features.
|
|
|
182
194
|
|
|
183
195
|
### Cloudflare Workers
|
|
184
196
|
|
|
185
|
-
vinext has native integration with Cloudflare Workers through `@cloudflare/vite-plugin`, including bindings access via `cloudflare:workers`, KV caching, image optimization, and the
|
|
197
|
+
vinext has native integration with Cloudflare Workers through `@cloudflare/vite-plugin`, including bindings access via `cloudflare:workers`, KV caching, image optimization, and the `@vinext/cloudflare deploy` one-command workflow.
|
|
186
198
|
|
|
187
199
|
#### Prerequisites
|
|
188
200
|
|
|
189
|
-
Before running `vinext deploy` for the first time you need to authenticate with Cloudflare and tell wrangler which account to deploy to.
|
|
201
|
+
Before running `npx @vinext/cloudflare deploy` for the first time you need to authenticate with Cloudflare and tell wrangler which account to deploy to.
|
|
190
202
|
|
|
191
203
|
**Authentication — pick one:**
|
|
192
204
|
|
|
193
205
|
- **`wrangler login`** (recommended for local development) — opens a browser window to authenticate. Run it once and wrangler caches the token.
|
|
194
|
-
- **`CLOUDFLARE_API_TOKEN` env var** (CI / non-interactive) — create a token at [dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens) using the **Edit Cloudflare Workers** template. That template grants all the permissions
|
|
206
|
+
- **`CLOUDFLARE_API_TOKEN` env var** (CI / non-interactive) — create a token at [dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens) using the **Edit Cloudflare Workers** template. That template grants all the permissions `@vinext/cloudflare deploy` needs.
|
|
195
207
|
|
|
196
208
|
**Account ID:**
|
|
197
209
|
|
|
@@ -208,16 +220,24 @@ Find your account ID in the Cloudflare dashboard URL (`dash.cloudflare.com/<acco
|
|
|
208
220
|
|
|
209
221
|
Alternatively, set the `CLOUDFLARE_ACCOUNT_ID` environment variable instead of hardcoding it in the config file.
|
|
210
222
|
|
|
211
|
-
`vinext
|
|
223
|
+
Run `vinext init --platform=cloudflare` first to install dependencies and create or AST-update
|
|
224
|
+
`vite.config.*` and `wrangler.jsonc`. `@vinext/cloudflare deploy` then validates that setup, builds the
|
|
225
|
+
application, and deploys to Workers without rewriting project configuration.
|
|
226
|
+
|
|
227
|
+
Cloudflare init can also configure image optimization declaratively in the Vite config with
|
|
228
|
+
`imagesOptimizer()` and add the matching Wrangler Images binding. The built-in fetch handlers register
|
|
229
|
+
that optimizer at runtime; image optimization is not implemented or generated by `@vinext/cloudflare deploy`.
|
|
212
230
|
|
|
213
231
|
```bash
|
|
214
|
-
vinext deploy
|
|
215
|
-
vinext deploy
|
|
232
|
+
npx @vinext/cloudflare deploy
|
|
233
|
+
vp exec vinext-cloudflare deploy
|
|
234
|
+
npx @vinext/cloudflare deploy --env staging
|
|
235
|
+
vp exec vinext-cloudflare deploy --env staging
|
|
216
236
|
```
|
|
217
237
|
|
|
218
238
|
Use `--env <name>` to target `wrangler.jsonc` `env.<name>`. `--preview` is shorthand for `--env preview`.
|
|
219
239
|
|
|
220
|
-
The
|
|
240
|
+
The init command also auto-detects and fixes common migration issues:
|
|
221
241
|
|
|
222
242
|
- Adds `"type": "module"` to package.json if missing
|
|
223
243
|
- Resolves tsconfig.json path aliases automatically (via `vite-tsconfig-paths`)
|
|
@@ -263,10 +283,11 @@ For TypeScript types, generate them with `wrangler types` and the `env` import w
|
|
|
263
283
|
TPR queries Cloudflare zone analytics at deploy time to find which pages actually get traffic, pre-renders only those, and uploads them to KV cache. The result is SSG-level latency for popular pages without pre-rendering your entire site.
|
|
264
284
|
|
|
265
285
|
```bash
|
|
266
|
-
vinext deploy --experimental-tpr # Pre-render pages covering 90% of traffic
|
|
267
|
-
vinext deploy --experimental-tpr
|
|
268
|
-
vinext deploy --experimental-tpr --tpr-
|
|
269
|
-
vinext deploy --experimental-tpr --tpr-
|
|
286
|
+
npx @vinext/cloudflare deploy --experimental-tpr # Pre-render pages covering 90% of traffic
|
|
287
|
+
vp exec vinext-cloudflare deploy --experimental-tpr # Same, with Vite+
|
|
288
|
+
npx @vinext/cloudflare deploy --experimental-tpr --tpr-coverage 95 # More aggressive coverage
|
|
289
|
+
npx @vinext/cloudflare deploy --experimental-tpr --tpr-limit 500 # Cap at 500 pages
|
|
290
|
+
npx @vinext/cloudflare deploy --experimental-tpr --tpr-window 48 # Use 48h of analytics
|
|
270
291
|
```
|
|
271
292
|
|
|
272
293
|
Requires a custom domain (zone analytics are unavailable on `*.workers.dev`) and `CLOUDFLARE_API_TOKEN` with Zone.Analytics read permission.
|
|
@@ -326,7 +347,7 @@ NITRO_PRESET=netlify npx vite build
|
|
|
326
347
|
NITRO_PRESET=deno_deploy npx vite build
|
|
327
348
|
```
|
|
328
349
|
|
|
329
|
-
> **Deploying to Cloudflare?** You can use Nitro, but the native integration (`vinext deploy`
|
|
350
|
+
> **Deploying to Cloudflare?** You can use Nitro, but the native integration (`npx @vinext/cloudflare deploy`, `vp exec vinext-cloudflare deploy`, and `@cloudflare/vite-plugin`) is recommended. It provides the best developer experience with `cloudflare:workers` bindings, KV caching, image optimization, and one-command deploys.
|
|
330
351
|
|
|
331
352
|
<details>
|
|
332
353
|
<summary>Vercel</summary>
|
|
@@ -508,7 +529,7 @@ Load order matches Next.js (highest priority first):
|
|
|
508
529
|
Modes:
|
|
509
530
|
|
|
510
531
|
- `vinext dev` uses `development`
|
|
511
|
-
- `vinext build`, `vinext start`, and
|
|
532
|
+
- `vinext build`, `vinext start`, and `@vinext/cloudflare deploy` use `production`
|
|
512
533
|
|
|
513
534
|
Variable expansion (`$VAR` / `${VAR}`) is supported.
|
|
514
535
|
|
|
@@ -597,7 +618,7 @@ These are gaps we'd like to close — distinct from the [intentional exclusions]
|
|
|
597
618
|
- **Google Fonts are loaded from the CDN, not self-hosted.** No `size-adjust` fallback font metrics. Local fonts work but `@font-face` CSS is injected at runtime, not extracted at build time.
|
|
598
619
|
- **Route segment config** — `runtime` and `preferredRegion` are ignored (everything runs in the same environment).
|
|
599
620
|
- **Node.js production server (`vinext start`)** works for testing but is less complete than Workers deployment. Cloudflare Workers is the primary target.
|
|
600
|
-
- **Native Node modules (sharp, resvg, satori, lightningcss, @napi-rs/canvas)** crash Vite's RSC dev environment. Dynamic OG image/icon routes using these work in production builds but not in dev mode. These are auto-stubbed during
|
|
621
|
+
- **Native Node modules (sharp, resvg, satori, lightningcss, @napi-rs/canvas)** crash Vite's RSC dev environment. Dynamic OG image/icon routes using these work in production builds but not in dev mode. These are auto-stubbed during `@vinext/cloudflare deploy`.
|
|
601
622
|
- **`next.config.ts` `baseUrl` bare imports require Vite 8.** A `next.config.ts` that imports a bare specifier resolved through `tsconfig.json`'s `compilerOptions.baseUrl` (e.g. `import { bar } from "bar"` resolving to a local `bar.ts`) relies on Vite 8's native `resolve.tsconfigPaths` (Rolldown/oxc-resolver). On Vite 7 there is no native equivalent, so these imports are not resolved. `compilerOptions.paths` aliases (e.g. `@/foo`) work on both Vite 7 and 8. Note that if a bare import matches both a `baseUrl`-local file and an installed package of the same name, the installed package wins (vinext keeps packages externalized so CJS config plugins like `@next/mdx` keep working).
|
|
602
623
|
|
|
603
624
|
## Benchmarks
|
|
@@ -13,6 +13,8 @@ type EmitRestoredCssUrlAsset = (asset: {
|
|
|
13
13
|
* safe to run on every build-environment stylesheet, vendored CSS included.
|
|
14
14
|
*/
|
|
15
15
|
declare function markCssUrlAssetReferences(code: string, id: string): string | null;
|
|
16
|
+
/** Rebase relative asset URLs when Sass inlines a partial into an entry file. */
|
|
17
|
+
declare function rebaseCssUrlAssetReferences(code: string, sourceDirectory: string, targetDirectory: string): string | null;
|
|
16
18
|
/**
|
|
17
19
|
* Mutates emitted CSS assets in place so byte-identical `url()` dependencies
|
|
18
20
|
* keep their distinct Next-compatible filenames. Sibling files are produced via
|
|
@@ -26,4 +28,4 @@ declare function markCssUrlAssetReferences(code: string, id: string): string | n
|
|
|
26
28
|
*/
|
|
27
29
|
declare function restoreDedupedCssAssetReferences(bundle: CssUrlAssetBundle, emitRestoredAsset: EmitRestoredCssUrlAsset): void;
|
|
28
30
|
//#endregion
|
|
29
|
-
export { markCssUrlAssetReferences, restoreDedupedCssAssetReferences };
|
|
31
|
+
export { markCssUrlAssetReferences, rebaseCssUrlAssetReferences, restoreDedupedCssAssetReferences };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
//#region src/build/css-url-assets.ts
|
|
2
3
|
/**
|
|
3
4
|
* Next-parity for CSS `url()` asset dependencies.
|
|
@@ -128,6 +129,22 @@ function markCssUrlAssetReferences(code, id) {
|
|
|
128
129
|
});
|
|
129
130
|
});
|
|
130
131
|
}
|
|
132
|
+
/** Rebase relative asset URLs when Sass inlines a partial into an entry file. */
|
|
133
|
+
function rebaseCssUrlAssetReferences(code, sourceDirectory, targetDirectory) {
|
|
134
|
+
return rewriteCssUrls(code, (rawUrl) => {
|
|
135
|
+
const parts = splitUrl(rawUrl.trim());
|
|
136
|
+
if (!isRelativeAssetUrl(parts.path)) return null;
|
|
137
|
+
const lower = parts.path.toLowerCase();
|
|
138
|
+
if (!CSS_ASSET_EXT_RE.test(lower) || lower.endsWith(".css")) return null;
|
|
139
|
+
const absolutePath = path.resolve(sourceDirectory, parts.path);
|
|
140
|
+
let rebasedPath = path.relative(targetDirectory, absolutePath).replaceAll("\\", "/");
|
|
141
|
+
if (!rebasedPath.startsWith(".")) rebasedPath = `./${rebasedPath}`;
|
|
142
|
+
return joinUrl({
|
|
143
|
+
...parts,
|
|
144
|
+
path: rebasedPath
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
131
148
|
function isCssFileName(fileName) {
|
|
132
149
|
return fileName.toLowerCase().endsWith(".css");
|
|
133
150
|
}
|
|
@@ -210,4 +227,4 @@ function stripChunkMarkers(code) {
|
|
|
210
227
|
return code.replace(CSS_URL_MARKER_PARAM_RE, (_m, leadingQuestion) => leadingQuestion ? leadingQuestion : "");
|
|
211
228
|
}
|
|
212
229
|
//#endregion
|
|
213
|
-
export { markCssUrlAssetReferences, restoreDedupedCssAssetReferences };
|
|
230
|
+
export { markCssUrlAssetReferences, rebaseCssUrlAssetReferences, restoreDedupedCssAssetReferences };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { escapeRegExp } from "../utils/regex.js";
|
|
2
|
+
import { readPrerenderManifest } from "../server/prerender-manifest.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
//#region src/build/inject-pregenerated-paths.ts
|
|
6
|
+
const VINEXT_PREGEN_START = "/* __VINEXT_PREGENERATED_CONCRETE_PATHS_START__ */";
|
|
7
|
+
const VINEXT_PREGEN_END = "/* __VINEXT_PREGENERATED_CONCRETE_PATHS_END__ */";
|
|
8
|
+
const VINEXT_PREGEN_RE = new RegExp(`${escapeRegExp(VINEXT_PREGEN_START)}[\\s\\S]*?${escapeRegExp(VINEXT_PREGEN_END)}\\n?`, "g");
|
|
9
|
+
function injectPregeneratedConcretePaths(root) {
|
|
10
|
+
const workerEntry = path.resolve(root, "dist", "server", "index.js");
|
|
11
|
+
if (!fs.existsSync(workerEntry)) return;
|
|
12
|
+
let code = fs.readFileSync(workerEntry, "utf-8").replace(VINEXT_PREGEN_RE, "");
|
|
13
|
+
const table = readPrerenderManifest(path.join(root, "dist", "server", "vinext-prerender.json"))?.pregeneratedConcretePaths ?? [];
|
|
14
|
+
if (table.length > 0) {
|
|
15
|
+
globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS = table;
|
|
16
|
+
code = `${VINEXT_PREGEN_START}\nglobalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS = ${JSON.stringify(table)};\n${VINEXT_PREGEN_END}\n` + code;
|
|
17
|
+
} else delete globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS;
|
|
18
|
+
fs.writeFileSync(workerEntry, code);
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { injectPregeneratedConcretePaths };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { classifyLayoutSegmentConfig } from "./report.js";
|
|
1
2
|
import { AppElementsWire } from "../server/app-elements-wire.js";
|
|
2
3
|
import "../server/app-elements.js";
|
|
3
|
-
import { classifyLayoutSegmentConfig } from "./report.js";
|
|
4
4
|
import { createAppPageTreePath } from "../server/app-page-route-wiring.js";
|
|
5
5
|
//#region src/build/layout-classification.ts
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { apiRouter, pagesRouter } from "../routing/pages-router.js";
|
|
2
|
-
import { appRouter } from "../routing/app-router.js";
|
|
3
2
|
import { buildReportRows } from "./report.js";
|
|
3
|
+
import { appRouter } from "../routing/app-router.js";
|
|
4
4
|
//#region src/build/nitro-route-rules.ts
|
|
5
5
|
/**
|
|
6
6
|
* Scans the filesystem for route files and generates Nitro `routeRules` for ISR routes.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PagesClientAssets } from "../server/pages-client-assets.js";
|
|
2
|
+
|
|
3
|
+
//#region src/build/pages-client-assets-module.d.ts
|
|
4
|
+
declare const PAGES_CLIENT_ASSETS_MODULE = "vinext-client-assets.js";
|
|
5
|
+
declare function buildPagesClientAssetsModule(assets: PagesClientAssets): string;
|
|
6
|
+
declare function writePagesClientAssetsModuleIfMissing(outputDir: string, moduleSource: string): void;
|
|
7
|
+
declare function setPagesClientAssetsBuildMetadata(buildSession: string, moduleSource: string): void;
|
|
8
|
+
declare function takePagesClientAssetsBuildMetadata(buildSession: string): string | null;
|
|
9
|
+
declare function clearPagesClientAssetsBuildMetadata(buildSession: string): void;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { PAGES_CLIENT_ASSETS_MODULE, buildPagesClientAssetsModule, clearPagesClientAssetsBuildMetadata, setPagesClientAssetsBuildMetadata, takePagesClientAssetsBuildMetadata, writePagesClientAssetsModuleIfMissing };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/build/pages-client-assets-module.ts
|
|
4
|
+
const PAGES_CLIENT_ASSETS_MODULE = "vinext-client-assets.js";
|
|
5
|
+
const pagesClientAssetsByBuildSession = /* @__PURE__ */ new Map();
|
|
6
|
+
function buildPagesClientAssetsModule(assets) {
|
|
7
|
+
return `export default ${JSON.stringify(assets)};\n`;
|
|
8
|
+
}
|
|
9
|
+
function writePagesClientAssetsModuleIfMissing(outputDir, moduleSource) {
|
|
10
|
+
const outputPath = path.join(outputDir, PAGES_CLIENT_ASSETS_MODULE);
|
|
11
|
+
if (fs.existsSync(outputPath)) return;
|
|
12
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
13
|
+
fs.writeFileSync(outputPath, moduleSource);
|
|
14
|
+
}
|
|
15
|
+
function setPagesClientAssetsBuildMetadata(buildSession, moduleSource) {
|
|
16
|
+
pagesClientAssetsByBuildSession.set(buildSession, moduleSource);
|
|
17
|
+
}
|
|
18
|
+
function takePagesClientAssetsBuildMetadata(buildSession) {
|
|
19
|
+
const moduleSource = pagesClientAssetsByBuildSession.get(buildSession) ?? null;
|
|
20
|
+
pagesClientAssetsByBuildSession.delete(buildSession);
|
|
21
|
+
return moduleSource;
|
|
22
|
+
}
|
|
23
|
+
function clearPagesClientAssetsBuildMetadata(buildSession) {
|
|
24
|
+
pagesClientAssetsByBuildSession.delete(buildSession);
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { PAGES_CLIENT_ASSETS_MODULE, buildPagesClientAssetsModule, clearPagesClientAssetsBuildMetadata, setPagesClientAssetsBuildMetadata, takePagesClientAssetsBuildMetadata, writePagesClientAssetsModuleIfMissing };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { NoOpCacheHandler, setCacheHandler } from "../shims/cache-handler.js";
|
|
2
|
+
import { startProdServer } from "../server/prod-server.js";
|
|
3
|
+
//#region src/build/prerender-server-entry.ts
|
|
4
|
+
/**
|
|
5
|
+
* Child-process entry for a parallel prerender render server.
|
|
6
|
+
*
|
|
7
|
+
* Forked by `prerender-server-pool.ts`. React SSR/RSC rendering is CPU-bound
|
|
8
|
+
* JS, so a single-process promise pool serializes every render on one core.
|
|
9
|
+
* This entry lets the prerender phase fan rendering out across N OS processes
|
|
10
|
+
* (one per core, like Next.js's static worker pool): each child starts a normal
|
|
11
|
+
* vinext production server on an ephemeral port and reports it to the parent
|
|
12
|
+
* over IPC. The parent then load-balances per-route fetches across the pool.
|
|
13
|
+
*
|
|
14
|
+
* `VINEXT_PRERENDER` / `VINEXT_PRERENDER_OUTDIR` are passed via the fork env so
|
|
15
|
+
* they are set before any module loads (some server modules read the flag at
|
|
16
|
+
* import time).
|
|
17
|
+
*/
|
|
18
|
+
async function main() {
|
|
19
|
+
const outDir = process.env.VINEXT_PRERENDER_OUTDIR;
|
|
20
|
+
if (!outDir) throw new Error("[vinext] prerender server worker: VINEXT_PRERENDER_OUTDIR not set");
|
|
21
|
+
setCacheHandler(new NoOpCacheHandler());
|
|
22
|
+
const { port } = await startProdServer({
|
|
23
|
+
port: 0,
|
|
24
|
+
host: "127.0.0.1",
|
|
25
|
+
outDir,
|
|
26
|
+
noCompression: true,
|
|
27
|
+
purpose: "prerender",
|
|
28
|
+
silent: true
|
|
29
|
+
});
|
|
30
|
+
if (typeof process.send === "function") process.send({
|
|
31
|
+
type: "ready",
|
|
32
|
+
port
|
|
33
|
+
});
|
|
34
|
+
else throw new Error("[vinext] prerender server worker: no IPC channel to parent");
|
|
35
|
+
}
|
|
36
|
+
process.on("disconnect", () => process.exit(0));
|
|
37
|
+
main().catch((err) => {
|
|
38
|
+
const message = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
39
|
+
if (typeof process.send === "function") {
|
|
40
|
+
process.send({
|
|
41
|
+
type: "error",
|
|
42
|
+
error: message
|
|
43
|
+
}, () => process.exit(1));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
48
|
+
//#endregion
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/build/prerender-server-pool.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Whether the forkable worker entry exists as a runnable `.js` sibling. False
|
|
4
|
+
* when vinext runs from source (e.g. the test suite transpiles `.ts` on the
|
|
5
|
+
* fly and a forked plain-node child can't load `.ts`); callers fall back to the
|
|
6
|
+
* single in-process server. True in the published/built package.
|
|
7
|
+
*/
|
|
8
|
+
declare function prerenderPoolAvailable(): boolean;
|
|
9
|
+
type PrerenderServerPool = {
|
|
10
|
+
/** Ports of the forked render servers, for round-robin per-route fetches. */ports: number[];
|
|
11
|
+
/**
|
|
12
|
+
* Throw if any worker exited unexpectedly (i.e. not via `close()`). The
|
|
13
|
+
* caller MUST call this after the render loop: a crashed worker makes every
|
|
14
|
+
* route routed to it fail with a connection error, which the per-route
|
|
15
|
+
* handler records as a (non-fatal) error — without this check the build
|
|
16
|
+
* would emit partial output and still exit 0. Mirrors the single-process
|
|
17
|
+
* design, where a server crash takes the whole build down loudly.
|
|
18
|
+
*/
|
|
19
|
+
assertHealthy: () => void;
|
|
20
|
+
/**
|
|
21
|
+
* Record a failed fetch to a render worker. This closes the tiny ordering gap
|
|
22
|
+
* where the request can fail before Node has delivered the child `exit` event.
|
|
23
|
+
*/
|
|
24
|
+
recordRenderError: (err: unknown) => void; /** Kill every forked server. Safe to call more than once. */
|
|
25
|
+
close: () => Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Choose how many render processes to fork for `routeCount` routes.
|
|
29
|
+
*
|
|
30
|
+
* Returns 1 to mean "don't fork — render in-process as before" (small apps,
|
|
31
|
+
* single/dual-core, low memory, or an explicit `--prerender-concurrency 1`).
|
|
32
|
+
* `maxOverride` comes from `--prerender-concurrency`, which caps both the
|
|
33
|
+
* number of in-flight route fetches and the worker count (the default is capped
|
|
34
|
+
* at `min(cores, 8)`).
|
|
35
|
+
*/
|
|
36
|
+
declare function resolvePrerenderPoolSize(routeCount: number, maxOverride?: number): number;
|
|
37
|
+
/**
|
|
38
|
+
* Fork `size` production servers against `outDir` and resolve once all are
|
|
39
|
+
* listening. Each child reports its port over IPC. Rejects (and tears down any
|
|
40
|
+
* already-started children) if any child fails to come up.
|
|
41
|
+
*/
|
|
42
|
+
declare function startPrerenderServerPool(outDir: string, size: number, entry?: string): Promise<PrerenderServerPool>;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { PrerenderServerPool, prerenderPoolAvailable, resolvePrerenderPoolSize, startPrerenderServerPool };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { fork } from "node:child_process";
|
|
6
|
+
//#region src/build/prerender-server-pool.ts
|
|
7
|
+
/**
|
|
8
|
+
* Parallel prerender server pool.
|
|
9
|
+
*
|
|
10
|
+
* React SSR/RSC rendering during prerender is CPU-bound JS. vinext renders
|
|
11
|
+
* every route by fetching it from a single in-process production server, so the
|
|
12
|
+
* promise pool in `prerender.ts` only overlaps I/O — the actual rendering
|
|
13
|
+
* serializes on one core. This module forks a pool of OS processes (one vinext
|
|
14
|
+
* production server each, on its own ephemeral port) so the prerender phase can
|
|
15
|
+
* spread rendering across cores, mirroring Next.js's static worker pool. The
|
|
16
|
+
* caller load-balances per-route fetches across `ports`.
|
|
17
|
+
*
|
|
18
|
+
* child_process (not worker_threads) is deliberate: worker threads share the
|
|
19
|
+
* process and contend badly for CPU on this workload (measured ~2× slower
|
|
20
|
+
* per route and non-scaling), which is also why Next.js uses processes.
|
|
21
|
+
*/
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const WORKER_ENTRY = path.join(__dirname, "prerender-server-entry.js");
|
|
24
|
+
/**
|
|
25
|
+
* Whether the forkable worker entry exists as a runnable `.js` sibling. False
|
|
26
|
+
* when vinext runs from source (e.g. the test suite transpiles `.ts` on the
|
|
27
|
+
* fly and a forked plain-node child can't load `.ts`); callers fall back to the
|
|
28
|
+
* single in-process server. True in the published/built package.
|
|
29
|
+
*/
|
|
30
|
+
function prerenderPoolAvailable() {
|
|
31
|
+
return fs.existsSync(WORKER_ENTRY);
|
|
32
|
+
}
|
|
33
|
+
/** Hard cap on render processes. Beyond this the main thread (which collects
|
|
34
|
+
* HTML and writes files) becomes the bottleneck, so more workers don't help. */
|
|
35
|
+
const MAX_POOL_SIZE = 8;
|
|
36
|
+
/** Don't fork a dedicated process unless a worker gets at least this many
|
|
37
|
+
* routes — below this the fork + bundle-import startup cost outweighs the
|
|
38
|
+
* parallel-render saving (measured: a 2-worker pool only reliably beats the
|
|
39
|
+
* single server from ~50 routes/worker up). */
|
|
40
|
+
const MIN_ROUTES_PER_WORKER = 48;
|
|
41
|
+
/** Rough memory budget reserved for the main build process plus its in-process
|
|
42
|
+
* prerender server. */
|
|
43
|
+
const APPROX_MAIN_PROCESS_BYTES = 768 * 1024 * 1024;
|
|
44
|
+
/** Rough memory budget per worker (a worker loads the whole server bundle).
|
|
45
|
+
* Used with total system memory as a coarse upper bound on constrained machines
|
|
46
|
+
* so K render processes don't OOM. Deliberately under-forks rather than risking
|
|
47
|
+
* OOM; measured mid-size apps are usually lower (~260-380 MB/worker). */
|
|
48
|
+
const APPROX_BYTES_PER_WORKER = 768 * 1024 * 1024;
|
|
49
|
+
/** Per-worker readiness timeout. */
|
|
50
|
+
const WORKER_READY_TIMEOUT_MS = 6e4;
|
|
51
|
+
/** Best-effort close timeout so build cleanup is not blocked forever by a wedged child. */
|
|
52
|
+
const WORKER_CLOSE_TIMEOUT_MS = 5e3;
|
|
53
|
+
const PRERENDER_WORKER_TRANSPORT_ERROR_CODES = new Set([
|
|
54
|
+
"ECONNREFUSED",
|
|
55
|
+
"ECONNRESET",
|
|
56
|
+
"EPIPE",
|
|
57
|
+
"UND_ERR_SOCKET"
|
|
58
|
+
]);
|
|
59
|
+
/**
|
|
60
|
+
* Choose how many render processes to fork for `routeCount` routes.
|
|
61
|
+
*
|
|
62
|
+
* Returns 1 to mean "don't fork — render in-process as before" (small apps,
|
|
63
|
+
* single/dual-core, low memory, or an explicit `--prerender-concurrency 1`).
|
|
64
|
+
* `maxOverride` comes from `--prerender-concurrency`, which caps both the
|
|
65
|
+
* number of in-flight route fetches and the worker count (the default is capped
|
|
66
|
+
* at `min(cores, 8)`).
|
|
67
|
+
*/
|
|
68
|
+
function resolvePrerenderPoolSize(routeCount, maxOverride) {
|
|
69
|
+
const cores = Math.max(1, os.availableParallelism());
|
|
70
|
+
const byCores = Math.max(1, Math.min(cores - 1, MAX_POOL_SIZE));
|
|
71
|
+
const memoryForWorkers = Math.max(0, os.totalmem() - APPROX_MAIN_PROCESS_BYTES);
|
|
72
|
+
const byMemory = Math.max(1, Math.floor(memoryForWorkers / APPROX_BYTES_PER_WORKER));
|
|
73
|
+
const cap = maxOverride && maxOverride > 0 ? Math.min(maxOverride, byCores) : byCores;
|
|
74
|
+
const byRoutes = Math.floor(routeCount / MIN_ROUTES_PER_WORKER);
|
|
75
|
+
return Math.max(1, Math.min(cap, byMemory, byRoutes));
|
|
76
|
+
}
|
|
77
|
+
function getErrorCauseCode(err) {
|
|
78
|
+
if (!(err instanceof Error)) return void 0;
|
|
79
|
+
const cause = err.cause;
|
|
80
|
+
return typeof cause?.code === "string" ? cause.code : void 0;
|
|
81
|
+
}
|
|
82
|
+
function isWorkerTransportError(err) {
|
|
83
|
+
const code = getErrorCauseCode(err);
|
|
84
|
+
if (code && PRERENDER_WORKER_TRANSPORT_ERROR_CODES.has(code)) return true;
|
|
85
|
+
return err instanceof TypeError && err.message === "fetch failed";
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Fork `size` production servers against `outDir` and resolve once all are
|
|
89
|
+
* listening. Each child reports its port over IPC. Rejects (and tears down any
|
|
90
|
+
* already-started children) if any child fails to come up.
|
|
91
|
+
*/
|
|
92
|
+
async function startPrerenderServerPool(outDir, size, entry = WORKER_ENTRY) {
|
|
93
|
+
const children = [];
|
|
94
|
+
let shuttingDown = false;
|
|
95
|
+
let crash = null;
|
|
96
|
+
let renderTransportError = null;
|
|
97
|
+
const close = async () => {
|
|
98
|
+
shuttingDown = true;
|
|
99
|
+
await Promise.all(children.map(closeChild));
|
|
100
|
+
};
|
|
101
|
+
try {
|
|
102
|
+
const readies = Array.from({ length: size }, () => {
|
|
103
|
+
const child = fork(entry, [], {
|
|
104
|
+
cwd: process.cwd(),
|
|
105
|
+
env: {
|
|
106
|
+
...process.env,
|
|
107
|
+
VINEXT_PRERENDER: "1",
|
|
108
|
+
VINEXT_PRERENDER_OUTDIR: outDir
|
|
109
|
+
},
|
|
110
|
+
stdio: [
|
|
111
|
+
"ignore",
|
|
112
|
+
"inherit",
|
|
113
|
+
"inherit",
|
|
114
|
+
"ipc"
|
|
115
|
+
]
|
|
116
|
+
});
|
|
117
|
+
children.push(child);
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const timer = setTimeout(() => {
|
|
120
|
+
reject(/* @__PURE__ */ new Error("[vinext] prerender render server did not start within 60s"));
|
|
121
|
+
}, WORKER_READY_TIMEOUT_MS);
|
|
122
|
+
const cleanupStartupListeners = () => {
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
child.off("message", onMessage);
|
|
125
|
+
child.off("error", onError);
|
|
126
|
+
child.off("exit", onStartupExit);
|
|
127
|
+
};
|
|
128
|
+
const onMessage = (msg) => {
|
|
129
|
+
cleanupStartupListeners();
|
|
130
|
+
if (msg?.type === "ready" && typeof msg.port === "number") {
|
|
131
|
+
child.once("exit", (code, signal) => {
|
|
132
|
+
if (!shuttingDown && !crash) crash = {
|
|
133
|
+
port: msg.port,
|
|
134
|
+
code,
|
|
135
|
+
signal
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
resolve(msg.port);
|
|
139
|
+
} else reject(/* @__PURE__ */ new Error(`[vinext] prerender render server failed: ${msg?.error ?? "unknown"}`));
|
|
140
|
+
};
|
|
141
|
+
const onError = (err) => {
|
|
142
|
+
cleanupStartupListeners();
|
|
143
|
+
reject(err);
|
|
144
|
+
};
|
|
145
|
+
const onStartupExit = (code, signal) => {
|
|
146
|
+
if (code !== null || signal !== null) {
|
|
147
|
+
cleanupStartupListeners();
|
|
148
|
+
reject(/* @__PURE__ */ new Error(`[vinext] prerender render server exited during startup (code ${code}, signal ${signal})`));
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
child.once("message", onMessage);
|
|
152
|
+
child.once("error", onError);
|
|
153
|
+
child.once("exit", onStartupExit);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
const ports = await Promise.all(readies);
|
|
157
|
+
const recordRenderError = (err) => {
|
|
158
|
+
if (!renderTransportError && isWorkerTransportError(err)) renderTransportError = err instanceof Error ? err : new Error(String(err));
|
|
159
|
+
};
|
|
160
|
+
const assertHealthy = () => {
|
|
161
|
+
if (crash) throw new Error(`[vinext] A prerender render worker (port ${crash.port}) exited unexpectedly (code ${crash.code}, signal ${crash.signal}) during the build. Prerender output is incomplete; failing the build. This is often an out-of-memory kill — try a lower --prerender-concurrency.`);
|
|
162
|
+
if (renderTransportError) {
|
|
163
|
+
const causeCode = getErrorCauseCode(renderTransportError);
|
|
164
|
+
throw new Error(`[vinext] A prerender render worker request failed` + (causeCode ? ` (${causeCode})` : "") + " before the worker exit was observed. Prerender output is incomplete; failing the build. This is often an out-of-memory kill — try a lower --prerender-concurrency.");
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
ports,
|
|
169
|
+
assertHealthy,
|
|
170
|
+
recordRenderError,
|
|
171
|
+
close
|
|
172
|
+
};
|
|
173
|
+
} catch (err) {
|
|
174
|
+
await close();
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function closeChild(child) {
|
|
179
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
180
|
+
const exited = new Promise((resolve) => {
|
|
181
|
+
const timer = setTimeout(resolve, WORKER_CLOSE_TIMEOUT_MS);
|
|
182
|
+
timer.unref?.();
|
|
183
|
+
const finish = () => {
|
|
184
|
+
clearTimeout(timer);
|
|
185
|
+
resolve();
|
|
186
|
+
};
|
|
187
|
+
child.once("exit", finish);
|
|
188
|
+
if (child.exitCode !== null || child.signalCode !== null) finish();
|
|
189
|
+
});
|
|
190
|
+
if (!child.killed) child.kill("SIGKILL");
|
|
191
|
+
await exited;
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
export { prerenderPoolAvailable, resolvePrerenderPoolSize, startPrerenderServerPool };
|
|
@@ -22,7 +22,8 @@ type PrerenderRouteResult = {
|
|
|
22
22
|
* Omitted for non-dynamic routes where pattern === path.
|
|
23
23
|
*/
|
|
24
24
|
path?: string; /** Which router produced this route. Used by cache seeding. */
|
|
25
|
-
router: "app" | "pages"; /**
|
|
25
|
+
router: "app" | "pages"; /** Response headers that must be replayed with the prerendered artifact. */
|
|
26
|
+
headers?: Record<string, string>; /** Set to true when this is a PPR fallback shell. */
|
|
26
27
|
fallback?: boolean;
|
|
27
28
|
} | {
|
|
28
29
|
route: string;
|