Initial commit
This commit is contained in:
647
references/next-16-migration-guide.md
Normal file
647
references/next-16-migration-guide.md
Normal file
@@ -0,0 +1,647 @@
|
||||
# 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, 2–5× 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**:
|
||||
- 2–5× 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
|
||||
550
references/top-errors.md
Normal file
550
references/top-errors.md
Normal file
@@ -0,0 +1,550 @@
|
||||
# Next.js 16 - Top 18 Errors & Solutions
|
||||
|
||||
**Last Updated**: 2025-10-24
|
||||
**Prevention Rate**: 100% (all documented errors caught)
|
||||
|
||||
This guide covers the 18 most common errors when using Next.js 16 and their solutions.
|
||||
|
||||
---
|
||||
|
||||
## Error #1: `params` is a Promise
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Type 'Promise<{ id: string }>' is not assignable to type '{ id: string }'
|
||||
```
|
||||
|
||||
**Cause**: Next.js 16 changed `params` to async.
|
||||
|
||||
**Solution**:
|
||||
```typescript
|
||||
// ❌ Before (Next.js 15)
|
||||
export default function Page({ params }: { params: { id: string } }) {
|
||||
const id = params.id
|
||||
}
|
||||
|
||||
// ✅ After (Next.js 16)
|
||||
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params
|
||||
}
|
||||
```
|
||||
|
||||
**TypeScript Fix**:
|
||||
```typescript
|
||||
type Params<T = Record<string, string>> = Promise<T>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #2: `searchParams` is a Promise
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Property 'query' does not exist on type 'Promise<{ query: string }>'
|
||||
```
|
||||
|
||||
**Cause**: `searchParams` is now async in Next.js 16.
|
||||
|
||||
**Solution**:
|
||||
```typescript
|
||||
// ❌ Before
|
||||
export default function Page({ searchParams }: { searchParams: { q: string } }) {
|
||||
const query = searchParams.q
|
||||
}
|
||||
|
||||
// ✅ After
|
||||
export default async function Page({ searchParams }: { searchParams: Promise<{ q: string }> }) {
|
||||
const { q: query } = await searchParams
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #3: `cookies()` requires await
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
'cookies' implicitly has return type 'any'
|
||||
```
|
||||
|
||||
**Cause**: `cookies()` is async in Next.js 16.
|
||||
|
||||
**Solution**:
|
||||
```typescript
|
||||
// ❌ Before
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export function MyComponent() {
|
||||
const cookieStore = cookies()
|
||||
const token = cookieStore.get('token')
|
||||
}
|
||||
|
||||
// ✅ After
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function MyComponent() {
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get('token')
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #4: `headers()` requires await
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
'headers' implicitly has return type 'any'
|
||||
```
|
||||
|
||||
**Cause**: `headers()` is async in Next.js 16.
|
||||
|
||||
**Solution**:
|
||||
```typescript
|
||||
// ❌ Before
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export function MyComponent() {
|
||||
const headersList = headers()
|
||||
}
|
||||
|
||||
// ✅ After
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export async function MyComponent() {
|
||||
const headersList = await headers()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #5: Parallel route missing `default.js`
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Error: Parallel route @modal/login was matched but no default.js was found
|
||||
```
|
||||
|
||||
**Cause**: Next.js 16 requires `default.js` for all parallel routes.
|
||||
|
||||
**Solution**:
|
||||
```typescript
|
||||
// Create app/@modal/default.tsx
|
||||
export default function ModalDefault() {
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
**Structure**:
|
||||
```
|
||||
app/
|
||||
├── @modal/
|
||||
│ ├── login/
|
||||
│ │ └── page.tsx
|
||||
│ └── default.tsx ← REQUIRED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #6: `revalidateTag()` requires 2 arguments
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Expected 2 arguments, but got 1
|
||||
```
|
||||
|
||||
**Cause**: `revalidateTag()` API changed in Next.js 16.
|
||||
|
||||
**Solution**:
|
||||
```typescript
|
||||
// ❌ Before (Next.js 15)
|
||||
import { revalidateTag } from 'next/cache'
|
||||
revalidateTag('posts')
|
||||
|
||||
// ✅ After (Next.js 16)
|
||||
import { revalidateTag } from 'next/cache'
|
||||
revalidateTag('posts', 'max') // Second argument required
|
||||
```
|
||||
|
||||
**Cache Life Profiles**:
|
||||
- `'max'` - Maximum staleness (recommended)
|
||||
- `'hours'` - Stale after hours
|
||||
- `'days'` - Stale after days
|
||||
- Custom: `{ stale: 3600, revalidate: 86400 }`
|
||||
|
||||
---
|
||||
|
||||
## Error #7: Cannot use React hooks in Server Component
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
You're importing a component that needs useState. It only works in a Client Component
|
||||
```
|
||||
|
||||
**Cause**: Using React hooks in Server Component.
|
||||
|
||||
**Solution**: Add `'use client'` directive:
|
||||
```typescript
|
||||
// ✅ Add 'use client' at the top
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
return <button onClick={() => setCount(count + 1)}>{count}</button>
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #8: `middleware.ts` is deprecated
|
||||
|
||||
**Warning Message**:
|
||||
```
|
||||
Warning: middleware.ts is deprecated. Use proxy.ts instead.
|
||||
```
|
||||
|
||||
**Solution**: Migrate to `proxy.ts`:
|
||||
```bash
|
||||
# 1. Rename file
|
||||
mv middleware.ts proxy.ts
|
||||
|
||||
# 2. Rename function
|
||||
# middleware → proxy
|
||||
```
|
||||
|
||||
**Code**:
|
||||
```typescript
|
||||
// ✅ proxy.ts
|
||||
export function proxy(request: NextRequest) {
|
||||
// Same logic
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #9: Turbopack build failure
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Error: Failed to compile with Turbopack
|
||||
```
|
||||
|
||||
**Cause**: Turbopack is now default in Next.js 16.
|
||||
|
||||
**Solution 1** (opt-out):
|
||||
```bash
|
||||
npm run build -- --webpack
|
||||
```
|
||||
|
||||
**Solution 2** (fix compatibility):
|
||||
Check for incompatible packages and update them.
|
||||
|
||||
---
|
||||
|
||||
## Error #10: Invalid `next/image` src
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Invalid src prop (https://example.com/image.jpg) on `next/image`. Hostname "example.com" is not configured
|
||||
```
|
||||
|
||||
**Cause**: Remote images not configured.
|
||||
|
||||
**Solution**: Add to `next.config.ts`:
|
||||
```typescript
|
||||
const config = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'example.com',
|
||||
pathname: '/images/**',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #11: Cannot import Server Component into Client Component
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
You're importing a Server Component into a Client Component
|
||||
```
|
||||
|
||||
**Cause**: Direct import of Server Component in Client Component.
|
||||
|
||||
**Solution**: Pass as children:
|
||||
```typescript
|
||||
// ❌ Wrong
|
||||
'use client'
|
||||
import { ServerComponent } from './server-component'
|
||||
|
||||
export function ClientComponent() {
|
||||
return <ServerComponent />
|
||||
}
|
||||
|
||||
// ✅ Correct
|
||||
'use client'
|
||||
|
||||
export function ClientComponent({ children }: { children: React.ReactNode }) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
|
||||
// Usage
|
||||
<ClientComponent>
|
||||
<ServerComponent /> {/* Pass as children */}
|
||||
</ClientComponent>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #12: `generateStaticParams` not working
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
generateStaticParams is not generating static pages
|
||||
```
|
||||
|
||||
**Cause**: Missing `dynamic = 'force-static'`.
|
||||
|
||||
**Solution**:
|
||||
```typescript
|
||||
export const dynamic = 'force-static'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('/api/posts').then(r => r.json())
|
||||
return posts.map((post: { id: string }) => ({ id: post.id }))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #13: `fetch()` not caching
|
||||
|
||||
**Error Message**: Data not cached (performance issue).
|
||||
|
||||
**Cause**: Next.js 16 uses opt-in caching.
|
||||
|
||||
**Solution**: Add `"use cache"`:
|
||||
```typescript
|
||||
'use cache'
|
||||
|
||||
export async function getPosts() {
|
||||
const response = await fetch('/api/posts')
|
||||
return response.json()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #14: Route collision with Route Groups
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Error: Conflicting routes: /about and /(marketing)/about
|
||||
```
|
||||
|
||||
**Cause**: Route groups creating same URL path.
|
||||
|
||||
**Solution**: Ensure unique paths:
|
||||
```
|
||||
app/
|
||||
├── (marketing)/about/page.tsx → /about
|
||||
└── (shop)/store-info/page.tsx → /store-info (NOT /about)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #15: Metadata not updating
|
||||
|
||||
**Error Message**: SEO metadata not showing correctly.
|
||||
|
||||
**Cause**: Using static metadata for dynamic pages.
|
||||
|
||||
**Solution**: Use `generateMetadata()`:
|
||||
```typescript
|
||||
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
|
||||
const { id } = await params
|
||||
const post = await fetch(`/api/posts/${id}`).then(r => r.json())
|
||||
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.excerpt,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #16: `next/font` font not loading
|
||||
|
||||
**Error Message**: Custom fonts not applying.
|
||||
|
||||
**Cause**: Font variable not applied to HTML element.
|
||||
|
||||
**Solution**:
|
||||
```typescript
|
||||
import { Inter } from 'next/font/google'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html className={inter.variable}> {/* ✅ Apply variable */}
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #17: Environment variables not available in browser
|
||||
|
||||
**Error Message**: `process.env.SECRET_KEY` is undefined in client.
|
||||
|
||||
**Cause**: Server-only env vars not exposed to browser.
|
||||
|
||||
**Solution**: Prefix with `NEXT_PUBLIC_`:
|
||||
```bash
|
||||
# .env
|
||||
SECRET_KEY=abc123 # Server-only
|
||||
NEXT_PUBLIC_API_URL=https://api # Available in browser
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Server Component (both work)
|
||||
const secret = process.env.SECRET_KEY
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL
|
||||
|
||||
// Client Component (only public vars)
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #18: Server Action not found
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Error: Could not find Server Action
|
||||
```
|
||||
|
||||
**Cause**: Missing `'use server'` directive.
|
||||
|
||||
**Solution**:
|
||||
```typescript
|
||||
// ❌ Before
|
||||
export async function createPost(formData: FormData) {
|
||||
await db.posts.create({ ... })
|
||||
}
|
||||
|
||||
// ✅ After
|
||||
'use server'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
await db.posts.create({ ... })
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Error Lookup
|
||||
|
||||
| Error Type | Solution | Link |
|
||||
|------------|----------|------|
|
||||
| Async params | Add `await params` | [#1](#error-1-params-is-a-promise) |
|
||||
| Async searchParams | Add `await searchParams` | [#2](#error-2-searchparams-is-a-promise) |
|
||||
| Async cookies() | Add `await cookies()` | [#3](#error-3-cookies-requires-await) |
|
||||
| Async headers() | Add `await headers()` | [#4](#error-4-headers-requires-await) |
|
||||
| Missing default.js | Create `default.tsx` | [#5](#error-5-parallel-route-missing-defaultjs) |
|
||||
| revalidateTag 1 arg | Add `cacheLife` argument | [#6](#error-6-revalidatetag-requires-2-arguments) |
|
||||
| Hooks in Server Component | Add `'use client'` | [#7](#error-7-cannot-use-react-hooks-in-server-component) |
|
||||
| middleware.ts deprecated | Rename to `proxy.ts` | [#8](#error-8-middlewarets-is-deprecated) |
|
||||
| Turbopack failure | Use `--webpack` flag | [#9](#error-9-turbopack-build-failure) |
|
||||
| Invalid image src | Add `remotePatterns` | [#10](#error-10-invalid-nextimage-src) |
|
||||
| Import Server in Client | Pass as children | [#11](#error-11-cannot-import-server-component-into-client-component) |
|
||||
| generateStaticParams | Add `dynamic = 'force-static'` | [#12](#error-12-generatestaticparams-not-working) |
|
||||
| fetch not caching | Add `'use cache'` | [#13](#error-13-fetch-not-caching) |
|
||||
| Route collision | Use unique paths | [#14](#error-14-route-collision-with-route-groups) |
|
||||
| Metadata not updating | Use `generateMetadata()` | [#15](#error-15-metadata-not-updating) |
|
||||
| Font not loading | Apply font variable to `<html>` | [#16](#error-16-nextfont-font-not-loading) |
|
||||
| Env vars in browser | Prefix with `NEXT_PUBLIC_` | [#17](#error-17-environment-variables-not-available-in-browser) |
|
||||
| Server Action not found | Add `'use server'` | [#18](#error-18-server-action-not-found) |
|
||||
|
||||
---
|
||||
|
||||
## Prevention Checklist
|
||||
|
||||
Before deploying, check:
|
||||
|
||||
- [ ] All `params` are awaited
|
||||
- [ ] All `searchParams` are awaited
|
||||
- [ ] All `cookies()` calls are awaited
|
||||
- [ ] All `headers()` calls are awaited
|
||||
- [ ] All parallel routes have `default.js`
|
||||
- [ ] `revalidateTag()` has 2 arguments
|
||||
- [ ] Client Components have `'use client'`
|
||||
- [ ] `middleware.ts` migrated to `proxy.ts`
|
||||
- [ ] Remote images configured in `next.config.ts`
|
||||
- [ ] Server Components not imported directly in Client Components
|
||||
- [ ] Static pages have `dynamic = 'force-static'`
|
||||
- [ ] Cached components have `'use cache'`
|
||||
- [ ] No route collisions with Route Groups
|
||||
- [ ] Dynamic pages use `generateMetadata()`
|
||||
- [ ] Fonts applied to `<html>` or `<body>`
|
||||
- [ ] Public env vars prefixed with `NEXT_PUBLIC_`
|
||||
- [ ] Server Actions have `'use server'`
|
||||
- [ ] Node.js version is 20.9+
|
||||
|
||||
---
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
### Enable TypeScript Strict Mode
|
||||
|
||||
```json
|
||||
// tsconfig.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Check Build Output
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Look for warnings and errors in build logs.
|
||||
|
||||
### Use Type Checking
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
### Check Runtime Logs
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Watch console for errors and warnings.
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **Migration Guide**: `references/next-16-migration-guide.md`
|
||||
- **Templates**: `templates/` directory
|
||||
- **Next.js 16 Blog**: https://nextjs.org/blog/next-16
|
||||
- **Support**: jeremy@jezweb.net
|
||||
Reference in New Issue
Block a user