How to Migrate from Create React App to Next.js
How do I migrate from Create React App to Next.js?
TL;DR
- Bottom line: Install
next, create anapp/directory with a root layout and optional catch-all[[...slug]]page, move your CRA entry point into a'use client'dynamic import (ssr: false), then incrementally adopt file-based routing, server components, and SSR to replacereact-scripts. Usenpx @next/codemod@canary upgrade latestfor automated migration to Next.js 16. - Key tool/command:
npm install next@latest && npx next dev(Next.js 16.2+ is current as of May 2026 — runs Turbopack by default with 400% faster startup vs 16.0). - Watch out for: Accessing
window/documentin components that Next.js renders on the server — wrap browser-only code inuseEffector dynamic imports withssr: false. Next.js 16 makes all request APIs (params,searchParams,cookies(),headers(),draftMode()) fully async — synchronous access is removed.revalidateTag()now requires acacheLifeprofile as a second argument. - Works with: Next.js 15+/16.2+ (App Router), React 18+/19.2, Node.js 20.9+ (18 dropped in Next.js 16), TypeScript 5.1+. CRA any version (react-scripts 4.x/5.x). Pin to the May 2026 security patch or later.
Constraints
- Next.js 16 requires Node.js >= 20.9.0 — Node.js 18 is no longer supported. Verify with
node --versionbefore starting. [src5, src8] - Next.js 16 requires TypeScript >= 5.1. Older versions fail to type-check. [src8]
output: 'export'disablesuseParams, API routes, proxy (formerly middleware), and all server-side features. Remove it only after confirming the deployment target supports Node.js server. [src1]params,searchParams,cookies(),headers(), anddraftMode()are fully async Promises in Next.js 16 — synchronous access was removed. All page/layout components receiving these mustawaitthem. [src5, src8]- Turbopack is the default bundler in Next.js 16 for both
devandbuild. If the project has a customwebpackconfig, the build will fail. Use--webpackflag on bothnext devandnext buildto opt out, or migrate the config. [src5, src8] revalidateTag()now requires acacheLifeprofile as a second argument in Next.js 16 (e.g.,revalidateTag('tag', 'max')). The single-argument form is deprecated and produces a TypeScript error. For read-your-writes semantics in Server Actions, use the newupdateTag(tag)API. [src8]- Global CSS can only be imported in
app/layout.tsx(or layout files). Importing global CSS in page or component files causes a build error. [src1] - Never let react-router-dom and Next.js file-based routing handle the same route simultaneously — use the catch-all
[[...slug]]approach during incremental migration. [src2] - Pin Next.js to the May 2026 security release or later. That release patched 13 CVEs across denial of service, middleware/proxy bypass, SSRF, cache poisoning, and XSS — both 16.x and 15.x patch lines received fixes. [src9]
Quick Reference
| CRA Pattern | Next.js Equivalent | Example |
|---|---|---|
react-scripts start | next dev | npm run dev uses Turbopack by default (Next.js 16) |
react-scripts build | next build | Outputs to .next/ (or build/ with distDir); Turbopack default in 16 |
public/index.html | app/layout.tsx (Root Layout) | Metadata API replaces manual <head> tags |
src/index.tsx (ReactDOM.render) | app/page.tsx + 'use client' wrapper | Entry point becomes a Server Component page |
react-router-dom <Route> | File-based routing (app/{route}/page.tsx) | app/dashboard/page.tsx = /dashboard |
<Link to="/path"> | <Link href="/path"> | import Link from 'next/link' |
useNavigate() / useHistory() | useRouter() from next/navigation | const router = useRouter(); router.push('/path') |
useParams() (react-router) | useParams() from next/navigation | Dynamic route: app/users/[id]/page.tsx |
REACT_APP_* env vars | NEXT_PUBLIC_* env vars | NEXT_PUBLIC_API_URL exposed to browser |
proxy in package.json | rewrites() in next.config.ts | { source: '/api/:path*', destination: 'http://backend:3001/:path*' } |
import img from './image.png' (string) | import img from './image.png' (object with .src) | Use <img src={img.src} /> or <Image src={img} /> |
CSS Modules (*.module.css) | CSS Modules (same, built-in) | No change needed |
import './global.css' in index.tsx | import './global.css' in app/layout.tsx | Global CSS must be imported in layout |
reportWebVitals() | Built-in Next.js analytics or @next/third-parties | Remove CRA’s web vitals setup |
Custom webpack in eject / craco | webpack key in next.config.ts (requires --webpack in 16) | webpack: (config) => { ... return config } |
homepage field in package.json | basePath in next.config.ts | basePath: '/my-app' |
middleware.ts (Next.js 15) | proxy.ts (Next.js 16) | Renamed: export proxy(request) instead of middleware(request) |
Decision Tree
START
├── Is the CRA app purely client-side (SPA) with no SSR needs?
│ ├── YES → Use output: 'export' in next.config.ts for static SPA
│ └── NO ↓
├── Does the app use react-router-dom?
│ ├── YES → Phase 1: Keep react-router via catch-all route, Phase 2: Migrate to file-based routing
│ └── NO ↓
├── Does the app need server-side rendering or API routes?
│ ├── YES → Remove output: 'export', create API routes in app/api/, use Server Components
│ └── NO ↓
├── Does the app use CRACO or ejected webpack config?
│ ├── YES → Use --webpack flag in Next.js 16, map custom webpack plugins to next.config.ts
│ └── NO ↓
├── Does the app rely heavily on window/document APIs?
│ ├── YES → Wrap those components with dynamic(() => import(...), { ssr: false })
│ └── NO ↓
├── Targeting Next.js 16 specifically?
│ ├── YES → Ensure Node.js >= 20.9, await all params/searchParams, rename middleware.ts to proxy.ts
│ └── NO (Next.js 15) → Synchronous params still work with deprecation warnings
└── DEFAULT → Follow the 8-step migration, start with SPA mode, adopt features incrementally
Step-by-Step Guide
1. Install Next.js and update scripts
Add Next.js alongside your existing CRA dependencies. Do not remove react-scripts yet. [src1]
npm install next@latest
Update package.json scripts:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"dev:old": "react-scripts start"
}
}
Add .next, .next/dev, and next-env.d.ts to .gitignore. (Next.js 16 uses .next/dev for development output.)
Verify: npx next --version shows the installed version (15.x+ or 16.x+).
2. Create next.config.ts
Start with static export mode to match CRA's SPA behavior. [src1]
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export', // Static SPA mode (matches CRA behavior)
distDir: 'build', // Match CRA's output directory
}
export default nextConfig
Note: If you have a custom webpack config and are using Next.js 16, Turbopack is the default. Add --webpack to your build script or migrate to Turbopack-compatible options. [src5]
Verify: File exists at project root alongside package.json.
3. Create the root layout
Replace public/index.html with app/layout.tsx. [src1, src4]
// app/layout.tsx
import type { Metadata } from 'next'
import '../src/index.css'
export const metadata: Metadata = {
title: 'My App',
description: 'Migrated from CRA to Next.js',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
Verify: Compare with your public/index.html — all <meta> tags accounted for via the Metadata API.
4. Create the catch-all entrypoint page
Use an optional catch-all route to handle all URLs, preserving your existing react-router. [src1, src2]
// app/[[...slug]]/page.tsx
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
// app/[[...slug]]/client.tsx
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../src/App'), { ssr: false })
export function ClientOnly() {
return <App />
}
Verify: npm run dev → app loads at http://localhost:3000 with existing routing intact.
5. Rename REACT_APP_ environment variables to NEXT_PUBLIC_
Next.js requires the NEXT_PUBLIC_ prefix for client-side environment variables. [src1]
# .env
# BEFORE
REACT_APP_API_URL=https://api.example.com
# AFTER
NEXT_PUBLIC_API_URL=https://api.example.com
Verify: grep -rn 'REACT_APP_' --include='*.ts' --include='*.tsx' returns zero results.
6. Fix static image imports
CRA image imports return a string URL. Next.js imports return an object with a .src property. [src1]
// BEFORE (CRA): import returns string
import logo from './logo.png'
<img src={logo} alt="Logo" />
// AFTER (Next.js): import returns object
import logo from './logo.png'
<img src={logo.src} alt="Logo" />
// OR use next/image for optimization
import Image from 'next/image'
<Image src={logo} alt="Logo" />
Verify: All images render correctly. Check Network tab for 404s.
7. Replace CRA proxy with Next.js rewrites
Replace proxy in package.json with Next.js rewrites. [src1]
// next.config.ts
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://localhost:3001/api/:path*',
},
]
},
}
Note: In Next.js 16, if you need request-level middleware logic, create a proxy.ts file (renamed from middleware.ts). [src5]
Verify: API requests proxied correctly in the Network tab.
8. Clean up CRA artifacts and uninstall react-scripts
Remove CRA-specific files and dependencies. [src1, src4]
npm uninstall react-scripts
rm public/index.html
rm src/index.tsx
rm src/react-app-env.d.ts
rm src/reportWebVitals.ts
Verify: npm run build completes without errors. npm run dev starts cleanly.
Code Examples
TypeScript/Next.js: Migrating React Router routes to file-based routing
Full script: typescript-next-js-migrating-a-react-router-app-to.tsx (46 lines)
// Input: CRA app with react-router-dom route definitions
// Output: Equivalent Next.js App Router file structure
// BEFORE: CRA with react-router-dom (src/App.tsx)
// <BrowserRouter>
// <Routes>
// <Route path="/" element={<Home />} />
// <Route path="/dashboard" element={<Dashboard />} />
// <Route path="/users/:id" element={<UserProfile />} />
// </Routes>
// </BrowserRouter>
// AFTER: Next.js App Router
// app/page.tsx → /
export default function Home() {
return <h1>Home Page</h1>
}
// app/dashboard/page.tsx → /dashboard
'use client'
export default function Dashboard() {
return <h1>Dashboard</h1>
}
// app/users/[id]/page.tsx → /users/:id (Next.js 16: async params)
export default async function UserProfile({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
return <h1>User {id}</h1>
}
TypeScript/Next.js: Converting CRA navigation patterns
Full script: typescript-next-js-converting-cra-navigation-patte.tsx (38 lines)
// Input: CRA components using react-router hooks
// Output: Next.js equivalents using next/navigation
'use client'
import { useRouter, useParams, usePathname, useSearchParams } from 'next/navigation'
import Link from 'next/link'
function MyComponent() {
const router = useRouter()
const params = useParams() // { id: '123' }
const pathname = usePathname() // '/users/123'
const searchParams = useSearchParams() // URLSearchParams
return (
<div>
{/* Programmatic navigation */}
<button onClick={() => router.push('/dashboard')}>Go</button>
{/* Declarative navigation (preferred) */}
<Link href="/dashboard">Dashboard</Link>
{/* Replace instead of push */}
<button onClick={() => router.replace('/login')}>Login</button>
{/* Go back */}
<button onClick={() => router.back()}>Back</button>
</div>
)
}
export default MyComponent
TypeScript/Next.js: Wrapping browser-only code for SSR safety
Full script: typescript-next-js-wrapping-browser-only-code-for-.tsx (36 lines)
// Input: CRA component using window/document/localStorage
// Output: Next.js component with SSR-safe browser API access
'use client'
import { useEffect, useState } from 'react'
import dynamic from 'next/dynamic'
// Pattern 1: useEffect guard
function ThemeToggle() {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
useEffect(() => {
const saved = localStorage.getItem('theme') as 'light' | 'dark' | null
if (saved) setTheme(saved)
}, [])
const toggle = () => {
const next = theme === 'light' ? 'dark' : 'light'
setTheme(next)
localStorage.setItem('theme', next)
}
return <button onClick={toggle}>Theme: {theme}</button>
}
// Pattern 2: Dynamic import with ssr: false
const MapComponent = dynamic(
() => import('../components/Map'),
{ ssr: false, loading: () => <p>Loading map...</p> }
)
export default function LocationPage() {
return (
<div>
<ThemeToggle />
<MapComponent />
</div>
)
}
TypeScript/Next.js: Async params pattern (Next.js 16)
// Input: Next.js 15 page component with synchronous params
// Output: Next.js 16 page component with async params (required)
// BEFORE (Next.js 15 — synchronous, deprecated):
// export default function UserPage({ params }: { params: { id: string } }) {
// return <h1>User {params.id}</h1>
// }
// AFTER (Next.js 16 — async params required):
export default async function UserPage({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
return <h1>User {id}</h1>
}
// With searchParams (also async in Next.js 16):
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ q?: string }>
}) {
const { q } = await searchParams
return <h1>Results for: {q}</h1>
}
Anti-Patterns
Wrong: Importing global CSS in a page component
// ❌ BAD — Next.js only allows global CSS imports in layout.tsx
// app/dashboard/page.tsx
import '../../styles/global.css' // Build error
export default function Dashboard() {
return <h1>Dashboard</h1>
}
Correct: Import global CSS in the root layout only
// ✅ GOOD — Global CSS imported in app/layout.tsx
import '../styles/global.css'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
Wrong: Using react-router Link syntax in Next.js
// ❌ BAD — react-router-dom Link uses 'to' prop
import { Link } from 'react-router-dom'
function Nav() {
return <Link to="/about">About</Link>
}
Correct: Use next/link with href prop
// ✅ GOOD — next/link uses 'href' prop
import Link from 'next/link'
function Nav() {
return <Link href="/about">About</Link>
}
Wrong: Accessing window directly at module level in a server component
// ❌ BAD — Server Component runs on the server, crashes
// app/analytics/page.tsx (Server Component by default)
const url = window.location.href // ReferenceError: window is not defined
export default function Analytics() {
return <p>Current URL: {url}</p>
}
Correct: Use 'use client' and useEffect for browser APIs
// ✅ GOOD — Client component with browser API in useEffect
'use client'
import { useState, useEffect } from 'react'
export default function Analytics() {
const [url, setUrl] = useState('')
useEffect(() => {
setUrl(window.location.href)
}, [])
return <p>Current URL: {url}</p>
}
Wrong: Keeping CRA's REACT_APP_ prefix
// ❌ BAD — REACT_APP_ variables are undefined in Next.js
const apiKey = process.env.REACT_APP_API_KEY // undefined
Correct: Use NEXT_PUBLIC_ prefix for client-side env vars
// ✅ GOOD — NEXT_PUBLIC_ prefix works in both server and client
const apiKey = process.env.NEXT_PUBLIC_API_KEY
Wrong: Using CRA image import as string directly
// ❌ BAD — Next.js image imports return an object, not a string
import logo from './logo.png'
<img src={logo} /> // Renders as [object Object]
Correct: Access the .src property or use next/image
// ✅ GOOD — Use .src for <img> or the Image component
import logo from './logo.png'
<img src={logo.src} alt="Logo" />
// Even better: Next.js Image with auto optimization
import Image from 'next/image'
<Image src={logo} alt="Logo" width={200} height={50} />
Wrong: Big-bang migration of all routes at once
// ❌ BAD — Removing react-router and converting 50 routes in one PR
// This causes weeks of merge conflicts and blocks feature development
npm uninstall react-router-dom // Too early!
Correct: Incremental route migration with catch-all fallback
// ✅ GOOD — Catch-all route handles unmigrated pages
// app/[[...slug]]/page.tsx keeps react-router working
// Migrate one route at a time:
// 1. Create app/dashboard/page.tsx
// 2. Remove /dashboard from react-router
// 3. Test, ship, repeat
Wrong: Synchronous params access in Next.js 16
// ❌ BAD — Synchronous params access removed in Next.js 16
export default function Page({ params }: { params: { id: string } }) {
return <h1>{params.id}</h1> // TypeError in Next.js 16
}
Correct: Await params as a Promise in Next.js 16
// ✅ GOOD — Async params access required in Next.js 16
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
return <h1>{id}</h1>
}
Common Pitfalls
- "window is not defined" errors during build: Components accessing
window,document, orlocalStoragecrash during SSR. Fix: Add'use client'directive and wrap browser API calls inuseEffect, or usedynamic(() => import(...), { ssr: false }). [src1, src2] - Global CSS imported outside layout.tsx: Next.js restricts global CSS imports to
app/layout.tsx. Importing in page or component files throws a build error. Fix: Move all global CSS imports toapp/layout.tsxand use CSS Modules for component-scoped styles. [src1] - Hydration mismatch between server and client: Server-rendered HTML differing from client output causes React warnings. Fix: Use
suppressHydrationWarningon specific elements, or render a loading placeholder server-side. [src2] - Forgetting to rename environment variables: All
REACT_APP_*variables silently becomeundefinedbecause Next.js only exposesNEXT_PUBLIC_*to the client. Fix: Bulk rename withsed -i 's/REACT_APP_/NEXT_PUBLIC_/g' .env*. [src1] - SVG imports breaking: CRA auto-converts SVGs to React components. Next.js does not. Fix: Install
@svgr/webpackand add it tonext.config.tswebpack config. [src2] - Conflicts with pages/ directory name: If CRA project has
src/pages/, Next.js may treat it as Pages Router routes. Fix: Rename tosrc/views/orsrc/router-pages/. [src2] - Static export incompatible with dynamic features:
output: 'export'disablesuseParams, API routes, proxy, and server features. Fix: Removeoutput: 'export'once you need server-side capabilities. [src1] - Missing TypeScript types after migration: CRA's
react-app-env.d.tsprovides image import types. Fix: Ensurenext-env.d.tsis in yourtsconfig.jsonincludearray. [src1] - Turbopack build failure with custom webpack config (Next.js 16): Turbopack is default for both dev and build. Projects with custom
webpackconfigs will fail. Fix: Add--webpackflag or migrate to Turbopack-compatibleturbopack.resolveAliasand loaders. [src5] - Sass tilde imports breaking with Turbopack: Turbopack does not support the legacy
~prefix for Sass node_modules imports. Fix: Remove the~prefix or useturbopack.resolveAliasto map~*to*. [src5] revalidateTag()single-argument call fails to type-check (Next.js 16): The single-argument form is deprecated and raises a TypeScript error. Fix: Pass acacheLifeprofile as the second argument (revalidateTag('blog-posts', 'max')), or switch toupdateTag(tag)inside Server Actions when you need read-your-writes semantics. [src8]next lintremoved in Next.js 16: Runningnext lintafter upgrading errors out —next buildalso no longer runs linting. Fix: Run ESLint or Biome directly, or run the codemodnpx @next/codemod@canary next-lint-to-eslint-cli .to migrate the configuration. [src8]- AMP support removed in Next.js 16:
useAmp,export const config = { amp: true }, and all AMP APIs/configs are gone. Fix: Remove every AMP code path before upgrading — there is no compatibility shim. [src8] serverRuntimeConfig/publicRuntimeConfigremoved: Both runtime-config helpers are deleted in Next.js 16. Fix: Move all runtime config to.envfiles and read viaprocess.env.NEXT_PUBLIC_*(client) orprocess.env.*(server). [src8]next.config.tsTurbopack key moved out of experimental:experimental.turbopackno longer exists — the config is top-levelturbopack. Fix: Rename the key after the codemod runs; otherwise startup logs a "config key not recognised" warning and the options are silently ignored. [src8]
Diagnostic Commands
# Verify Next.js is installed and working
npx next --version
# Check Node.js version (must be >= 20.9 for Next.js 16)
node --version
# Check for remaining CRA references
grep -rn 'react-scripts' --include='*.json' --include='*.js' --include='*.ts'
# Find remaining REACT_APP_ environment variable usage
grep -rn 'REACT_APP_' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.env*'
# Count remaining react-router-dom imports (track migration progress)
grep -rn "from 'react-router" --include='*.tsx' --include='*.ts' | wc -l
# Analyze Next.js build output
npx next build 2>&1 | tail -20
# Check for SSR-unsafe window/document access in server components
grep -rn 'window\.\|document\.' --include='*.tsx' --include='*.ts' | grep -v 'node_modules' | grep -v "'use client'"
# Check for synchronous params access (needs async in Next.js 16)
grep -rn 'params\.' --include='page.tsx' --include='layout.tsx' | grep -v 'await'
# Run Next.js codemod for automated migration
npx @next/codemod@canary upgrade latest
Version History & Compatibility
| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| Next.js 16.2 (2026-03) | Current | 400% faster dev server startup; 200+ Turbopack fixes; minor caching API refinements | Recommended target for any new CRA migration in 2026; covered by May 2026 security release [src8, src9] |
| Next.js 16.0 (2025-10) | Stable | Async request APIs enforced (params, searchParams, cookies, headers, draftMode); Turbopack default; middleware → proxy; Node.js 18 dropped; TypeScript 5.1 minimum; AMP removed; next lint removed; serverRuntimeConfig removed; revalidateTag() requires cacheLife arg; experimental.ppr → cacheComponents | npx @next/codemod@canary upgrade latest for automated migration. --webpack flag available for custom configs. [src8] |
| Next.js 15 (2025) | LTS | params is a Promise (sync still works with warnings); Turbopack stable for dev; fetch/GET no longer cached by default | const { id } = await params in page components; safer target for maximum library compatibility |
| Next.js 14 (2023) | Maintenance | App Router stable, Server Actions stable | Solid target for CRA migrations |
| Next.js 13 (2022) | EOL | App Router introduced (beta) | Pages Router still supported alongside App Router |
| CRA 5.x (2021) | Deprecated (Feb 2025) | Last release, no active maintainers | Migrate to Next.js, Vite, or React Router 7 |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| App needs SSR, SSG, or ISR for SEO/performance | App is a pure client-side dashboard with no SEO needs | Vite + React Router |
| Team wants built-in routing, API routes, and proxy (middleware) | App is tiny (<10 components) and CRA still works | Stay on CRA or migrate to Vite |
| Need image/font optimization and streaming SSR | Backend team owns API and app is purely a SPA consumer | Vite (faster builds, simpler config) |
| Deploying to Vercel or need edge runtime support | Need full control over server (Express/Fastify) | Remix or custom Vite + Express |
| Enterprise app with SEO requirements and complex routing | Building a React Native app | Expo |
| Want React Server Components and React Compiler | Complex custom webpack config with no time to migrate | Vite with manual React setup |
Important Caveats
- Create React App was officially deprecated (“sunset”) on February 14, 2025, by the React team. No further updates, security patches, or maintenance will be provided beyond critical fixes. Migration is strongly recommended. [src3]
- The initial migration can keep your app as a pure SPA using
output: 'export'innext.config.ts. Server features can be adopted incrementally by removing this flag. [src1] - Next.js 16 defaults to Turbopack for both local development and production builds. If you have custom webpack plugins from CRA/CRACO, you must use
--webpackflag or migrate to Turbopack-compatible options. [src5] - The
pages/andapp/directories can coexist in Next.js for incremental migration, but the same route must not exist in both. [src6] - CRA's
public/folder works similarly in Next.js, but%PUBLIC_URL%references must be removed — Next.js uses/directly. [src1] - Next.js 16 renamed
middleware.tstoproxy.tsand themiddleware()export toproxy(). The edge runtime is not supported inproxy— it runs on the Node.js runtime. [src5] - Next.js 16 ships with React 19.2 (View Transitions,
useEffectEvent, Activity API). The React Compiler is now stable (opt-in viareactCompiler: true). [src5] - Next.js 16 requires Node.js >= 20.9.0 (LTS). Node.js 18 support was dropped. [src5]
- Next.js 16 introduces Cache Components (the new
"use cache"directive, opt-in viacacheComponents: trueinnext.config.ts). Previousexperimental.pprandexperimental.dynamicIOflags have been removed — their functionality is now part of Cache Components. CRA migrations can ignore this initially: all dynamic code is executed at request time by default. Opt into Cache Components later once the migration is stable. [src8] - Next.js 16 ships Next.js DevTools MCP — a Model Context Protocol server that gives AI coding agents contextual access to routing, caching, render behavior, browser+server logs, and stack traces during development. Particularly useful for agent-driven CRA-to-Next.js migrations. [src8]
- The May 2026 Next.js security release patched 13 CVEs across denial of service, middleware/proxy bypass, SSRF, cache poisoning, and XSS. Both the 16.x and 15.x patch lines received fixes — pin to the patched version on any branch you ship. [src9]
- New Server Actions caching APIs in Next.js 16:
updateTag(tag)for read-your-writes semantics (form submissions, user settings),refresh()for refreshing uncached data without touching the cache. Use these instead of barerevalidateTag(tag)in Server Actions. [src8]