**Version**: Next.js 16.1.1 **React Version**: 19.2.3 **Node.js**: 20.9+ **Last Verified**: 2026-01-09 * * * 1. [When to Use This Skill](#when-to-use-this-skill)
"use cache" directive (NEW in Next.js 16)revalidateTag(), updateTag(), refresh() (Updated in Next.js 16)params, searchParams, cookies(), headers() now async)useEffectEvent(), React Compiler)cloudflare-nextjs skill insteadclerk-auth, better-auth, or other auth-specific skillscloudflare-d1, drizzle-orm-d1, or database-specific skillstailwind-v4-shadcn skill for Tailwind + shadcn/uizustand-state-management, tanstack-query skillsreact-hook-form-zod skillnpm update next # Verify: npm list next should show 16.1.1+
next dev --inspect supportparams, searchParams, cookies(), headers(), draftMode() are now async and must be awaited.// ❌ This no longer works in Next.js 16 export default function Page({ params, searchParams }: { params: { slug: string } searchParams: { query: string } }) { const slug = params.slug // ❌ Error: params is a Promise const query = searchParams.query // ❌ Error: searchParams is a Promise return <div>{slug}</div> } `**After (Next.js 16)**:` // ✅ Correct: await params and searchParams export default async function Page({ params, searchParams }: { params: Promise<{ slug: string }> searchParams: Promise<{ query: string }> }) { const { slug } = await params // ✅ Await the promise const { query } = await searchParams // ✅ Await the promise return <div>{slug}</div> }
params in pages, layouts, route handlerssearchParams in pagescookies() from next/headersheaders() from next/headersdraftMode() from next/headers// ❌ Before import { cookies, headers } from 'next/headers' export function MyComponent() { const cookieStore = cookies() // ❌ Sync access const headersList = headers() // ❌ Sync access } // ✅ After import { cookies, headers } from 'next/headers' export async function MyComponent() { const cookieStore = await cookies() // ✅ Async access const headersList = await headers() // ✅ Async access }
npx @next/codemod@canary upgrade latest to automatically migrate.@next-codemod-error comments marking places it couldn't auto-fix.// For client components, use React.use() to unwrap promises 'use client'; import { use } from 'react'; export default function ClientComponent({ params }: { params: Promise<{ id: string }> }) { const { id } = use(params); // Unwrap Promise in client return <div>{id}</div>; }
templates/app-router-async-params.tsxmiddleware.ts is deprecated in Next.js 16. Use proxy.ts instead.proxy.ts makes the network boundary explicit by running on Node.js runtime (not Edge runtime). This provides better clarity between edge middleware and server-side proxies.middleware.ts → proxy.tsmiddleware → proxymatcher → config.matcher (same syntax)// middleware.ts ❌ Deprecated in Next.js 16 import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { const response = NextResponse.next() response.headers.set('x-custom-header', 'value') return response } export const config = { matcher: '/api/:path*', } `**After (Next.js 16)**:` // proxy.ts ✅ New in Next.js 16 import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function proxy(request: NextRequest) { const response = NextResponse.next() response.headers.set('x-custom-header', 'value') return response } export const config = { matcher: '/api/:path*', }
middleware.ts still works in Next.js 16 but is deprecated. Migrate to proxy.ts for future compatibility.templates/proxy-migration.ts See Reference: references/proxy-vs-middleware.mddefault.js (BREAKING)default.js files. Without them, routes will fail during soft navigation.app/ ├── @auth/ │ ├── login/ │ │ └── page.tsx │ └── default.tsx ← REQUIRED in Next.js 16 ├── @dashboard/ │ ├── overview/ │ │ └── page.tsx │ └── default.tsx ← REQUIRED in Next.js 16 └── layout.tsx `**Layout**:` // app/layout.tsx export default function Layout({ children, auth, dashboard, }: { children: React.ReactNode auth: React.ReactNode dashboard: React.ReactNode }) { return ( <html> <body> {auth} {dashboard} {children} </body> </html> ) } `**Default Fallback** (REQUIRED):` // app/@auth/default.tsx export default function AuthDefault() { return null // or <Skeleton /> or redirect } // app/@dashboard/default.tsx export default function DashboardDefault() { return null }
default.js, unmatched slots will error during client-side navigation.templates/parallel-routes-with-default.tsxnext lint command - Use ESLint or Biome directly.serverRuntimeConfig and publicRuntimeConfig - Use environment variables instead.experimental.ppr flag - Evolved into Cache Components. Use "use cache" directive.scroll-behavior: smooth - Add manually if needed.npx eslint . or npx biome lint . directly.serverRuntimeConfig with process.env.VARIABLE.experimental.ppr to "use cache" directive (see Cache Components section).node --version # Should be 20.9+ npm --version # Should be 10+ npx next --version # Should be 16.0.0+ `**Upgrade Node.js**:` # Using nvm nvm install 20 nvm use 20 nvm alias default 20 # Using Homebrew (macOS) brew install node@20 # Using apt (Ubuntu/Debian) sudo apt update sudo apt install nodejs npm
next/image defaults:[16, 32, 48, 64, 96, 128, 256, 384][640, 750, 828, 1080, 1200] (reduced)[75, 90, 100][75] (single quality)// next.config.ts import type { NextConfig } from 'next' const config: NextConfig = { images: { minimumCacheTTL: 60, // Revert to 60 seconds deviceSizes: [640, 750, 828, 1080, 1200, 1920], // Add larger sizes imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // Restore old sizes formats: ['image/webp'], // Default }, } export default config
templates/image-optimization.tsx"use cache" directive, replacing implicit caching from Next.js 15."use cache" directive"use cache" Directive"use cache" at the top of a Server Component, function, or route handler.// app/components/expensive-component.tsx 'use cache' export async function ExpensiveComponent() { const data = await fetch('https://api.example.com/data') const json = await data.json() return ( <div> <h1>{json.title}</h1> <p>{json.description}</p> </div> ) } `**Function-level caching**:` // lib/data.ts 'use cache' export async function getExpensiveData(id: string) { const response = await fetch(`https://api.example.com/items/${id}`) return response.json() } // Usage in component import { getExpensiveData } from '@/lib/data' export async function ProductPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params const product = await getExpensiveData(id) // Cached return <div>{product.name}</div> } `**Page-level caching**:` // app/blog/[slug]/page.tsx 'use cache' export async function generateStaticParams() { const posts = await fetch('https://api.example.com/posts').then(r => r.json()) return posts.map((post: { slug: string }) => ({ slug: post.slug })) } export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params const post = await fetch(`https://api.example.com/posts/${slug}`).then(r => r.json()) return ( <article> <h1>{post.title}</h1> <div>{post.content}</div> </article> ) }
templates/cache-component-use-cache.tsx// app/dashboard/page.tsx // Static header (cached) 'use cache' async function StaticHeader() { return <header>My App</header> } // Dynamic user info (not cached) async function DynamicUserInfo() { const cookieStore = await cookies() const userId = cookieStore.get('userId')?.value const user = await fetch(`/api/users/${userId}`).then(r => r.json()) return <div>Welcome, {user.name}</div> } // Page combines both export default function Dashboard() { return ( <div> <StaticHeader /> {/* Cached */} <DynamicUserInfo /> {/* Dynamic */} </div> ) }
references/cache-components-guide.mdrevalidateTag() - Updated APIrevalidateTag() now requires a second argument (cacheLife profile) for stale-while-revalidate behavior.import { revalidateTag } from 'next/cache' export async function updatePost(id: string) { await fetch(`/api/posts/${id}`, { method: 'PATCH' }) revalidateTag('posts') // ❌ Only one argument in Next.js 15 } `**After (Next.js 16)**:` import { revalidateTag } from 'next/cache' export async function updatePost(id: string) { await fetch(`/api/posts/${id}`, { method: 'PATCH' }) revalidateTag('posts', 'max') // ✅ Second argument required in Next.js 16 }
'max' - Maximum staleness (recommended for most use cases)'hours' - Stale after hours'days' - Stale after days'weeks' - Stale after weeks'default' - Default cache behaviorrevalidateTag('posts', { stale: 3600, // Stale after 1 hour (seconds) revalidate: 86400, // Revalidate every 24 hours (seconds) expire: false, // Never expire (optional) }) `**Pattern in Server Actions**:` 'use server' import { revalidateTag } from 'next/cache' export async function createPost(formData: FormData) { const title = formData.get('title') as string const content = formData.get('content') as string await fetch('/api/posts', { method: 'POST', body: JSON.stringify({ title, content }), }) revalidateTag('posts', 'max') // ✅ Revalidate with max staleness }
templates/revalidate-tag-cache-life.tsupdateTag() - NEW API (Server Actions Only)updateTag() provides read-your-writes semantics for Server Actions.revalidateTag():revalidateTag(): Stale-while-revalidate (shows stale data, revalidates in background)updateTag(): Immediate refresh (expires cache, fetches fresh data in same request)'use server' import { updateTag } from 'next/cache' export async function updateUserProfile(formData: FormData) { const name = formData.get('name') as string const email = formData.get('email') as string // Update database await db.users.update({ name, email }) // Immediately refresh cache (read-your-writes) updateTag('user-profile') // User sees updated data immediately (no stale data) }
updateTag(): User settings, profile updates, critical mutations (immediate feedback)revalidateTag(): Blog posts, product listings, non-critical updates (background revalidation)templates/server-action-update-tag.tsrefresh() - NEW API (Server Actions Only)refresh() refreshes uncached data only (complements client-side router.refresh()).router.refresh() on server side'use server' import { refresh } from 'next/cache' export async function refreshDashboard() { // Refresh uncached data (e.g., real-time metrics) refresh() // Cached data (e.g., static header) remains cached }
revalidateTag() and updateTag():refresh(): Only refreshes uncached datarevalidateTag(): Revalidates specific tagged data (stale-while-revalidate)updateTag(): Immediately expires and refreshes specific tagged datareferences/cache-components-guide.mdparams and headers() are now async in Next.js 16 route handlers.// app/api/posts/[id]/route.ts import { NextResponse } from 'next/server' import { headers } from 'next/headers' export async function GET( request: Request, { params }: { params: Promise<{ id: string }> } ) { const { id } = await params // ✅ Await params in Next.js 16 const headersList = await headers() // ✅ Await headers in Next.js 16 const post = await db.posts.findUnique({ where: { id } }) return NextResponse.json(post) }
templates/route-handler-api.tsproxy.ts to replace middleware.ts.middleware.ts: Runs on Edge runtime (limited Node.js APIs)proxy.ts: Runs on Node.js runtime (full Node.js APIs)proxy.ts makes the network boundary explicit and provides more flexibility.// middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { // Check auth const token = request.cookies.get('token') if (!token) { return NextResponse.redirect(new URL('/login', request.url)) } return NextResponse.next() } export const config = { matcher: '/dashboard/:path*', } `**After (proxy.ts)**:` // proxy.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function proxy(request: NextRequest) { // Check auth const token = request.cookies.get('token') if (!token) { return NextResponse.redirect(new URL('/login', request.url)) } return NextResponse.next() } export const config = { matcher: '/dashboard/:path*', }
templates/proxy-migration.ts See Reference: references/proxy-vs-middleware.mddefault.js files.app/ ├── @modal/ │ ├── login/page.tsx │ └── default.tsx ← REQUIRED in Next.js 16 ├── @feed/ │ ├── trending/page.tsx │ └── default.tsx ← REQUIRED in Next.js 16 └── layout.tsx `**Default Files (REQUIRED)**:` // app/@modal/default.tsx export default function ModalDefault() { return null // or <Skeleton /> or redirect }
default.js, unmatched slots error during client-side navigation.default.js files, hard navigating or refreshing routes with parallel routes can return 404 errors. The workaround is adding a catch-all route.// app/@modal/[...catchAll]/page.tsx export default function CatchAll() { return null; } // OR use catch-all in default.tsx // app/@modal/default.tsx export default function ModalDefault({ params }: { params: { catchAll?: string[] } }) { return null; // Handles all unmatched routes }
templates/parallel-routes-with-default.tsx'use client' import { useRouter } from 'next/navigation' import { startTransition } from 'react' export function NavigationLink({ href, children }: { href: string; children: React.ReactNode }) { const router = useRouter() function handleClick(e: React.MouseEvent) { e.preventDefault() // Wrap navigation in startTransition for View Transitions startTransition(() => { router.push(href) }) } return <a href={href} onClick={handleClick}>{children}</a> } `**With CSS View Transitions API**:` /* app/globals.css */ @view-transition { navigation: auto; } /* Animate elements with view-transition-name */ .page-title { view-transition-name: page-title; }
templates/view-transitions-react-19.tsxuseEffectEvent() (Experimental)useEffect.'use client' import { useEffect, experimental_useEffectEvent as useEffectEvent } from 'react' export function ChatRoom({ roomId }: { roomId: string }) { const onConnected = useEffectEvent(() => { console.log('Connected to room:', roomId) }) useEffect(() => { const connection = connectToRoom(roomId) onConnected() // Non-reactive callback return () => connection.disconnect() }, [roomId]) // Only re-run when roomId changes return <div>Chat Room {roomId}</div> }
useEffect re-runs when callback dependencies change.useMemo, useCallback.import type { NextConfig } from 'next' const config: NextConfig = { experimental: { reactCompiler: true, }, } export default config `**Install Plugin**:` npm install babel-plugin-react-compiler `**Example** (no manual memoization needed):` 'use client' export function ExpensiveList({ items }: { items: string[] }) { // React Compiler automatically memoizes this const filteredItems = items.filter(item => item.length > 3) return ( <ul> {filteredItems.map(item => ( <li key={item}>{item}</li> ))} </ul> ) }
references/react-19-integration.mdnpm run build -- --webpack// next.config.ts import type { NextConfig } from 'next' const config: NextConfig = { experimental: { turbopack: { fileSystemCaching: true, // Beta: Persist cache between runs }, }, } export default config
# Use webpack for production builds npm run build -- --webpack `Or in `next.config.ts`:` const config: NextConfig = { experimental: { turbo: false, // Disable Turbopack for production }, };
// next.config.ts const config: NextConfig = { productionBrowserSourceMaps: false, // Disable source maps }; `Or exclude `.map` files in deployment:` # .vercelignore or similar *.map
node_modules structure differs (pnpm, yarn workspaces, monorepos). This causes "Module not found" errors in production builds.// next.config.ts const config: NextConfig = { experimental: { serverExternalPackages: ['package-name'], // Explicitly externalize packages }, };
params is a PromiseType 'Promise<{ id: string }>' is not assignable to type '{ id: string }'params to async.params:// ❌ Before export default function Page({ params }: { params: { id: string } }) { const id = params.id } // ✅ After export default async function Page({ params }: { params: Promise<{ id: string }> }) { const { id } = await params }
searchParams is a PromiseProperty 'query' does not exist on type 'Promise<{ query: string }>'searchParams is now async in Next.js 16.// ❌ Before export default function Page({ searchParams }: { searchParams: { query: string } }) { const query = searchParams.query } // ✅ After export default async function Page({ searchParams }: { searchParams: Promise<{ query: string }> }) { const { query } = await searchParams }
cookies() requires await'cookies' implicitly has return type 'any'cookies() is now async in Next.js 16.// ❌ Before import { cookies } from 'next/headers' export function MyComponent() { const cookieStore = cookies() } // ✅ After import { cookies } from 'next/headers' export async function MyComponent() { const cookieStore = await cookies() }
default.jsError: Parallel route @modal/login was matched but no default.js was founddefault.js for all parallel routes.default.tsx files:// app/@modal/default.tsx export default function ModalDefault() { return null }
revalidateTag() requires 2 argumentsExpected 2 arguments, but got 1revalidateTag() now requires a cacheLife argument in Next.js 16.// ❌ Before revalidateTag('posts') // ✅ After revalidateTag('posts', 'max')
You're importing a component that needs useState. It only works in a Client Component'use client' directive:// ✅ Add 'use client' at the top 'use client' import { useState } from 'react' export function Counter() { const [count, setCount] = useState(0) return <button onClick={() => setCount(count + 1)}>{count}</button> }
middleware.ts is deprecatedWarning: middleware.ts is deprecated. Use proxy.ts instead.proxy.ts:// Rename: middleware.ts → proxy.ts // Rename function: middleware → proxy export function proxy(request: NextRequest) { // Same logic }
Error: Failed to compile with Turbopacknpm run build -- --webpacknext/image srcInvalid src prop (https://example.com/image.jpg) on next/image. Hostname "example.com" is not configured under images in your next.config.js``next.config.ts:You're importing a Server Component into a Client ComponentgenerateStaticParams not workinggenerateStaticParams only works with static generation (export const dynamic = 'force-static').fetch() not caching"use cache" directive."use cache" to component or function:Error: Conflicting routes: /about and /(marketing)/aboutgenerateMetadata().generateMetadata() for dynamic pages:next/font font not loading<html> or <body>:NEXT_PUBLIC_ for client-side access:Error: Could not find Server Action'use server' directive.'use server':baseUrl or paths in tsconfig.json.references/top-errors.mdThrottling navigation to prevent the browser from hanging Source: GitHub Issue #87245proxy.ts (or middleware.ts) performs a redirect to add query params AND a Server Component also calls redirect() to add different query params, client-side navigation via <Link> fails in production builds. This is a regression from Next.js 14 to 16.next dev (development mode)<Link> in production buildgenerateStaticParams Source: GitHub Issue #86870"use cache" directive) do NOT work on dynamic segments when using internationalization (i18n) frameworks like intlayer, next-intl, or lingui. Accessing params forces the route to be dynamic, even with generateStaticParams at the layout level.params to get the locale. Accessing params is an async call in Next.js 16, which opts the entire page out of caching.generateStaticParams at EACH dynamic segment level:[locale] dynamic segment receives invalid values like _next during compilation, causing RangeError: Incorrect locale information provided when initializing i18n providers.instanceof CustomError returns false even though it is CustomError Source: GitHub Issue #87614error.name or error.constructor.name instead of instanceof:The 'path' argument must be of type string Source: GitHub Discussion #77721npm run build -- --webpackModule not found in production despite successful local build Source: GitHub Issue #87737node_modules structure differs (pnpm, yarn workspaces, monorepos).references/top-errors.mdtemplates/):app-router-async-params.tsx - Async params migration patternsparallel-routes-with-default.tsx - Required default.js filescache-component-use-cache.tsx - Cache Components with "use cache"revalidate-tag-cache-life.ts - Updated revalidateTag() with cacheLifeserver-action-update-tag.ts - updateTag() for read-your-writesproxy-migration.ts - Migrate from middleware.ts to proxy.tsview-transitions-react-19.tsx - React 19.2 View Transitionsnext.config.ts - Next.js 16 configurationreferences/):next-16-migration-guide.md - Complete Next.js 15→16 migration guidecache-components-guide.md - Cache Components deep diveproxy-vs-middleware.md - Proxy.ts vs middleware.tsasync-route-params.md - Async params breaking change detailsreact-19-integration.md - React 19.2 features in Next.js 16top-errors.md - 18+ common errors with solutions/websites/nextjs for latest reference./scripts/check-versions.shcd skills/nextjs ./scripts/check-versions.sh