Skip to main content
The Next.js Best Practices skill provides comprehensive guidance for building applications with Next.js App Router, covering file conventions, React Server Components, data fetching patterns, and framework-specific optimizations.

Overview

This skill covers:
  • File Conventions - Project structure and special files
  • RSC Boundaries - Server/client component patterns
  • Data Patterns - Server Components vs Server Actions vs Route Handlers
  • Async APIs - Next.js 15/16 async request APIs
This skill includes Next.js 15/16 specific guidance, including async request APIs (cookies(), headers(), params). The demo app uses Next.js 16.1.6.

File Conventions

Special Files

Next.js App Router uses file-based routing with special conventions:
FilePurpose
page.tsxUI for a route segment
layout.tsxShared UI for segment and children
loading.tsxLoading UI (Suspense boundary)
error.tsxError UI (Error boundary)
not-found.tsx404 UI
route.tsAPI endpoint
template.tsxLike layout but re-renders
default.tsxFallback for parallel routes

Route Segments

app/blog/page.tsx → /blog
Standard static route segment.

Parallel & Intercepting Routes

// app/layout.tsx
export default function Layout({
  children,
  analytics,
  sidebar
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  sidebar: React.ReactNode
}) {
  return (
    <>
      {children}
      {analytics}
      {sidebar}
    </>
  )
}

// app/@analytics/page.tsx
// app/@sidebar/page.tsx

Data Fetching Patterns

Decision Tree

Need to fetch data?
├── From Server Component?
│   └── Use: Fetch directly (no API needed)

├── From Client Component?
│   ├── Mutation (POST/PUT/DELETE)?
│   │   └── Use: Server Action
│   └── Read (GET)?
│       └── Use: Route Handler OR pass from Server Component

├── External API access (webhooks)?
│   └── Use: Route Handler

└── REST API for mobile/external?
    └── Use: Route Handler

Pattern 1: Server Components (Preferred)

// app/users/page.tsx
async function UsersPage() {
  // Direct database access - no API round-trip
  const users = await db.user.findMany()

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}
Benefits:
  • No API to maintain
  • No client-server waterfall
  • Secrets stay on server
  • Direct database access

Pattern 2: Server Actions (Mutations)

'use server'

import { revalidatePath } from 'next/cache'
import { z } from 'zod'

const createPostSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string().min(1)
})

export async function createPost(formData: FormData) {
  // 1. Validate input
  const validated = createPostSchema.parse({
    title: formData.get('title'),
    content: formData.get('content')
  })

  // 2. Authenticate
  const session = await auth()
  if (!session) {
    throw new Error('Unauthorized')
  }

  // 3. Perform mutation
  await db.post.create({
    data: {
      ...validated,
      authorId: session.user.id
    }
  })

  // 4. Revalidate
  revalidatePath('/posts')
}
Benefits:
  • End-to-end type safety
  • Progressive enhancement (works without JS)
  • Automatic request handling
  • Integrated with React transitions
Constraints:
  • POST only (no GET caching semantics)
  • Internal use only (no external access)
  • Cannot return non-serializable data

Pattern 3: Route Handlers (APIs)

import { NextRequest, NextResponse } from 'next/server'

// GET is cacheable
export async function GET(request: NextRequest) {
  const posts = await db.post.findMany()
  return NextResponse.json(posts)
}

// POST for mutations
export async function POST(request: NextRequest) {
  const body = await request.json()
  const post = await db.post.create({ data: body })
  return NextResponse.json(post, { status: 201 })
}
When to use:
  • External API access (mobile apps, third parties)
  • Webhooks from external services
  • GET endpoints that need HTTP caching
  • OpenAPI/Swagger documentation needed
When NOT to use:
  • Internal data fetching (use Server Components)
  • Mutations from your UI (use Server Actions)

RSC Boundaries

Invalid Patterns

'use client'

// This will error - client components cannot be async
export default async function ClientComponent() {
  const data = await fetchData()
  return <div>{data}</div>
}

Non-Serializable Props

// Server Component
function ServerComponent() {
  const handleClick = () => console.log('clicked')
  return <ClientComponent onClick={handleClick} />
  // Error: Functions are not serializable
}

Async APIs (Next.js 15/16)

Next.js 15 and 16 use async APIs for request-specific data for performance reasons.
Before (Next.js 14)
export default function Page({ params }: Props) {
  const { id } = params
  return <div>{id}</div>
}
After (Next.js 15/16)
export default async function Page({ params }: Props) {
  const { id } = await params
  return <div>{id}</div>
}
Run npx @next/codemod@latest upgrade to automatically migrate async APIs.

Image & Font Optimization

next/image

<img src="/profile.jpg" alt="Profile" />
// No optimization, no lazy loading

next/font

import { Inter, Roboto_Mono } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap'
})

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap'
})

export default function RootLayout({ children }) {
  return (
    <html className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

Error Handling

app/error.tsx
'use client'

export default function Error({
  error,
  reset
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  )
}

When to Use This Skill

Load this skill when:
  • Setting up new Next.js projects
  • Choosing between Server Components, Server Actions, and Route Handlers
  • Implementing data fetching patterns
  • Optimizing images and fonts
  • Handling errors and edge cases
  • Migrating from Pages Router to App Router

Skill Structure

.github/skills/next-best-practices/
├── SKILL.md              # Quick reference
├── file-conventions.md   # Project structure
├── data-patterns.md      # Data fetching
├── rsc-boundaries.md     # Server/client patterns
├── async-patterns.md     # Next.js 15/16 async APIs
├── error-handling.md     # Error boundaries
├── image.md             # Image optimization
├── font.md              # Font optimization
└── metadata.md          # SEO & Open Graph

References

Build docs developers (and LLMs) love