Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .agents/skills/migrate-to-vinext/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ Add `"type": "module"` to package.json. Rename any CJS config files:

See [references/config-examples.md](references/config-examples.md) for config variants per router and deployment target.

If the project already has custom Vite config, prefer Vite 8-native keys when editing it: `oxc`, `optimizeDeps.rolldownOptions`, and `build.rolldownOptions`. Older `esbuild` and `build.rollupOptions` settings still work for now but are migration targets.

**Pages Router (minimal):**

```ts
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Vite Config Examples

These examples stay minimal on purpose. If you add custom build tuning on Vite 8, prefer `oxc`, `optimizeDeps.rolldownOptions`, and `build.rolldownOptions` / `worker.rolldownOptions` over older `esbuild` and `build.rollupOptions` settings.

## Pages Router — Local Development

No Cloudflare, no deployment. Simplest possible config.
Expand Down
14 changes: 14 additions & 0 deletions .agents/skills/migrate-to-vinext/references/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@
| RSC environment crash on dev start | Native Node module (sharp, satori) loaded in RSC env | vinext auto-stubs these in production; in dev, ensure these are only imported in server code behind dynamic `import()` |
| `ASSETS binding not found` | wrangler.jsonc missing assets config | Add `"assets": { "not_found_handling": "none" }` to wrangler.jsonc |

## Vite 8 Migration Notes

- **Symptom:** deprecation warnings for `esbuild`, `optimizeDeps.esbuildOptions`, or `build.rollupOptions`.
**Cause:** Vite 8 now defaults to Oxc and Rolldown.
**Fix:** Prefer `oxc`, `optimizeDeps.rolldownOptions`, and `build.rolldownOptions` / `worker.rolldownOptions` in custom Vite config.

- **Symptom:** a package only breaks on Vite 8 with a bad `default` import from CommonJS.
**Cause:** Vite 8 made CommonJS default import handling more consistent.
**Fix:** Fix the import or package if possible. As a temporary workaround, set `legacy.inconsistentCjsInterop: true`.

- **Symptom:** older browsers stop working after migration.
**Cause:** Vite 8 raised the default `build.target` browser baseline.
**Fix:** Set `build.target` explicitly in `vite.config.*` if you need older browser support.

## ESM Conversion Issues

When adding `"type": "module"`, any `.js` file using `module.exports` or `require()` will break. Common files that need renaming to `.cjs`:
Expand Down
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,16 @@ The RSC entry's `default` export is the request handler. The plugin calls it for

You **must** use `createBuilder()` + `builder.buildApp()` for production builds, not `build()` directly. Calling `build()` from the Vite JS API doesn't trigger the RSC plugin's multi-environment build pipeline. `buildApp()` runs the 5-step RSC/SSR/client build sequence in the correct order.

### Vite 8 Defaults

This repo currently resolves `vite` to `@voidzero-dev/vite-plus-core`, which bundles Vite 8. Keep that in mind when touching Vite config or plugin integration code:

- Prefer `oxc` over `esbuild` for new JavaScript transform config
- Prefer `optimizeDeps.rolldownOptions` over `optimizeDeps.esbuildOptions`
- Prefer `build.rolldownOptions` / `worker.rolldownOptions` over adding new `*.rollupOptions` config
- When touching existing `build.rollupOptions` or `manualChunks`, preserve Vite 7 compatibility but treat them as migration targets, not patterns to copy forward
- If something breaks only on Vite 8, check the newer `build.target` baseline and stricter CommonJS default import behavior first

### Virtual Module Resolution Quirks

- **Build-time root prefix:** Vite prefixes virtual module IDs with the project root path when resolving SSR build entries. The `resolveId` hook must handle both `virtual:vinext-server-entry` and `<root>/virtual:vinext-server-entry`.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ This will:

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.

vinext supports both Vite 7 and Vite 8. If you bring custom Vite config or plugins from an older setup, note that Vite 8 now defaults to Rolldown, Oxc, Lightning CSS, and a newer browser baseline. Prefer `oxc`, `optimizeDeps.rolldownOptions`, and `build.rolldownOptions` over older `esbuild` and `build.rollupOptions` knobs, and override `build.target` if you still need older browsers. If a dependency only breaks on Vite 8 because of stricter CommonJS default import handling, fix the import or use `legacy.inconsistentCjsInterop: true` as a temporary escape hatch. See the [Vite 8 migration guide](https://vite.dev/guide/migration).

```bash
npm run dev:vinext # Start the vinext dev server (port 3001)
npm run dev # Still runs Next.js as before
Expand Down Expand Up @@ -696,7 +698,7 @@ Or add it to your `package.json` as a file dependency:
}
```

vinext has peer dependencies on `react ^19.2.4`, `react-dom ^19.2.4`, and `vite ^7.0.0`. Then replace `next` with `vinext` in your scripts and run as normal.
vinext has peer dependencies on `react ^19.2.4`, `react-dom ^19.2.4`, and `vite ^7.0.0 || ^8.0.0`. Then replace `next` with `vinext` in your scripts and run as normal.

## Contributing

Expand Down
2 changes: 1 addition & 1 deletion packages/vinext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"peerDependencies": {
"@mdx-js/rollup": "^3.0.0",
"@vitejs/plugin-react": "^5.1.4 || ^6.0.0",
"@vitejs/plugin-rsc": "^0.5.19",
"@vitejs/plugin-rsc": "^0.5.21",
"react": ">=19.2.0",
"react-dom": ">=19.2.0",
"react-server-dom-webpack": "^19.2.4",
Expand Down
22 changes: 14 additions & 8 deletions packages/vinext/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* needed for most Next.js apps.
*/

import vinext, { clientOutputConfig, clientTreeshakeConfig } from "./index.js";
import vinext, { clientTreeshakeConfig, getClientOutputConfigForVite } from "./index.js";
import { printBuildReport } from "./build/report.js";
import { runPrerender } from "./build/run-prerender.js";
import path from "node:path";
Expand Down Expand Up @@ -358,6 +358,12 @@ async function buildApp() {
applyViteConfigCompatibility(process.cwd());

const vite = await loadVite();
const viteMajorVersion = Number.parseInt(vite.version, 10) || 7;

const withBuildBundlerOptions = (bundlerOptions: Record<string, unknown>) =>
viteMajorVersion >= 8
? { rolldownOptions: bundlerOptions }
: { rollupOptions: bundlerOptions };

console.log(`\n vinext build (Vite ${getViteVersion()})\n`);

Expand Down Expand Up @@ -445,11 +451,11 @@ async function buildApp() {
outDir: "dist/server",
emptyOutDir: false, // preserve RSC artefacts from buildApp()
ssr: "virtual:vinext-server-entry",
rollupOptions: {
...withBuildBundlerOptions({
output: {
entryFileNames: "entry.js",
},
},
}),
},
});
}
Expand All @@ -465,11 +471,11 @@ async function buildApp() {
outDir: "dist/client",
manifest: true,
ssrManifest: true,
rollupOptions: {
...withBuildBundlerOptions({
input: "virtual:vinext-client-entry",
output: clientOutputConfig,
output: getClientOutputConfigForVite(viteMajorVersion),
treeshake: clientTreeshakeConfig,
},
}),
},
},
logger,
Expand All @@ -483,11 +489,11 @@ async function buildApp() {
build: {
outDir: "dist/server",
ssr: "virtual:vinext-server-entry",
rollupOptions: {
...withBuildBundlerOptions({
output: {
entryFileNames: "entry.js",
},
},
}),
},
},
logger,
Expand Down
79 changes: 64 additions & 15 deletions packages/vinext/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,17 @@ const clientOutputConfig = {
experimentalMinChunkSize: 10_000,
};

const clientCodeSplittingConfig = {
minSize: 10_000,
groups: [
{
name(moduleId: string) {
return clientManualChunks(moduleId) ?? null;
},
},
],
};

/**
* Rollup treeshake configuration for production client builds.
*
Expand Down Expand Up @@ -570,6 +581,36 @@ const clientTreeshakeConfig = {
moduleSideEffects: "no-external" as const,
};

type VinextBuildConfig = NonNullable<UserConfig["build"]>;
type VinextBuildBundlerOptions = NonNullable<VinextBuildConfig["rolldownOptions"]>;
type VinextBuildConfigWithLegacy = VinextBuildConfig & {
rollupOptions?: VinextBuildBundlerOptions;
};

function getBuildBundlerOptions(
build: UserConfig["build"] | undefined,
): VinextBuildBundlerOptions | undefined {
const buildConfig = build as VinextBuildConfigWithLegacy | undefined;
return buildConfig?.rolldownOptions ?? buildConfig?.rollupOptions;
}

function withBuildBundlerOptions(
viteMajorVersion: number,
bundlerOptions: VinextBuildBundlerOptions,
): Partial<VinextBuildConfigWithLegacy> {
return viteMajorVersion >= 8
? { rolldownOptions: bundlerOptions }
: { rollupOptions: bundlerOptions };
}

function getClientOutputConfigForVite(viteMajorVersion: number) {
return viteMajorVersion >= 8
? {
codeSplitting: clientCodeSplittingConfig,
}
: clientOutputConfig;
}

type BuildManifestChunk = {
file: string;
isEntry?: boolean;
Expand Down Expand Up @@ -1766,15 +1807,15 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] {
// Disable Vite's default HTML serving - we handle all routing
appType: "custom",
build: {
rollupOptions: {
...withBuildBundlerOptions(viteMajorVersion, {
// Suppress "Module level directives cause errors when bundled"
// warnings for "use client" / "use server" directives. Our shims
// and third-party libraries legitimately use these directives;
// they are handled by the RSC plugin and are harmless in the
// final bundle. We preserve any user-supplied onwarn so custom
// warning handling is not lost.
onwarn: (() => {
const userOnwarn = config.build?.rollupOptions?.onwarn;
const userOnwarn = getBuildBundlerOptions(config.build)?.onwarn;
return (warning, defaultHandler) => {
if (
warning.code === "MODULE_LEVEL_DIRECTIVE" &&
Expand Down Expand Up @@ -1817,8 +1858,10 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] {
// Router). For multi-environment builds (App Router, Cloudflare),
// manualChunks is set per-environment on the client env below
// to avoid leaking into RSC/SSR environments.
...(!isSSR && !isMultiEnv ? { output: clientOutputConfig } : {}),
},
...(!isSSR && !isMultiEnv
? { output: getClientOutputConfigForVite(viteMajorVersion) }
: {}),
}),
},
// Let OPTIONS requests pass through Vite's CORS middleware to our
// route handlers so they can set the Allow header and run user-defined
Expand Down Expand Up @@ -1959,9 +2002,9 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] {
},
build: {
outDir: options.rscOutDir ?? "dist/server",
rollupOptions: {
...withBuildBundlerOptions(viteMajorVersion, {
input: { index: VIRTUAL_RSC_ENTRY },
},
}),
},
},
ssr: {
Expand All @@ -1984,9 +2027,9 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] {
},
build: {
outDir: options.ssrOutDir ?? "dist/server/ssr",
rollupOptions: {
...withBuildBundlerOptions(viteMajorVersion, {
input: { index: VIRTUAL_APP_SSR_ENTRY },
},
}),
},
},
client: {
Expand Down Expand Up @@ -2035,11 +2078,11 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] {
// on every page — defeating code-splitting for React.lazy() and
// next/dynamic boundaries.
...(hasCloudflarePlugin ? { manifest: true } : {}),
rollupOptions: {
...withBuildBundlerOptions(viteMajorVersion, {
input: { index: VIRTUAL_APP_BROWSER_ENTRY },
output: clientOutputConfig,
output: getClientOutputConfigForVite(viteMajorVersion),
treeshake: clientTreeshakeConfig,
},
}),
},
},
};
Expand All @@ -2054,11 +2097,11 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] {
build: {
manifest: true,
ssrManifest: true,
rollupOptions: {
...withBuildBundlerOptions(viteMajorVersion, {
input: { index: VIRTUAL_CLIENT_ENTRY },
output: clientOutputConfig,
output: getClientOutputConfigForVite(viteMajorVersion),
treeshake: clientTreeshakeConfig,
},
}),
},
},
};
Expand Down Expand Up @@ -4504,7 +4547,13 @@ export type {
export type { NextConfig } from "./config/next-config.js";

// Exported for CLI and testing
export { clientManualChunks, clientOutputConfig, clientTreeshakeConfig, computeLazyChunks };
export {
clientManualChunks,
clientOutputConfig,
clientTreeshakeConfig,
computeLazyChunks,
getClientOutputConfigForVite,
};
export { augmentSsrManifestFromBundle as _augmentSsrManifestFromBundle };
export { resolvePostcssStringPlugins as _resolvePostcssStringPlugins };
export { _postcssCache };
Expand Down
Loading