
Next Js 16 Launchpad
- 3 installs
- 404 repo stars
- Updated August 5, 2026
- aiskillstore/marketplace
next-js-16-launchpad is a Claude Code skill that bootstraps and migrates Next.js 16 App Router apps using Turbopack, Cache Components, and proxy.ts.
About
next-js-16-launchpad is a Claude Code skill for bootstrapping, migrating, and building Next.js 16 apps with the App Router and React 19. It covers Turbopack, Cache Components ('use cache' with cacheLife), the proxy.ts boundary that replaces middleware.ts, server actions, and streaming with Suspense. A developer uses it when starting a new Next.js 16 project or upgrading a v15 codebase.
- Bootstraps and migrates Next.js 16 apps with Turbopack, Cache Components, and proxy.ts
- Ships an App Router starter, a bootstrap PowerShell script, and five reference guides
- Covers the v15 to v16 migration: async params, middleware.ts to proxy.ts, cacheComponents
Next Js 16 Launchpad by the numbers
- 3 all-time installs (skills.sh)
- Ranked #1,841 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
next-js-16-launchpad capabilities & compatibility
- Capabilities
- frontend · app router setup · framework migration · cache config
- Use cases
- frontend · api development
- IDEs
- vscode · cursor ide
- Pricing
- Free
What next-js-16-launchpad says it does
Next.js 16 with Turbopack, Cache Components, and proxy.ts. Use for bootstrapping, migrating, and building with App Router and React 19.
Next.js 16: Turbopack default (2-5× faster builds), Cache Components (`'use cache'`), and `proxy.ts` for explicit control.
npx skills add https://github.com/aiskillstore/marketplace --skill next-js-16-launchpadAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 404 |
| Last updated | August 5, 2026 |
| Repository | aiskillstore/marketplace ↗ |
What it does
Bootstrap or migrate a Next.js 16 App Router app using Turbopack, Cache Components, and proxy.ts.
Who is it for?
Developers starting or upgrading Next.js 16 App Router projects on React 19.2
Skip if: Pages Router, Next.js 15 or earlier, or generic React questions
When should I use this skill?
bootstrapping, migrating, or building with Next.js 16, Turbopack, Cache Components, or proxy.ts
What you get
A working Next.js 16 App Router app is scaffolded or an existing v15 app is migrated to the new APIs.
- App Router starter template
- bootstrap-nextjs16.ps1 script
- Next.js 16 migration checklist
By the numbers
- 6 core patterns
- 5 reference guides
- 4-step migration checklist
Files
Next.js 16 Launchpad
Next.js 16: Turbopack default (2-5× faster builds), Cache Components ('use cache'), and proxy.ts for explicit control.
When to Use
✅ Next.js 16, Turbopack, Cache Components, proxy migration, App Router, React 19.2
❌ Pages Router, Next.js ≤15, generic React questions
Requirements
| Tool | Version |
|---|---|
| Node.js | 20.9.0+ |
| TypeScript | 5.1.0+ |
| React | 19.2+ |
Quick Start
# New project
npx create-next-app@latest my-app
# Upgrade existing
npx @next/codemod@canary upgrade latest
npm install next@latest react@latest react-dom@latestRecommended: TypeScript, ESLint, Tailwind, App Router, Turbopack, @/* alias.
Minimal Setup
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}// app/page.tsx
export default function Page() {
return <h1>Hello, Next.js 16!</h1>
}Configuration
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
reactCompiler: true,
}
export default nextConfigv15 → v16 Changes
| v15 | v16 |
|---|---|
experimental.turbopack | Default |
experimental.ppr | cacheComponents |
middleware.ts (Edge) | proxy.ts (Node) |
Sync params | await params |
Core Patterns
1. Server Components (Default)
export default async function BlogPage() {
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return <PostList posts={posts} />
}2. Cache Components
import { cacheLife } from 'next/cache'
export default async function BlogPage() {
'use cache'
cacheLife('hours')
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return <PostList posts={posts} />
}3. Client Components
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
)
}4. Proxy Boundary
// app/proxy.ts
export function proxy(request: NextRequest) {
if (!request.cookies.get('auth') && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}5. Cache Tags + Server Actions
// app/blog/page.tsx
'use cache'
cacheLife('hours')
cacheTag('blog-posts')
export default async function BlogList() {
const posts = await db.posts.findMany()
return <PostList posts={posts} />
}// app/actions.ts
'use server'
import { updateTag } from 'next/cache'
export async function createPost(data: PostData) {
await db.posts.create(data)
updateTag('blog-posts')
}6. Streaming with Suspense
export default function Dashboard() {
return (
<div>
<Suspense fallback={<Skeleton />}>
<RevenueCard />
</Suspense>
<Suspense fallback={<Skeleton />}>
<UsersCard />
</Suspense>
</div>
)
}
async function RevenueCard() {
const data = await db.analytics.revenue()
return <div>{data}</div>
}Key Concepts
1. Turbopack - Rust bundler, incremental compilation, Fast Refresh 2. Server Components - Default in app/, zero client JS 3. Client Components - 'use client', hooks, browser APIs 4. Cache Components - 'use cache' + cacheLife() for PPR 5. Proxy Boundary - proxy.ts for auth/rewrites/redirects 6. Partial Pre-Rendering - Static shell + dynamic streaming
Migration Checklist
1. Async Request APIs
npx @next/codemod@canary async-request-apiUpdate: const { slug } = await params
2. middleware.ts → proxy.ts
- Rename file, export
proxy - Node runtime only (not Edge)
3. Config updates
- Remove
experimental.*flags - Enable
cacheComponents,reactCompiler - Remove
serverRuntimeConfig/publicRuntimeConfig
4. Cache Components
- Replace
experimental.pprwithcacheComponents: true - Wrap dynamic sections with
<Suspense>
5. Images
- Configure
images.localPatternsfor query strings
See references/nextjs16-migration-playbook.md for complete guide.
Common Pitfalls
❌ Mixing 'use cache' with runtime APIs (cookies(), headers()) ❌ Missing <Suspense> when Cache Components enabled ❌ Tilde Sass imports under Turbopack ❌ Running proxy.ts on Edge runtime
✅ Read cookies/headers first, pass as props to cached components ✅ Wrap dynamic children in <Suspense> ✅ Use standard Sass imports ✅ Use Node runtime for proxy
Decision Guide
Enable Cache Components? → Yes for static/semi-static content → No for fully dynamic dashboards
Where does auth live? → proxy.ts for cross-route checks → Route handlers for API-specific logic
When to use `'use client'`? → Only when you need hooks, state, or browser APIs → Keep presentational components server-side
Production Patterns
E-commerce
// Product page with streaming
export default async function Product({ params }) {
const { id } = await params
const product = await db.products.findById(id)
return (
<>
<ProductInfo product={product} />
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={id} />
</Suspense>
</>
)
}Authenticated Dashboard
// proxy.ts
export function proxy(request: NextRequest) {
const session = request.cookies.get('session')
if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
}See references/nextjs16-advanced-patterns.md for more blueprints.
Performance
- Keep Turbopack enabled (opt-out with
--webpackonly if needed) - Parallelize fetches with
Promise.all - Use
<Suspense>for streaming boundaries - Enable file system cache for large repos
Security
- Use
server-onlypackage + React Taint API - Keep auth in
proxy.ts - Validate inputs in Server Actions
- Gate env vars with
NEXT_PUBLIC_prefix - Extract cookies/headers before cached scopes
Deployment
- Vercel: Zero-config
- Docker/Node:
output: 'standalone' - Monitor build times (2-5× speedup expected)
- Configure cache lifecycles to match CDN
Reference Files
- references/nextjs16-reference.md - Install/config/checklists
- references/nextjs16-migration-playbook.md - Migration guide with codemods
- references/nextjs16-advanced-patterns.md - Streaming, caching, auth patterns
- references/NEXTJS_16_COMPLETE_GUIDE.md - Complete documentation
- scripts/bootstrap-nextjs16.ps1 - Automated setup script
- assets/app-router-starter/ - Reference implementation
Resources
- Docs: https://nextjs.org/docs
- GitHub: https://github.com/vercel/next.js
Version: 1.1.0 | Updated: 2025-12-27
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div className="rounded border border-red-200 bg-red-50 p-4">
<p className="font-medium text-red-800">{error.message}</p>
<button
onClick={() => reset()}
className="mt-2 rounded border border-red-300 px-3 py-1 text-sm"
>
Try again
</button>
</div>
)
}
export default function Loading() {
return (
<div className="space-y-2">
<div className="h-4 w-1/2 animate-pulse rounded bg-gray-200" />
<div className="h-4 w-2/3 animate-pulse rounded bg-gray-200" />
<div className="h-4 w-1/3 animate-pulse rounded bg-gray-200" />
</div>
)
}
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { Suspense } from 'react'
async function loadStats() {
const res = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5', {
cache: 'no-store',
})
return res.json()
}
async function StatsPanel() {
const stats = await loadStats()
return (
<ul className="space-y-2">
{stats.map((item: { id: number; title: string }) => (
<li key={item.id} className="rounded border border-gray-200 p-3">
{item.title}
</li>
))}
</ul>
)
}
export default async function DashboardPage() {
const session = (await cookies()).get('auth-token')?.value
if (!session) {
redirect('/login')
}
return (
<div className="space-y-4">
<h2 className="text-2xl font-semibold">Dashboard</h2>
<Suspense fallback={<p className="text-gray-500">Loading stats...</p>}>
<StatsPanel />
</Suspense>
</div>
)
}
export const metadata = {
title: 'Next.js 16 Starter',
description: 'Turbopack + Cache Components baseline layout',
}
export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode
modal?: React.ReactNode
}) {
return (
<html lang="en" suppressHydrationWarning>
<body className="min-h-screen bg-gray-50 text-gray-900">
<div className="mx-auto flex max-w-4xl flex-col gap-6 p-6">
<header className="flex flex-col gap-2">
<p className="text-sm uppercase tracking-wide text-gray-500">
Next.js 16 Reference Shell
</p>
<h1 className="text-3xl font-semibold">Next.js 16 Launchpad</h1>
</header>
<main>{children}</main>
</div>
{modal}
</body>
</html>
)
}
import Link from 'next/link'
import { Suspense } from 'react'
async function fetchPosts() {
'use cache'
const data = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=3', {
next: { revalidate: 3600 },
})
return data.json()
}
async function FeaturedPosts() {
const posts = await fetchPosts()
return (
<ul className="space-y-3">
{posts.map((post: { id: number; title: string }) => (
<li key={post.id} className="rounded border border-gray-200 p-4">
<h3 className="font-medium">{post.title}</h3>
</li>
))}
</ul>
)
}
export default function Page() {
return (
<div className="space-y-6">
<section className="space-y-2">
<h2 className="text-2xl font-semibold">Ready for Turbopack</h2>
<p className="text-gray-600">
This starter uses Server Components by default, Cache Components for predictable freshness,
and a Suspense boundary for streaming.
</p>
</section>
<Suspense fallback={<p className="text-gray-500">Loading featured posts...</p>}>
{/* Cached content, streamed when ready */}
<FeaturedPosts />
</Suspense>
<Link
href="/dashboard"
className="inline-flex items-center gap-2 rounded border border-gray-300 px-4 py-2 text-sm font-medium"
>
Go to dashboard
</Link>
</div>
)
}
import { NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl
if (pathname.startsWith('/dashboard')) {
const token = request.cookies.get('auth-token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*'],
}
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
reactCompiler: true,
images: {
localPatterns: [
{
pathname: '/assets/**',
search: '?v=*',
},
],
},
}
export default nextConfig
Next.js 16 App Router Starter
Includes:
- Root layout with modal slot and Tailwind-ready body.
- Cache-aware home page streaming featured posts with Suspense.
- Auth-gated dashboard route with error/loading boundaries.
proxy.tssample that redirects unauthenticated dashboard hits.next.config.tswithcacheComponents+reactCompilerenabled and localPatterns example.
Copy this folder into a new project created via npx create-next-app@latest to get a reference implementation aligned with the skill workflows.
Next.js 16 Core Workflows
Detailed workflows for Next.js 16 setup, configuration, and data patterns.
Workflow 1: Install and Bootstrap
Step-by-Step
1. Verify Requirements
node --version # Should be 20.9.0+
npx tsc --version # Should be 5.1.0+2. Install Next.js
For new projects:
npx create-next-app@latest my-appFor upgrades:
npx @next/codemod@canary upgrade latest
npm install next@latest react@latest react-dom@latest3. Configure package.json scripts
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}4. Scaffold Core Files
Create baseline structure:
my-app/
├── app/
│ ├── layout.tsx
│ ├── page.tsx
│ ├── proxy.ts
│ ├── error.tsx
│ └── loading.tsx
├── public/
├── next.config.ts
├── package.json
└── tsconfig.json5. Add Styling System
Tailwind (recommended):
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -pMinimal Files
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}// app/page.tsx
export default function Page() {
return <h1>Hello, Next.js 16!</h1>
}// app/error.tsx
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
)
}// app/loading.tsx
export default function Loading() {
return <div>Loading...</div>
}---
Workflow 2: Configuration Modernization
TypeScript Config
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
// Cache Components
cacheComponents: true,
// React Compiler
reactCompiler: true,
// Custom cache lifecycles
cacheLife: {
'product-catalog': {
stale: 3600, // 1 hour
revalidate: 7200, // 2 hours
expire: 86400 // 24 hours
},
},
// Image configuration
images: {
localPatterns: [
{
pathname: '/assets/**',
search: '',
},
],
},
// Custom Turbopack aliases (if needed)
turbopack: {
resolveAlias: {
'@components': './components',
'@lib': './lib',
},
},
}
export default nextConfigMigration Table: v15 → v16
| Feature | v15 | v16 | Action |
|---|---|---|---|
| Bundler | experimental.turbopack | Default | Remove flag |
| PPR | experimental.ppr | cacheComponents | Rename |
| Dynamic IO | experimental.dynamicIO | cacheComponents | Merge into cacheComponents |
| Middleware | middleware.ts (Edge) | proxy.ts (Node) | Rename & update |
| React Compiler | experimental.reactCompiler | reactCompiler | Remove experimental prefix |
| Runtime Config | serverRuntimeConfig | Env variables | Remove entirely |
| Lint in Build | next lint in build | External ESLint | Run separately |
| Sass Imports | ~bootstrap/ | bootstrap/ | Remove tilde |
Step-by-Step Migration
1. Remove experimental flags
// ❌ Old
experimental: {
turbopack: true,
ppr: true,
reactCompiler: true
}
// ✅ New
cacheComponents: true,
reactCompiler: true2. Update middleware → proxy
mv middleware.ts app/proxy.ts // Update exports
// ❌ Old: export default function middleware()
// ✅ New: export function proxy()3. Remove deprecated configs
// Remove these entirely
delete config.serverRuntimeConfig
delete config.publicRuntimeConfig4. Update ESLint
// package.json - remove from build
{
"scripts": {
"build": "next build", // No longer runs lint
"lint": "eslint ." // Run separately
}
}5. Fix Turbopack-incompatible imports
// ❌ Old: import 'bootstrap/scss/bootstrap.scss'
// ✅ New: import 'bootstrap/scss/bootstrap.scss' (remove tilde if using ~)---
Workflow 3: Execution & Data Patterns
Pattern 1: Basic Server Component
// app/blog/page.tsx
export default async function BlogPage() {
// Fetch runs on server
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return (
<div>
<h1>Blog Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}Benefits:
- Zero client JavaScript
- SEO-friendly
- Direct database access possible
- Automatic streaming
Pattern 2: Cache Components
import { cacheLife, cacheTag } from 'next/cache'
export default async function BlogPage() {
'use cache'
cacheLife('hours')
cacheTag('blog-posts')
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return <PostList posts={posts} />
}Cache Lifecycle Profiles:
// Built-in profiles
cacheLife('seconds') // 1 second
cacheLife('minutes') // 1 minute
cacheLife('hours') // 1 hour
cacheLife('days') // 1 day
cacheLife('weeks') // 1 week
cacheLife('max') // 1 year
// Custom profiles (define in next.config.ts)
cacheLife('product-catalog')Pattern 3: Client Components
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
)
}When to use `'use client'`:
- Need React hooks (
useState,useEffect, etc.) - Browser APIs (localStorage, window, etc.)
- Event handlers
- Third-party libraries requiring client-side
Keep server-side when:
- No interactivity needed
- SEO important
- Reduce bundle size
- Direct data fetching
Pattern 4: Streaming with Suspense
import { Suspense } from 'react'
export default function DashboardPage() {
return (
<div className="dashboard">
<h1>Analytics Dashboard</h1>
<div className="grid">
<Suspense fallback={<CardSkeleton />}>
<RevenueCard />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<UsersCard />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<ActivityCard />
</Suspense>
</div>
</div>
)
}
async function RevenueCard() {
const revenue = await db.analytics.getRevenue()
return (
<div className="card">
<h2>Revenue</h2>
<p>${revenue}</p>
</div>
)
}
async function UsersCard() {
const users = await db.analytics.getActiveUsers()
return (
<div className="card">
<h2>Active Users</h2>
<p>{users}</p>
</div>
)
}
async function ActivityCard() {
const activity = await db.analytics.getRecentActivity()
return (
<div className="card">
<h2>Recent Activity</h2>
<ul>
{activity.map(item => (
<li key={item.id}>{item.description}</li>
))}
</ul>
</div>
)
}
function CardSkeleton() {
return <div className="card skeleton animate-pulse" />
}Benefits:
- Sections load independently
- Fast initial paint
- No loading waterfalls
- Progressive enhancement
Pattern 5: Parallel Data Fetching
export default async function ArtistPage({ params }) {
const { id } = await params
// Start fetches in parallel
const artistPromise = fetch(`/api/artists/${id}`).then(r => r.json())
const albumsPromise = fetch(`/api/artists/${id}/albums`).then(r => r.json())
const toursPromise = fetch(`/api/artists/${id}/tours`).then(r => r.json())
// Wait for all
const [artist, albums, tours] = await Promise.all([
artistPromise,
albumsPromise,
toursPromise
])
return (
<div>
<h1>{artist.name}</h1>
<AlbumList albums={albums} />
<TourDates tours={tours} />
</div>
)
}Pattern 6: Route States
// app/blog/error.tsx
'use client'
export default function BlogError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div className="error-container">
<h2>Failed to load blog posts</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
)
}// app/blog/loading.tsx
export default function BlogLoading() {
return (
<div className="skeleton">
<div className="skeleton-title" />
<div className="skeleton-text" />
<div className="skeleton-text" />
</div>
)
}// app/blog/not-found.tsx
export default function BlogNotFound() {
return (
<div>
<h2>Blog post not found</h2>
<a href="/blog">Return to blog</a>
</div>
)
}---
Advanced Patterns
Cache Components with Custom Lifecycle
// next.config.ts
const nextConfig: NextConfig = {
cacheComponents: true,
cacheLife: {
'product-catalog': {
stale: 3600, // Serve stale for 1 hour
revalidate: 7200, // Revalidate in background after 2 hours
expire: 86400 // Hard expire after 24 hours
},
},
}// app/products/page.tsx
import { cacheLife } from 'next/cache'
export default async function ProductsPage() {
'use cache'
cacheLife('product-catalog')
const products = await db.products.findMany()
return <ProductGrid products={products} />
}Cache Tags with Server Actions
// app/blog/page.tsx
import { cacheLife, cacheTag } from 'next/cache'
export default async function BlogList() {
'use cache'
cacheLife('hours')
cacheTag('blog-posts')
const posts = await db.posts.findMany()
return <PostList posts={posts} />
}// app/actions.ts
'use server'
import { updateTag } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
await db.posts.create({ title, content })
// Immediately invalidate cache
updateTag('blog-posts')
redirect(`/blog/${newPost.slug}`)
}Proxy with Authentication
// app/proxy.ts
import { NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
const session = request.cookies.get('session')
const { pathname } = request.nextUrl
// Protect dashboard routes
if (pathname.startsWith('/dashboard')) {
if (!session) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
// Redirect logged-in users away from auth pages
if (pathname.startsWith('/login') && session) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/login']
}---
Common Patterns Summary
| Pattern | Use When | Example |
|---|---|---|
| Server Component | Default, SEO, zero JS | Blog posts, product listings |
| Client Component | Interactivity needed | Forms, modals, interactive widgets |
| Cache Component | Semi-static data | Product catalogs, blog archives |
| Suspense Streaming | Multiple data sources | Dashboards, analytics pages |
| Proxy | Auth, redirects | Login checks, route guards |
| Server Actions | Form submissions | Create/update/delete operations |
---
Decision Tree
Need interactivity?
├─ Yes → Client Component ('use client')
└─ No → Server Component
│
├─ Data rarely changes?
│ └─ Yes → Cache Component ('use cache')
│
├─ Multiple data sources?
│ └─ Yes → Suspense boundaries
│
└─ Need auth check?
└─ Yes → Add proxy.tsNext.js 16: Complete Deep-Dive Guide
Zero to Hero Learning + Migration Guide
Based on official Next.js documentation (https://nextjs.org/docs) and GitHub repository (https://github.com/vercel/next.js)
---
Executive Summary
What is Next.js 16's Headline Feature?
Turbopack as Default Bundler + Cache Components Architecture
Next.js 16 represents a fundamental shift in how Next.js handles bundling and caching:
1. Turbopack (Stable) - Now the default bundler replacing Webpack with 2-5× faster production builds and up to 10× faster Fast Refresh 2. Cache Components - A revolutionary opt-in caching model using the "use cache" directive that replaces implicit caching with explicit, developer-controlled caching 3. `proxy.ts` Convention - Replaces middleware.ts to clarify the network boundary and routing focus
Why Was It Introduced?
- Performance: Turbopack dramatically improves build times and development experience
- Explicit Caching: The old implicit caching model confused developers about when data would be cached vs. dynamic
- Better DX: More predictable, opt-in behavior with clearer mental models
- React 19.2 Support: Leverages newest React features (View Transitions,
useEffectEvent, Activity components)
Who Benefits / Who Gets Impacted?
Benefits:
- All developers: Faster builds and refresh times out of the box
- Large projects: Massive performance gains with Turbopack file system caching
- Teams: More predictable caching behavior reduces production surprises
Impacted (Breaking Changes):
- Existing middleware users: Must migrate to
proxy.ts - Projects with custom webpack: Must opt-out explicitly or migrate
- Async Request API users: Must use
awaitforparams,searchParams,cookies(),headers() - PPR users: New programming model via Cache Components
---
1. Installation & Setup (The Modern Way)
System Requirements
| Requirement | Version |
|---|---|
| Node.js | 20.9.0+ (LTS) - Node 18 no longer supported |
| TypeScript | 5.1.0+ |
| Browsers | Chrome 111+, Edge 111+, Firefox 111+, Safari 16.4+ |
Quick Start
# Automated upgrade with codemods
npx @next/codemod@canary upgrade latest
# Or manual upgrade
npm install next@latest react@latest react-dom@latest
# Start a new project
npx create-next-app@latest my-appNew Installation Experience
The create-next-app has been simplified:
npx create-next-app@latestPrompts:
What is your project named? my-app
Would you like to use the recommended Next.js defaults?
✓ Yes, use recommended defaults - TypeScript, ESLint, Tailwind CSS, App Router, Turbopack
○ No, reuse previous settings
○ No, customize settingsRecommended defaults include:
- TypeScript-first configuration
- ESLint for code quality
- Tailwind CSS for styling
- App Router (not Pages Router)
- Turbopack bundler
- Import alias
@/*
Minimal Working Setup
If installing manually:
npm install next@latest react@latest react-dom@latestpackage.json:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
"lint:fix": "eslint --fix"
}
}Key changes:
- Turbopack is now default (no
--turbopackflag needed) next lintcommand removed - use ESLint directlynext buildno longer runs linter automatically
Project Structure
my-app/
├── app/ # App Router (required)
│ ├── layout.tsx # Root layout (required)
│ ├── page.tsx # Home page
│ ├── proxy.ts # Network boundary (replaces middleware.ts)
│ └── ...
├── public/ # Static assets
├── next.config.ts # Next.js configuration
├── package.json
└── tsconfig.json # TypeScript configMinimal `app/layout.tsx`:
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}Minimal `app/page.tsx`:
export default function Page() {
return <h1>Hello, Next.js 16!</h1>
}Configuration File
next.config.ts (New TypeScript support):
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
// Turbopack is now default
// turbopack: { /* options */ },
// Enable Cache Components (opt-in)
cacheComponents: true,
// React Compiler (opt-in)
reactCompiler: true,
}
export default nextConfigKey Configuration Changes
| Old (v15) | New (v16) |
|---|---|
experimental.turbopack | Top-level turbopack |
experimental.ppr | cacheComponents |
experimental.dynamicIO | cacheComponents |
middleware.ts | proxy.ts |
experimental.reactCompiler | reactCompiler (stable) |
---
2. Concept Deep Dive: Mental Models + Core Patterns
2.1 Core Mental Model
Next.js 16 Execution Model:
┌─────────────────────────────────────────────────────────┐
│ BUILD TIME │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Turbopack compiles & bundles │ │
│ │ - 2-5x faster than Webpack │ │
│ │ - File system caching (beta) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Prerendering (Partial Pre-Rendering) │ │
│ │ - Static shell generated │ │
│ │ - "use cache" content included in shell │ │
│ │ - Dynamic content marked for runtime │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ REQUEST TIME │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Static Shell (instant) │ │
│ │ - Pre-rendered HTML sent immediately │ │
│ │ - Includes cached components │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Dynamic Content (streamed) │ │
│ │ - Wrapped in <Suspense> │ │
│ │ - Fetched at request time │ │
│ │ - Streamed to client progressively │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘Key Primitives
1. Turbopack (Build Layer)
- Rust-based bundler (default in v16)
- Incremental compilation
- Built-in Fast Refresh
- File system caching (beta)
2. Server Components (Rendering Layer)
- Default for all components in
app/ - Run on server only
- Can access databases directly
- Zero JavaScript sent to client
3. Client Components (Interactivity Layer)
- Marked with
'use client' - Run on both server (pre-render) and client (hydration)
- Can use hooks, state, browser APIs
- JavaScript sent to client
4. Cache Components (Data Layer)
- Opt-in via
cacheComponents: trueconfig - Uses
'use cache'directive - Explicit caching control
- Works with Partial Pre-Rendering (PPR)
5. Proxy (Network Boundary)
- Replaces middleware
- Node.js runtime only
- Intercepts requests before routing
- Authentication, redirects, rewrites
2.2 Core Patterns
Pattern 1: Routing (File-System Based)
app/
├── layout.tsx → Applies to all routes
├── page.tsx → / route
├── about/
│ └── page.tsx → /about route
├── blog/
│ ├── layout.tsx → Nested layout for /blog/*
│ ├── page.tsx → /blog route
│ └── [slug]/
│ └── page.tsx → /blog/[slug] dynamic route
└── api/
└── users/
└── route.ts → /api/users API routePattern 2: Data Fetching (Server Components)
Old Way (Pages Router):
// pages/blog/index.tsx
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return { props: { posts } }
}
export default function Blog({ posts }) {
return <PostList posts={posts} />
}New Way (App Router + Next.js 16):
// app/blog/page.tsx
export default async function BlogPage() {
// Fetch directly in component
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return <PostList posts={posts} />
}With Cache Components:
// app/blog/page.tsx
import { cacheLife } from 'next/cache'
export default async function BlogPage() {
'use cache'
cacheLife('hours') // Cache for 1 hour
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return <PostList posts={posts} />
}Pattern 3: State & Interactivity (Client Components)
// app/ui/counter.tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
)
}Pattern 4: Styling (Tailwind CSS Default)
// app/page.tsx
export default function HomePage() {
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24">
<h1 className="text-4xl font-bold">Welcome to Next.js 16</h1>
<p className="mt-4 text-lg text-gray-600">
Built with Turbopack and Cache Components
</p>
</main>
)
}Pattern 5: Error Handling
// app/blog/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}Pattern 6: Loading States
// app/blog/loading.tsx
export default function Loading() {
return <div>Loading blog posts...</div>
}Pattern 7: Deployment (Vercel/Node)
# Build for production
npm run build
# Start production server
npm run startOutput directory structure:
.next/
├── cache/ # Build cache
├── server/ # Server-side code
├── static/ # Static assets
└── standalone/ # Self-contained deployment (if enabled)---
3. "Old Way vs New Way" Comparison
3.1 Routing
| Task | Before (Pages Router) | After (App Router + v16) |
|---|---|---|
| Create route | pages/about.tsx | app/about/page.tsx |
| Dynamic route | pages/blog/[slug].tsx | app/blog/[slug]/page.tsx |
| Nested layouts | Custom _app.tsx logic | app/layout.tsx + nested layout.tsx |
| API routes | pages/api/users.ts | app/api/users/route.ts |
| Middleware | middleware.ts (Edge) | proxy.ts (Node.js) |
3.2 Data Fetching
| Task | Pages Router (v15) | App Router (v16) |
|---|---|---|
| SSR | getServerSideProps | async component + fetch(..., { cache: 'no-store' }) |
| SSG | getStaticProps | async component + fetch() (cached by default) |
| ISR | getStaticProps + revalidate | fetch(..., { next: { revalidate: 60 } }) |
| CSR | useEffect + fetch | Client Component + use hook or SWR/React Query |
| Caching | Implicit (automatic) | Explicit with 'use cache' directive |
3.3 Configuration
| Feature | Next.js 15 | Next.js 16 |
|---|---|---|
| Bundler | Webpack (default), opt-in Turbopack | Turbopack (default), opt-out Webpack |
| Turbopack config | experimental.turbopack | Top-level turbopack |
| PPR | experimental.ppr | cacheComponents |
| Dynamic IO | experimental.dynamicIO | cacheComponents |
| React Compiler | experimental.reactCompiler | reactCompiler (stable) |
| Linting | next lint | Direct ESLint (eslint command) |
3.4 Caching APIs
| API | Next.js 15 | Next.js 16 |
|---|---|---|
| revalidateTag | revalidateTag('tag') | revalidateTag('tag', 'max') + cacheLife profile |
| Update & revalidate | N/A | updateTag('tag') - read-your-writes |
| Refresh uncached | router.refresh() (client) | refresh() (Server Actions) |
| Cache functions | unstable_cacheLife, unstable_cacheTag | cacheLife, cacheTag (stable) |
3.5 Middleware vs Proxy
| Feature | middleware.ts (v15) | proxy.ts (v16) |
|---|---|---|
| Runtime | Edge | Node.js |
| Export name | middleware | proxy |
| Use case | Edge-optimized logic | Standard server-side interception |
| Status | Deprecated | Recommended |
---
4. Critical Migration Paths & Gotchas
4.1 Breaking Changes
1. Turbopack by Default
What changed: Turbopack is now the default bundler for next dev and next build.
What breaks: Projects with custom webpack configuration will fail to build.
Fix:
Option A: Use Turbopack (remove webpack config)
next build # Now uses TurbopackOption B: Opt-out to Webpack
next build --webpack// package.json
{
"scripts": {
"build": "next build --webpack"
}
}Option C: Migrate to Turbopack config
// next.config.ts
const nextConfig: NextConfig = {
turbopack: {
resolveAlias: {
fs: { browser: './empty.ts' }
}
}
}2. Async Request APIs
What changed: params, searchParams, cookies(), headers(), draftMode() must be awaited.
What breaks: Synchronous access to these APIs.
Before (v15):
export default function Page({ params, searchParams }) {
const { id } = params // ❌ No longer works
const query = searchParams.q // ❌ No longer works
return <div>{id}</div>
}After (v16):
export default async function Page({ params, searchParams }) {
const { id } = await params // ✅ Must await
const query = (await searchParams).q // ✅ Must await
return <div>{id}</div>
}Codemod available:
npx @next/codemod@canary async-request-api3. middleware.ts → proxy.ts
What changed: middleware.ts is deprecated, renamed to proxy.ts.
What breaks: Existing middleware files won't be recognized.
Fix:
# Rename file
mv middleware.ts proxy.ts// proxy.ts
import { NextRequest, NextResponse } from 'next/server'
// ❌ Old (deprecated)
export function middleware(request: NextRequest) {
return NextResponse.next()
}
// ✅ New (recommended)
export function proxy(request: NextRequest) {
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*']
}Note: proxy.ts runs on Node.js runtime only (Edge not supported).
4. `next/image` Local Images with Query Strings
What changed: Local images with query strings require explicit images.localPatterns configuration.
What breaks: <Image src="/assets/photo?v=1" /> without config.
Fix:
// ❌ Breaks in v16
<Image src="/assets/photo?v=1" alt="Photo" width={100} height={100} />// next.config.ts
const nextConfig: NextConfig = {
images: {
localPatterns: [
{
pathname: '/assets/**',
search: '?v=1',
},
],
},
}5. Partial Pre-Rendering (PPR) Flag Removed
What changed: experimental.ppr and route-level experimental_ppr removed.
What breaks: Existing PPR configurations.
Fix:
// ❌ Old (v15)
const nextConfig = {
experimental: {
ppr: true,
},
}
// ✅ New (v16)
const nextConfig = {
cacheComponents: true, // Replaces PPR
}6. Async `id` Parameter for Metadata Image Routes
What changed: id in metadata image routes is now a Promise<string>.
Before (v15):
// app/shop/[slug]/opengraph-image.js
export default function Image({ params, id }) {
const slug = params.slug // ❌
const imageId = id // ❌ string
// ...
}After (v16):
// app/shop/[slug]/opengraph-image.js
export default async function Image({ params, id }) {
const { slug } = await params // ✅ await params
const imageId = await id // ✅ Promise<string>
// ...
}4.2 Pitfalls (Common Mistakes)
Pitfall 1: Forgetting to Wrap Dynamic Content in Suspense
Symptom: Build error: Uncached data was accessed outside of <Suspense>
Cause: With cacheComponents enabled, all dynamic content must be wrapped in <Suspense> or cached with 'use cache'.
Fix:
// ❌ Breaks with cacheComponents
export default async function Page() {
const data = await fetch('https://api.example.com/data')
return <div>{data.title}</div>
}// ✅ Wrap in Suspense
import { Suspense } from 'react'
export default function Page() {
return (
<Suspense fallback={<div>Loading...</div>}>
<DynamicContent />
</Suspense>
)
}
async function DynamicContent() {
const data = await fetch('https://api.example.com/data')
const json = await data.json()
return <div>{json.title}</div>
}Pitfall 2: Using use cache with Runtime Data
Symptom: Runtime data (cookies, headers) not available in cached scope.
Cause: 'use cache' cannot access request-specific data.
Fix:
// ❌ Won't work
export default async function Page() {
'use cache'
const session = (await cookies()).get('session') // ❌ Error
return <div>{session}</div>
}// ✅ Extract runtime data first, pass to cached component
import { cookies } from 'next/headers'
export default async function Page() {
const session = (await cookies()).get('session')?.value
return <CachedContent sessionId={session} />
}
async function CachedContent({ sessionId }: { sessionId: string }) {
'use cache'
const data = await fetchUserData(sessionId)
return <div>{data}</div>
}Pitfall 3: Misconfigured revalidateTag in v16
Symptom: revalidateTag doesn't work as expected.
Cause: v16 requires a cacheLife profile as the second argument.
Fix:
// ❌ Old API (deprecated)
revalidateTag('posts')
// ✅ New API (v16)
revalidateTag('posts', 'max') // Stale-while-revalidate
// Or use updateTag for read-your-writes
import { updateTag } from 'next/cache'
updateTag('posts') // Immediate refreshPitfall 4: Webpack Config Found (But Not Defined)
Symptom: Build fails saying webpack config found, but you didn't define one.
Cause: A plugin is adding a webpack configuration.
Fix:
- Check
next.config.jsfor plugins that might inject webpack config - Use
--turbopackexplicitly or migrate the plugin
Pitfall 5: Sass Imports with Tilde (~) Prefix
Symptom: Sass imports fail with Turbopack.
Cause: Turbopack doesn't support legacy tilde (~) prefix for node_modules.
Fix:
/* ❌ Old (Webpack) */
@import '~bootstrap/dist/css/bootstrap.min.css';
/* ✅ New (Turbopack) */
@import 'bootstrap/dist/css/bootstrap.min.css';Or use resolveAlias:
// next.config.ts
const nextConfig: NextConfig = {
turbopack: {
resolveAlias: {
'~*': '*',
},
},
}4.3 Deprecations
| Deprecated | Replacement | Timeline |
|---|---|---|
middleware.ts | proxy.ts | Remove in future major |
next/legacy/image | next/image | Remove in future major |
images.domains | images.remotePatterns | Remove in future major |
revalidateTag(tag) | revalidateTag(tag, profile) or updateTag(tag) | Breaking in v16 |
serverRuntimeConfig, publicRuntimeConfig | Environment variables | Removed in v16 |
next lint command | Direct ESLint | Removed in v16 |
experimental.ppr | cacheComponents | Removed in v16 |
4.4 Compatibility Notes
Runtime Requirements:
- Node.js: 20.9.0+ (LTS)
- TypeScript: 5.1.0+
- React: 19.2+ (App Router uses React Canary)
Tooling:
- ESLint: v8+ (v10 will drop legacy config)
- Sass: Modern API via
sass-loaderv16
Deployment:
- Vercel: Fully supported
- Node.js: Fully supported
- Docker: Fully supported (use
standaloneoutput) - Edge Runtime: Limited (not supported in
proxy.ts)
---
5. Feature Analysis: Basic → Advanced
5.1 Basic Usage
Hello World (Minimal Example)
// app/page.tsx
export default function HomePage() {
return <h1>Hello, Next.js 16!</h1>
}Basic Routing
app/
├── page.tsx → / route
├── about/
│ └── page.tsx → /about
└── blog/
├── page.tsx → /blog
└── [slug]/
└── page.tsx → /blog/[slug]Basic Data Fetching
// app/blog/page.tsx
export default async function BlogPage() {
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}Basic Styling (Tailwind)
// app/page.tsx
export default function HomePage() {
return (
<main className="container mx-auto p-4">
<h1 className="text-4xl font-bold">Welcome</h1>
<p className="text-gray-600 mt-2">Next.js 16 with Turbopack</p>
</main>
)
}Basic Error Handling
// app/error.tsx
'use client'
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}Basic Loading State
// app/loading.tsx
export default function Loading() {
return <div>Loading...</div>
}5.2 Intermediate Concepts
1. Composition: Server + Client Components
// app/dashboard/page.tsx (Server Component)
import { Suspense } from 'react'
import { getUser } from '@/lib/auth'
import DashboardClient from './dashboard-client'
export default async function DashboardPage() {
const user = await getUser() // Server-side only
return (
<div>
<h1>Welcome, {user.name}</h1>
<Suspense fallback={<div>Loading stats...</div>}>
<DashboardClient userId={user.id} />
</Suspense>
</div>
)
}// app/dashboard/dashboard-client.tsx (Client Component)
'use client'
import { useState, useEffect } from 'react'
export default function DashboardClient({ userId }) {
const [stats, setStats] = useState(null)
useEffect(() => {
fetch(`/api/stats/${userId}`)
.then(res => res.json())
.then(setStats)
}, [userId])
return stats ? <div>Stats: {stats.count}</div> : null
}2. Data Handling: Parallel Fetching
// app/artist/[username]/page.tsx
export default async function ArtistPage({ params }) {
const { username } = await params
// Initiate requests in parallel
const artistData = fetch(`https://api.example.com/artist/${username}`)
const albumsData = fetch(`https://api.example.com/artist/${username}/albums`)
// Wait for both
const [artist, albums] = await Promise.all([
artistData.then(r => r.json()),
albumsData.then(r => r.json()),
])
return (
<div>
<h1>{artist.name}</h1>
<ul>
{albums.map(album => (
<li key={album.id}>{album.title}</li>
))}
</ul>
</div>
)
}3. Forms & Actions (Server Actions)
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
await db.posts.create({ title, content })
revalidatePath('/blog')
}// app/blog/new/page.tsx
import { createPost } from '@/app/actions'
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="content" required />
<button type="submit">Create Post</button>
</form>
)
}4. Middleware → Proxy Pattern
// proxy.ts
import { NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
const token = request.cookies.get('auth-token')
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*']
}5. Testing (Basic E2E with Playwright)
// tests/home.spec.ts
import { test, expect } from '@playwright/test'
test('homepage loads correctly', async ({ page }) => {
await page.goto('http://localhost:3000')
await expect(page.locator('h1')).toContainText('Hello, Next.js 16!')
})5.3 Advanced Concepts
1. Advanced Caching: Cache Components with Tags
// app/blog/page.tsx
import { cacheLife, cacheTag } from 'next/cache'
export default async function BlogPage() {
'use cache'
cacheLife('hours')
cacheTag('blog-posts')
const posts = await db.posts.findMany()
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}// app/actions.ts
'use server'
import { updateTag } from 'next/cache'
export async function createPost(data: PostData) {
await db.posts.create(data)
// Immediately refresh blog page
updateTag('blog-posts')
}2. Advanced Caching: Custom `cacheLife` Profiles
// next.config.ts
const nextConfig: NextConfig = {
cacheComponents: true,
cacheLife: {
'product-catalog': {
stale: 3600, // 1 hour stale
revalidate: 7200, // 2 hours revalidate
expire: 86400, // 1 day expire
},
},
}// app/products/page.tsx
export default async function ProductsPage() {
'use cache'
cacheLife('product-catalog') // Use custom profile
const products = await db.products.findMany()
return <ProductGrid products={products} />
}3. Advanced Routing: Parallel Routes + Intercepting Routes
app/
├── @modal/ # Parallel route slot
│ └── (.)photo/
│ └── [id]/
│ └── page.tsx # Intercepted route
├── layout.tsx # Root layout with modal slot
├── photo/
│ └── [id]/
│ └── page.tsx # Full page route
└── page.tsx// app/layout.tsx
export default function RootLayout({ children, modal }) {
return (
<html>
<body>
{children}
{modal}
</body>
</html>
)
}4. Advanced Performance: Turbopack File System Caching
// next.config.ts
const nextConfig: NextConfig = {
experimental: {
turbopackFileSystemCacheForDev: true, // Beta feature
},
}Benefits:
- Dramatically faster startup on large projects
- Persistent cache between restarts
- Especially useful for monorepos
5. Advanced Rendering: Streaming with Suspense Composition
// app/dashboard/page.tsx
import { Suspense } from 'react'
export default function DashboardPage() {
return (
<div>
{/* Static content renders immediately */}
<h1>Dashboard</h1>
{/* Each section streams independently */}
<Suspense fallback={<SkeletonStats />}>
<Stats />
</Suspense>
<Suspense fallback={<SkeletonChart />}>
<Chart />
</Suspense>
<Suspense fallback={<SkeletonActivity />}>
<RecentActivity />
</Suspense>
</div>
)
}Key insight: Each <Suspense> boundary creates an independent streaming chunk, improving perceived performance.
6. Advanced Data Security: Taint API
// lib/data.ts
import { experimental_taintObjectReference } from 'react'
export async function getUser(id: string) {
const user = await db.users.findUnique({ where: { id } })
// Prevent accidentally passing sensitive data to Client Components
experimental_taintObjectReference(
'Do not pass user object to client',
user
)
return user
}7. Advanced Optimization: React Compiler
// next.config.ts
const nextConfig: NextConfig = {
reactCompiler: true, // Automatic memoization
}Install plugin:
npm install -D babel-plugin-react-compilerWhat it does:
- Automatically memoizes components
- Reduces unnecessary re-renders
- Zero manual
useMemo/useCallbackneeded
Tradeoff: Slower build times (uses Babel)
---
6. Robust Code Examples (Real-World)
Example 1: E-commerce Product Page (Server + Client + Caching)
Goal: Product page with static product info, cached reviews, and dynamic cart.
// app/products/[id]/page.tsx
import { Suspense } from 'react'
import { cacheLife, cacheTag } from 'next/cache'
import AddToCartButton from './add-to-cart-button'
// Main page component (Server)
export default async function ProductPage({ params }) {
const { id } = await params
// Fetch product info (static shell)
const product = await getProduct(id)
return (
<div>
<ProductInfo product={product} />
{/* Cached reviews included in static shell */}
<Suspense fallback={<div>Loading reviews...</div>}>
<ProductReviews productId={id} />
</Suspense>
{/* Dynamic cart button */}
<Suspense fallback={<div>Loading cart...</div>}>
<AddToCartButton productId={id} />
</Suspense>
</div>
)
}
// Cached reviews component
async function ProductReviews({ productId }) {
'use cache'
cacheLife('hours')
cacheTag(`product-${productId}-reviews`)
const reviews = await db.reviews.findMany({
where: { productId },
orderBy: { createdAt: 'desc' },
take: 5,
})
return (
<div>
<h2>Recent Reviews</h2>
{reviews.map(review => (
<div key={review.id}>
<p>{review.content}</p>
<p>Rating: {review.rating}/5</p>
</div>
))}
</div>
)
}
// Client component for cart interaction
// app/products/[id]/add-to-cart-button.tsx
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
export default function AddToCartButton({ productId }) {
const [loading, setLoading] = useState(false)
const router = useRouter()
async function handleAddToCart() {
setLoading(true)
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId }),
})
setLoading(false)
router.refresh() // Refresh server data
}
return (
<button onClick={handleAddToCart} disabled={loading}>
{loading ? 'Adding...' : 'Add to Cart'}
</button>
)
}Why this works:
- Product info pre-rendered (instant)
- Reviews cached (fast, but fresh within 1 hour)
- Cart button uses client state (interactive)
- Each section streams independently
---
Example 2: Blog with Forms + Server Actions + Revalidation
Goal: Blog with post creation, immediate updates, and proper caching.
// app/blog/page.tsx
import { cacheLife, cacheTag } from 'next/cache'
import Link from 'next/link'
export default async function BlogPage() {
'use cache'
cacheLife('hours')
cacheTag('blog-posts')
const posts = await db.posts.findMany({
orderBy: { createdAt: 'desc' },
})
return (
<div>
<h1>Blog</h1>
<Link href="/blog/new">Create New Post</Link>
<ul>
{posts.map(post => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>
{post.title}
</Link>
</li>
))}
</ul>
</div>
)
}
// app/blog/new/page.tsx
import { createPost } from '@/app/actions'
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" required placeholder="Title" />
<textarea name="content" required placeholder="Content" />
<button type="submit">Publish</button>
</form>
)
}
// app/actions.ts
'use server'
import { updateTag } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
const slug = generateSlug(title)
await db.posts.create({
data: { title, content, slug },
})
// Immediately update blog page cache
updateTag('blog-posts')
// Redirect to new post
redirect(`/blog/${slug}`)
}
function generateSlug(title: string) {
return title.toLowerCase().replace(/\s+/g, '-')
}Why this works:
- Blog list cached for fast loads
- Server Action creates post
updateTagimmediately refreshes cache- User sees their new post instantly (read-your-writes)
---
Example 3: Auth + Protected Routes (Proxy + Sessions)
Goal: Authentication flow with protected dashboard routes.
// proxy.ts
import { NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl
// Check if route is protected
if (pathname.startsWith('/dashboard')) {
const session = await getSession(request)
if (!session) {
// Redirect to login
return NextResponse.redirect(new URL('/login', request.url))
}
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*']
}
// lib/auth.ts
import { cookies } from 'next/headers'
import jwt from 'jsonwebtoken'
export async function getSession(request?: NextRequest) {
const cookieStore = request
? request.cookies
: await cookies()
const token = cookieStore.get('auth-token')?.value
if (!token) return null
try {
return jwt.verify(token, process.env.JWT_SECRET!)
} catch {
return null
}
}
// app/dashboard/page.tsx
import { getSession } from '@/lib/auth'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const session = await getSession()
if (!session) {
redirect('/login')
}
return (
<div>
<h1>Welcome, {session.user.name}</h1>
</div>
)
}Why this works:
proxy.tsintercepts requests before routing- Server-side session check
- Redirect unauthorized users
- Dashboard page gets session data
---
7. Practical "Use This Today" Checklist
✅ Must-Do Setup Choices
- [ ] Upgrade to Node.js 20.9+ (20 LTS)
- [ ] Use `create-next-app@latest` for new projects
- [ ] Enable TypeScript (recommended defaults)
- [ ] Use Tailwind CSS (built-in, optimized)
- [ ] Keep Turbopack as default (unless custom webpack required)
- [ ] Run automated codemod for migrations:
npx @next/codemod@canary upgrade latest✅ Recommended Conventions
- [ ] Use App Router (not Pages Router)
- [ ] Keep Server Components as default (mark Client with
'use client') - [ ] Wrap dynamic content in `<Suspense>` when using
cacheComponents - [ ] Use `proxy.ts` instead of
middleware.ts - [ ] Adopt ESLint directly (not
next lint) - [ ] Use `'use cache'` explicitly for cacheable content
- [ ] Enable `cacheComponents: true` for PPR benefits
- [ ] Use `updateTag` for immediate updates,
revalidateTagfor eventual consistency
❌ Anti-Patterns to Avoid
- [ ] ❌ Don't use synchronous `params`/`searchParams` (must await)
- [ ] ❌ Don't mix `'use cache'` with `cookies()`/`headers()`
- [ ] ❌ Don't forget `<Suspense>` around dynamic content (with
cacheComponents) - [ ] ❌ Don't use `revalidateTag` without `cacheLife` profile (v16 requirement)
- [ ] ❌ Don't use tilde (~) in Sass imports with Turbopack
- [ ] ❌ Don't use Edge runtime in `proxy.ts` (Node.js only)
- [ ] ❌ Don't keep webpack config without explicit opt-out
✅ Migration Must-Check Items (Upgrading)
If migrating from v15:
- [ ] Rename `middleware.ts` → `proxy.ts`
- [ ] Update function name `middleware` → `proxy`
- [ ] Add `await` to `params`, `searchParams`, `cookies()`, `headers()`
- [ ] Replace `experimental.turbopack` → `turbopack` in config
- [ ] Replace `experimental.ppr` → `cacheComponents`
- [ ] Update `revalidateTag` calls to include
cacheLifeprofile - [ ] Replace `next lint` → `eslint` in package.json
- [ ] Add `images.localPatterns` if using query strings in local images
- [ ] Remove `serverRuntimeConfig`/`publicRuntimeConfig` (use env vars)
- [ ] Test with Turbopack (or opt-out with
--webpack)
If using PPR (experimental.ppr):
- [ ] Migrate to `cacheComponents: true`
- [ ] Review `'use cache'` directive usage
- [ ] Update route-level `experimental_ppr` exports (no longer supported)
- [ ] Test prerendering with new model
If using custom webpack:
- [ ] Decide: migrate to Turbopack or opt-out
- [ ] If migrating: use `turbopack.resolveAlias` for aliases
- [ ] If opting out: add `--webpack` to build script
---
8. Version History & Key Milestones
| Version | Date | Key Features |
|---|---|---|
| 16.0.10 | Dec 2025 | Current stable release |
| 16.0.0 | Oct 2025 | Turbopack stable, Cache Components, proxy.ts, React 19.2 |
| 15.5.0 | N/A | typegen for async params |
| 15.3.0 | N/A | 50%+ adoption of Turbopack in dev |
| 15.0.0 | N/A | Async Request APIs introduced (breaking) |
| 14.3.0-canary.77 | N/A | Next.js 14 Canary with PPR experiments |
---
9. Additional Resources
Official Documentation
- Next.js 16 Docs: https://nextjs.org/docs
- Next.js 16 Blog Post: https://nextjs.org/blog/next-16
- Next.js GitHub: https://github.com/vercel/next.js
- Upgrade Guide: https://nextjs.org/docs/app/guides/upgrading/version-16
Community & Support
- GitHub Discussions: https://github.com/vercel/next.js/discussions
- Discord: https://nextjs.org/discord
- X (Twitter): https://x.com/nextjs
- Reddit: https://www.reddit.com/r/nextjs
Learning Resources
- React Foundations: https://nextjs.org/learn/react-foundations
- Next.js Foundations: https://nextjs.org/learn/dashboard-app
- Next.js Conf 2025: https://nextjs.org/conf
Tools & Extensions
- VS Code Extension: TypeScript plugin built-in
- ESLint Plugin:
@next/eslint-plugin-next - React DevTools: For inspecting component trees
- Vercel Analytics: For performance monitoring
---
10. Final Notes & Best Practices
Performance Optimization Principles
1. Leverage Turbopack's speed - Don't opt-out unless necessary 2. Use `'use cache'` strategically - Cache stable data, stream dynamic 3. Wrap dynamic content in `<Suspense>` - Enable progressive rendering 4. Parallelize data fetching - Use Promise.all for independent requests 5. Minimize Client Components - Keep JavaScript bundle small 6. Enable Turbopack file system caching - For large projects
Security Best Practices
1. Use `server-only` package - Prevent server code in client bundles 2. Use Taint API - Mark sensitive objects server-only 3. Prefix public env vars - NEXT_PUBLIC_ for client-accessible vars 4. Validate in Server Actions - Never trust client input 5. Use `proxy.ts` for auth - Protect routes at network boundary
Deployment Best Practices
1. Use Vercel - Optimized for Next.js (automatic) 2. Enable standalone output - For Docker deployments 3. Monitor build times - With Turbopack, should be 2-5× faster 4. Use CDN - For static assets (public/ folder) 5. Configure caching - cacheLife profiles for optimal performance
---
Conclusion
Next.js 16 represents a major evolution with Turbopack as the default bundler and Cache Components providing explicit, opt-in caching. The migration requires careful attention to breaking changes (especially async params and proxy.ts), but the performance gains and improved developer experience make it worthwhile.
Key Takeaways: 1. Turbopack is 2-5× faster - use it 2. cacheComponents replaces PPR - opt-in for best performance 3. proxy.ts replaces middleware.ts - clearer boundaries 4. Async APIs everywhere - await params, searchParams, etc. 5. Explicit caching - use 'use cache' directive
Start by upgrading with the automated codemod, then incrementally adopt Cache Components for optimal performance.
---
Document Version: 1.0 Last Updated: December 13, 2025 Based On: Next.js 16.0.10, React 19.2, Official Documentation Generated By: Next.js 16 Research Task using exa-code, octocode, and official docs
Next.js 16 Advanced Patterns & Blueprints
Source: NEXTJS_16_COMPLETE_GUIDE.md (sections 5-6, 10)
1. Cache Components Patterns
Cache + Tags
import { cacheLife, cacheTag } from 'next/cache'
export default async function BlogPage() {
'use cache'
cacheLife('hours')
cacheTag('blog-posts')
const posts = await db.posts.findMany({ orderBy: { createdAt: 'desc' } })
return <PostList posts={posts} />
}'use server'
import { updateTag } from 'next/cache'
export async function createPost(data: PostData) {
await db.posts.create(data)
updateTag('blog-posts')
}Custom Cache Profiles
const nextConfig: NextConfig = {
cacheComponents: true,
cacheLife: {
'product-catalog': { stale: 3600, revalidate: 7200, expire: 86400 },
},
}export default async function ProductsPage() {
'use cache'
cacheLife('product-catalog')
const products = await db.products.findMany()
return <ProductGrid products={products} />
}2. Streaming & Suspense Composition
import { Suspense } from 'react'
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<SkeletonStats />}>
<Stats />
</Suspense>
<Suspense fallback={<SkeletonChart />}>
<Chart />
</Suspense>
<Suspense fallback={<SkeletonActivity />}>
<RecentActivity />
</Suspense>
</div>
)
}Guidelines
- Each
<Suspense>boundary streams independently. - Place
cache-backed sections outside fallback-critical areas to send static shell immediately.
3. Parallel Routes & Interception
app/
├── layout.tsx # Renders { children, modal }
├── page.tsx
├── photo/[id]/page.tsx # Full page
└── @modal/(.)photo/[id]/page.tsx # Intercepted modalUse for modal overlays, previews, or multi-pane dashboards.
4. Proxy & Auth Architecture
// proxy.ts
import { NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
export async function proxy(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/dashboard')) {
const session = await getSession(request)
if (!session) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
return NextResponse.next()
}
export const config = { matcher: ['/dashboard/:path*'] }// lib/auth.ts
import { cookies } from 'next/headers'
import jwt from 'jsonwebtoken'
export async function getSession(request?: NextRequest) {
const store = request ? request.cookies : await cookies()
const token = store.get('auth-token')?.value
if (!token) return null
try {
return jwt.verify(token, process.env.JWT_SECRET!)
} catch {
return null
}
}5. Server Actions & Forms
// app/blog/new/page.tsx
import { createPost } from '@/app/actions'
export default function NewPostPage() {
return (
<form action={createPost} className="space-y-4">
<input name="title" required />
<textarea name="content" required />
<button type="submit">Publish</button>
</form>
)
}// app/actions.ts
'use server'
import { redirect } from 'next/navigation'
import { updateTag } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
const slug = generateSlug(title)
await db.posts.create({ data: { title, content, slug } })
updateTag('blog-posts')
redirect(`/blog/${slug}`)
}6. Data Security & React Compiler
- Taint API:
experimental_taintObjectReference('message', object)to prevent leaking sensitive server objects into Client Components. - `server-only` package: Import to assert server-only modules.
- React Compiler: Enable via
reactCompiler: trueand installbabel-plugin-react-compiler; reduces manual memoization.
7. Performance Principles
1. Turbopack Everywhere: Keep default bundler, leverage incremental compilation and file system cache. 2. Parallel Fetching: Kick off independent fetches before await Promise.all. 3. Minimal Client Components: Prefer server by default; mark 'use client' only when interactivity needed. 4. Cache Lifecycle Alignment: Match cacheLife durations with business freshness requirements. 5. Monitor Build Metrics: Track next build timing to confirm 2-5× improvement; investigate regressions early.
8. Real-World Blueprints
E-commerce Product Page
- Server component loads product shell.
- Cached reviews (
cacheLife('hours'),cacheTag('product-:id-reviews')). - Client
AddToCartButtonposts to API, callsrouter.refresh(). - Multiple
<Suspense>fallbacks keep UX responsive.
Blog CMS
- Blog index cached with tags; Server Action updates tag + redirects.
- Parallel fetching for metadata & content.
- Proxy protects
/dashboardauthor routes.
SaaS Dashboard
- Proxy enforces auth.
- Layout uses parallel routes for modals.
- Streaming sections deliver stats, charts, and activity in parallel.
Use this file when implementing complex Next.js 16 experiences that go beyond basic bootstrapping.
Next.js 16 Migration Playbook
Source: NEXTJS_16_COMPLETE_GUIDE.md (sections 3-4, 7)
1. Old vs New Snapshot
| Area | v15 Behavior | v16 Behavior |
|---|---|---|
| Routing | pages/*, _app.tsx, middleware.ts | app/*, nested layout.tsx, proxy.ts |
| Data Fetching | getServerSideProps, implicit cache | Async Server Components, explicit caching via 'use cache', cacheLife, cacheTag |
| Bundler | Webpack default, Turbopack opt-in | Turbopack default, opt-out --webpack |
| Caching API | revalidateTag('tag') | cacheLife(), cacheTag(), revalidateTag('tag','profile'), updateTag('tag') |
| Async APIs | Sync params, searchParams, cookies() | All request APIs return Promises and must be awaited |
2. Critical Migration Steps
1. Adopt Turbopack
- Remove custom webpack config or opt-out with
next build --webpack. - For custom aliases/plugins, configure
turbopack.resolveAliasinnext.config.ts.
2. Await Request APIs
- Change all components to
export default async function Page({ params })and destructure viaconst { slug } = await params. - Run
npx @next/codemod@canary async-request-apifor automated updates.
3. Rename Middleware
- Rename
middleware.ts→proxy.ts, exportproxy, and keepconfig.matcherfor scoped interception. - Remember
proxy.tsruns on Node runtime only.
4. Enable Cache Components
- Replace
experimental.ppr/experimental.dynamicIOwithcacheComponents: true. - Wrap dynamic fetches inside
<Suspense>or use'use cache'blocks withcacheLife()/cacheTag().
5. Handle Image Query Strings
- Configure
images.localPatternsfor local assets using?v=or other search parameters.
6. Metadata Image Routes
- Await
paramsandidinsideopengraph-image.tsxandtwitter-image.tsxhandlers.
7. Clean Deprecated APIs
- Remove
serverRuntimeConfig,publicRuntimeConfig,next lint,next/legacy/image, tilde Sass imports.
3. Pitfalls & Solutions
| Pitfall | Symptom | Fix |
|---|---|---|
Missing <Suspense> | Error: "Uncached data accessed outside of <Suspense>" | Wrap dynamic content inside <Suspense> when cacheComponents enabled |
'use cache' with runtime data | Cookies/headers unavailable | Read request data first, pass as props into cached component |
revalidateTag without profile | No cache refresh | Call revalidateTag('tag', 'max') or updateTag('tag') |
| Webpack config detected | Turbopack build fails | Remove plugin or opt-out to Webpack |
| Tilde Sass imports | Import error | Use bare module imports or set turbopack.resolveAlias['~*'] = '*' |
4. Migration Checklists
Core Steps
- [ ] Upgrade Node.js to 20.9+
- [ ] Run
npx @next/codemod@canary upgrade latest - [ ] Move to App Router structure (
app/) - [ ] Rename
middleware.ts→proxy.ts - [ ] Await
params,searchParams,cookies(),headers() - [ ] Enable
cacheComponents: true&reactCompiler: trueas needed - [ ] Replace
next lintscripts with directeslint - [ ] Configure
images.localPatternswhen using query strings - [ ] Remove deprecated runtime configs and tilde imports
Cache Components Rollout
- [ ] Identify cacheable sections (blog list, product catalog)
- [ ] Add
'use cache'andcacheLife()directives - [ ] Tag data with
cacheTag()and callupdateTag()after mutations - [ ] Wrap dynamic subsections with
<Suspense>fallbacks - [ ] Ensure runtime data (cookies/headers) handled outside cached scope
Turbopack Validation
- [ ] Run
next devandnext buildwith Turbopack - [ ] Measure build/startup vs prior versions
- [ ] Enable
turbopackFileSystemCacheForDev(beta) for large repos - [ ] Verify third-party integrations (Sass, SVGR, etc.)
5. Reference Commands
# Upgrade entire project
npx @next/codemod@canary upgrade latest
# Fix async params
npx @next/codemod@canary async-request-api
# Opt-out to webpack when necessary
next build --webpack
# Rename middleware
mv middleware.ts proxy.tsUse this playbook during audits or migrations to Next.js 16 to ensure coverage of breaking changes and performance upgrades.
Next.js 16 Deep Reference
Source: NEXTJS_16_COMPLETE_GUIDE.md (lines 1-1614)
1. Installation & Tooling
- Runtime Requirements: Node.js 20.9+, TypeScript 5.1+, React 19.2+, modern browsers (Chrome/Edge/Firefox 111+, Safari 16.4+)
- Bootstrap Commands:
npx @next/codemod@canary upgrade latestnpm install next@latest react@latest react-dom@latestnpx create-next-app@latest my-app- Recommended `create-next-app` Defaults: TypeScript, ESLint, Tailwind CSS, App Router, Turbopack, alias
@/* - Scripts:
{
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
"lint:fix": "eslint --fix"
}- Project Skeleton:
app/(layout/page/proxy),public/,next.config.ts,package.json,tsconfig.json
2. Configuration Changes (v15 → v16)
| Feature | v15 | v16 |
|---|---|---|
| Bundler | Webpack default, opt-in Turbopack | Turbopack default, opt-out --webpack |
| PPR | experimental.ppr | cacheComponents |
| Dynamic IO | experimental.dynamicIO | cacheComponents |
| React Compiler | experimental.reactCompiler | reactCompiler |
| Middleware | middleware.ts (Edge) | proxy.ts (Node) |
| Linting | next lint command | Use ESLint directly |
TypeScript Config Example:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
reactCompiler: true,
}
export default nextConfig3. Old Way vs New Way
- Routing:
pages/*replaced byapp/*, nestedlayout.tsx, slots, parallel routes. - Data Fetching:
getServerSideProps/getStaticPropsreplaced by async Server Components with explicit caching via'use cache',cacheLife, and fetch options like{ next: { revalidate: 60 } }. - Async APIs:
params,searchParams,cookies(),headers(),draftMode()now return Promises; must await. - Caching:
revalidateTag(tag)nowrevalidateTag(tag, profile); newupdateTagfor read-your-writes;cacheTag,cacheLifestable. - Proxy vs Middleware:
proxy.tsintercepts requests on Node runtime;middleware.tsdeprecated.
4. Migration Steps & Gotchas
1. Turbopack by Default
- Remove webpack config or opt-out via
next build --webpackorturbopackoverrides.
2. Async Request Codemod
npx @next/codemod@canary async-request-api- Update component signatures to await
params,searchParams.
3. `middleware.ts` → `proxy.ts`
- Rename file, export
proxy, maintainconfig.matcher.
4. Image Query Strings
- Configure
images.localPatternsfor/assets/photo?v=1style imports.
5. PPR Flag Removal
- Replace
experimental.pprand route-level flags withcacheComponents: true.
6. Metadata Image Routes
idnowPromise<string>; await inside route handlers.
7. Deprecations
- Remove
serverRuntimeConfig,publicRuntimeConfig,next lint, tilde Sass imports.
Common Pitfalls
- Missing
<Suspense>when Cache Components enabled. - Using
'use cache'around runtime-only APIs (cookies/headers) without extracting first. - Calling
revalidateTag('tag')without profile argument. - Plugins injecting webpack config unexpectedly on Turbopack builds.
5. Core Patterns & Workflows
5.1 Server + Client Composition
// app/dashboard/page.tsx (Server)
import { Suspense } from 'react'
import DashboardClient from './dashboard-client'
export default async function DashboardPage() {
const user = await getUser()
return (
<div>
<h1>Welcome, {user.name}</h1>
<Suspense fallback={<div>Loading stats...</div>}>
<DashboardClient userId={user.id} />
</Suspense>
</div>
)
}// app/dashboard/dashboard-client.tsx (Client)
'use client'
import { useEffect, useState } from 'react'
export default function DashboardClient({ userId }) {
const [stats, setStats] = useState(null)
useEffect(() => {
fetch(`/api/stats/${userId}`).then(res => res.json()).then(setStats)
}, [userId])
return stats ? <div>Stats: {stats.count}</div> : null
}5.2 Parallel Data Fetching
const artist = fetch(`https://api.example.com/artist/${username}`)
const albums = fetch(`https://api.example.com/artist/${username}/albums`)
const [artistData, albumsData] = await Promise.all([
artist.then(r => r.json()),
albums.then(r => r.json()),
])5.3 Server Actions + Revalidation
// app/actions.ts
'use server'
import { updateTag } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
const slug = generateSlug(title)
await db.posts.create({ data: { title, content, slug } })
updateTag('blog-posts')
redirect(`/blog/${slug}`)
}5.4 Proxy Guarded Routes
// proxy.ts
import { NextRequest, NextResponse } from 'next/server'
export async function proxy(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/dashboard')) {
const token = request.cookies.get('auth-token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
return NextResponse.next()
}
export const config = { matcher: ['/dashboard/:path*'] }6. Advanced Topics
6.1 Cache Components
import { cacheLife, cacheTag } from 'next/cache'
export default async function BlogPage() {
'use cache'
cacheLife('hours')
cacheTag('blog-posts')
const posts = await db.posts.findMany({ orderBy: { createdAt: 'desc' } })
return <PostList posts={posts} />
}// app/actions.ts
'use server'
import { updateTag } from 'next/cache'
export async function createPost(data: PostData) {
await db.posts.create(data)
updateTag('blog-posts')
}6.2 Custom cacheLife Profiles
const nextConfig: NextConfig = {
cacheComponents: true,
cacheLife: {
'product-catalog': {
stale: 3600,
revalidate: 7200,
expire: 86400,
},
},
}6.3 Parallel Routes & Intercepting Modals
app/
├── @modal/(.)photo/[id]/page.tsx
├── photo/[id]/page.tsx
├── layout.tsx (renders { children, modal })
└── page.tsx6.4 Streaming Dashboard Example
<Suspense fallback={<SkeletonStats />}>
<Stats />
</Suspense>
<Suspense fallback={<SkeletonChart />}>
<Chart />
</Suspense>
<Suspense fallback={<SkeletonActivity />}>
<RecentActivity />
</Suspense>6.5 React Compiler
- Enable via
reactCompiler: trueinnext.config.ts - Install
babel-plugin-react-compiler - Reduces manual memoization; trade-off is slower builds due to Babel pass
6.6 Taint API for Sensitive Data
import { experimental_taintObjectReference } from 'react'
export async function getUser(id: string) {
const user = await db.users.findUnique({ where: { id } })
experimental_taintObjectReference('Server-only user object', user)
return user
}7. Deployment & Ops
- Vercel: First-class support; zero-config for
next build+next start - Standalone Output: Use
output: 'standalone'for Docker/Node deployments - Monitoring: Track
next buildtimes to ensure Turbopack retains 2-5× speedup - CDN Strategy: Serve
/publicassets via CDN, align Cache Component TTL with CDN cache headers - Turbopack File System Cache: (Beta)
experimental.turbopackFileSystemCacheForDev = truefor large repos
8. Checklists
Setup Checklist
- [ ] Upgrade Node.js to 20.9+
- [ ] Run
npx @next/codemod@canary upgrade latest - [ ] Use App Router + TypeScript defaults
- [ ] Keep Turbopack default (opt-out only if blocker)
- [ ] Configure ESLint scripts (
eslint,eslint --fix)
Migration Checklist
- [ ] Rename
middleware.ts→proxy.ts - [ ] Await
params,searchParams,cookies(),headers() - [ ] Replace
experimental.*flags with stable config keys - [ ] Update
revalidateTagcalls to include profile or useupdateTag - [ ] Configure
images.localPatternsfor query-string assets - [ ] Remove
serverRuntimeConfig/publicRuntimeConfig - [ ] Replace tilde Sass imports
Anti-Patterns to Avoid
- [ ] ❌ Using
'use cache'with runtime request data - [ ] ❌ Forgetting
<Suspense>when Cache Components enabled - [ ] ❌ Leaving webpack-specific plugins without opt-out
- [ ] ❌ Attempting to run
proxy.tson Edge runtime - [ ] ❌ Relying on
next lintcommand (removed)
9. Version Timeline
| Version | Date | Notes |
|---|---|---|
| 16.0.10 | Dec 2025 | Current stable |
| 16.0.0 | Oct 2025 | Turbopack default, Cache Components, proxy.ts |
| 15.5.0 | — | Async params typegen |
| 15.0.0 | — | Async Request APIs introduced |
Use this table to confirm behavior when diagnosing historical issues.
<#!
.SYNOPSIS
Bootstraps or upgrades a project to the Next.js 16 recommended stack.
.DESCRIPTION
Validates Node.js 20.9+, applies the official codemod (optional), installs latest next/react packages,
and runs create-next-app with TypeScript, ESLint, Tailwind, App Router, Turbopack, and @/* alias defaults.
.PARAMETER ProjectName
Target directory for create-next-app (defaults to "next16-app").
.PARAMETER SkipCodemod
Skip running the upgrade codemod (set when starting from scratch).
#>
param(
[string]$ProjectName = "next16-app",
[switch]$SkipCodemod
)
$ErrorActionPreference = "Stop"
function Require-Command {
param([string]$Name)
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
throw "Command '$Name' is required but not installed."
}
}
Require-Command node
Require-Command npm
Require-Command npx
$nodeVersion = (node -v).TrimStart('v')
try {
$parsed = [version]$nodeVersion
} catch {
throw "Unable to parse Node.js version '$nodeVersion'."
}
if ($parsed.Major -lt 20 -or ($parsed.Major -eq 20 -and $parsed.Minor -lt 9)) {
throw "Next.js 16 requires Node.js 20.9+. Detected v$nodeVersion."
}
if (-not $SkipCodemod) {
Write-Host "Running Next.js codemod upgrade..." -ForegroundColor Cyan
npx @next/codemod@canary upgrade latest
}
Write-Host "Installing latest Next.js + React packages..." -ForegroundColor Cyan
npm install next@latest react@latest react-dom@latest --save
Write-Host "Scaffolding project '$ProjectName' with Next.js 16 defaults..." -ForegroundColor Cyan
npx create-next-app@latest $ProjectName `
--ts `
--eslint `
--tailwind `
--app `
--turbopack `
--src-dir false `
--import-alias "@/*" `
--use-npm `
--yes
Write-Host "Next.js 16 project ready." -ForegroundColor Green
{
"schema_version": "2.0",
"meta": {
"generated_at": "2026-01-16T19:44:56.019Z",
"slug": "calel33-next-js-16-launchpad",
"source_url": "https://github.com/Calel33/my-flash-ui-app--1-/tree/main/skills/nextjs16-core",
"source_ref": "main",
"model": "claude",
"analysis_version": "3.0.0",
"source_type": "community",
"content_hash": "dce9eba4d69e23a99cdfdc2d90d8681c697334c45a7a23f799f6825368bd505c",
"tree_hash": "5eea26d00a99bbda84288d35f462e838b23b9ac294d388a91281d5ef3a705570"
},
"skill": {
"name": "next-js-16-launchpad",
"description": "Next.js 16 with Turbopack, Cache Components, and proxy.ts. Use for bootstrapping, migrating, and building with App Router and React 19.",
"summary": "Next.js 16 with Turbopack, Cache Components, and proxy.ts. Use for bootstrapping, migrating, and bui...",
"icon": "⚡",
"version": "1.1.0",
"author": "Calel33",
"license": "MIT",
"category": "documentation",
"tags": [
"nextjs",
"react",
"turbopack",
"app-router",
"web-dev"
],
"supported_tools": [
"claude",
"codex",
"claude-code"
],
"risk_factors": [
"external_commands",
"network"
]
},
"security_audit": {
"risk_level": "safe",
"is_blocked": false,
"safe_to_publish": true,
"summary": "All 849 static findings are false positives. The skill is a legitimate Next.js 16 documentation resource. External commands, network calls, and crypto references are all from markdown documentation showing code examples, not actual executable code with security implications.",
"risk_factor_evidence": [
{
"factor": "external_commands",
"evidence": [
{
"file": "SKILL.md",
"line_start": 33,
"line_end": 40
},
{
"file": "references/NEXTJS_16_COMPLETE_GUIDE.md",
"line_start": 54,
"line_end": 63
},
{
"file": "references/core-workflows.md",
"line_start": 10,
"line_end": 13
}
]
},
{
"factor": "network",
"evidence": [
{
"file": "SKILL.md",
"line_start": 93,
"line_end": 96
},
{
"file": "assets/app-router-starter/app/dashboard/page.tsx",
"line_start": 6,
"line_end": 9
}
]
}
],
"critical_findings": [],
"high_findings": [],
"medium_findings": [],
"low_findings": [],
"dangerous_patterns": [],
"files_scanned": 16,
"total_lines": 3648,
"audit_model": "claude",
"audited_at": "2026-01-16T19:44:56.019Z"
},
"content": {
"user_title": "Bootstrap Next.js 16 projects with Turbopack",
"value_statement": "Get started with Next.js 16 features including Turbopack, Cache Components, and proxy.ts. This reference includes migration guides, production patterns, and ready-to-use code examples for building modern React applications.",
"seo_keywords": [
"Next.js 16",
"Turbopack",
"Cache Components",
"React 19",
"App Router",
"proxy.ts",
"Claude",
"Codex",
"Claude Code",
"Next.js migration"
],
"actual_capabilities": [
"Create new Next.js 16 projects with Turbopack enabled by default",
"Migrate existing Next.js projects to version 16 with codemods",
"Implement Cache Components with use cache directive and cacheLife",
"Set up proxy.ts for authentication and request handling",
"Build Server Components and Client Components patterns",
"Configure streaming with Suspense for better UX"
],
"limitations": [
"Does not execute code or modify files directly",
"Does not include runtime dependencies or npm packages",
"Does not provide deployment infrastructure or CI/CD pipelines",
"Focused on App Router patterns, not Pages Router"
],
"use_cases": [
{
"target_user": "Frontend developers",
"title": "Quick start Next.js 16",
"description": "Set up a new Next.js 16 project with recommended defaults and Turbopack enabled."
},
{
"target_user": "Backend engineers",
"title": "Migrate to proxy.ts",
"description": "Move from middleware.ts to proxy.ts for Node.js runtime request handling."
},
{
"target_user": "Full-stack teams",
"title": "Production patterns",
"description": "Apply caching strategies, streaming, and server actions in production applications."
}
],
"prompt_templates": [
{
"title": "New project setup",
"scenario": "Starting Next.js 16 app",
"prompt": "Show me how to create a new Next.js 16 project with Turbopack and TypeScript using the recommended defaults."
},
{
"title": "Cache implementation",
"scenario": "Adding caching to components",
"prompt": "How do I use Cache Components with use cache and cacheLife in Next.js 16? Show me a complete example."
},
{
"title": "Migration guide",
"scenario": "Upgrading from Next.js 15",
"prompt": "What are the key changes when migrating from Next.js 15 to 16? Include async params, proxy.ts migration, and config updates."
},
{
"title": "Production patterns",
"scenario": "Building for production",
"prompt": "Show me production patterns for Next.js 16 including streaming with Suspense, cache tags, and authenticated proxy boundaries."
}
],
"output_examples": [
{
"input": "How do I create a new Next.js 16 project?",
"output": [
"Run: npx create-next-app@latest my-app",
"Recommended defaults: TypeScript, ESLint, Tailwind CSS, App Router, Turbopack",
"For existing projects: npx @next/codemod@canary upgrade latest"
]
},
{
"input": "What is use cache in Next.js 16?",
"output": [
"Cache Components use explicit caching with 'use cache' directive",
"Use cacheLife('hours') to set duration",
"Cannot mix with cookies() or headers() - extract before caching",
"Combine with cacheTag for invalidation"
]
},
{
"input": "How do I replace middleware.ts with proxy.ts?",
"output": [
"Rename middleware.ts to proxy.ts",
"Export proxy function instead of middleware object",
"Use Node.js runtime (not Edge)",
"Handle auth, redirects, and rewrites in the proxy function"
]
}
],
"best_practices": [
"Keep Turbopack enabled for 2-5x faster builds and faster development cycles",
"Extract cookies and headers before caching to avoid dynamic data in cached components",
"Use Suspense boundaries with Cache Components for progressive streaming"
],
"anti_patterns": [
"Mixing 'use cache' with cookies(), headers(), or other runtime APIs",
"Running proxy.ts on Edge runtime instead of Node.js",
"Missing Suspense boundaries when Cache Components are enabled"
],
"faq": [
{
"question": "Is Turbopack production ready?",
"answer": "Yes, Turbopack is stable and default in Next.js 16 with 2-5x faster builds."
},
{
"question": "How does use cache differ from PPR?",
"answer": "Cache Components replace experimental PPR with explicit opt-in caching via 'use cache'."
},
{
"question": "Can I use middleware.ts in Next.js 16?",
"answer": "No, middleware.ts is replaced by proxy.ts for Node.js runtime request handling."
},
{
"question": "Do I need to update async params?",
"answer": "Yes, params is now async in Next.js 16. Use await params in all page components."
},
{
"question": "What is the minimum Node.js version?",
"answer": "Next.js 16 requires Node.js 20.9.0 or higher (LTS versions only)."
},
{
"question": "How do I cache data with specific durations?",
"answer": "Use 'use cache' with cacheLife('seconds', 'minutes', 'hours', 'days') for fine-grained control."
}
]
},
"file_structure": [
{
"name": "assets",
"type": "dir",
"path": "assets",
"children": [
{
"name": "app-router-starter",
"type": "dir",
"path": "assets/app-router-starter",
"children": [
{
"name": "app",
"type": "dir",
"path": "assets/app-router-starter/app",
"children": [
{
"name": "dashboard",
"type": "dir",
"path": "assets/app-router-starter/app/dashboard"
},
{
"name": "layout.tsx",
"type": "file",
"path": "assets/app-router-starter/app/layout.tsx",
"lines": 30
},
{
"name": "page.tsx",
"type": "file",
"path": "assets/app-router-starter/app/page.tsx",
"lines": 50
},
{
"name": "proxy.ts",
"type": "file",
"path": "assets/app-router-starter/app/proxy.ts",
"lines": 19
}
]
},
{
"name": "next.config.ts",
"type": "file",
"path": "assets/app-router-starter/next.config.ts",
"lines": 17
},
{
"name": "README.md",
"type": "file",
"path": "assets/app-router-starter/README.md",
"lines": 11
}
]
}
]
},
{
"name": "references",
"type": "dir",
"path": "references",
"children": [
{
"name": "core-workflows.md",
"type": "file",
"path": "references/core-workflows.md",
"lines": 591
},
{
"name": "NEXTJS_16_COMPLETE_GUIDE.md",
"type": "file",
"path": "references/NEXTJS_16_COMPLETE_GUIDE.md",
"lines": 1614
},
{
"name": "nextjs16-advanced-patterns.md",
"type": "file",
"path": "references/nextjs16-advanced-patterns.md",
"lines": 191
},
{
"name": "nextjs16-migration-playbook.md",
"type": "file",
"path": "references/nextjs16-migration-playbook.md",
"lines": 89
},
{
"name": "nextjs16-reference.md",
"type": "file",
"path": "references/nextjs16-reference.md",
"lines": 291
}
]
},
{
"name": "scripts",
"type": "dir",
"path": "scripts",
"children": [
{
"name": "bootstrap-nextjs16.ps1",
"type": "file",
"path": "scripts/bootstrap-nextjs16.ps1",
"lines": 62
}
]
},
{
"name": "SKILL.md",
"type": "file",
"path": "SKILL.md",
"lines": 319
}
]
}
Related skills
FAQ
What version requirements does it assume?
Node.js 20.9.0+, TypeScript 5.1.0+, and React 19.2+.
Does it help migrate from Next.js 15?
Yes, it includes a v15 to v16 migration checklist covering async params, proxy.ts, and cacheComponents config.