Migrate Next.js to vinext
vinext reimplements the Next.js API surface on Vite. Existing
,
, and
work as-is — migration is a package swap, config generation, and ESM conversion. No changes to application code required.
FIRST: Verify Next.js Project
Confirm
is in
or
in
. If not found, STOP — this skill does not apply.
Detect the package manager from the lockfile:
| Lockfile | Manager | Install | Uninstall |
|---|
| pnpm | | |
| yarn | | |
| / | bun | | |
| or none | npm | | |
Detect the router: if an
directory exists at root or under
, it's App Router. If only
exists, it's Pages Router. Both can coexist.
Quick Reference
| Command | Purpose |
|---|
| Scan project for compatibility issues, produce scored report |
| Automated migration — installs deps, generates config, converts to ESM |
| Development server with HMR |
| Production build (multi-environment for App Router) |
| Local production server |
| Build and deploy to Cloudflare Workers |
Phase 1: Check Compatibility
Run
(install vinext first if needed via
). Review the scored report. If critical incompatibilities exist, inform the user before proceeding.
See references/compatibility.md for supported/unsupported features and ecosystem library status.
Phase 2: Automated Migration (Recommended)
- Runs for a compatibility report
- Installs as a devDependency (and for App Router)
- Adds to package.json
- Renames CJS config files (e.g., → ) to avoid ESM conflicts
- Adds and scripts to package.json
- Generates a minimal
This is non-destructive — the existing Next.js setup continues to work alongside vinext. Use the
script to test before fully switching over.
If
succeeds, skip to Phase 4 (Verify). If it fails or the user prefers manual control, continue to Phase 3.
Phase 3: Manual Migration
Use this as a fallback when
doesn't work or the user wants full control.
3a. Replace packages
bash
# Example with npm:
npm uninstall next
npm install vinext
npm install -D vite
# App Router only:
npm install -D @vitejs/plugin-rsc
3b. Update scripts
Replace all
commands in
scripts:
| Before | After | Notes |
|---|
| | Dev server with HMR |
| | Production build |
| | Local production server |
| | Delegates to eslint/oxlint |
3c. Convert to ESM
Add
to package.json. Rename any CJS config files:
- →
- →
- Any other config that uses
3d. Generate vite.config.ts
See 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:
,
optimizeDeps.rolldownOptions
, and
. Older
and
settings still work for now but are migration targets.
Pages Router (minimal):
ts
import vinext from "vinext";
import { defineConfig } from "vite";
export default defineConfig({ plugins: [vinext()] });
App Router (minimal):
ts
import vinext from "vinext";
import { defineConfig } from "vite";
export default defineConfig({ plugins: [vinext()] });
vinext auto-registers
for App Router when the
option is not explicitly
. No manual RSC plugin config needed for local development.
Phase 4: Deployment (Optional)
Option A: Cloudflare Workers (recommended for Cloudflare)
If the user wants to deploy to Cloudflare Workers, use
. It auto-generates
, worker entry, and Vite config if missing, installs
and
, then builds and deploys.
For manual setup or custom worker entries, see references/config-examples.md.
Cloudflare Bindings (D1, R2, KV, AI, etc.)
To access Cloudflare bindings (D1, R2, KV, AI, Queues, Durable Objects, etc.), use
import { env } from "cloudflare:workers"
in any server component, route handler, or server action:
tsx
import { env } from "cloudflare:workers";
export default async function Page() {
const result = await env.DB.prepare("SELECT * FROM posts").all();
return <div>{JSON.stringify(result)}</div>;
}
This works because
runs server environments in workerd, where
is a native module. No custom worker entry, no
, no special configuration needed. Just import and use.
Bindings must be defined in
. For TypeScript types, run
.
IMPORTANT: Do not use
,
, or custom worker entries with
to access bindings. These are older patterns.
is the recommended approach and works out of the box with vinext.
Option B: Other platforms (via Nitro)
For deploying to Vercel, Netlify, AWS, Deno Deploy, or any other
Nitro-supported platform, add the Nitro Vite plugin:
ts
// vite.config.ts
import { defineConfig } from "vite";
import vinext from "vinext";
import { nitro } from "nitro/vite";
export default defineConfig({
plugins: [vinext(), nitro()],
});
Build and deploy:
bash
NITRO_PRESET=vercel npx vite build # Vercel
NITRO_PRESET=netlify npx vite build # Netlify
NITRO_PRESET=deno_deploy npx vite build # Deno Deploy
NITRO_PRESET=node npx vite build # Node.js server
Nitro auto-detects the platform in most CI/CD environments, so the preset is often unnecessary.
Note: For Cloudflare Workers, Nitro works but the native integration (
/
) is recommended for the best developer experience with
bindings, KV caching, and one-command deploys.
Phase 5: Verify
- Run to start the development server
- Confirm the server starts without errors
- Navigate key routes and check functionality
- Report the result to the user — if errors occur, share full output
See references/troubleshooting.md for common migration errors.
Known Limitations
| Feature | Status |
|---|
| optimization | Remote images via @unpic; no build-time optimization |
| CDN-loaded, not self-hosted |
| Domain-based i18n | Not supported; path-prefix i18n works |
| Not supported; use Vitest |
| Turbopack/webpack config | Ignored; use Vite plugins instead |
| / | Route segment configs ignored |
| PPR (Partial Prerendering) | Use directive instead (Next.js 16 approach) |
Anti-patterns
- Do not modify , , or application code. vinext shims all imports — no import rewrites needed.
- Do not rewrite imports to in application code. Imports like , , resolve automatically.
- Do not copy webpack/Turbopack config into Vite config. Use Vite-native plugins instead.
- Do not skip the compatibility check. Run before migration to surface issues early.
- Do not remove unless replacing it with or . vinext reads it for redirects, rewrites, headers, basePath, i18n, images, and env config.
- Do not use or custom worker entries for bindings. Use
import { env } from "cloudflare:workers"
instead. This is the modern pattern and works out of the box with vinext and .
- For Cloudflare Workers, prefer the native integration over Nitro. / provides the best experience with bindings, KV caching, and image optimization. Nitro works for Cloudflare but the native setup is recommended.