Files
gh-jezweb-claude-skills-ski…/references/next-16-migration-guide.md
2025-11-30 08:25:04 +08:00

648 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Next.js 16 Migration Guide
**From**: Next.js 15.x
**To**: Next.js 16.0.0
**Last Updated**: 2025-10-24
---
## Table of Contents
1. [Overview](#overview)
2. [Breaking Changes](#breaking-changes)
3. [New Features](#new-features)
4. [Migration Steps](#migration-steps)
5. [Automated Migration](#automated-migration)
6. [Manual Migration](#manual-migration)
7. [Troubleshooting](#troubleshooting)
---
## Overview
Next.js 16 introduces significant changes:
- **Breaking Changes**: 6 major breaking changes
- **New Features**: Cache Components, updated caching APIs, React 19.2
- **Performance**: Turbopack stable, 25× faster builds
- **Migration Time**: ~1-2 hours for medium-sized apps
**Recommendation**: Use automated codemod first, then manually fix remaining issues.
---
## Breaking Changes
### 1. Async Route Parameters ⚠️
**What Changed**: `params`, `searchParams`, `cookies()`, `headers()`, `draftMode()` are now async.
**Before** (Next.js 15):
```typescript
export default function Page({ params, searchParams }) {
const slug = params.slug
const query = searchParams.q
}
```
**After** (Next.js 16):
```typescript
export default async function Page({ params, searchParams }) {
const { slug } = await params
const { q: query } = await searchParams
}
```
**TypeScript Types**:
```typescript
// Before
type PageProps = {
params: { slug: string }
searchParams: { q: string }
}
// After
type PageProps = {
params: Promise<{ slug: string }>
searchParams: Promise<{ q: string }>
}
```
**Fix**:
1. Add `async` to function
2. Add `await` before params/searchParams
3. Update TypeScript types to `Promise<>`
---
### 2. Middleware → Proxy ⚠️
**What Changed**: `middleware.ts` is deprecated. Use `proxy.ts` instead.
**Migration**:
```bash
# 1. Rename file
mv middleware.ts proxy.ts
# 2. Rename function in file
# middleware → proxy
```
**Before** (middleware.ts):
```typescript
export function middleware(request: NextRequest) {
return NextResponse.next()
}
```
**After** (proxy.ts):
```typescript
export function proxy(request: NextRequest) {
return NextResponse.next()
}
```
**Note**: `middleware.ts` still works in Next.js 16 but is deprecated.
---
### 3. Parallel Routes Require `default.js` ⚠️
**What Changed**: All parallel routes now REQUIRE explicit `default.js` files.
**Before** (Next.js 15):
```
app/
├── @modal/
│ └── login/
│ └── page.tsx
```
**After** (Next.js 16):
```
app/
├── @modal/
│ ├── login/
│ │ └── page.tsx
│ └── default.tsx ← REQUIRED
```
**default.tsx**:
```typescript
export default function ModalDefault() {
return null
}
```
**Fix**: Add `default.tsx` to every `@folder` in parallel routes.
---
### 4. Removed Features ⚠️
**Removed**:
- AMP support
- `next lint` command
- `serverRuntimeConfig` and `publicRuntimeConfig`
- `experimental.ppr` flag
- Automatic `scroll-behavior: smooth`
- Node.js 18 support
**Migration**:
**AMP**:
```typescript
// Before
export const config = { amp: true }
// After
// No direct replacement - use separate AMP pages or frameworks
```
**Linting**:
```bash
# Before
npm run lint
# After
npx eslint .
# or
npx biome lint .
```
**Runtime Config**:
```typescript
// Before
module.exports = {
serverRuntimeConfig: { secret: 'abc' },
publicRuntimeConfig: { apiUrl: 'https://api' },
}
// After
// Use environment variables
process.env.SECRET
process.env.NEXT_PUBLIC_API_URL
```
---
### 5. Version Requirements ⚠️
**Minimum Versions**:
- Node.js: 20.9+ (Node 18 removed)
- TypeScript: 5.1+
- React: 19.2+
- Browsers: Chrome 111+, Safari 16.4+, Firefox 109+
**Upgrade Node.js**:
```bash
# Check current version
node --version
# Upgrade (using nvm)
nvm install 20
nvm use 20
nvm alias default 20
# Verify
node --version # Should be 20.9+
```
---
### 6. Image Defaults Changed ⚠️
**What Changed**: `next/image` default settings changed.
| Setting | Next.js 15 | Next.js 16 |
|---------|------------|------------|
| TTL | 60s | 4 hours |
| imageSizes | 8 sizes | 5 sizes |
| qualities | 3 qualities | 1 quality (75) |
**Impact**: Images cache longer, fewer sizes generated.
**Revert** (if needed):
```typescript
// next.config.ts
const config = {
images: {
minimumCacheTTL: 60, // Revert to 60 seconds
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // Old sizes
},
}
```
---
## New Features
### 1. Cache Components ✨
**Opt-in caching** with `"use cache"` directive.
**Before** (Next.js 15 - implicit caching):
```typescript
// All Server Components cached by default
export async function MyComponent() {
const data = await fetch('/api/data')
return <div>{data.value}</div>
}
```
**After** (Next.js 16 - opt-in):
```typescript
// NOT cached by default
export async function MyComponent() {
const data = await fetch('/api/data')
return <div>{data.value}</div>
}
// Opt-in to caching
'use cache'
export async function CachedComponent() {
const data = await fetch('/api/data')
return <div>{data.value}</div>
}
```
**See**: `references/cache-components-guide.md`
---
### 2. Updated Caching APIs ✨
**`revalidateTag()` now requires 2 arguments**:
**Before**:
```typescript
revalidateTag('posts')
```
**After**:
```typescript
revalidateTag('posts', 'max') // Second argument required
```
**New APIs**:
- `updateTag()` - Immediate refresh (read-your-writes)
- `refresh()` - Refresh uncached data only
---
### 3. React 19.2 Integration ✨
**New React features**:
- View Transitions
- `useEffectEvent()` (experimental)
- React Compiler (stable)
**See**: `references/react-19-integration.md`
---
### 4. Turbopack Stable ✨
**Default bundler**: Turbopack is now stable and default.
**Metrics**:
- 25× faster production builds
- Up to 10× faster Fast Refresh
**Opt-out** (if incompatible):
```bash
npm run build -- --webpack
```
---
## Migration Steps
### Step 1: Prerequisites
1. **Backup your project**:
```bash
git commit -am "Pre-migration checkpoint"
```
2. **Check Node.js version**:
```bash
node --version # Should be 20.9+
```
3. **Update dependencies**:
```bash
npm install next@16 react@19.2 react-dom@19.2
```
---
### Step 2: Run Automated Codemod
```bash
npx @next/codemod@canary upgrade latest
```
**What it fixes**:
- ✅ Async params (adds `await`)
- ✅ Async searchParams
- ✅ Async cookies()
- ✅ Async headers()
- ✅ Updates TypeScript types
**What it does NOT fix**:
- ❌ middleware.ts → proxy.ts (manual)
- ❌ Parallel routes default.js (manual)
- ❌ Removed features (manual)
---
### Step 3: Manual Fixes
#### Fix 1: Migrate middleware.ts → proxy.ts
```bash
# Rename file
mv middleware.ts proxy.ts
# Update function name
# middleware → proxy
```
#### Fix 2: Add default.js to Parallel Routes
```bash
# For each @folder, create default.tsx
touch app/@modal/default.tsx
touch app/@feed/default.tsx
```
```typescript
// app/@modal/default.tsx
export default function ModalDefault() {
return null
}
```
#### Fix 3: Replace Removed Features
**AMP**: Remove AMP config or migrate to separate AMP implementation.
**Linting**: Update scripts in `package.json`:
```json
{
"scripts": {
"lint": "eslint ."
}
}
```
**Runtime Config**: Use environment variables.
---
### Step 4: Update Caching
**Migrate from implicit to explicit caching**:
1. Find Server Components with expensive operations
2. Add `"use cache"` directive
3. Update `revalidateTag()` calls to include `cacheLife`
**Example**:
```typescript
// Before
export async function ExpensiveComponent() {
const data = await fetch('/api/data') // Cached implicitly
return <div>{data.value}</div>
}
// After
'use cache'
export async function ExpensiveComponent() {
const data = await fetch('/api/data') // Cached explicitly
return <div>{data.value}</div>
}
```
---
### Step 5: Test
```bash
# Development
npm run dev
# Production build
npm run build
# Check for errors
npm run type-check
```
---
### Step 6: Update CI/CD
**Update Node.js version** in CI config:
**.github/workflows/ci.yml**:
```yaml
- uses: actions/setup-node@v4
with:
node-version: '20.9' # Update from 18
```
**Dockerfile**:
```dockerfile
FROM node:20.9-alpine # Update from node:18
```
---
## Automated Migration
**Codemod** (recommended):
```bash
npx @next/codemod@canary upgrade latest
```
**Options**:
- `--dry` - Preview changes without applying
- `--force` - Skip confirmation prompts
**What it migrates**:
1. ✅ Async params
2. ✅ Async searchParams
3. ✅ Async cookies()
4. ✅ Async headers()
5. ✅ TypeScript types
**Manual steps after codemod**:
1. Rename middleware.ts → proxy.ts
2. Add default.js to parallel routes
3. Replace removed features
4. Update caching patterns
---
## Manual Migration
If codemod fails or you prefer manual migration:
### 1. Async Params
**Find**:
```bash
grep -r "params\." app/
grep -r "searchParams\." app/
```
**Replace**:
```typescript
// Before
const slug = params.slug
// After
const { slug } = await params
```
### 2. Async Cookies/Headers
**Find**:
```bash
grep -r "cookies()" app/
grep -r "headers()" app/
```
**Replace**:
```typescript
// Before
const cookieStore = cookies()
// After
const cookieStore = await cookies()
```
### 3. TypeScript Types
**Find**: All `PageProps` types
**Replace**:
```typescript
// Before
type PageProps = {
params: { id: string }
searchParams: { q: string }
}
// After
type PageProps = {
params: Promise<{ id: string }>
searchParams: Promise<{ q: string }>
}
```
---
## Troubleshooting
### Error: `params` is a Promise
**Cause**: Not awaiting params in Next.js 16.
**Fix**:
```typescript
// ❌ Before
const id = params.id
// ✅ After
const { id } = await params
```
---
### Error: Parallel route missing `default.js`
**Cause**: Next.js 16 requires `default.js` for all parallel routes.
**Fix**: Create `default.tsx`:
```typescript
// app/@modal/default.tsx
export default function ModalDefault() {
return null
}
```
---
### Error: `revalidateTag` requires 2 arguments
**Cause**: `revalidateTag()` API changed in Next.js 16.
**Fix**:
```typescript
// ❌ Before
revalidateTag('posts')
// ✅ After
revalidateTag('posts', 'max')
```
---
### Error: Turbopack build failure
**Cause**: Turbopack is now default in Next.js 16.
**Fix**: Opt-out if incompatible:
```bash
npm run build -- --webpack
```
---
### Error: Node.js version too old
**Cause**: Next.js 16 requires Node.js 20.9+.
**Fix**: Upgrade Node.js:
```bash
nvm install 20
nvm use 20
nvm alias default 20
```
---
## Migration Checklist
- [ ] Backup project (git commit)
- [ ] Check Node.js version (20.9+)
- [ ] Update dependencies (`npm install next@16 react@19.2 react-dom@19.2`)
- [ ] Run codemod (`npx @next/codemod@canary upgrade latest`)
- [ ] Rename middleware.ts → proxy.ts
- [ ] Add default.js to parallel routes
- [ ] Remove AMP config (if used)
- [ ] Replace runtime config with env vars
- [ ] Update `revalidateTag()` calls (add `cacheLife`)
- [ ] Add `"use cache"` where needed
- [ ] Test dev server (`npm run dev`)
- [ ] Test production build (`npm run build`)
- [ ] Update CI/CD Node.js version
- [ ] Deploy to staging
- [ ] Deploy to production
---
## Resources
- **Next.js 16 Blog**: https://nextjs.org/blog/next-16
- **Codemod**: `npx @next/codemod@canary upgrade latest`
- **Templates**: See `templates/` directory
- **Common Errors**: See `references/top-errors.md`
---
**Migration Support**: jeremy@jezweb.net