packages/next/src/migrations/update-22-2-0/files/ai-instructions-for-next-16.md
These instructions guide you through migrating an Nx workspace containing Next.js projects from Next.js 15 to Next.js 16. Work systematically through each breaking change category.
Identify all Next.js projects:
nx show projects --with-target build | xargs -I {} nx show project {} --json | jq -r 'select(.targets.build.executor | contains("next")) | .name'
Or search for Next.js configuration files:
find . -name "next.config.*" -not -path "*/node_modules/*"
Update packages:
npm install next@latest react@latest react-dom@latest
npm install -D @types/react @types/react-dom # if using TypeScript
Verify minimum requirements:
This is the most impactful change in Next.js 16. All dynamic request APIs are now asynchronous.
Search Patterns:
cookies() usage in server componentsheaders() usage in server componentsdraftMode() usageparams in page, layout, route handlers, and metadata filessearchParams in page componentsChanges Required:
// BEFORE (Next.js 15)
export default function Page({ params }) {
const { slug } = params;
return <h1>{slug}</h1>;
}
// AFTER (Next.js 16)
export default async function Page(props) {
const { slug } = await props.params;
return <h1>{slug}</h1>;
}
Action Items:
params asyncawait before accessing props.paramsChanges Required:
// BEFORE (Next.js 15)
export default function Page({ searchParams }) {
const query = searchParams.q;
return <Results query={query} />;
}
// AFTER (Next.js 16)
export default async function Page(props) {
const searchParams = await props.searchParams;
const query = searchParams.q;
return <Results query={query} />;
}
Action Items:
searchParams asyncawait before accessing props.searchParamsChanges Required:
// BEFORE (Next.js 15)
export default function Layout({ children, params }) {
const { locale } = params;
return <div data-locale={locale}>{children}</div>;
}
// AFTER (Next.js 16)
export default async function Layout(props) {
const { locale } = await props.params;
return <div data-locale={locale}>{props.children}</div>;
}
Changes Required:
// BEFORE (Next.js 15)
export async function GET(request, { params }) {
const { id } = params;
return Response.json({ id });
}
// AFTER (Next.js 16)
export async function GET(request, props) {
const { id } = await props.params;
return Response.json({ id });
}
Changes Required:
// BEFORE (Next.js 15)
import { cookies, headers } from 'next/headers';
export default function Page() {
const cookieStore = cookies();
const headersList = headers();
const theme = cookieStore.get('theme');
const userAgent = headersList.get('user-agent');
return <div>...</div>;
}
// AFTER (Next.js 16)
import { cookies, headers } from 'next/headers';
export default async function Page() {
const cookieStore = await cookies();
const headersList = await headers();
const theme = cookieStore.get('theme');
const userAgent = headersList.get('user-agent');
return <div>...</div>;
}
Changes Required:
// BEFORE (Next.js 15)
import { draftMode } from 'next/headers';
export default function Page() {
const { isEnabled } = draftMode();
return <div>{isEnabled ? 'Draft' : 'Published'}</div>;
}
// AFTER (Next.js 16)
import { draftMode } from 'next/headers';
export default async function Page() {
const { isEnabled } = await draftMode();
return <div>{isEnabled ? 'Draft' : 'Published'}</div>;
}
Changes Required:
// BEFORE (Next.js 15)
export async function generateMetadata({ params }) {
const { slug } = params;
return { title: slug };
}
// AFTER (Next.js 16)
export async function generateMetadata(props) {
const { slug } = await props.params;
return { title: slug };
}
Run the Next.js codemod for automated migration:
npx @next/codemod@canary upgrade latest
Generate type helpers for safer migrations (Next.js 15.5+):
npx next typegen
This generates PageProps, LayoutProps, and RouteContext helpers.
Search Pattern: generateImageMetadata, default function Image in opengraph-image or twitter-image files
Changes Required:
// BEFORE (Next.js 15)
export function generateImageMetadata({ params }) {
const { slug } = params;
return [{ id: '1' }];
}
export default function Image({ params, id }) {
const slug = params.slug;
return new ImageResponse(/* ... */);
}
// AFTER (Next.js 16)
export async function generateImageMetadata({ params }) {
const { slug } = await params;
return [{ id: '1' }];
}
export default async function Image({ params, id }) {
const { slug } = await params;
const imageId = await id;
return new ImageResponse(/* ... */);
}
Action Items:
generateImageMetadata functions asyncawait for both params and id accessSearch Pattern: sitemap functions with id parameter
Changes Required:
// BEFORE (Next.js 15)
export default async function sitemap({ id }) {
const start = id * 50000;
// ...
}
// AFTER (Next.js 16)
export default async function sitemap({ id }) {
const resolvedId = await id;
const start = resolvedId * 50000;
// ...
}
Turbopack is now the default bundler for development.
Search Pattern: --turbo or --turbopack flags in package.json scripts, turbopack in next.config
// BEFORE (Next.js 15)
{
"scripts": {
"dev": "next dev --turbo"
}
}
// AFTER (Next.js 16) - Turbopack is default
{
"scripts": {
"dev": "next dev"
}
}
{
"scripts": {
"build": "next build --webpack"
}
}
// BEFORE (Next.js 15)
const nextConfig = {
experimental: {
turbopack: {
/* options */
},
},
};
// AFTER (Next.js 16)
const nextConfig = {
turbopack: {
/* options */
},
};
/* BEFORE */
@import '~bootstrap/dist/css/bootstrap.min.css';
/* AFTER - Remove tilde prefix */
@import 'bootstrap/dist/css/bootstrap.min.css';
Action Items:
--turbo and --turbopack flags from scriptsturbopack config from experimental to root level~) prefix from Sass imports--webpack flag if Webpack is requiredSearch Pattern: middleware.ts or middleware.js files
Changes Required:
# Rename the file
mv middleware.ts proxy.ts
// BEFORE (middleware.ts)
export function middleware(request) {
// ...
}
// AFTER (proxy.ts)
export function proxy(request) {
// ...
}
Config Updates:
// BEFORE
{
skipMiddlewareUrlNormalize: true;
}
// AFTER
{
skipProxyUrlNormalize: true;
}
Important: The Edge runtime is no longer supported in proxy. It now uses Node.js runtime.
Action Items:
middleware.ts/js to proxy.ts/jsmiddleware to proxySearch Pattern: Directories starting with @ in the app folder (parallel route slots)
All parallel route slots now require an explicit default.js file.
Changes Required:
// Create app/@modal/default.tsx for each parallel route slot
import { notFound } from 'next/navigation';
export default function Default() {
notFound(); // or return null
}
Action Items:
app/@*/)default.tsx in each slot that doesn't have one// Now requires explicit configuration
<Image src="/assets/photo?v=1" alt="Photo" width="100" height="100" />
// next.config.js
module.exports = {
images: {
localPatterns: [
{
pathname: '/assets/**',
search: '?v=1',
},
],
},
};
Add these to next.config.js if you need the old defaults:
module.exports = {
images: {
// minimumCacheTTL changed from 60 to 14400 seconds
minimumCacheTTL: 60,
// Value 16 removed from default imageSizes
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
// qualities now defaults to [75] only
qualities: [50, 75, 100],
// Local IP now blocked by default
dangerouslyAllowLocalIP: true, // only for private networks
// Maximum redirects changed from unlimited to 3
maximumRedirects: 5,
},
};
// BEFORE - Remove this
module.exports = {
images: {
domains: ['example.com'],
},
};
// AFTER - Use remotePatterns instead
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
},
],
},
};
Action Items:
localPatterns for images with query stringsimages.domains to images.remotePatterns// BEFORE (Next.js 15)
import {
unstable_cacheLife as cacheLife,
unstable_cacheTag as cacheTag,
} from 'next/cache';
// AFTER (Next.js 16)
import { cacheLife, cacheTag } from 'next/cache';
revalidateTag with cacheLife profile:
'use server';
import { revalidateTag } from 'next/cache';
export async function updateArticle(articleId: string) {
revalidateTag(`article-${articleId}`, 'max');
}
updateTag (new):
'use server';
import { updateTag } from 'next/cache';
export async function updateUserProfile(userId: string, profile: Profile) {
await db.users.update(userId, profile);
updateTag(`user-${userId}`);
}
refresh (new):
'use server';
import { refresh } from 'next/cache';
export async function markNotificationAsRead(notificationId: string) {
await db.notifications.markAsRead(notificationId);
refresh();
}
Action Items:
unstable_ prefix from cacheLife and cacheTag importsupdateTag and refresh functionsReact Compiler is now stable and supported:
// next.config.ts
const nextConfig = {
reactCompiler: true,
};
export default nextConfig;
Install the plugin:
npm install -D babel-plugin-react-compiler
Note: Expect higher compile times with React Compiler enabled.
Next.js no longer overrides scroll-behavior: smooth during navigation.
To restore previous behavior:
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="en" data-scroll-behavior="smooth">
<body>{children}</body>
</html>
);
}
The next lint command has been removed. Migrate to ESLint CLI directly.
# Run migration codemod
npx @next/codemod@canary next-lint-to-eslint-cli .
Remove from next.config.js:
// Remove this
{
eslint: {
}
}
Action Items:
eslint config from next.config.jseslint directly instead of next lintuseAmp hook usageamp config option// BEFORE - Remove these
module.exports = {
serverRuntimeConfig: { dbUrl: process.env.DATABASE_URL },
publicRuntimeConfig: { apiUrl: '/api' },
};
Migration for server-side config:
// Use environment variables directly
async function fetchData() {
const dbUrl = process.env.DATABASE_URL;
return await db.query(dbUrl, 'SELECT * FROM users');
}
Migration for client-side config:
# .env.local
NEXT_PUBLIC_API_URL="/api"
'use client';
export default function Component() {
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
// ...
}
Remove these from next.config.js:
appIsrStatusbuildActivitybuildActivityPosition// BEFORE
{
experimental: {
dynamicIO: true;
}
}
// AFTER
{
cacheComponents: true;
}
This API is removed. Await alternative API in a future minor release.
Action Items:
dynamicIO to cacheComponentsDevelopment now outputs to .next/dev (separate from build).
Update Turbopack tracing command:
npx next internal trace .next/dev/trace-turbopack
# Build each Next.js project individually
nx run PROJECT_NAME:build
# Start dev server to verify Turbopack works
nx run PROJECT_NAME:serve
# Build all affected projects
nx affected -t build
# Run full CI validation
nx prepush
Solution: Make the function async and await cookies()
Solution: Add await before accessing props.params
Solution: Add --webpack flag to build script, then gradually address Turbopack compatibility
Solution: Ensure both file and function are renamed from middleware to proxy
Solution: Add default.tsx file to the parallel route slot
Solution: Add localPatterns configuration for those images
Solution: Run npx next typegen to generate type helpers, then use PageProps, LayoutProps types
Create a checklist of all files that need review:
# Find all pages with potential params usage
find . -path "*/app/*" -name "page.tsx" -o -name "page.ts" | xargs grep -l "params\|searchParams"
# Find all layouts
find . -path "*/app/*" -name "layout.tsx" -o -name "layout.ts"
# Find all route handlers
find . -path "*/app/*" -name "route.ts" -o -name "route.tsx"
# Find middleware files
find . -name "middleware.ts" -o -name "middleware.js"
# Find files using cookies/headers
rg "from 'next/headers'" --type ts --type tsx
# Find next.config files
find . -name "next.config.*" -not -path "*/node_modules/*"
# Find parallel routes
find . -path "*/app/@*" -type d
npx @next/codemod@canary upgrade latest for automated fixesnpx next typegen for type-safe migrations# Find all Next.js projects
nx show projects --with-target build
# Build specific project
nx build PROJECT_NAME
# Serve specific project
nx serve PROJECT_NAME
# Build all affected
nx affected -t build
# View project details
nx show project PROJECT_NAME --web
# Clear Nx cache if needed
nx reset
When executing this migration:
@next/codemod handle repetitive async/await changes