### Installation
Source: https://github.com/vercel-labs/next-skills/blob/main/README.md
Install essential Next.js best practices skills or all skills.
```bash
# Install essentials (recommended)
npx skills add vercel-labs/next-skills --skill next-best-practices
# Or install everything
npx skills add vercel-labs/next-skills
```
--------------------------------
### Example: Get Errors via MCP
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/debug-tricks.md
Example curl command to fetch errors using the MCP endpoint.
```bash
curl -X POST http://localhost:/_next/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"get_errors","arguments":{}}}'
```
--------------------------------
### Code Examples
Source: https://github.com/vercel-labs/next-skills/blob/main/AGENTS.md
Examples showing bad vs good patterns.
```tsx
// Bad: description of the problem
// Good: description of the solution
```
--------------------------------
### Static Content Example
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-cache-components/SKILL.md
Example of static content that is auto-prerendered at build time.
```tsx
export default function Page() {
return (
Our Blog
{/* Static - instant */}
)
}
```
--------------------------------
### OpenNext CLI Commands
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/self-hosting.md
Example commands for setting up a project with OpenNext for serverless deployment.
```bash
npx create-sst@latest
# or
npx @opennextjs/aws build
```
--------------------------------
### Dynamic Content Example (Suspense)
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-cache-components/SKILL.md
Example of runtime data that must be fresh, wrapped in Suspense.
```tsx
import { Suspense } from 'react'
export default function Page() {
return (
<>
{/* Cached */}
Loading...
}>
{/* Dynamic - streams in */}
>
)
}
async function UserPreferences() {
const theme = (await cookies()).get('theme')?.value
return
Theme: {theme}
}
```
--------------------------------
### Docker Compose Configuration
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/self-hosting.md
Example `docker-compose.yml` for deploying a Next.js application.
```yaml
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
```
--------------------------------
### Skill Contribution Structure
Source: https://github.com/vercel-labs/next-skills/blob/main/README.md
Example YAML frontmatter for a new skill.
```yaml
---
name: next-skill-name
description: Brief description
user-invocable: false # for background skills
---
```
--------------------------------
### Migrating from Webpack to Turbopack
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/bundling.md
Example of `next.config.js` showing configurations compatible with Turbopack versus those that are Webpack-only.
```js
// next.config.js
module.exports = {
// Good: Works with Turbopack
serverExternalPackages: ['package'],
transpilePackages: ['package'],
// Bad: Webpack-only - migrate away from this
webpack: (config) => {
// custom webpack config
},
}
```
--------------------------------
### Runtime Configuration Fetching
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/self-hosting.md
Example of fetching runtime configuration from an API endpoint in a Next.js application.
```typescript
// app/api/config/route.ts
export async function GET() {
return Response.json({
apiUrl: process.env.API_URL,
features: process.env.FEATURES?.split(','),
});
}
```
--------------------------------
### Complete Cache Components Example
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-cache-components/SKILL.md
A full example demonstrating the usage of `use cache`, `cacheLife`, and `cacheTag` within a Next.js dashboard page.
```tsx
import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag } from 'next/cache'
export default function DashboardPage() {
return (
<>
{/* Static shell - instant from CDN */}
Dashboard
{/* Cached - fast, revalidates hourly */}
{/* Dynamic - streams in with fresh data */}
}>
>
)
}
async function Stats() {
'use cache'
cacheLife('hours')
cacheTag('dashboard-stats')
const stats = await db.stats.aggregate()
return
}
async function Notifications() {
const userId = (await cookies()).get('userId')?.value
const notifications = await db.notifications.findMany({
where: { userId, read: false }
})
return
}
```
--------------------------------
### Route Handlers Environment Behavior Example
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/route-handlers.md
Shows an example of code that will not work in route handlers due to the lack of React DOM APIs, highlighting the server-like environment.
```typescript
// Bad: This won't work - no React DOM in route handlers
import { renderToString } from 'react-dom/server'
export async function GET() {
const html = renderToString() // Error!
return new Response(html)
}
```
--------------------------------
### Cached Content Example (`use cache`)
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-cache-components/SKILL.md
Example of async data that doesn't need fresh fetches every request, using the 'use cache' directive.
```typescript
async function BlogPosts() {
'use cache'
cacheLife('hours')
const posts = await db.posts.findMany()
return
}
```
--------------------------------
### Dockerfile for Standalone Output
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/self-hosting.md
A Dockerfile example for building a production-ready Next.js application using standalone output.
```dockerfile
FROM node:20-alpine AS base
# Install dependencies
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
# Build
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./
COPY . .
RUN npm run build
# Production
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy standalone output
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
```
--------------------------------
### Related Skills
Source: https://github.com/vercel-labs/next-skills/blob/main/README.md
Install agent skills for React-specific patterns.
```bash
npx skills add vercel-labs/agent-skills --skill vercel-react-best-practices
```
--------------------------------
### Next.js Upgrade Skill
Source: https://github.com/vercel-labs/next-skills/blob/main/README.md
Install the skill for upgrading between Next.js versions.
```bash
npx skills add vercel-labs/next-skills --skill next-upgrade
```
--------------------------------
### Loading Strategies for next/script
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/scripts.md
Provides examples of different loading strategies available for `next/script`.
```tsx
// afterInteractive (default) - Load after page is interactive
// lazyOnload - Load during idle time
// beforeInteractive - Load before page is interactive (use sparingly)
// Only works in app/layout.tsx or pages/_document.js
// worker - Load in web worker (experimental)
```
--------------------------------
### Custom Image Loader Implementation
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/self-hosting.md
An example implementation of a custom image loader for Next.js, specifically for Cloudinary.
```javascript
// lib/image-loader.js
export default function cloudinaryLoader({ src, width, quality }) {
const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`];
return `https://res.cloudinary.com/demo/image/upload/${params.join(',')}${src}`;
}
```
--------------------------------
### Multiple Sitemaps
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/metadata.md
Example of using `generateSitemaps` to create multiple sitemaps for large sites.
```tsx
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export async function generateSitemaps() {
// Return array of sitemap IDs
return [{ id: 0 }, { id: 1 }, { id: 2 }]
}
export default async function sitemap({
id,
}: {
id: number
}): Promise {
const start = id * 50000
const end = start + 50000
const products = await getProducts(start, end)
return products.map((product) => ({
url: `https://example.com/product/${product.id}`,
lastModified: product.updatedAt,
}))
}
```
--------------------------------
### Runtime Configuration Example
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/runtime-selection.md
Demonstrates how to configure the runtime for a Next.js page. The default Node.js runtime requires no explicit configuration, while the Edge runtime needs to be explicitly set.
```tsx
// Good: Default - no runtime config needed (uses Node.js)
export default function Page() { ... }
// Caution: Only if already used in project or specifically required
export const runtime = 'edge'
```
--------------------------------
### Next.js Cache Components Skill
Source: https://github.com/vercel-labs/next-skills/blob/main/README.md
Install the skill for Next.js 16 Cache Components and PPR.
```bash
npx skills add vercel-labs/next-skills --skill next-cache-components
```
--------------------------------
### Dynamic OG Image
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/metadata.md
Example of generating a dynamic Open Graph image for a blog post.
```tsx
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export const alt = 'Blog Post'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
type Props = { params: Promise<{ slug: string }> }
export default async function Image({ params }: Props) {
const { slug } = await params
const post = await getPost(slug)
return new ImageResponse(
(
{post.title}
{post.description}
),
{ ...size }
)
}
```
--------------------------------
### Rebuild Specific Routes
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/debug-tricks.md
Examples of using the `next build --debug-build-paths` command to rebuild specific routes.
```bash
# Rebuild a specific route
next build --debug-build-paths "/dashboard"
# Rebuild routes matching a glob
next build --debug-build-paths "/api/*"
# Dynamic routes
next build --debug-build-paths "/blog/[slug]"
```
--------------------------------
### Async Params in Pages and Layouts
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/async-patterns.md
Example of how to type and await asynchronous params in Next.js pages and layouts.
```tsx
type Props = {
params: Promise<{ slug: string }>
}
export default async function Page({ params }: Props) {
const { slug } = await params
}
```
--------------------------------
### Dynamic Route Handler Example
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/route-handlers.md
Demonstrates how to create a dynamic route handler for fetching a specific user by ID, including handling not found cases.
```typescript
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const user = await getUser(id)
if (!user) {
return Response.json({ error: 'Not found' }, { status: 404 })
}
return Response.json(user)
}
```
--------------------------------
### Async SearchParams in Pages
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/async-patterns.md
Example demonstrating how to type and await both asynchronous params and searchParams in Next.js pages.
```tsx
type Props = {
params: Promise<{ slug: string }>
searchParams: Promise<{ query?: string }>
}
export default async function Page({ params, searchParams }: Props) {
const { slug } = await params
const { query } = await searchParams
}
```
--------------------------------
### Redirects
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/error-handling.md
Examples of using `redirect` for temporary redirects and `permanentRedirect` for permanent URL migrations.
```tsx
import { redirect, permanentRedirect } from 'next/navigation'
// 307 Temporary - use for most cases
redirect('/new-path')
// 308 Permanent - use for URL migrations (cached by browsers)
permanentRedirect('/new-url')
```
--------------------------------
### Cache Key Generation Example
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-cache-components/SKILL.md
Demonstrates how cache keys are automatically generated based on Build ID, Function ID, serializable arguments, and closure variables.
```tsx
async function Component({ userId }: { userId: string }) {
const getData = async (filter: string) => {
'use cache'
// Cache key = userId (closure) + filter (argument)
return fetch(`/api/users/${userId}?filter=${filter}`)
}
return getData('active')
}
```
--------------------------------
### Async generateMetadata
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/async-patterns.md
Example of implementing an asynchronous `generateMetadata` function that awaits params.
```tsx
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise {
const { slug } = await params
return { title: slug }
}
```
--------------------------------
### Preloading Subsets
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/font.md
Example of specifying character subsets to preload for Google Fonts.
```tsx
// Latin only (most common)
const inter = Inter({ subsets: ['latin'] })
// Multiple subsets
const inter = Inter({ subsets: ['latin', 'latin-ext', 'cyrillic'] })
```
--------------------------------
### MCP Endpoint Request Format
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/debug-tricks.md
Example of how to make a request to the MCP endpoint using curl.
```bash
curl -X POST http://localhost:/_next/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "tools/call",
"params": {
"name": "",
"arguments": {}
}
}'
```
--------------------------------
### Font Weights and Styles
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/font.md
Examples of specifying font weights and styles for Google Fonts.
```tsx
// Single weight
const inter = Inter({
subsets: ['latin'],
weight: '400',
})
// Multiple weights
const inter = Inter({
subsets: ['latin'],
weight: ['400', '500', '700'],
})
// Variable font (recommended) - includes all weights
const inter = Inter({
subsets: ['latin'],
// No weight needed - variable fonts support all weights
})
// With italic
const inter = Inter({
subsets: ['latin'],
style: ['normal', 'italic'],
})
```
--------------------------------
### Basic Usage of Route Handlers
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/route-handlers.md
Demonstrates how to define GET and POST handlers in a route.ts file to create API endpoints for fetching and creating users.
```typescript
// app/api/users/route.ts
export async function GET() {
const users = await getUsers()
return Response.json(users)
}
export async function POST(request: Request) {
const body = await request.json()
const user = await createUser(body)
return Response.json(user, { status: 201 })
}
```
--------------------------------
### Redis Cache Handler Implementation
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/self-hosting.md
Example implementation of a custom cache handler using Redis for Next.js ISR.
```javascript
// cache-handler.js
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
const CACHE_PREFIX = 'nextjs:';
module.exports = class CacheHandler {
constructor(options) {
this.options = options;
}
async get(key) {
const data = await redis.get(CACHE_PREFIX + key);
if (!data) return null;
const parsed = JSON.parse(data);
return {
value: parsed.value,
lastModified: parsed.lastModified,
};
}
async set(key, data, ctx) {
const cacheData = {
value: data,
lastModified: Date.now(),
};
// Set TTL based on revalidate option
if (ctx?.revalidate) {
await redis.setex(
CACHE_PREFIX + key,
ctx.revalidate,
JSON.stringify(cacheData)
);
} else {
await redis.set(CACHE_PREFIX + key, JSON.stringify(cacheData));
}
}
async revalidateTag(tags) {
// Implement tag-based invalidation
// This requires tracking which keys have which tags
}
};
```
--------------------------------
### Display Strategy
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/font.md
Example of controlling font loading behavior with the 'display' property.
```tsx
const inter = Inter({
subsets: ['latin'],
display: 'swap', // Default - shows fallback, swaps when loaded
})
// Options:
// 'auto' - browser decides
// 'block' - short block period, then swap
// 'swap' - immediate fallback, swap when ready (recommended)
// 'fallback' - short block, short swap, then fallback
// 'optional' - short block, no swap (use if font is optional)
```
--------------------------------
### Multiple OG Images
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/metadata.md
Example of using `generateImageMetadata` to create multiple OG images per route.
```tsx
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export async function generateImageMetadata({ params }) {
const images = await getPostImages(params.slug)
return images.map((img, idx) => ({
id: idx,
alt: img.alt,
size: { width: 1200, height: 630 },
contentType: 'image/png',
}))
}
export default async function Image({ params, id }) {
const images = await getPostImages(params.slug)
const image = images[id]
return new ImageResponse(/* ... */)
}
```
--------------------------------
### Local Fonts Integration
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/font.md
Example of integrating local fonts using next/font/local.
```tsx
import localFont from 'next/font/local'
const myFont = localFont({
src: './fonts/MyFont.woff2',
})
// Multiple files for different weights
const myFont = localFont({
src: [
{
path: './fonts/MyFont-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: './fonts/MyFont-Bold.woff2',
weight: '700',
style: 'normal',
},
],
})
// Variable font
const myFont = localFont({
src: './fonts/MyFont-Variable.woff2',
variable: '--font-my-font',
})
```
--------------------------------
### Other Third-Party Scripts with @next/third-parties
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/scripts.md
Provides examples for integrating YouTube embeds and Google Maps using `@next/third-parties`.
```tsx
// YouTube embed
import { YouTubeEmbed } from '@next/third-parties/google'
// Google Maps
import { GoogleMapsEmbed } from '@next/third-parties/google'
```
--------------------------------
### MCP Tool: get_project_metadata
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/debug-tricks.md
JSON payload to get project path and dev server URL.
```json
{ "name": "get_project_metadata", "arguments": {} }
```
--------------------------------
### useSearchParams - Good Example
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/suspense-boundaries.md
Shows the correct way to use `useSearchParams` by wrapping the component in a `Suspense` boundary, providing a fallback UI while data is loading.
```tsx
import { Suspense } from 'react'
import SearchBar from './search-bar'
export default function Page() {
return (
Loading...}>
)
}
```
--------------------------------
### Static Generation with generateStaticParams
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/functions.md
Example of using `generateStaticParams` to pre-render dynamic routes at build time.
```tsx
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ slug: post.slug }))
}
```
--------------------------------
### S3 Cache Handler Implementation
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/self-hosting.md
Example implementation of a custom cache handler using AWS S3 for Next.js ISR.
```javascript
// cache-handler.js
const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3 = new S3Client({ region: process.env.AWS_REGION });
const BUCKET = process.env.CACHE_BUCKET;
module.exports = class CacheHandler {
async get(key) {
try {
const response = await s3.send(new GetObjectCommand({
Bucket: BUCKET,
Key: `cache/${key}`,
}));
const body = await response.Body.transformToString();
return JSON.parse(body);
} catch (err) {
if (err.name === 'NoSuchKey') return null;
throw err;
}
}
async set(key, data, ctx) {
await s3.send(new PutObjectCommand({
Bucket: BUCKET,
Key: `cache/${key}`,
Body: JSON.stringify({
value: data,
lastModified: Date.now(),
}),
ContentType: 'application/json',
}));
}
};
```
--------------------------------
### usePathname - Good Example
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/suspense-boundaries.md
Demonstrates wrapping a component using `usePathname` in a dynamic route with a `Suspense` boundary to handle loading states correctly.
```tsx
}>
```
--------------------------------
### Root Layout with Slot
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/parallel-routes.md
Example of a root layout that accepts and renders a 'modal' slot alongside the main children.
```tsx
import type { ReactNode } from 'react';
export default function RootLayout({
children,
modal,
}: {
children: ReactNode;
modal: ReactNode;
}) {
return (
{children}
{modal}
);
}
```
--------------------------------
### Running Code After Response with after
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/functions.md
Example demonstrating how to use the `after` function to execute code after the response has finished streaming.
```tsx
import { after } from 'next/server'
export async function POST(request: Request) {
const data = await processRequest(request)
after(async () => {
await logAnalytics(data)
})
return Response.json({ success: true })
}
```
--------------------------------
### Async Params in Route Handlers
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/async-patterns.md
Example of how to handle asynchronous params within Next.js route handlers.
```tsx
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
}
```
--------------------------------
### Middleware (Next.js 14-15)
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/file-conventions.md
Example of a middleware function in Next.js versions 14-15 for handling requests, redirects, and rewrites.
```typescript
// middleware.ts (root of project)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Auth, redirects, rewrites, etc.
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};
```
--------------------------------
### Multiple Fonts Integration
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/font.md
Example of integrating multiple Google Fonts and using CSS variables for them.
```tsx
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-roboto-mono',
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
```css
body {
font-family: var(--font-inter);
}
code {
font-family: var(--font-roboto-mono);
}
```
--------------------------------
### Environment Variables: Build-time vs Runtime
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/self-hosting.md
Illustrates the difference between environment variables available only at build time and those available at runtime.
```javascript
// Available at build time only (baked into bundle)
NEXT_PUBLIC_API_URL=https://api.example.com
// Available at runtime (server-side only)
DATABASE_URL=postgresql://...
API_SECRET=...
```
--------------------------------
### Response Helpers in Route Handlers
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/route-handlers.md
Provides examples of various response helpers available in route handlers, including JSON responses, status codes, headers, redirects, and streams.
```typescript
// JSON response
return Response.json({ data })
// With status
return Response.json({ error: 'Not found' }, { status: 404 })
// With headers
return Response.json(data, {
headers: {
'Cache-Control': 'max-age=3600',
},
})
// Redirect
return Response.redirect(new URL('/login', request.url))
// Stream
return new Response(stream, {
headers: { 'Content-Type': 'text/event-stream' },
})
```
--------------------------------
### Proxy (Next.js 16+)
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/file-conventions.md
Example of a proxy function in Next.js versions 16+ which serves the same capabilities as middleware but with a different name for clarity.
```typescript
// proxy.ts (root of project)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function proxy(request: NextRequest) {
// Same logic as middleware
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};
```
--------------------------------
### Gallery Grid with Links to Photos
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/parallel-routes.md
Example of a photo gallery grid where each photo is a link to its individual page, intended to be intercepted by a modal.
```tsx
// app/photos/page.tsx
import Link from 'next/link';
export default async function Gallery() {
const photos = await getPhotos();
return (
{photos.map(photo => (
))}
);
}
```
--------------------------------
### Active Link Styling
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/functions.md
Example of how to style an active link using the `usePathname` hook to compare the current path with the link's href.
```tsx
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
export function NavLink({ href, children }) {
const pathname = usePathname()
return (
{children}
)
}
```
--------------------------------
### SEO Best Practice: Static Files
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/metadata.md
Demonstrates a common structure for static metadata files providing good SEO coverage.
```treeview
app/
├── favicon.ico
├── opengraph-image.png # Works for both OG and Twitter
├── sitemap.ts
├── robots.ts
└── layout.tsx # With title/description metadata
```
--------------------------------
### Mark as Client-Only with Dynamic Import
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/bundling.md
Demonstrates how to dynamically import a package that uses browser APIs, preventing it from running on the server.
```tsx
// Bad: Fails - package uses window
import SomeChart from 'some-chart-library'
export default function Page() {
return
}
// Good: Use dynamic import with ssr: false
import dynamic from 'next/dynamic'
const SomeChart = dynamic(() => import('some-chart-library'), {
ssr: false,
})
export default function Page() {
return
}
```
--------------------------------
### Custom Fonts
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/metadata.md
Example of using custom fonts in an Open Graph image.
```tsx
import { ImageResponse } from 'next/og'
import { join } from 'path'
import { readFile } from 'fs/promises'
export default async function Image() {
const fontPath = join(process.cwd(), 'assets/fonts/Inter-Bold.ttf')
const fontData = await readFile(fontPath)
return new ImageResponse(
(
Custom Font Text
),
{
width: 1200,
height: 630,
fonts: [{ name: 'Inter', data: fontData, style: 'normal' }],
}
)
}
```
--------------------------------
### Option 2: Fetch on Mount (When Necessary)
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/data-patterns.md
Shows how to fetch data on the client side using `useEffect` when direct Server Component data passing is not feasible or necessary.
```tsx
'use client';
import { useEffect, useState } from 'react';
function ClientComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(r => r.json())
.then(setData);
}, []);
if (!data) return ;
return
{data.value}
;
}
```
--------------------------------
### Title Templates
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/metadata.md
Sets up a consistent title template in the root layout.
```tsx
export const metadata: Metadata = {
title: { default: 'Site Name', template: '%s | Site Name' },
}
```
--------------------------------
### Triggering Not Found
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/error-handling.md
Example of how to programmatically trigger the `not-found.tsx` component when a resource is not found.
```tsx
import { notFound } from 'next/navigation'
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const post = await getPost(id)
if (!post) {
notFound() // Renders closest not-found.tsx
}
return
{post.title}
}
```
--------------------------------
### Tailwind CSS Integration
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/font.md
Example of integrating next/font with Tailwind CSS.
```tsx
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})
export default function RootLayout({ children }) {
return (
{children}
)
}
```
```js
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'],
},
},
},
}
```
--------------------------------
### Route Segments
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/file-conventions.md
Demonstrates different types of route segments including static, dynamic, catch-all, optional catch-all, and route groups.
```bash
app/
├── blog/ # Static segment: /blog
├── [slug]/ # Dynamic segment: /:slug
├── [...slug]/ # Catch-all: /a/b/c
├── [[...slug]]/ # Optional catch-all: / or /a/b/c
└── (marketing)/ # Route group (ignored in URL)
```
--------------------------------
### Auth Error Page Components
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/error-handling.md
Example components for `forbidden.tsx` and `unauthorized.tsx` error pages.
```tsx
// app/forbidden.tsx
export default function Forbidden() {
return
You don't have access to this resource
}
// app/unauthorized.tsx
export default function Unauthorized() {
return
Please log in to continue
}
```
--------------------------------
### Analyze Bundle Size
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/bundling.md
Command to run the built-in bundle analyzer in Next.js.
```bash
next experimental-analyze
```
--------------------------------
### Avoiding Data Waterfalls - Problem: Sequential Fetches
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/data-patterns.md
Example of sequential fetches leading to data waterfalls.
```typescript
// Bad: Sequential waterfalls
async function Dashboard() {
const user = await getUser(); // Wait...
const posts = await getPosts(); // Then wait...
const comments = await getComments(); // Then wait...
return
...
;
}
```
--------------------------------
### Async Cookies and Headers
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/async-patterns.md
Demonstrates how to asynchronously access cookies and headers in Next.js.
```tsx
import { cookies, headers } from 'next/headers'
export default async function Page() {
const cookieStore = await cookies()
const headersList = await headers()
const theme = cookieStore.get('theme')
const userAgent = headersList.get('user-agent')
}
```
--------------------------------
### Save Bundle Analysis Output
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/bundling.md
Command to save the bundle analysis output for comparison.
```bash
next experimental-analyze --output
# Output saved to .next/diagnostics/analyze
```
--------------------------------
### Responsive Images with `sizes` Prop
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/image.md
Illustrates how to use the `sizes` prop with `next/image` to inform the browser about the image's display size across different viewports, enabling efficient image downloading.
```tsx
// Full-width hero
// Responsive grid (3 columns on desktop, 1 on mobile)
// Fixed sidebar image
```
--------------------------------
### Using next/script vs native script tag
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/scripts.md
Demonstrates the correct way to load external scripts using `next/script` compared to a native `
// Good: Next.js Script component
import Script from 'next/script'
```
--------------------------------
### App Router Project Structure
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/file-conventions.md
Illustrates the directory structure and the purpose of special files within the app directory for routing and UI.
```tsx
app/
├── layout.tsx # Root layout (required)
├── page.tsx # Home page (/
├── loading.tsx # Loading UI
├── error.tsx # Error UI
├── not-found.tsx # 404 UI
├── global-error.tsx # Global error UI
├── route.ts # API endpoint
├── template.tsx # Re-rendered layout
├── default.tsx # Parallel route fallback
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/:slug
└── (group)/ # Route group (no URL impact)
└── page.tsx
```
--------------------------------
### MCP Tool: get_errors
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/debug-tricks.md
JSON payload to get current errors from the dev server.
```json
{ "name": "get_errors", "arguments": {} }
```
--------------------------------
### Request-time Randomness Outside Cache
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-cache-components/SKILL.md
Example of how to achieve request-time randomness when using `use cache` limitations.
```tsx
import { connection } from 'next/server'
async function DynamicContent() {
await connection() // Defer to request time
const id = crypto.randomUUID() // Different per request
return
{id}
}
```
--------------------------------
### Standalone Output Configuration
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/self-hosting.md
Configure `next.config.js` to use standalone output for minimal production dependencies.
```javascript
// next.config.js
module.exports = {
output: 'standalone',
};
```
--------------------------------
### Intercepting Routes
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/file-conventions.md
Explains how to intercept routes from different levels using parentheses notation in the folder structure.
```tsx
app/
├── feed/
│ └── page.tsx
├── @modal/
│ └── (.)photo/[id]/ # Intercepts /photo/[id] from /feed
│ └── page.tsx
└── photo/[id]/
└── page.tsx
```
--------------------------------
### Private Folders
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/file-conventions.md
Demonstrates how to create private folders that are not part of the routing system by prefixing them with an underscore.
```tsx
app/
├── _components/ # Private folder (not a route)
│ └── Button.tsx
└── page.tsx
```
--------------------------------
### Google Fonts Integration
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/font.md
Example of integrating Google Fonts using next/font in the app directory.
```tsx
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
--------------------------------
### Testing Cache Handler with Multiple Instances
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/self-hosting.md
Demonstrates how to test the cache handler for ISR revalidation by running multiple instances of the Next.js application and verifying content consistency.
```bash
# Start multiple instances
PORT=3001 node .next/standalone/server.js &
PORT=3002 node .next/standalone/server.js &
# Trigger ISR revalidation
curl http://localhost:3001/api/revalidate?path=/posts
# Verify both instances see the update
curl http://localhost:3001/posts
curl http://localhost:3002/posts
# Should return identical content
```
--------------------------------
### Basic Usage: Native img vs. next/image
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/image.md
Compares the traditional `` tag with the recommended `next/image` component, highlighting the benefits of `next/image` for optimization.
```tsx
// Bad: Avoid native img
// Good: Use next/image
import Image from 'next/image'
```
--------------------------------
### Invalid HTML Nesting
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/hydration-error.md
Provides examples of invalid HTML nesting that can cause hydration errors and shows valid nesting.
```html
// Bad: Invalid - div inside p
Content
// Bad: Invalid - p inside p
Nested
// Good: Valid nesting
Content
```
--------------------------------
### Parallel Routes
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/file-conventions.md
Shows how to define parallel routes using route segment configurations and how the layout receives them as props.
```tsx
app/
├── @analytics/
│ └── page.tsx
├── @sidebar/
│ └── page.tsx
└── layout.tsx # Receives { analytics, sidebar } as props
```
--------------------------------
### MCP Tool: get_logs
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/debug-tricks.md
JSON payload to get the path to the Next.js development log file.
```json
{ "name": "get_logs", "arguments": {} }
```
--------------------------------
### MCP Tool: get_page_metadata
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/debug-tricks.md
JSON payload to get runtime metadata about the current page render.
```json
{ "name": "get_page_metadata", "arguments": {} }
```
--------------------------------
### Transpile Package
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/bundling.md
Configuration in `next.config.js` to transpile a package, resolving ESM/CommonJS issues.
```js
// next.config.js
module.exports = {
transpilePackages: ['some-esm-package', 'another-package'],
}
```
--------------------------------
### Exception: `use cache: private`
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-cache-components/SKILL.md
Example of using `use cache: private` for compliance requirements when refactoring is not possible.
```tsx
async function getData() {
'use cache: private'
const session = (await cookies()).get('session')?.value // Allowed
return fetchData(session)
}
```
--------------------------------
### Viewport Configuration
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/metadata.md
Configures viewport settings for responsive design and streaming support.
```tsx
import type { Viewport } from 'next'
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
themeColor: '#000000',
}
// Or dynamic
export function generateViewport({ params }): Viewport {
return { themeColor: getThemeColor(params) }
}
```
--------------------------------
### Server Actions (Preferred for Mutations)
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/data-patterns.md
Server Actions are the recommended way to handle mutations.
```typescript
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
await db.post.create({ data: { title } });
revalidatePath('/posts');
}
export async function deletePost(id: string) {
await db.post.delete({ where: { id } });
revalidateTag('posts');
}
```
```tsx
// app/posts/new/page.tsx
import { createPost } from '@/app/actions';
export default function NewPost() {
return (
);
}
```
--------------------------------
### Re-throwing Navigation Errors with `unstable_rethrow`
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/error-handling.md
Example of using `unstable_rethrow` to properly re-throw Next.js internal navigation errors within a catch block.
```tsx
import { unstable_rethrow } from 'next/navigation'
async function action() {
try {
// ...
redirect('/success')
} catch (error) {
unstable_rethrow(error) // Re-throws Next.js internal errors
return { error: 'Something went wrong' }
}
}
```
--------------------------------
### Runtime Data Constraint Solution
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-cache-components/SKILL.md
Demonstrates the correct way to handle runtime data (like cookies) when using 'use cache' by passing them as arguments.
```typescript
// Wrong - runtime API inside use cache
async function CachedProfile() {
'use cache'
const session = (await cookies()).get('session')?.value // Error!
return
{session}
}
// Correct - extract outside, pass as argument
async function ProfilePage() {
const session = (await cookies()).get('session')?.value
return
}
async function CachedProfile({ sessionId }: { sessionId: string }) {
'use cache'
// sessionId becomes part of cache key automatically
const data = await fetchUserData(sessionId)
return
{data.name}
}
```
--------------------------------
### Rendering Modal on Direct Access
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/parallel-routes.md
Example of how to render a modal component even when a user directly accesses a route, bypassing the intercepting route.
```tsx
// app/photos/[id]/page.tsx
import { Modal } from '@/components/modal';
export default async function PhotoPage({ params }) {
const { id } = await params;
const photo = await getPhoto(id);
// Option: Render as modal on direct access too
return (
);
}
```
--------------------------------
### Migrating `unstable_cache` to `use cache` - Before
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-cache-components/SKILL.md
Illustrates the 'before' state of migrating from `unstable_cache` to the `use cache` directive.
```tsx
import { unstable_cache } from 'next/cache'
const getCachedUser = unstable_cache(
async (id) => getUser(id),
['my-app-user'],
{
tags: ['users'],
revalidate: 60,
}
)
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const user = await getCachedUser(id)
return
{user.name}
}
```
--------------------------------
### Intercepting Route for Modal Content
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/parallel-routes.md
An example of an intercepting route (`(.)photos/[id]/page.tsx`) that renders modal content for a specific photo ID.
```tsx
import { Modal } from '@/components/modal';
// Assume getPhoto is defined elsewhere and fetches photo data
// async function getPhoto(id: string) { /* ... */ }
export default async function PhotoModal({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
// const photo = await getPhoto(id);
return (
{/* */}
Photo Modal Content for ID: {id}
);
}
```
--------------------------------
### Option 1: Pass from Server Component (Preferred)
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/data-patterns.md
Demonstrates passing data from a Server Component to a Client Component via props. This is the preferred method for initial data loading.
```tsx
// Server Component
async function Page() {
const data = await fetchData();
return ;
}
// Client Component
'use client';
function ClientComponent({ initialData }) {
const [data, setData] = useState(initialData);
// ...
}
```
--------------------------------
### CSS Imports
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/bundling.md
Demonstrates correct ways to import CSS files in Next.js, preferring direct imports over manual `` tags.
```tsx
// Bad: Manual link tag
// Good: Import CSS
import './styles.css'
// Good: CSS Modules
import styles from './Button.module.css'
```
--------------------------------
### usePathname - Bad Example (Dynamic Route)
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/suspense-boundaries.md
Illustrates the issue with using `usePathname` in a dynamic route segment without a Suspense boundary, causing CSR bailout.
```tsx
// In dynamic route [slug]
// Bad: No Suspense
'use client'
import { usePathname } from 'next/navigation'
export function Breadcrumb() {
const pathname = usePathname()
return
}
```
--------------------------------
### Non-Serializable Props to Client Components (Class Instance)
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/rsc-boundaries.md
Provides an example of passing a class instance to a client component, which is invalid, and the correct way using a plain object.
```tsx
// Bad: Class instance
const user = new UserModel(data)
// Methods will be stripped
// Good: Pass plain object
const user = await getUser()
```
--------------------------------
### Migrating `unstable_cache` to `use cache` - After
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-cache-components/SKILL.md
Illustrates the 'after' state of migrating from `unstable_cache` to the `use cache` directive.
```tsx
import { cacheLife, cacheTag } from 'next/cache'
async function getCachedUser(id: string) {
'use cache'
cacheTag('users')
cacheLife({ revalidate: 60 })
return getUser(id)
}
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const user = await getCachedUser(id)
return
{user.name}
}
```
--------------------------------
### Option 3: Server Action for Reads (Works But Not Ideal)
Source: https://github.com/vercel-labs/next-skills/blob/main/skills/next-best-practices/data-patterns.md
Illustrates using a Server Action to read data from a Client Component. While functional, it's not the ideal pattern for reads due to the POST-only nature of Server Actions.
```tsx
'use client';
import { getData } from './actions';
import { useEffect, useState } from 'react';
function ClientComponent() {
const [data, setData] = useState(null);
useEffect(() => {
getData().then(setData);
}, []);
return