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.
ā Next.js 16, Turbopack, Cache Components, proxy migration, App Router, React 19.2
ā Pages Router, Next.js ā¤15, generic React questions
| Tool | Version |
|---|---|
| Node.js | 20.9.0+ |
| TypeScript | 5.1.0+ |
| React | 19.2+ |
# 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@latest
Recommended: TypeScript, ESLint, Tailwind, App Router, Turbopack, @/* alias.
// 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>
}
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
reactCompiler: true,
}
export default nextConfig
| v15 | v16 |
|---|---|
experimental.turbopack |
Default |
experimental.ppr |
cacheComponents |
middleware.ts (Edge) |
proxy.ts (Node) |
Sync params |
await params |
export default async function BlogPage() {
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return <PostList posts={posts} />
}
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} />
}
'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>
)
}
// 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()
}
// 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')
}
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>
}
app/, zero client JS'use client', hooks, browser APIs'use cache' + cacheLife() for PPRproxy.ts for auth/rewrites/redirectsAsync Request APIs
npx @next/codemod@canary async-request-api
Update: const { slug } = await params
middleware.ts ā proxy.ts
proxyConfig updates
experimental.* flagscacheComponents, reactCompilerserverRuntimeConfig/publicRuntimeConfigCache Components
experimental.ppr with cacheComponents: true<Suspense>Images
images.localPatterns for query stringsSee references/nextjs16-migration-playbook.md for complete guide.
ā 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
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
// 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>
</>
)
}
// 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.
--webpack only if needed)Promise.all<Suspense> for streaming boundariesserver-only package + React Taint APIproxy.tsNEXT_PUBLIC_ prefixoutput: 'standalone'Version: 1.1.0 | Updated: 2025-12-27