
Migrate To Vinext
- 4.3k installs
- 8.5k repo stars
- Updated July 27, 2026
- cloudflare/vinext
migrate-to-vinext is an agent skill: Migrates Next.js projects to vinext (Vite-based Next.js reimplementation). Load when asked to migrate, convert, or switch from Next.js to vi
About
The migrate-to-vinext skill Migrates Next.js projects to vinext (Vite-based Next.js reimplementation). Load when asked to migrate, convert, or switch from Next.js to vinext. Handles compatibility scanning, package replacement, Vite config generation, ESM conversion, and deployment setup (Cloudflare Workers natively, other platforms via Nitro).. Migrate Next.js to vinext vinext reimplements the Next.js API surface on Vite. Existing `app/`, `pages/`, and `next.config.js` 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 `next` is in `dependencies` or `devDependencies` in `package.json`. If not found, STOP — this skill does not apply. Detect the package manager from the lockfile: | Lockfile | Manager | Install | Uninstall | | Agents should read SKILL.md quick start steps, verify required binaries and environment variables, and follow reference files for exact parameters before calling tools.
- Covers migrate-to-vinext quick start, workflow steps, and reference pointers from SKILL.md.
- Tagged for stage build and subphase frontend in the closed Skillselion taxonomy.
- Documents prerequisites, permissions shell, filesystem, and compatible agents.
- Includes AEO tagMeta with task queries, keywords, and evidence quotes for discovery.
- Cross-links related skills and generated REFERENCE.md tables where the repo provides them.
Migrate To Vinext by the numbers
- 4,281 all-time installs (skills.sh)
- +78 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #111 of 2,277 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
migrate-to-vinext capabilities & compatibility
- Capabilities
- migrate to vinext documented workflow · quick start examples · reference parameter lookup · taxonomy aligned metadata · aeo discovery fields
- Works with
- cloudflare · vercel
- Use cases
- frontend · ci cd
What migrate-to-vinext says it does
Migrates Next.js projects to vinext (Vite-based Next.js reimplementation). Load when asked to migrat
npx skills add https://github.com/cloudflare/vinext --skill migrate-to-vinextAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4.3k |
|---|---|
| repo stars | ★ 8.5k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | cloudflare/vinext ↗ |
How do I run migrate-to-vinext correctly without guessing steps, tools, or parameters?
Migrates Next.js projects to vinext (Vite-based Next.js reimplementation). Load when asked to migrate, convert, or switch from Next.js to vinext. Handles compatibility scanning, package replaceme
Who is it for?
Teams using migrate-to-vinext when SKILL.md triggers match the user request.
Skip if: Skip when the task is outside migrate-to-vinext documented triggers or sibling skill scope.
When should I use this skill?
User mentions migrate-to-vinext, related trigger phrases, or asks to follow this SKILL.md workflow.
What you get
Completed migrate-to-vinext workflow with outputs and checks defined in SKILL.md.
- migrate-to-vinext output per SKILL.md
By the numbers
- Stage build/frontend
- Category Frontend Development
- Complexity intermediate
Files
Migrate Next.js to vinext
vinext reimplements the Next.js API surface on Vite. Existing app/, pages/, and next.config.js 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 next is in dependencies or devDependencies in package.json. If not found, STOP — this skill does not apply.
Detect the package manager from the lockfile:
| Lockfile | Manager | Install | Uninstall |
|---|---|---|---|
pnpm-lock.yaml | pnpm | pnpm add | pnpm remove |
yarn.lock | yarn | yarn add | yarn remove |
bun.lockb / bun.lock | bun | bun add | bun remove |
package-lock.json or none | npm | npm install | npm uninstall |
Detect the router: if an app/ directory exists at root or under src/, it's App Router. If only pages/ exists, it's Pages Router. Both can coexist.
Quick Reference
| Command | Purpose |
|---|---|
vinext check | Scan project for compatibility issues, produce scored report |
vinext init | Automated migration — installs deps, generates config, converts to ESM |
vinext dev | Development server with HMR |
vinext build | Production build (multi-environment for App Router) |
vinext start | Local production server |
vinext deploy | Build and deploy to Cloudflare Workers |
Phase 1: Check Compatibility
Run vinext check (install vinext first if needed via npx vinext check). 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)
Run vinext init. This command:
1. Runs vinext check for a compatibility report 2. Installs vite as a devDependency (and @vitejs/plugin-rsc for App Router) 3. Adds "type": "module" to package.json 4. Renames CJS config files (e.g., postcss.config.js → .cjs) to avoid ESM conflicts 5. Adds dev:vinext and build:vinext scripts to package.json 6. Generates a minimal vite.config.ts 7. Adds /dist/ and .vinext/ to .gitignore
This is non-destructive — the existing Next.js setup continues to work alongside vinext. Use the dev:vinext script to test before fully switching over.
If vinext init 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 vinext init doesn't work or the user wants full control.
3a. Replace packages
# Example with npm:
npm uninstall next
npm install vinext
npm install -D vite
# App Router only:
npm install -D @vitejs/plugin-rsc3b. Update scripts
Replace all next commands in package.json scripts:
| Before | After | Notes |
|---|---|---|
next dev | vinext dev | Dev server with HMR |
next build | vinext build | Production build |
next start | vinext start | Local production server |
next lint | vinext lint | Delegates to eslint/oxlint |
Preserve flags: next dev --port 3001 → vinext dev --port 3001.
3c. Convert to ESM
Add "type": "module" to package.json. Rename any CJS config files:
postcss.config.js→postcss.config.cjstailwind.config.js→tailwind.config.cjs- Any other
.jsconfig that usesmodule.exports
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: oxc, optimizeDeps.rolldownOptions, and build.rolldownOptions. Older esbuild and build.rollupOptions settings still work for now but are migration targets.
Pages Router (minimal):
import vinext from "vinext";
import { defineConfig } from "vite";
export default defineConfig({ plugins: [vinext()] });App Router (minimal):
import vinext from "vinext";
import { defineConfig } from "vite";
export default defineConfig({ plugins: [vinext()] });vinext auto-registers @vitejs/plugin-rsc for App Router when the rsc option is not explicitly false. No manual RSC plugin config needed for local development.
3e. Update .gitignore
Ensure vinext-generated output and caches are ignored:
/dist/
.vinext/Phase 4: Deployment (Optional)
Option A: Cloudflare Workers (recommended for Cloudflare)
If the user wants to deploy to Cloudflare Workers, use vinext deploy. It auto-generates wrangler.jsonc, worker entry, and Vite config if missing, installs @cloudflare/vite-plugin and wrangler, 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:
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 @cloudflare/vite-plugin runs server environments in workerd, where cloudflare:workers is a native module. No custom worker entry, no getPlatformProxy(), no special configuration needed. Just import and use.
Bindings must be defined in wrangler.jsonc. For TypeScript types, run wrangler types.
IMPORTANT: Do not use getPlatformProxy(), getRequestContext(), or custom worker entries with fetch(request, env) to access bindings. These are older patterns. cloudflare:workers 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:
npm install nitro// 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:
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 serverNitro 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 (vinext deploy / @cloudflare/vite-plugin) is recommended for the best developer experience with cloudflare:workers bindings, KV caching, and one-command deploys.
Phase 5: Verify
1. Run vinext dev to start the development server 2. Confirm the server starts without errors 3. Navigate key routes and check functionality 4. Report the result to the user — if errors occur, share full output
See references/troubleshooting.md for common migration errors.
Known Limitations
| Feature | Status |
|---|---|
next/image optimization | Remote images via @unpic; no build-time optimization |
next/font/google | CDN-loaded, not self-hosted |
| Domain-based i18n | Not supported; path-prefix i18n works |
next/jest | Not supported; use Vitest |
| Turbopack/webpack config | Ignored; use Vite plugins instead |
runtime / preferredRegion | Route segment configs ignored |
| PPR (Partial Prerendering) | Use "use cache" directive instead (Next.js 16 approach) |
Anti-patterns
- Do not modify `app/`, `pages/`, or application code. vinext shims all
next/*imports — no import rewrites needed. - *Do not rewrite `next/
imports** tovinext/*in application code. Imports likenext/image,next/link,next/server` resolve automatically. - Do not copy webpack/Turbopack config into Vite config. Use Vite-native plugins instead.
- Do not skip the compatibility check. Run
vinext checkbefore migration to surface issues early. - Do not remove `next.config.js` unless replacing it with
next.config.tsor.mjs. vinext reads it for redirects, rewrites, headers, basePath, i18n, images, and env config. - Do not use `getPlatformProxy()` 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@cloudflare/vite-plugin. - For Cloudflare Workers, prefer the native integration over Nitro.
vinext deploy/@cloudflare/vite-pluginprovides the best experience withcloudflare:workersbindings, KV caching, and image optimization. Nitro works for Cloudflare but the native setup is recommended.
Compatibility Reference
Supported next/\* Imports
All of these resolve automatically to vinext shims. Do not rewrite imports in application code.
| Import | Status | Notes |
|---|---|---|
next/link | Full | |
next/image | Partial | Remote images via @unpic; no build-time optimization |
next/head | Full | |
next/router | Full | Pages Router |
next/navigation | Full | App Router |
next/server | Full | NextRequest, NextResponse, cookies, userAgent, after, connection |
next/headers | Full | |
next/dynamic | Full | |
next/script | Full | |
next/font/google | Partial | CDN-loaded, not self-hosted |
next/font/local | Partial | Runtime injection |
next/og | Full | Via @vercel/og |
next/cache | Full | Pluggable CacheHandler |
next/form | Full | |
next/legacy/image | Full | |
next/error | Full | |
next/config | Full | |
next/document | Full | Pages Router |
next/constants | Full | |
next/amp | Stub | No-op; AMP deprecated since Next.js 13 |
next/web-vitals | Stub | No-op |
server-only | Full | |
client-only | Full |
Routing Features
| Feature | Supported |
|---|---|
Pages Router (pages/) | Yes |
App Router (app/) | Yes |
Dynamic routes [param] | Yes |
Catch-all [...slug] | Yes |
Optional catch-all [[...slug]] | Yes |
Route groups (group) | Yes |
Parallel routes @slot | Yes |
Intercepting routes (.), (..), (...) | Yes |
Route handlers (route.ts) | Yes |
Middleware / proxy.ts (Next.js 16) | Yes |
| i18n (path prefix) | Yes |
| i18n (domain-based) | No |
basePath | Yes |
trailingSlash | Yes |
Server Features
| Feature | Supported |
|---|---|
| SSR (streaming) | Yes |
| React Server Components | Yes |
Server Actions ("use server") | Yes |
getStaticProps / getStaticPaths | Yes |
getServerSideProps | Yes |
| ISR (stale-while-revalidate) | Yes |
"use cache" / cacheLife() / cacheTag() | Yes |
Metadata API (metadata, generateMetadata) | Yes |
generateStaticParams | Yes |
Static export (output: 'export') | Yes |
instrumentation.ts | Yes |
connection() | Yes |
| Pluggable CacheHandler | Yes |
| PPR (Partial Prerendering) | No — use "use cache" |
Route Segment Config
| Config | Supported |
|---|---|
revalidate | Yes |
dynamic | Yes |
dynamicParams | Yes |
runtime | Ignored |
preferredRegion | Ignored |
next.config.js Options
vinext loads and respects: redirects, rewrites, headers, basePath, trailingSlash, i18n (path prefix), images, env, NEXT_PUBLIC_* env vars, output, serverExternalPackages (consider using Vite's ssr.external instead).
Ignored: webpack, turbopack, experimental.turbo.
Ecosystem Libraries
Tested and working:
- next-themes
- nuqs
- next-view-transitions
- next-intl
- better-auth
- @vercel/analytics
- tailwindcss
- framer-motion
- shadcn-ui
- lucide-react
- drizzle
- prisma
Not Supported
These features are intentionally excluded:
- Vercel-specific bindings (@vercel/og edge runtime, Vercel Analytics server bindings)
- AMP (deprecated since Next.js 13)
next export(legacy — useoutput: 'export')- Turbopack/webpack configuration
next/jest(use Vitest)create-next-appscaffolding- Bug-for-bug parity with undocumented Next.js behavior
- Native Node modules in Workers (sharp, resvg, satori — auto-stubbed in production)
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.
import vinext from "vinext";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [vinext()],
});App Router — Local Development
vinext auto-registers @vitejs/plugin-rsc when an app/ directory is detected and the rsc option is not false. No extra config needed.
import vinext from "vinext";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [vinext()],
});To disable auto-registration (e.g., Pages Router only project with an unused app/ dir):
export default defineConfig({
plugins: [vinext({ rsc: false })],
});Pages Router — Cloudflare Workers
import vinext from "vinext";
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [vinext(), cloudflare()],
});App Router — Cloudflare Workers
Full manual config with explicit RSC plugin registration and Cloudflare multi-environment setup:
import { defineConfig } from "vite";
import vinext from "vinext";
import rsc from "@vitejs/plugin-rsc";
import { cloudflare } from "@cloudflare/vite-plugin";
export default defineConfig({
plugins: [
vinext(),
rsc({
entries: {
rsc: "virtual:vinext-rsc-entry",
ssr: "virtual:vinext-app-ssr-entry",
client: "virtual:vinext-app-browser-entry",
},
}),
cloudflare({
viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] },
}),
],
});In most cases vinext deploy generates this automatically. Only use manual config when customizing the worker entry or adding bindings.
wrangler.jsonc — Cloudflare Workers
Minimal config for deployment:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-app",
"compatibility_date": "2026-02-12",
"compatibility_flags": ["nodejs_compat"],
"main": "vinext/server/app-router-entry",
"assets": {
"not_found_handling": "none",
},
}For custom worker entries (e.g., adding KV cache, image optimization bindings):
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-app",
"compatibility_date": "2026-02-12",
"compatibility_flags": ["nodejs_compat"],
"main": "./worker/index.ts",
"assets": {
"not_found_handling": "none",
"binding": "ASSETS",
},
"images": { "binding": "IMAGES" },
}Accessing Cloudflare Bindings
Use import { env } from "cloudflare:workers" in server components, route handlers, and server actions. No custom worker entry needed.
// app/page.tsx (server component)
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>;
}// app/api/data/route.ts (route handler)
import { env } from "cloudflare:workers";
export async function GET() {
const value = await env.CACHE.get("key");
return Response.json({ value });
}Define bindings in wrangler.jsonc:
{
"name": "my-app",
"compatibility_date": "2026-02-12",
"compatibility_flags": ["nodejs_compat"],
"main": "vinext/server/app-router-entry",
"d1_databases": [{ "binding": "DB", "database_name": "my-db", "database_id": "..." }],
"kv_namespaces": [{ "binding": "CACHE", "id": "..." }],
"r2_buckets": [{ "binding": "BUCKET", "bucket_name": "my-bucket" }],
"ai": { "binding": "AI" },
}Run wrangler types to generate TypeScript types for the env object.
Do NOT use getPlatformProxy(), getRequestContext(), or custom worker entries with fetch(request, env). These are older patterns. cloudflare:workers is the recommended approach.
App Router — Other Platforms (via Nitro)
For deploying to Vercel, Netlify, AWS, Deno Deploy, or any other Nitro-supported platform:
import { defineConfig } from "vite";
import vinext from "vinext";
import { nitro } from "nitro/vite";
export default defineConfig({
plugins: [vinext(), nitro()],
});Build with a preset:
NITRO_PRESET=vercel npx vite build
NITRO_PRESET=netlify npx vite build
NITRO_PRESET=deno_deploy npx vite build
NITRO_PRESET=node npx vite buildNitro auto-detects the platform in most CI/CD environments, so the NITRO_PRESET is often unnecessary.
For Cloudflare Workers, Nitro works but the native integration (vinext deploy / @cloudflare/vite-plugin) is recommended for the best experience with cloudflare:workers bindings, KV caching, and one-command deploys.
VinextOptions
| Option | Type | Default | Description |
|---|---|---|---|
appDir | string | project root | Custom base directory for app/ and pages/ |
rsc | boolean | true | Auto-register @vitejs/plugin-rsc for App Router |
vinext deploy flags
| Flag | Description |
|---|---|
--preview | Deploy to preview environment |
--name <name> | Override worker name |
--skip-build | Skip build step (deploy existing output) |
--dry-run | Generate config without deploying |
--experimental-tpr | Enable Traffic-aware Pre-Rendering |
Troubleshooting
Common Migration Errors
| Error | Cause | Fix |
|---|---|---|
ERR_REQUIRE_ESM or require() of ES Module | Project missing "type": "module" | Add "type": "module" to package.json |
module.exports syntax error in config file | CJS config loaded as ESM | Rename .js config to .cjs (e.g., postcss.config.js → postcss.config.cjs) |
Cannot find module '@vitejs/plugin-rsc' | App Router project missing RSC plugin | npm install -D @vitejs/plugin-rsc |
Cannot find module 'vite' | Vite not installed | npm install -D vite |
vinext: command not found | vinext not installed or not in PATH | Install vinext: npm install vinext, then run via npx vinext or package.json scripts |
| 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, orbuild.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
defaultimport 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:
postcss.config.jstailwind.config.js.eslintrc.jsjest.config.js(if kept alongside Vitest)prettier.config.js
Alternatively, convert these files to ESM (export default syntax) and keep the .js extension.
Third-Party Package ESM Resolution Errors
Symptom: Cannot find module '...' errors in dev server when using certain npm packages.
Example Error:
Cannot find module '\node_modules.pnpm\validator@13.15.26\node_modules\validator\es\lib\util\assertString'
imported from \node_modules.pnpm\validator@13.15.26\node_modules\validator\es\lib\isEmail.jsCause: Some ESM packages have complex internal import structures that Node.js module resolution can't handle when externalized.
vinext Fix: vinext sets noExternal: true in all server environments (RSC and SSR), which forces all dependencies through Vite's transform pipeline. This resolves extensionless import issues automatically.
No configuration needed — this is the default behavior.
App Router vs Pages Router Issues
Symptom: RSC-related errors, "client/server component" boundary violations. Cause: App Router requires @vitejs/plugin-rsc for React Server Components. Fix: vinext auto-registers this plugin when it detects app/. If auto-registration is disabled (rsc: false), enable it or add the plugin manually. See config-examples.md.
Symptom: getServerSideProps / getStaticProps not executing. Cause: These are Pages Router APIs. They only work in pages/, not app/. Fix: This is expected Next.js behavior, not a vinext issue.
Cloudflare Deployment Issues
Symptom: Build succeeds but deploy fails with worker size errors. Cause: Bundle too large for Workers free tier (1 MB) or paid tier (10 MB). Fix: Check for large dependencies. Use vinext build + inspect output size. Consider code splitting or moving large deps to external services.
Symptom: Image optimization returns 404 or broken images. Cause: Missing Cloudflare Images binding. Fix: Add "images": { "binding": "IMAGES" } and "assets": { "binding": "ASSETS" } to wrangler.jsonc.
Symptom: ISR pages not caching across requests. Cause: Default MemoryCacheHandler doesn't persist across Worker invocations. Fix: Use KVCacheHandler from vinext/cloudflare with a KV namespace binding. See config-examples.md.
Verification Checklist
After migration, confirm:
- [ ]
vinext devstarts without errors - [ ] Home page renders correctly
- [ ] Dynamic routes resolve (e.g.,
/posts/[id]) - [ ] API routes respond (Pages Router) or route handlers respond (App Router)
- [ ] Client-side navigation works (Link component)
- [ ] Static assets load (images, fonts, CSS)
- [ ] Environment variables (
NEXT_PUBLIC_*) are available - [ ] Middleware or proxy.ts executes on matching routes
Related skills
How it compares
migrate-to-vinext implements its own SKILL.md workflow rather than a generic substitute skill.
FAQ
Who is migrate-to-vinext for?
Agents and developers following the migrate-to-vinext SKILL.md guidance.
When should I use migrate-to-vinext?
When user intent matches description triggers and quick start scenarios.
Is migrate-to-vinext safe to install?
Review the Security Audits panel before production shell or network use.