runtime = 'edge' is not supported with Cache Components
Cache Components requires the Node.js runtime. Switch to the Node.js runtime (the default) by removing the deprecated runtime = 'edge' export. If you need edge behavior for specific routes, use Proxy instead.
experimental_ppr is removed in Next.js 16; use cacheComponents instead
Next.js 16 removes the experimental Partial Prerendering flag (experimental.ppr) and the experimental_ppr route segment config. Partial Prerendering is now part of Cache Components. Remove experimental.ppr from next.config and experimental_ppr from your segments. A codemod is available to remove the segment config automatically.
Component state persists across navigations with Cache Components
With Cache Components, Next.js preserves routes using React's <Activity> component in 'hidden' mode instead of unmounting them. useState values, form inputs, and scroll position are no longer reset when navigating away and back. Effects clean up and re-run normally.
instant = false allows blocking routes in Cache Components
The instant: false config can be set on a segment (layout, page, or parallel slot) to mark it as allowed to block. It does not force the route to be dynamic, so a genuinely prerenderable route still ships a static shell. However, it does not clear synchronous IO build errors like new Date(), Math.random(), or crypto.randomUUID() which still fail the prerender.
Use cache-components-instant-false codemod to opt out routes incrementally
The cache-components-instant-false codemod adds instant: false to every page, layout, and default in the app that doesn't already declare instant. Run it with: npx @next/codemod@canary cache-components-instant-false ./app (or ./src/app for src/ projects). This allows incremental adoption of Cache Components by deferring validation for opted-out routes.
Synchronous IO errors cannot be deferred with instant = false
Calls like new Date(), Date.now(), Math.random(), and crypto.randomUUID() during prerender throw a build error that instant = false does not clear. A route that uses synchronous IO won't build until you address it by moving the call out of the prerendered shell: wrap the part that needs it in <Suspense> and call connection() before the call, or move it into a Client Component.
validation errors and insights guide Cache Components migration
Cache Components validates your routes in development and surfaces errors and insights in the dev overlay. Errors direct you to cache the component or data with 'use cache' or wrap in <Suspense>. Insights don't show up in the HTTP response; an offending route still returns 200 with rendered HTML in dev. The insight only appears in the dev overlay, the dev-server log, or the MCP get_errors tool.
next-cache-components-adoption skill automates Cache Components migration
The next-cache-components-adoption skill drives Cache Components migration with a coding agent. It supports two modes: Incremental (opens a single mechanical PR that opts every route out of validation, then ships each feature as a follow-up PR) and Direct (adopts every route in place on one branch). Install with: npx skills add vercel/next.js --skill next-cache-components-adoption
Incremental adoption of Cache Components uses three steps
To migrate incrementally with Cache Components: 1) Enable the flag and remove the route segment configs (dynamic, revalidate, fetchCache). Routes that still render instantly need no further work. 2) Opt out the routes that aren't ready by setting instant: false on the segment that raised an insight or error. 3) Convert one route at a time by removing instant: false and resolving insights by caching data with 'use cache' or wrapping runtime data in <Suspense>.
Dropdowns and popovers need explicit reset with Cache Components
With Cache Components, dropdowns and popovers stay open when navigating back because component state is preserved. Close them in a useLayoutEffect cleanup function to prevent this behavior.
Dialogs with initialization logic need URL-derived state with Cache Components
With Cache Components, effects that depend on dialog state (like focusing an input) won't re-fire if the state was preserved. Instead of relying on effects, derive dialog state from the URL.
Form state persists after submission with Cache Components
With Cache Components, input values and useActionState results (success/error messages) persist when returning to a form after submission. Reset in the submit handler or user action when possible, otherwise use a cleanup effect.
Cache Components requires Next.js 16
Cache Components is a feature that requires Next.js 16 or later. If you are on Next.js 15 or earlier, you must upgrade first by following the version 16 upgrade guide before you can use Cache Components.
cacheComponents replaces experimental.dynamicIO and experimental.useCache
The cacheComponents configuration flag replaces the previously experimental features experimental.dynamicIO and experimental.useCache. If you were using either of those, you should migrate to cacheComponents instead.
generateStaticParams must return at least one param with Cache Components
With Cache Components enabled, generateStaticParams() must return at least one param so Next.js can prerender the route and validate it produces a non-empty static shell. Returning an empty array raises an 'empty-generate-static-params' error. Paths not returned are still served as Next.js prerenders a static shell for unknown params and streams the rest at request time.
dynamicParams export is not supported with Cache Components
Exporting dynamicParams fails the build with Cache Components enabled with the error 'Route segment config "dynamicParams" is not compatible with nextConfig.cacheComponents'. Delete the export. Params not returned by generateStaticParams() are rendered on request. If you used dynamicParams: false to reject unknown params, call notFound() in the page when the param doesn't resolve to real data.
Await params inside <Suspense> with Cache Components
To produce the static shell with Cache Components, pass the params promise into a <Suspense> boundary instead of awaiting it at the top of the component. This allows unknown params to still prerender. Example: export default function Page({ params }) { return ( <Suspense fallback={<div>Loading...</div>}> <Post params={params} /> </Suspense> ); } async function Post({ params }) { const { slug } = await params; }
Client hooks that read dynamic routes must be wrapped in <Suspense>
When a route's pathname depends on dynamic params not yet known, client hooks that read the route suspend. Hooks like usePathname(), useParams(), useSelectedLayoutSegment(), and useSelectedLayoutSegments() need to be wrapped in <Suspense> when they depend on unknown dynamic params. The useSearchParams() hook always needs a <Suspense> boundary since search params are only known at request time.
Wrap runtime data access in <Suspense> with Cache Components
With Cache Components, accessing cookies(), headers(), or searchParams outside a <Suspense> boundary surfaces a 'blocking-prerender-runtime' insight. Move the access into a component wrapped in <Suspense> so the rest of the page prerenders as a static shell and the dynamic part streams in at request time.
Cache external data in generateMetadata with 'use cache'
Under Cache Components, generateMetadata() and generateViewport() follow the same rules as components. If they read runtime data or fetch uncached data while the rest of the page is prerenderable, Next.js raises an error. If the metadata depends on external but not runtime data, add 'use cache' inside generateMetadata(). If metadata genuinely needs runtime data, add a dynamic marker component to the page so the static content still prerenders.
Next.js Image component benefits
The Next.js <Image> component automatically optimizes images and sets the width and height attributes based on the image's dimensions, which prevents layout shifts when the image loads. However, this can cause issues if images have only one dimension styled without the other styled to auto.
Environment variable prefix migration from Vite to Next.js
When migrating from Vite to Next.js, change all environment variables with the VITE_ prefix to NEXT_PUBLIC_ prefix. This is the main difference in how environment variables are exposed on the client-side.
import.meta properties supported by Turbopack
Turbopack, the default Next.js bundler, supports Vite's built-in import.meta.env properties: MODE, DEV, PROD, BASE_URL, and SSR with no changes needed. BASE_URL reflects the Next.js basePath configuration and includes a trailing slash, matching Vite's format.
import.meta.glob support in Turbopack
Turbopack supports Vite's import.meta.glob with no changes needed during migration from Vite to Next.js.
Vite ?raw and ?url query handling in Next.js
Turbopack does not have built-in handling for Vite's ?raw and ?url queries. When migrating from Vite, change the deprecated as option to the query option (e.g., from { as: 'raw' } to { query: '?raw' }) and add a matching rule in next.config.ts to handle these imports as text.
Next.js basePath configuration for custom base URL
If your Vite application configured a custom base URL, set the equivalent basePath in your next.config.mjs file. For example, if Vite had a custom base path, add basePath: '/some-base-path' to the Next.js configuration.
Update package.json scripts for Next.js migration
When migrating from Vite to Next.js, update the scripts in package.json: set dev to "next dev", build to "next build", and start to "next start".
Add entries to .gitignore after Next.js migration
After migrating from Vite to Next.js, add .next, next-env.d.ts, and dist to the .gitignore file.
Next steps after SPA migration to Next.js
After successfully migrating a Vite SPA to Next.js, incremental improvements can include: migrating from React Router to the Next.js App Router to get automatic code splitting, streaming server-rendering, and React Server Components; optimizing images with the <Image> component; optimizing fonts with next/font; optimizing third-party scripts with the <Script> component; and updating ESLint configuration to support Next.js rules.
Clean up Vite artifacts after migration
After migrating to Next.js, delete the following Vite-related files: main.tsx, index.html, vite-env.d.ts, tsconfig.node.json, and vite.config.ts. Also uninstall Vite dependencies.
Why switch from Vite to Next.js
Vite applications built with the default React plugin are single-page applications (SPAs) that experience slow initial page loading due to the browser needing to download and run the entire React code and application bundle before any data requests can be made. Application code grows with each new feature and dependency, further slowing initial load times.
Automatic code splitting in Next.js
Next.js provides automatic code splitting built into its router, whereas Vite does not. Manual code splitting in Vite often makes performance worse by inadvertently introducing network waterfalls.
Proxy in Next.js
Next.js Proxy allows you to run code on the server before a request is completed. This is especially useful to avoid showing unauthenticated content when a user visits an authenticated-only page by redirecting to a login page. Proxy is also useful for experimentation and internationalization.
Built-in optimizations in Next.js
Next.js comes with built-in components that automatically optimize images, fonts, and third-party scripts, all of which have significant impact on application performance.
Migration strategy: keep as SPA initially
When migrating from Vite to Next.js, the recommended approach is to first get a working Next.js application, keeping it as a purely client-side SPA without migrating the existing router. This minimizes the chances of encountering issues and reduces merge conflicts during migration.
Install Next.js dependency for migration
To migrate from Vite to Next.js, install the next package using your package manager: pnpm add next@latest, npm install next@latest, yarn add next@latest, or bun add next@latest.
Create next.config.mjs for Vite migration
Create a next.config.mjs file at the root of the project. For a Vite SPA migration, configure it with output: 'export' to output a Single-Page Application and distDir: './dist' to set the build output directory to ./dist/. The file can use either .js or .mjs extension.
TypeScript configuration updates for Next.js migration
When migrating a TypeScript Vite project to Next.js: (1) Remove the project reference to tsconfig.node.json; (2) Add ./dist/types/**/*.ts and ./next-env.d.ts to the include array; (3) Add ./node_modules to the exclude array; (4) Add { "name": "next" } to the plugins array in compilerOptions; (5) Set esModuleInterop to true; (6) Set jsx to react-jsx; (7) Set allowJs to true; (8) Set forceConsistentCasingInFileNames to true; (9) Set incremental to true.
Root layout file in Next.js App Router
A Next.js App Router application must include a root layout file, which is a React Server Component that wraps all pages in the application. This file is defined at the top level of the app directory. The root layout replaces the index.html file from Vite and contains the <html>, <head>, and <body> tags.
Convert index.html to root layout during migration
When migrating from Vite to Next.js, convert the index.html file into a root layout file by: (1) Creating a new app directory in src; (2) Creating app/layout.tsx inside that directory; (3) Copying content from index.html into the RootLayout component while replacing body.div#root and body.script tags with <div id="root">{children}</div>.
Default metadata tags in Next.js
Next.js includes by default the meta charset and meta viewport tags, so these can be safely removed from the <head> when migrating from Vite to Next.js.
Metadata files auto-added to head in Next.js
Metadata files such as favicon.ico, icon.png, and robots.txt are automatically added to the application <head> tag as long as they are placed into the top level of the app directory. After moving all supported metadata files into the app directory, their <link> tags can be safely deleted from the layout.
Use Metadata API in root layout
In Next.js, export a metadata object from the root layout file to manage <head> tags using the Metadata API. The metadata object should export title, description, and other metadata properties instead of declaring them in the HTML <head>.
Optional catch-all route for SPA migration
When migrating a Vite SPA to Next.js, create a [[...slug]] directory in the app directory to catch all possible routes of the application. This optional catch-all route segment ensures all routes are directed to the containing page.tsx file.
generateStaticParams for SPA migration
In the page.tsx file inside app/[[...slug]], export a generateStaticParams function that returns [{ slug: [''] }] to indicate that only the index route at / will be generated during the build.
Server Component prerendering in migration
The page.tsx file in app/[[...slug]] is a Server Component. When you run next build, the file is prerendered into a static asset and does not require any dynamic code.
Client Component with dynamic import for SPA
When migrating a Vite SPA to Next.js, create a client.tsx file (marked with 'use client') that uses dynamic import with ssr: false to disable prerendering from the App component down. This allows the client-side application to run as an SPA.
Static image imports difference between Vite and Next.js
With Vite, importing an image file returns its public URL as a string. With Next.js, static image imports return an object that can be used directly with the <Image> component or with the object's src property with an existing <img> tag.
Image handling during Vite to Next.js migration
When migrating from Vite to Next.js, convert absolute import paths for images imported from /public to relative imports (e.g., from '/logo.png' to '../public/logo.png'). Pass the image src property instead of the whole image object to <img> tags (e.g., from <img src={logo} /> to <img src={logo.src} />).
Link component <a> tag removal (13.0)
The new-link codemod removes <a> tags inside Link Components. Run with `npx @next/codemod@latest new-link .`. For example, `<Link href="/about"><a>About</a></Link>` becomes `<Link href="/about">About</Link>`. onClick handlers are moved to the Link component itself.
Basic codemod command syntax
Codemods are run with `npx @next/codemod <transform> <path>`. The transform parameter specifies the codemod name, and path specifies files or directory to transform. Common flags include `--dry` for a dry-run without editing code, and `--print` to print changed output for comparison.
Upgrade command and options
The `npx @next/codemod upgrade [revision]` command upgrades Next.js automatically, running codemods and updating Next.js, React, and React DOM. The revision parameter is optional and accepts: 'patch', 'minor', 'major', NPM dist tags (e.g. 'latest', 'canary', 'rc'), or exact versions (e.g. '15.0.0'). Defaults to 'minor' for stable versions. Options include `--verbose` for detailed output and `-y, --yes` to skip interactive prompts and accept defaults. The `--yes` flag is auto-enabled in CI and non-TTY environments.
Middleware to Proxy migration (16.0)
The middleware-to-proxy codemod migrates projects from the deprecated middleware convention to the proxy convention. Run with `npx @next/codemod@latest middleware-to-proxy .`. It renames middleware.<extension> to proxy.<extension>, renames the named export 'middleware' to 'proxy', renames config property experimental.middlewarePrefetch to experimental.proxyPrefetch, renames experimental.middlewareClientMaxBodySize to experimental.proxyClientMaxBodySize, renames experimental.externalMiddlewareRewritesResolve to experimental.externalProxyRewritesResolve, and renames skipMiddlewareUrlNormalize to skipProxyUrlNormalize.
Remove unstable_ prefix codemod (16.0)
The remove-unstable-prefix codemod removes the unstable_ prefix from stabilized APIs. Run with `npx @next/codemod@latest remove-unstable-prefix .`. For example, it transforms `import { unstable_cacheTag as cacheTag }` to `import { cacheTag }`.
ESLint migration codemod (16.0)
The next-lint-to-eslint-cli codemod migrates from `next lint` to ESLint CLI. Run with `npx @next/codemod@canary next-lint-to-eslint-cli .`. It creates an eslint.config.mjs file with Next.js recommended configurations, updates package.json scripts to use `eslint .` instead of `next lint`, adds necessary ESLint dependencies, and preserves existing ESLint configurations.
Async Dynamic APIs codemod (15.0)
The next-async-request-api codemod transforms dynamic APIs (cookies(), headers(), draftMode() from next/headers) that are now asynchronous. Run with `npx @next/codemod@latest next-async-request-api .`. It properly awaits or wraps with React.use() where applicable. When automatic migration isn't possible, it adds a typecast (for TypeScript) or comment prefixed with @next/codemod. These comments must be explicitly removed for builds to succeed.
Dynamic route params async transformation (15.0)
The next-async-request-api codemod detects property access on params or searchParams props in page.js, layout.js, route.js, default.js, and generateMetadata/generateViewport APIs. It transforms these from sync to async functions and awaits property access. For Client Components where it can't be made async, it uses React.use to unwrap the promise. The params and searchParams become Promise<T> types that must be awaited.
Runtime config experimental-edge to edge (15.0)
The app-dir-runtime-config-experimental-edge codemod transforms Route Segment Config runtime value from 'experimental-edge' to 'edge'. Run with `npx @next/codemod@latest app-dir-runtime-config-experimental-edge .`
ImageResponse import migration (14.0)
The next-og-import codemod transforms imports from next/server to next/og for Dynamic OG Image Generation. Run with `npx @next/codemod@latest next-og-import .`
Viewport export codemod (14.0)
The metadata-to-viewport-export codemod migrates certain viewport metadata to a separate viewport export. Run with `npx @next/codemod@latest metadata-to-viewport-export .`. It extracts viewport-related properties like themeColor and width from metadata export into a separate viewport export.