Back to skills
Nextjs
Use this skill for Next.js App Router patterns, Server Components, Server Actions, Cache Components, and framework-level optimizations. Covers Next.js 16 breaking changes including async params, proxy.ts migration, Cache Components with "use cache", and React 19.2 integration. For deploying to Cloudflare Workers, use the cloudflare-nextjs skill instead. This skill is deployment-agnostic and works with Vercel, AWS, self-hosted, or any platform. Keywords: Next.js 16, Next.js App Router, Next.j...
2 stars
0 votes
0 copies
0 views
Added 12/19/2025
developmentjavascripttypescriptgojavabashreactnextjsnodenodejsaws
Works with
cliapimcp
Files
SKILL.md
---
name: nextjs
description: |
Use this skill for Next.js App Router patterns, Server Components, Server Actions, Cache Components, and framework-level optimizations. Covers Next.js 16 breaking changes including async params, proxy.ts migration, Cache Components with "use cache", and React 19.2 integration. For deploying to Cloudflare Workers, use the cloudflare-nextjs skill instead. This skill is deployment-agnostic and works with Vercel, AWS, self-hosted, or any platform.
Keywords: Next.js 16, Next.js App Router, Next.js Pages Router, Server Components, React Server Components, Server Actions, Cache Components, use cache, Next.js 16 breaking changes, async params nextjs, proxy.ts migration, React 19.2, Next.js metadata, Next.js SEO, generateMetadata, static generation, dynamic rendering, streaming SSR, Suspense, parallel routes, intercepting routes, route groups, Next.js middleware, Next.js API routes, Route Handlers, revalidatePath, revalidateTag, next/navigation, useSearchParams, turbopack, next.config
license: MIT
metadata:
version: 1.0.0
last_verified: 2025-10-24
nextjs_version: 16.0.0
react_version: 19.2.0
node_version: 20.9+
author: Jezweb
repository: https://github.com/jezweb/claude-skills
production_tested: true
token_savings: 65-70%
errors_prevented: 18+
allowed-tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"]
---
# Next.js App Router - Production Patterns
**Version**: Next.js 16.0.0
**React Version**: 19.2.0
**Node.js**: 20.9+
**Last Verified**: 2025-10-24
---
## Table of Contents
1. [When to Use This Skill](#when-to-use-this-skill)
2. [When NOT to Use This Skill](#when-not-to-use-this-skill)
3. [Next.js 16 Breaking Changes](#nextjs-16-breaking-changes)
4. [Cache Components & Caching APIs](#cache-components--caching-apis)
5. [Server Components](#server-components)
6. [Server Actions](#server-actions)
7. [Route Handlers](#route-handlers)
8. [Proxy vs Middleware](#proxy-vs-middleware)
9. [Parallel Routes & Route Groups](#parallel-routes--route-groups)
10. [React 19.2 Features](#react-192-features)
11. [Metadata API](#metadata-api)
12. [Image & Font Optimization](#image--font-optimization)
13. [Performance Patterns](#performance-patterns)
14. [TypeScript Configuration](#typescript-configuration)
15. [Common Errors & Solutions](#common-errors--solutions)
16. [Templates Reference](#templates-reference)
17. [Additional Resources](#additional-resources)
---
## When to Use This Skill
Use this skill when you need:
- **Next.js 16 App Router patterns** (layouts, loading, error boundaries, routing)
- **Server Components** best practices (data fetching, composition, streaming)
- **Server Actions** patterns (forms, mutations, revalidation, error handling)
- **Cache Components** with `"use cache"` directive (NEW in Next.js 16)
- **New caching APIs**: `revalidateTag()`, `updateTag()`, `refresh()` (Updated in Next.js 16)
- **Migration from Next.js 15 to 16** (async params, proxy.ts, parallel routes)
- **Route Handlers** (API endpoints, webhooks, streaming responses)
- **Proxy patterns** (`proxy.ts` replaces `middleware.ts` in Next.js 16)
- **Async route params** (`params`, `searchParams`, `cookies()`, `headers()` now async)
- **Parallel routes with default.js** (breaking change in Next.js 16)
- **React 19.2 features** (View Transitions, `useEffectEvent()`, React Compiler)
- **Metadata API** (SEO, Open Graph, Twitter Cards, sitemaps)
- **Image optimization** (`next/image` with updated defaults in Next.js 16)
- **Font optimization** (`next/font` patterns)
- **Turbopack configuration** (stable and default in Next.js 16)
- **Performance optimization** (lazy loading, code splitting, PPR, ISR)
- **TypeScript configuration** (strict mode, path aliases)
---
## When NOT to Use This Skill
Do NOT use this skill for:
- **Cloudflare Workers deployment** → Use `cloudflare-nextjs` skill instead
- **Pages Router patterns** → This skill covers App Router ONLY (Pages Router is legacy)
- **Authentication libraries** → Use `clerk-auth`, `auth-js`, or other auth-specific skills
- **Database integration** → Use `cloudflare-d1`, `drizzle-orm-d1`, or database-specific skills
- **UI component libraries** → Use `tailwind-v4-shadcn` skill for Tailwind + shadcn/ui
- **State management** → Use `zustand-state-management`, `tanstack-query` skills
- **Form libraries** → Use `react-hook-form-zod` skill
- **Vercel-specific features** → Refer to Vercel platform documentation
- **Next.js Enterprise features** (ISR, DPR) → Refer to Next.js Enterprise docs
- **Deployment configuration** → Use platform-specific deployment skills
**Relationship with Other Skills**:
- **cloudflare-nextjs**: For deploying Next.js to Cloudflare Workers (use BOTH skills together if deploying to Cloudflare)
- **tailwind-v4-shadcn**: For Tailwind v4 + shadcn/ui setup (composable with this skill)
- **clerk-auth**: For Clerk authentication in Next.js (composable with this skill)
- **auth-js**: For Auth.js (NextAuth) integration (composable with this skill)
---
## Next.js 16 Breaking Changes
**IMPORTANT**: Next.js 16 introduces multiple breaking changes. Read this section carefully if migrating from Next.js 15 or earlier.
### 1. Async Route Parameters (BREAKING)
**Breaking Change**: `params`, `searchParams`, `cookies()`, `headers()`, `draftMode()` are now **async** and must be awaited.
**Before (Next.js 15)**:
```typescript
// ❌ This no longer works in Next.js 16
export default function Page({ params, searchParams }: {
params: { slug: string }
searchParams: { query: string }
}) {
const slug = params.slug // ❌ Error: params is a Promise
const query = searchParams.query // ❌ Error: searchParams is a Promise
return <div>{slug}</div>
}
```
**After (Next.js 16)**:
```typescript
// ✅ Correct: await params and searchParams
export default async function Page({ params, searchParams }: {
params: Promise<{ slug: string }>
searchParams: Promise<{ query: string }>
}) {
const { slug } = await params // ✅ Await the promise
const { query } = await searchParams // ✅ Await the promise
return <div>{slug}</div>
}
```
**Applies to**:
- `params` in pages, layouts, route handlers
- `searchParams` in pages
- `cookies()` from `next/headers`
- `headers()` from `next/headers`
- `draftMode()` from `next/headers`
**Migration**:
```typescript
// ❌ Before
import { cookies, headers } from 'next/headers'
export function MyComponent() {
const cookieStore = cookies() // ❌ Sync access
const headersList = headers() // ❌ Sync access
}
// ✅ After
import { cookies, headers } from 'next/headers'
export async function MyComponent() {
const cookieStore = await cookies() // ✅ Async access
const headersList = await headers() // ✅ Async access
}
```
**Codemod**: Run `npx @next/codemod@canary upgrade latest` to automatically migrate.
**See Template**: `templates/app-router-async-params.tsx`
---
### 2. Middleware → Proxy Migration (BREAKING)
**Breaking Change**: `middleware.ts` is **deprecated** in Next.js 16. Use `proxy.ts` instead.
**Why the Change**: `proxy.ts` makes the network boundary explicit by running on Node.js runtime (not Edge runtime). This provides better clarity between edge middleware and server-side proxies.
**Migration Steps**:
1. **Rename file**: `middleware.ts` → `proxy.ts`
2. **Rename function**: `middleware` → `proxy`
3. **Update config**: `matcher` → `config.matcher` (same syntax)
**Before (Next.js 15)**:
```typescript
// middleware.ts ❌ Deprecated in Next.js 16
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const response = NextResponse.next()
response.headers.set('x-custom-header', 'value')
return response
}
export const config = {
matcher: '/api/:path*',
}
```
**After (Next.js 16)**:
```typescript
// proxy.ts ✅ New in Next.js 16
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
const response = NextResponse.next()
response.headers.set('x-custom-header', 'value')
return response
}
export const config = {
matcher: '/api/:path*',
}
```
**Note**: `middleware.ts` still works in Next.js 16 but is deprecated. Migrate to `proxy.ts` for future compatibility.
**See Template**: `templates/proxy-migration.ts`
**See Reference**: `references/proxy-vs-middleware.md`
---
### 3. Parallel Routes Require `default.js` (BREAKING)
**Breaking Change**: Parallel routes now **require** explicit `default.js` files. Without them, routes will fail during soft navigation.
**Structure**:
```
app/
├── @auth/
│ ├── login/
│ │ └── page.tsx
│ └── default.tsx ← REQUIRED in Next.js 16
├── @dashboard/
│ ├── overview/
│ │ └── page.tsx
│ └── default.tsx ← REQUIRED in Next.js 16
└── layout.tsx
```
**Layout**:
```typescript
// app/layout.tsx
export default function Layout({
children,
auth,
dashboard,
}: {
children: React.ReactNode
auth: React.ReactNode
dashboard: React.ReactNode
}) {
return (
<html>
<body>
{auth}
{dashboard}
{children}
</body>
</html>
)
}
```
**Default Fallback** (REQUIRED):
```typescript
// app/@auth/default.tsx
export default function AuthDefault() {
return null // or <Skeleton /> or redirect
}
// app/@dashboard/default.tsx
export default function DashboardDefault() {
return null
}
```
**Why Required**: Next.js 16 changed how parallel routes handle soft navigation. Without `default.js`, unmatched slots will error during client-side navigation.
**See Template**: `templates/parallel-routes-with-default.tsx`
---
### 4. Removed Features (BREAKING)
**The following features are REMOVED in Next.js 16**:
1. **AMP Support** - Entirely removed. Migrate to standard pages.
2. **`next lint` command** - Use ESLint or Biome directly.
3. **`serverRuntimeConfig` and `publicRuntimeConfig`** - Use environment variables instead.
4. **`experimental.ppr` flag** - Evolved into Cache Components. Use `"use cache"` directive.
5. **Automatic `scroll-behavior: smooth`** - Add manually if needed.
6. **Node.js 18 support** - Minimum version is now **20.9+**.
**Migration**:
- **AMP**: Convert AMP pages to standard pages or use separate AMP implementation.
- **Linting**: Run `npx eslint .` or `npx biome lint .` directly.
- **Config**: Replace `serverRuntimeConfig` with `process.env.VARIABLE`.
- **PPR**: Migrate from `experimental.ppr` to `"use cache"` directive (see Cache Components section).
---
### 5. Version Requirements (BREAKING)
**Next.js 16 requires**:
- **Node.js**: 20.9+ (Node.js 18 no longer supported)
- **TypeScript**: 5.1+ (if using TypeScript)
- **React**: 19.2+ (automatically installed with Next.js 16)
- **Browsers**: Chrome 111+, Safari 16.4+, Firefox 109+, Edge 111+
**Check Versions**:
```bash
node --version # Should be 20.9+
npm --version # Should be 10+
npx next --version # Should be 16.0.0+
```
**Upgrade Node.js**:
```bash
# Using nvm
nvm install 20
nvm use 20
nvm alias default 20
# Using Homebrew (macOS)
brew install node@20
# Using apt (Ubuntu/Debian)
sudo apt update
sudo apt install nodejs npm
```
---
### 6. Image Defaults Changed (BREAKING)
**Next.js 16 changed `next/image` defaults**:
| Setting | Next.js 15 | Next.js 16 |
|---------|------------|------------|
| **TTL** (cache duration) | 60 seconds | 4 hours |
| **imageSizes** | `[16, 32, 48, 64, 96, 128, 256, 384]` | `[640, 750, 828, 1080, 1200]` (reduced) |
| **qualities** | `[75, 90, 100]` | `[75]` (single quality) |
**Impact**:
- Images cache longer (4 hours vs 60 seconds)
- Fewer image sizes generated (smaller builds, but less granular)
- Single quality (75) generated instead of multiple
**Override Defaults** (if needed):
```typescript
// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
images: {
minimumCacheTTL: 60, // Revert to 60 seconds
deviceSizes: [640, 750, 828, 1080, 1200, 1920], // Add larger sizes
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // Restore old sizes
formats: ['image/webp'], // Default
},
}
export default config
```
**See Template**: `templates/image-optimization.tsx`
---
## Cache Components & Caching APIs
**NEW in Next.js 16**: Cache Components introduce **opt-in caching** with the `"use cache"` directive, replacing implicit caching from Next.js 15.
### 1. Overview
**What Changed**:
- **Next.js 15**: Implicit caching (all Server Components cached by default)
- **Next.js 16**: Opt-in caching with `"use cache"` directive
**Why the Change**: Explicit caching gives developers more control and makes caching behavior predictable.
**Cache Components enable**:
- Component-level caching (cache specific components, not entire pages)
- Function-level caching (cache expensive computations)
- Page-level caching (cache entire pages selectively)
- **Partial Prerendering (PPR)** - Cache static parts, render dynamic parts on-demand
---
### 2. `"use cache"` Directive
**Syntax**: Add `"use cache"` at the top of a Server Component, function, or route handler.
**Component-level caching**:
```typescript
// app/components/expensive-component.tsx
'use cache'
export async function ExpensiveComponent() {
const data = await fetch('https://api.example.com/data')
const json = await data.json()
return (
<div>
<h1>{json.title}</h1>
<p>{json.description}</p>
</div>
)
}
```
**Function-level caching**:
```typescript
// lib/data.ts
'use cache'
export async function getExpensiveData(id: string) {
const response = await fetch(`https://api.example.com/items/${id}`)
return response.json()
}
// Usage in component
import { getExpensiveData } from '@/lib/data'
export async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const product = await getExpensiveData(id) // Cached
return <div>{product.name}</div>
}
```
**Page-level caching**:
```typescript
// app/blog/[slug]/page.tsx
'use cache'
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return posts.map((post: { slug: string }) => ({ slug: post.slug }))
}
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await fetch(`https://api.example.com/posts/${slug}`).then(r => r.json())
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
)
}
```
**See Template**: `templates/cache-component-use-cache.tsx`
---
### 3. Partial Prerendering (PPR)
**PPR** allows caching static parts of a page while rendering dynamic parts on-demand.
**Pattern**:
```typescript
// app/dashboard/page.tsx
// Static header (cached)
'use cache'
async function StaticHeader() {
return <header>My App</header>
}
// Dynamic user info (not cached)
async function DynamicUserInfo() {
const cookieStore = await cookies()
const userId = cookieStore.get('userId')?.value
const user = await fetch(`/api/users/${userId}`).then(r => r.json())
return <div>Welcome, {user.name}</div>
}
// Page combines both
export default function Dashboard() {
return (
<div>
<StaticHeader /> {/* Cached */}
<DynamicUserInfo /> {/* Dynamic */}
</div>
)
}
```
**When to Use PPR**:
- Page has both static and dynamic content
- Want to cache layout/header/footer but render user-specific content
- Need fast initial load (static parts) + personalization (dynamic parts)
**See Reference**: `references/cache-components-guide.md`
---
### 4. `revalidateTag()` - Updated API
**BREAKING CHANGE**: `revalidateTag()` now requires a **second argument** (`cacheLife` profile) for stale-while-revalidate behavior.
**Before (Next.js 15)**:
```typescript
import { revalidateTag } from 'next/cache'
export async function updatePost(id: string) {
await fetch(`/api/posts/${id}`, { method: 'PATCH' })
revalidateTag('posts') // ❌ Only one argument in Next.js 15
}
```
**After (Next.js 16)**:
```typescript
import { revalidateTag } from 'next/cache'
export async function updatePost(id: string) {
await fetch(`/api/posts/${id}`, { method: 'PATCH' })
revalidateTag('posts', 'max') // ✅ Second argument required in Next.js 16
}
```
**Built-in Cache Life Profiles**:
- `'max'` - Maximum staleness (recommended for most use cases)
- `'hours'` - Stale after hours
- `'days'` - Stale after days
- `'weeks'` - Stale after weeks
- `'default'` - Default cache behavior
**Custom Cache Life Profile**:
```typescript
revalidateTag('posts', {
stale: 3600, // Stale after 1 hour (seconds)
revalidate: 86400, // Revalidate every 24 hours (seconds)
expire: false, // Never expire (optional)
})
```
**Pattern in Server Actions**:
```typescript
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify({ title, content }),
})
revalidateTag('posts', 'max') // ✅ Revalidate with max staleness
}
```
**See Template**: `templates/revalidate-tag-cache-life.ts`
---
### 5. `updateTag()` - NEW API (Server Actions Only)
**NEW in Next.js 16**: `updateTag()` provides **read-your-writes semantics** for Server Actions.
**What it does**:
- Expires cache immediately
- Refreshes data within the same request
- Shows updated data right after mutation (no stale data)
**Difference from `revalidateTag()`**:
- `revalidateTag()`: **Stale-while-revalidate** (shows stale data, revalidates in background)
- `updateTag()`: **Immediate refresh** (expires cache, fetches fresh data in same request)
**Use Case**: Forms, user settings, or any mutation where user expects immediate feedback.
**Pattern**:
```typescript
'use server'
import { updateTag } from 'next/cache'
export async function updateUserProfile(formData: FormData) {
const name = formData.get('name') as string
const email = formData.get('email') as string
// Update database
await db.users.update({ name, email })
// Immediately refresh cache (read-your-writes)
updateTag('user-profile')
// User sees updated data immediately (no stale data)
}
```
**When to Use**:
- **`updateTag()`**: User settings, profile updates, critical mutations (immediate feedback)
- **`revalidateTag()`**: Blog posts, product listings, non-critical updates (background revalidation)
**See Template**: `templates/server-action-update-tag.ts`
---
### 6. `refresh()` - NEW API (Server Actions Only)
**NEW in Next.js 16**: `refresh()` refreshes **uncached data only** (complements client-side `router.refresh()`).
**When to Use**:
- Refresh dynamic data without affecting cached data
- Complement `router.refresh()` on server side
**Pattern**:
```typescript
'use server'
import { refresh } from 'next/cache'
export async function refreshDashboard() {
// Refresh uncached data (e.g., real-time metrics)
refresh()
// Cached data (e.g., static header) remains cached
}
```
**Difference from `revalidateTag()` and `updateTag()`**:
- `refresh()`: Only refreshes **uncached** data
- `revalidateTag()`: Revalidates **specific tagged** data (stale-while-revalidate)
- `updateTag()`: Immediately expires and refreshes **specific tagged** data
**See Reference**: `references/cache-components-guide.md`
---
## Server Components
Server Components are React components that render on the server. They enable efficient data fetching, reduce client bundle size, and improve performance.
### 1. Server Component Basics
**Default Behavior**: All components in the App Router are Server Components by default (unless marked with `'use client'`).
**Example**:
```typescript
// app/posts/page.tsx (Server Component by default)
export default async function PostsPage() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return (
<div>
{posts.map((post: { id: string; title: string }) => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
)
}
```
**Rules**:
- ✅ Can `await` promises directly in component body
- ✅ Can access `cookies()`, `headers()`, `draftMode()` (with `await`)
- ✅ Can use Node.js APIs (fs, path, etc.)
- ❌ Cannot use browser APIs (window, document, localStorage)
- ❌ Cannot use React hooks (`useState`, `useEffect`, etc.)
- ❌ Cannot use event handlers (`onClick`, `onChange`, etc.)
---
### 2. Data Fetching in Server Components
**Pattern**: Use `async/await` directly in component body.
```typescript
// app/products/[id]/page.tsx
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
// Fetch data directly in component
const product = await fetch(`https://api.example.com/products/${id}`)
.then(r => r.json())
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
</div>
)
}
```
**Parallel Data Fetching**:
```typescript
export default async function Dashboard() {
// Fetch in parallel with Promise.all
const [user, posts, comments] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json()),
])
return (
<div>
<UserInfo user={user} />
<PostsList posts={posts} />
<CommentsList comments={comments} />
</div>
)
}
```
**Sequential Data Fetching** (when needed):
```typescript
export default async function UserPosts({ params }: { params: Promise<{ userId: string }> }) {
const { userId } = await params
// Fetch user first
const user = await fetch(`/api/users/${userId}`).then(r => r.json())
// Then fetch user's posts (depends on user data)
const posts = await fetch(`/api/posts?userId=${user.id}`).then(r => r.json())
return (
<div>
<h1>{user.name}'s Posts</h1>
<ul>
{posts.map((post: { id: string; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
}
```
**See Template**: `templates/server-component-streaming.tsx`
---
### 3. Streaming with Suspense
**Pattern**: Wrap slow components in `<Suspense>` to stream content as it loads.
```typescript
import { Suspense } from 'react'
// Fast component (loads immediately)
async function Header() {
return <header>My App</header>
}
// Slow component (takes 2 seconds)
async function SlowData() {
await new Promise(resolve => setTimeout(resolve, 2000))
const data = await fetch('/api/slow-data').then(r => r.json())
return <div>{data.content}</div>
}
// Page streams content
export default function Page() {
return (
<div>
<Header /> {/* Loads immediately */}
<Suspense fallback={<div>Loading...</div>}>
<SlowData /> {/* Streams when ready */}
</Suspense>
</div>
)
}
```
**When to Use Streaming**:
- Page has slow API calls
- Want to show UI immediately (don't wait for all data)
- Improve perceived performance
**See Reference**: `references/server-components-patterns.md`
---
### 4. Server vs Client Components
**When to Use Server Components** (default):
- Fetch data from APIs/databases
- Access backend resources (files, environment variables)
- Keep large dependencies on server (reduce bundle size)
- Render static content
**When to Use Client Components** (`'use client'`):
- Need React hooks (`useState`, `useEffect`, `useContext`)
- Need event handlers (`onClick`, `onChange`, `onSubmit`)
- Need browser APIs (`window`, `localStorage`, `navigator`)
- Need third-party libraries that use browser APIs (charts, maps, etc.)
**Pattern**: Use Server Components by default, add `'use client'` only when needed.
```typescript
// app/components/interactive-button.tsx
'use client' // Client Component
import { useState } from 'react'
export function InteractiveButton() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
)
}
// app/page.tsx (Server Component)
import { InteractiveButton } from './components/interactive-button'
export default async function Page() {
const data = await fetch('/api/data').then(r => r.json())
return (
<div>
<h1>{data.title}</h1>
<InteractiveButton /> {/* Client Component inside Server Component */}
</div>
)
}
```
**Composition Rules**:
- ✅ Server Component can import Client Component
- ✅ Client Component can import Client Component
- ✅ Client Component can render Server Component as children (via props)
- ❌ Client Component cannot import Server Component directly
**See Reference**: `references/server-components-patterns.md`
---
## Server Actions
Server Actions are asynchronous functions that run on the server. They enable server-side mutations, form handling, and data revalidation.
### 1. Server Action Basics
**Definition**: Add `'use server'` directive to create a Server Action.
**File-level Server Actions**:
```typescript
// app/actions.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
// Mutate database
await db.posts.create({ title, content })
// Revalidate cache
revalidateTag('posts', 'max')
}
```
**Inline Server Actions**:
```typescript
// app/posts/new/page.tsx
export default function NewPostPage() {
async function createPost(formData: FormData) {
'use server'
const title = formData.get('title') as string
const content = formData.get('content') as string
await db.posts.create({ title, content })
revalidateTag('posts', 'max')
}
return (
<form action={createPost}>
<input name="title" />
<textarea name="content" />
<button type="submit">Create Post</button>
</form>
)
}
```
**See Template**: `templates/server-actions-form.tsx`
---
### 2. Form Handling
**Basic Form**:
```typescript
// app/components/create-post-form.tsx
import { createPost } from '@/app/actions'
export function CreatePostForm() {
return (
<form action={createPost}>
<label>
Title:
<input type="text" name="title" required />
</label>
<label>
Content:
<textarea name="content" required />
</label>
<button type="submit">Create Post</button>
</form>
)
}
```
**With Loading State** (useFormStatus):
```typescript
'use client'
import { useFormStatus } from 'react-dom'
import { createPost } from '@/app/actions'
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" disabled={pending}>
{pending ? 'Creating...' : 'Create Post'}
</button>
)
}
export function CreatePostForm() {
return (
<form action={createPost}>
<input type="text" name="title" required />
<textarea name="content" required />
<SubmitButton />
</form>
)
}
```
**With Validation**:
```typescript
// app/actions.ts
'use server'
import { z } from 'zod'
import { redirect } from 'next/navigation'
const PostSchema = z.object({
title: z.string().min(3, 'Title must be at least 3 characters'),
content: z.string().min(10, 'Content must be at least 10 characters'),
})
export async function createPost(formData: FormData) {
const rawData = {
title: formData.get('title'),
content: formData.get('content'),
}
// Validate
const parsed = PostSchema.safeParse(rawData)
if (!parsed.success) {
return {
errors: parsed.error.flatten().fieldErrors,
}
}
// Mutate
await db.posts.create(parsed.data)
// Revalidate and redirect
revalidateTag('posts', 'max')
redirect('/posts')
}
```
**See Template**: `templates/server-actions-form.tsx`
**See Reference**: `references/server-actions-guide.md`
---
### 3. Error Handling
**Pattern**: Return error objects from Server Actions, handle in Client Components.
**Server Action**:
```typescript
// app/actions.ts
'use server'
export async function deletePost(id: string) {
try {
await db.posts.delete({ where: { id } })
revalidateTag('posts', 'max')
return { success: true }
} catch (error) {
return {
success: false,
error: 'Failed to delete post. Please try again.'
}
}
}
```
**Client Component**:
```typescript
'use client'
import { useState } from 'react'
import { deletePost } from '@/app/actions'
export function DeleteButton({ postId }: { postId: string }) {
const [error, setError] = useState<string | null>(null)
async function handleDelete() {
const result = await deletePost(postId)
if (!result.success) {
setError(result.error)
}
}
return (
<div>
<button onClick={handleDelete}>Delete Post</button>
{error && <p className="error">{error}</p>}
</div>
)
}
```
---
### 4. Optimistic Updates
**Pattern**: Show UI update immediately, then sync with server.
```typescript
'use client'
import { useOptimistic } from 'react'
import { likePost } from '@/app/actions'
export function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: number }) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
initialLikes,
(state, amount: number) => state + amount
)
async function handleLike() {
// Update UI immediately
addOptimisticLike(1)
// Sync with server
await likePost(postId)
}
return (
<button onClick={handleLike}>
❤️ {optimisticLikes} likes
</button>
)
}
```
**See Reference**: `references/server-actions-guide.md`
---
## Route Handlers
Route Handlers are server-side API endpoints in the App Router. They replace API Routes from the Pages Router.
### 1. Basic Route Handler
**File**: `app/api/hello/route.ts`
```typescript
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.json({ message: 'Hello, World!' })
}
export async function POST(request: Request) {
const body = await request.json()
return NextResponse.json({
message: 'Post created',
data: body
})
}
```
**Supported Methods**: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`
**See Template**: `templates/route-handler-api.ts`
---
### 2. Dynamic Routes
**File**: `app/api/posts/[id]/route.ts`
```typescript
import { NextResponse } from 'next/server'
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params // ✅ Await params in Next.js 16
const post = await db.posts.findUnique({ where: { id } })
if (!post) {
return NextResponse.json(
{ error: 'Post not found' },
{ status: 404 }
)
}
return NextResponse.json(post)
}
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
await db.posts.delete({ where: { id } })
return NextResponse.json({ message: 'Post deleted' })
}
```
---
### 3. Search Params
**URL**: `/api/posts?tag=javascript&limit=10`
```typescript
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const tag = searchParams.get('tag')
const limit = parseInt(searchParams.get('limit') || '10')
const posts = await db.posts.findMany({
where: { tags: { has: tag } },
take: limit,
})
return NextResponse.json(posts)
}
```
---
### 4. Webhooks
**Pattern**: Handle incoming webhooks from third-party services.
```typescript
// app/api/webhooks/stripe/route.ts
import { NextResponse } from 'next/server'
import { headers } from 'next/headers'
export async function POST(request: Request) {
const body = await request.text()
const headersList = await headers() // ✅ Await headers in Next.js 16
const signature = headersList.get('stripe-signature')
// Verify webhook signature
const event = stripe.webhooks.constructEvent(
body,
signature!,
process.env.STRIPE_WEBHOOK_SECRET!
)
// Handle event
switch (event.type) {
case 'payment_intent.succeeded':
await handlePaymentSuccess(event.data.object)
break
case 'payment_intent.failed':
await handlePaymentFailure(event.data.object)
break
}
return NextResponse.json({ received: true })
}
```
**See Template**: `templates/route-handler-api.ts`
**See Reference**: `references/route-handlers-reference.md`
---
## Proxy vs Middleware
**Next.js 16 introduces `proxy.ts`** to replace `middleware.ts`.
### Why the Change?
- **`middleware.ts`**: Runs on Edge runtime (limited Node.js APIs)
- **`proxy.ts`**: Runs on Node.js runtime (full Node.js APIs)
The new `proxy.ts` makes the network boundary explicit and provides more flexibility.
### Migration
**Before (middleware.ts)**:
```typescript
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Check auth
const token = request.cookies.get('token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: '/dashboard/:path*',
}
```
**After (proxy.ts)**:
```typescript
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
// Check auth
const token = request.cookies.get('token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: '/dashboard/:path*',
}
```
**See Template**: `templates/proxy-migration.ts`
**See Reference**: `references/proxy-vs-middleware.md`
---
## Parallel Routes & Route Groups
### 1. Parallel Routes
**Use Case**: Render multiple pages in the same layout (e.g., modal + main content, dashboard panels).
**Structure**:
```
app/
├── @modal/
│ ├── login/
│ │ └── page.tsx
│ └── default.tsx ← REQUIRED in Next.js 16
├── @feed/
│ ├── trending/
│ │ └── page.tsx
│ └── default.tsx ← REQUIRED in Next.js 16
└── layout.tsx
```
**Layout**:
```typescript
// app/layout.tsx
export default function Layout({
children,
modal,
feed,
}: {
children: React.ReactNode
modal: React.ReactNode
feed: React.ReactNode
}) {
return (
<html>
<body>
{modal}
<main>
{children}
<aside>{feed}</aside>
</main>
</body>
</html>
)
}
```
**Default Files (REQUIRED)**:
```typescript
// app/@modal/default.tsx
export default function ModalDefault() {
return null
}
// app/@feed/default.tsx
export default function FeedDefault() {
return <div>Default Feed</div>
}
```
**See Template**: `templates/parallel-routes-with-default.tsx`
---
### 2. Route Groups
**Use Case**: Organize routes without affecting URL structure.
**Structure**:
```
app/
├── (marketing)/
│ ├── about/
│ │ └── page.tsx → /about
│ └── contact/
│ └── page.tsx → /contact
├── (shop)/
│ ├── products/
│ │ └── page.tsx → /products
│ └── cart/
│ └── page.tsx → /cart
└── layout.tsx
```
**Different Layouts per Group**:
```
app/
├── (marketing)/
│ ├── layout.tsx ← Marketing layout
│ └── about/page.tsx
├── (shop)/
│ ├── layout.tsx ← Shop layout
│ └── products/page.tsx
└── layout.tsx ← Root layout
```
**See Reference**: `references/app-router-fundamentals.md`
---
## React 19.2 Features
Next.js 16 integrates React 19.2, which includes new features from React Canary.
### 1. View Transitions
**Use Case**: Smooth animations between page transitions.
```typescript
'use client'
import { useRouter } from 'next/navigation'
import { startTransition } from 'react'
export function NavigationLink({ href, children }: { href: string; children: React.ReactNode }) {
const router = useRouter()
function handleClick(e: React.MouseEvent) {
e.preventDefault()
// Wrap navigation in startTransition for View Transitions
startTransition(() => {
router.push(href)
})
}
return <a href={href} onClick={handleClick}>{children}</a>
}
```
**With CSS View Transitions API**:
```css
/* app/globals.css */
@view-transition {
navigation: auto;
}
/* Animate elements with view-transition-name */
.page-title {
view-transition-name: page-title;
}
```
**See Template**: `templates/view-transitions-react-19.tsx`
---
### 2. `useEffectEvent()` (Experimental)
**Use Case**: Extract non-reactive logic from `useEffect`.
```typescript
'use client'
import { useEffect, experimental_useEffectEvent as useEffectEvent } from 'react'
export function ChatRoom({ roomId }: { roomId: string }) {
const onConnected = useEffectEvent(() => {
console.log('Connected to room:', roomId)
})
useEffect(() => {
const connection = connectToRoom(roomId)
onConnected() // Non-reactive callback
return () => connection.disconnect()
}, [roomId]) // Only re-run when roomId changes
return <div>Chat Room {roomId}</div>
}
```
**Why Use It**: Prevents unnecessary `useEffect` re-runs when callback dependencies change.
---
### 3. React Compiler (Stable)
**Use Case**: Automatic memoization without `useMemo`, `useCallback`.
**Enable in next.config.ts**:
```typescript
import type { NextConfig } from 'next'
const config: NextConfig = {
experimental: {
reactCompiler: true,
},
}
export default config
```
**Install Plugin**:
```bash
npm install babel-plugin-react-compiler
```
**Example** (no manual memoization needed):
```typescript
'use client'
export function ExpensiveList({ items }: { items: string[] }) {
// React Compiler automatically memoizes this
const filteredItems = items.filter(item => item.length > 3)
return (
<ul>
{filteredItems.map(item => (
<li key={item}>{item}</li>
))}
</ul>
)
}
```
**See Reference**: `references/react-19-integration.md`
---
## Metadata API
The Metadata API provides type-safe SEO and social sharing metadata.
### 1. Static Metadata
**Pattern**: Export `metadata` object from page or layout.
```typescript
// app/page.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My App',
description: 'Welcome to my app',
openGraph: {
title: 'My App',
description: 'Welcome to my app',
images: ['/og-image.jpg'],
},
twitter: {
card: 'summary_large_image',
title: 'My App',
description: 'Welcome to my app',
images: ['/twitter-image.jpg'],
},
}
export default function Page() {
return <h1>Home</h1>
}
```
---
### 2. Dynamic Metadata
**Pattern**: Export `generateMetadata` async function.
```typescript
// app/posts/[id]/page.tsx
import type { Metadata } from 'next'
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,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
}
}
export default async function PostPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const post = await fetch(`/api/posts/${id}`).then(r => r.json())
return <article>{post.content}</article>
}
```
---
### 3. Sitemap
**File**: `app/sitemap.ts`
```typescript
import type { MetadataRoute } from 'next'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await fetch('/api/posts').then(r => r.json())
const postUrls = posts.map((post: { id: string; updatedAt: string }) => ({
url: `https://example.com/posts/${post.id}`,
lastModified: post.updatedAt,
changeFrequency: 'weekly' as const,
priority: 0.8,
}))
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
...postUrls,
]
}
```
**See Template**: `templates/metadata-config.ts`
**See Reference**: `references/metadata-api-guide.md`
---
## Image & Font Optimization
### 1. next/image
**Basic Usage**:
```typescript
import Image from 'next/image'
export function MyImage() {
return (
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority // Load above the fold
/>
)
}
```
**Responsive Images**:
```typescript
<Image
src="/hero.jpg"
alt="Hero"
fill
style={{ objectFit: 'cover' }}
sizes="(max-width: 768px) 100vw, 50vw"
/>
```
**Remote Images** (configure in next.config.ts):
```typescript
import type { NextConfig } from 'next'
const config: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.example.com',
pathname: '/images/**',
},
],
},
}
export default config
```
**See Template**: `templates/image-optimization.tsx`
---
### 2. next/font
**Google Fonts**:
```typescript
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}
```
**Local Fonts**:
```typescript
import localFont from 'next/font/local'
const myFont = localFont({
src: './fonts/my-font.woff2',
display: 'swap',
variable: '--font-my-font',
})
```
**See Template**: `templates/font-optimization.tsx`
---
## Performance Patterns
### 1. Lazy Loading
**Component Lazy Loading**:
```typescript
import dynamic from 'next/dynamic'
const HeavyComponent = dynamic(() => import('./heavy-component'), {
loading: () => <div>Loading...</div>,
ssr: false, // Disable SSR for client-only components
})
export default function Page() {
return (
<div>
<h1>My Page</h1>
<HeavyComponent />
</div>
)
}
```
**Conditional Loading**:
```typescript
'use client'
import { useState } from 'react'
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('./chart'), { ssr: false })
export function Dashboard() {
const [showChart, setShowChart] = useState(false)
return (
<div>
<button onClick={() => setShowChart(true)}>Show Chart</button>
{showChart && <Chart />}
</div>
)
}
```
---
### 2. Code Splitting
**Route-level Splitting** (automatic):
```
app/
├── page.tsx → Bundles: /, shared
├── about/
│ └── page.tsx → Bundles: /about, shared
└── products/
└── page.tsx → Bundles: /products, shared
```
**Component-level Splitting** (with dynamic import):
```typescript
const Analytics = dynamic(() => import('./analytics'))
const Comments = dynamic(() => import('./comments'))
export default function BlogPost() {
return (
<article>
<h1>Post Title</h1>
<p>Content...</p>
<Analytics /> {/* Separate bundle */}
<Comments /> {/* Separate bundle */}
</article>
)
}
```
---
### 3. Turbopack (Stable in Next.js 16)
**Default**: Turbopack is now the default bundler in Next.js 16.
**Metrics**:
- **2–5× faster production builds**
- **Up to 10× faster Fast Refresh**
**Opt-out** (if needed):
```bash
npm run build -- --webpack
```
**Enable File System Caching** (beta):
```typescript
// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
experimental: {
turbopack: {
fileSystemCaching: true, // Beta: Persist cache between runs
},
},
}
export default config
```
**See Reference**: `references/performance-optimization.md`
---
## TypeScript Configuration
### 1. Strict Mode
**Enable strict mode** in `tsconfig.json`:
```json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
```
---
### 2. Path Aliases
**Configure in tsconfig.json**:
```json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./app/*"],
"@/components/*": ["./app/components/*"],
"@/lib/*": ["./lib/*"],
"@/styles/*": ["./styles/*"]
}
}
}
```
**Usage**:
```typescript
// Instead of: import { Button } from '../../../components/button'
import { Button } from '@/components/button'
```
---
### 3. Type-Safe Routing
**Generate types from routes**:
```bash
npm run build
```
**Usage**:
```typescript
import { useRouter } from 'next/navigation'
const router = useRouter()
// Type-safe routing
router.push('/posts/123') // ✅ Valid route
router.push('/invalid') // ❌ Type error if route doesn't exist
```
**See Reference**: `references/typescript-configuration.md`
---
## Common Errors & Solutions
### 1. Error: `params` is a Promise
**Error**:
```
Type 'Promise<{ id: string }>' is not assignable to type '{ id: string }'
```
**Cause**: Next.js 16 changed `params` to async.
**Solution**: Await `params`:
```typescript
// ❌ Before
export default function Page({ params }: { params: { id: string } }) {
const id = params.id
}
// ✅ After
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
}
```
---
### 2. Error: `searchParams` is a Promise
**Error**:
```
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: { query: string } }) {
const query = searchParams.query
}
// ✅ After
export default async function Page({ searchParams }: { searchParams: Promise<{ query: string }> }) {
const { query } = await searchParams
}
```
---
### 3. Error: `cookies()` requires await
**Error**:
```
'cookies' implicitly has return type 'any'
```
**Cause**: `cookies()` is now async in Next.js 16.
**Solution**:
```typescript
// ❌ Before
import { cookies } from 'next/headers'
export function MyComponent() {
const cookieStore = cookies()
}
// ✅ After
import { cookies } from 'next/headers'
export async function MyComponent() {
const cookieStore = await cookies()
}
```
---
### 4. Error: Parallel route missing `default.js`
**Error**:
```
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**: Add `default.tsx` files:
```typescript
// app/@modal/default.tsx
export default function ModalDefault() {
return null
}
```
---
### 5. Error: `revalidateTag()` requires 2 arguments
**Error**:
```
Expected 2 arguments, but got 1
```
**Cause**: `revalidateTag()` now requires a `cacheLife` argument in Next.js 16.
**Solution**:
```typescript
// ❌ Before
revalidateTag('posts')
// ✅ After
revalidateTag('posts', 'max')
```
---
### 6. Error: Cannot use React hooks in Server Component
**Error**:
```
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>
}
```
---
### 7. Error: `middleware.ts` is deprecated
**Warning**:
```
Warning: middleware.ts is deprecated. Use proxy.ts instead.
```
**Solution**: Migrate to `proxy.ts`:
```typescript
// Rename: middleware.ts → proxy.ts
// Rename function: middleware → proxy
export function proxy(request: NextRequest) {
// Same logic
}
```
---
### 8. Error: Turbopack build failure
**Error**:
```
Error: Failed to compile with Turbopack
```
**Cause**: Turbopack is now default in Next.js 16.
**Solution**: Opt out of Turbopack if incompatible:
```bash
npm run build -- --webpack
```
---
### 9. Error: Invalid `next/image` src
**Error**:
```
Invalid src prop (https://example.com/image.jpg) on `next/image`. Hostname "example.com" is not configured under images in your `next.config.js`
```
**Solution**: Add remote patterns in `next.config.ts`:
```typescript
const config: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
},
],
},
}
```
---
### 10. Error: Cannot import Server Component into Client Component
**Error**:
```
You're importing a Server Component into a Client Component
```
**Solution**: Pass Server Component as children:
```typescript
// ❌ Wrong
'use client'
import { ServerComponent } from './server-component' // Error
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>
```
---
### 11. Error: `generateStaticParams` not working
**Cause**: `generateStaticParams` only works with static generation (`export const 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 }))
}
```
---
### 12. Error: `fetch()` not caching
**Cause**: Next.js 16 uses opt-in caching with `"use cache"` directive.
**Solution**: Add `"use cache"` to component or function:
```typescript
'use cache'
export async function getPosts() {
const response = await fetch('/api/posts')
return response.json()
}
```
---
### 13. Error: Route collision with Route Groups
**Error**:
```
Error: Conflicting routes: /about and /(marketing)/about
```
**Cause**: Route groups create same URL path.
**Solution**: Ensure route groups don't conflict:
```
app/
├── (marketing)/about/page.tsx → /about
└── (shop)/about/page.tsx → ERROR: Duplicate /about
# Fix: Use different routes
app/
├── (marketing)/about/page.tsx → /about
└── (shop)/store-info/page.tsx → /store-info
```
---
### 14. Error: Metadata not updating
**Cause**: Using dynamic metadata without `generateMetadata()`.
**Solution**: Use `generateMetadata()` for dynamic pages:
```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,
}
}
```
---
### 15. Error: `next/font` font not loading
**Cause**: Font variable not applied to HTML element.
**Solution**: Apply font variable to `<html>` or `<body>`:
```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>
)
}
```
---
### 16. Error: Environment variables not available in browser
**Cause**: Server-only env vars are not exposed to browser.
**Solution**: Prefix with `NEXT_PUBLIC_` for client-side access:
```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 work)
const apiUrl = process.env.NEXT_PUBLIC_API_URL
```
---
### 17. Error: Server Action not found
**Error**:
```
Error: Could not find Server Action
```
**Cause**: Missing `'use server'` directive.
**Solution**: Add `'use server'`:
```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({ ... })
}
```
---
### 18. Error: TypeScript path alias not working
**Cause**: Incorrect `baseUrl` or `paths` in `tsconfig.json`.
**Solution**: Configure correctly:
```json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
"@/components/*": ["./app/components/*"]
}
}
}
```
**See Reference**: `references/top-errors.md`
---
## Templates Reference
The following templates are available in `templates/`:
**App Router Fundamentals**:
- `app-router-async-params.tsx` - Async params, searchParams patterns (Next.js 16)
- `parallel-routes-with-default.tsx` - Parallel routes with required default.js
- `route-groups-example.tsx` - Route groups organization
**Cache Components (Next.js 16)**:
- `cache-component-use-cache.tsx` - Cache Components with `"use cache"`
- `partial-prerendering.tsx` - PPR with static + dynamic parts
- `revalidate-tag-cache-life.ts` - Updated `revalidateTag()` API
- `server-action-update-tag.ts` - `updateTag()` for read-your-writes
**Server Components**:
- `server-component-data-fetching.tsx` - Data fetching patterns
- `server-component-streaming.tsx` - Streaming with Suspense
- `server-component-composition.tsx` - Server + Client component composition
**Server Actions**:
- `server-actions-form.tsx` - Form handling with Server Actions
- `server-actions-validation.ts` - Server Action validation with Zod
- `server-actions-optimistic.tsx` - Optimistic updates
**Route Handlers**:
- `route-handler-api.ts` - Basic CRUD API
- `route-handler-webhook.ts` - Webhook handling
- `route-handler-streaming.ts` - Streaming responses
**Proxy & Middleware**:
- `proxy-migration.ts` - Migrate from middleware.ts to proxy.ts
- `proxy-auth.ts` - Auth with proxy.ts
**React 19.2**:
- `view-transitions-react-19.tsx` - View Transitions API
- `use-effect-event.tsx` - `useEffectEvent()` pattern
- `react-compiler-example.tsx` - React Compiler usage
**Metadata**:
- `metadata-config.ts` - Static and dynamic metadata
- `sitemap.ts` - Sitemap generation
- `robots.ts` - robots.txt generation
**Optimization**:
- `image-optimization.tsx` - next/image patterns
- `font-optimization.tsx` - next/font patterns
- `lazy-loading.tsx` - Dynamic imports and lazy loading
- `code-splitting.tsx` - Code splitting strategies
**TypeScript**:
- `typescript-config.json` - Recommended TypeScript configuration
- `path-aliases.tsx` - Path alias usage
**Configuration**:
- `next.config.ts` - Full Next.js configuration
- `package.json` - Recommended dependencies for Next.js 16
---
## Additional Resources
**Bundled References** (in `references/`):
- `next-16-migration-guide.md` - Complete migration from Next.js 15 to 16
- `cache-components-guide.md` - Cache Components deep dive
- `proxy-vs-middleware.md` - Proxy.ts vs middleware.ts comparison
- `async-route-params.md` - Async params, searchParams, cookies(), headers()
- `app-router-fundamentals.md` - App Router concepts and patterns
- `server-components-patterns.md` - Server Components best practices
- `server-actions-guide.md` - Server Actions patterns and validation
- `route-handlers-reference.md` - Route Handlers API reference
- `metadata-api-guide.md` - Metadata API complete guide
- `performance-optimization.md` - Performance patterns and Turbopack
- `react-19-integration.md` - React 19.2 features in Next.js
- `top-errors.md` - 18+ common errors and solutions
**Scripts**:
- `scripts/check-versions.sh` - Verify Next.js and dependency versions
**External Documentation**:
- **Next.js 16 Blog**: https://nextjs.org/blog/next-16
- **Next.js Docs**: https://nextjs.org/docs
- **React Docs**: https://react.dev
- **Context7 MCP**: Use `/websites/nextjs` for latest Next.js reference
---
## Version Compatibility
| Package | Minimum Version | Recommended |
|---------|----------------|-------------|
| Next.js | 16.0.0 | 16.0.0+ |
| React | 19.2.0 | 19.2.0+ |
| Node.js | 20.9.0 | 20.9.0+ |
| TypeScript | 5.1.0 | 5.7.0+ |
| Turbopack | (built-in) | Stable |
**Check Versions**:
```bash
./scripts/check-versions.sh
```
---
## Token Efficiency
**Estimated Token Savings**: 65-70%
**Without Skill** (manual setup from docs):
- Read Next.js 16 migration guide: ~5k tokens
- Read App Router docs: ~8k tokens
- Read Server Actions docs: ~4k tokens
- Read Metadata API docs: ~3k tokens
- Trial-and-error fixes: ~8k tokens
- **Total**: ~28k tokens
**With Skill**:
- Load skill: ~8k tokens
- Use templates: ~2k tokens
- **Total**: ~10k tokens
- **Savings**: ~18k tokens (~64%)
**Errors Prevented**: 18+ common mistakes = 100% error prevention
---
## Maintenance
**Last Verified**: 2025-10-24
**Next Review**: 2026-01-24 (Quarterly)
**Maintainer**: Jezweb | jeremy@jezweb.net
**Repository**: https://github.com/jezweb/claude-skills
**Update Triggers**:
- Next.js major/minor releases
- React major releases
- Breaking changes in APIs
- New Turbopack features
**Version Check**:
```bash
cd skills/nextjs
./scripts/check-versions.sh
```
---
**End of SKILL.md**
Attribution
Comments (0)
No comments yet. Be the first to comment!
