# Next.js Best Practices (2025) Current best practices for Next.js 15/16 development with App Router, Server Components, and modern patterns. ## Framework Versions ### Current Versions - **Next.js**: 15.x or 16.x (latest stable) - **React**: 19.x (Server Components, Actions) - **TypeScript**: 5.x - **Node.js**: 20 LTS or higher ### Deprecated Versions to Flag - Next.js 12.x or older (Pages Router era) - React 17.x or older - TypeScript 4.x or older ## App Router vs Pages Router ### [OK] Modern (App Router) ``` app/ ├── layout.tsx # Root layout ├── page.tsx # Homepage ├── entities/ │ ├── layout.tsx # Nested layout │ ├── page.tsx # Entity list │ └── [id]/ │ └── page.tsx # Entity detail └── api/ # API route handlers (minimal) └── webhook/ └── route.ts # External webhooks only ``` ### [ERROR] Deprecated (Pages Router) ``` pages/ ├── _app.tsx # Old pattern ├── _document.tsx # Old pattern ├── index.tsx # Old pattern ├── entities/ │ ├── index.tsx │ └── [id].tsx └── api/ # Old API routes └── entities.ts ``` **Migration Check**: Flag any skills using `pages/` directory or `getServerSideProps` ## Server Components vs Client Components ### Default: Server Components ```tsx // app/entities/page.tsx // No 'use client' directive = Server Component (default) import { db } from '@/lib/db' export default async function EntitiesPage() { // Direct database access - only in Server Components const entities = await db.entity.findMany() return (

Entities

{entities.map(entity => ( ))}
) } ``` ### Client Components (When Needed) ```tsx // components/EntityForm.tsx 'use client' // Required for hooks, events, browser APIs import { useState } from 'react' import { useForm } from 'react-hook-form' export function EntityForm() { const [isSubmitting, setIsSubmitting] = useState(false) const form = useForm() return
...
} ``` **When to use 'use client':** - useState, useEffect, other hooks - Event handlers (onClick, onChange) - Browser APIs (localStorage, window) - Third-party libraries requiring browser **Best Practice**: Keep 'use client' boundary as low as possible in component tree ## Data Fetching ### [OK] Modern: Server Components + fetch ```tsx // app/characters/page.tsx export default async function CharactersPage() { // Fetch in Server Component const characters = await fetch('https://api.example.com/characters', { next: { revalidate: 3600 } // Cache for 1 hour }).then(res => res.json()) return } ``` ### [OK] Modern: Server Components + Database ```tsx // app/locations/page.tsx import { db } from '@/lib/db' export default async function LocationsPage() { // Direct database access const locations = await db.location.findMany({ include: { region: true } }) return } ``` ### [ERROR] Deprecated: getServerSideProps ```tsx // pages/characters.tsx - OLD PATTERN export async function getServerSideProps() { const characters = await fetchCharacters() return { props: { characters } } } ``` **Migration Check**: Replace `getServerSideProps`, `getStaticProps`, `getInitialProps` with Server Component async data fetching ## Server Actions ### [OK] Modern: Server Actions for Mutations ```tsx // app/actions/character.ts 'use server' import { db } from '@/lib/db' import { revalidatePath } from 'next/cache' export async function createCharacter(formData: FormData) { const name = formData.get('name') as string const character = await db.character.create({ data: { name } }) revalidatePath('/characters') return { success: true, character } } ``` ```tsx // app/characters/CreateForm.tsx 'use client' import { createCharacter } from '@/app/actions/character' export function CreateForm() { return (
) } ``` ### [ERROR] Deprecated: API Routes for Simple Mutations ```tsx // pages/api/characters.ts - OLD PATTERN export default async function handler(req, res) { if (req.method === 'POST') { const character = await createCharacter(req.body) res.json({ character }) } } ``` **When to still use API Routes:** - Webhooks from external services - OAuth callbacks - Third-party integrations requiring public endpoints **Migration Check**: Replace API routes used for form submissions with Server Actions ## Metadata and SEO ### [OK] Modern: generateMetadata ```tsx // app/characters/[id]/page.tsx import { Metadata } from 'next' export async function generateMetadata({ params }): Promise { const character = await db.character.findUnique({ where: { id: params.id } }) return { title: character.name, description: character.bio, openGraph: { title: character.name, description: character.bio, images: [character.image], }, } } ``` ### [ERROR] Deprecated: next/head ```tsx // OLD PATTERN import Head from 'next/head' {character.name} ``` ## Routing and Navigation ### [OK] Modern: Link from next/link ```tsx import Link from 'next/link' View Character ``` ### [OK] Modern: useRouter from next/navigation ```tsx 'use client' import { useRouter } from 'next/navigation' // Not 'next/router'! export function BackButton() { const router = useRouter() return } ``` ### [ERROR] Deprecated: useRouter from next/router ```tsx // OLD PATTERN import { useRouter } from 'next/router' // Pages Router only ``` ## Loading States ### [OK] Modern: loading.tsx ```tsx // app/characters/loading.tsx export default function Loading() { return
Loading characters...
} ``` ### [OK] Modern: Suspense Boundaries ```tsx // app/characters/page.tsx import { Suspense } from 'react' export default function Page() { return ( }> ) } ``` ## Error Handling ### [OK] Modern: error.tsx ```tsx // app/characters/error.tsx 'use client' export default function Error({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { return (

Something went wrong!

) } ``` ## Caching and Revalidation ### [OK] Modern: Revalidation Strategies ```tsx // Time-based revalidation fetch(url, { next: { revalidate: 3600 } }) // 1 hour // On-demand revalidation import { revalidatePath } from 'next/cache' revalidatePath('/characters') // Tag-based revalidation fetch(url, { next: { tags: ['characters'] } }) import { revalidateTag } from 'next/cache' revalidateTag('characters') // Disable caching fetch(url, { cache: 'no-store' }) ``` ## Middleware ### [OK] Modern: middleware.ts ```tsx // middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { // Auth check const token = request.cookies.get('token') if (!token && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)) } return NextResponse.next() } export const config = { matcher: '/dashboard/:path*', } ``` ## TypeScript Patterns ### [OK] Type-Safe Server Components ```tsx interface Character { id: string name: string bio: string } export default async function CharacterPage({ params, }: { params: { id: string } }) { const character: Character = await db.character.findUnique({ where: { id: params.id } }) return
{character.name}
} ``` ### [OK] Type-Safe Server Actions ```tsx 'use server' import { z } from 'zod' const createCharacterSchema = z.object({ name: z.string().min(2), bio: z.string().max(5000), }) export async function createCharacter( data: z.infer ) { const validated = createCharacterSchema.parse(data) // ... } ``` ## Environment Variables ### [OK] Modern: Type-Safe Env ```typescript // env.mjs import { createEnv } from "@t3-oss/env-nextjs" import { z } from "zod" export const env = createEnv({ server: { DATABASE_URL: z.string().url(), NEXTAUTH_SECRET: z.string().min(1), }, client: { NEXT_PUBLIC_API_URL: z.string().url(), }, runtimeEnv: { DATABASE_URL: process.env.DATABASE_URL, NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, }, }) ``` ## Configuration ### [OK] Modern: next.config.js ```javascript /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { serverActions: { bodySizeLimit: '2mb', }, }, images: { remotePatterns: [ { protocol: 'https', hostname: 'example.com', }, ], }, } export default nextConfig ``` ## Common Anti-Patterns to Flag ### [ERROR] Fetching in Client Components ```tsx 'use client' import { useEffect, useState } from 'react' // BAD: Fetch in client component export function Characters() { const [data, setData] = useState([]) useEffect(() => { fetch('/api/characters').then(r => r.json()).then(setData) }, []) return
{data.map(...)}
} ``` [OK] **Fix**: Move data fetching to Server Component or use React Query for client-side data management ### [ERROR] Unnecessary 'use client' ```tsx 'use client' // Not needed! export function StaticCard({ title, description }) { return (

{title}

{description}

) } ``` [OK] **Fix**: Remove 'use client' if component doesn't use hooks, events, or browser APIs ### [ERROR] Mixing Data Fetching Methods ```tsx // BAD: Using both old and new patterns export async function getServerSideProps() { ... } export default async function Page() { const data = await fetch(...) } ``` [OK] **Fix**: Use only Server Component data fetching in App Router ## Performance Best Practices ### Image Optimization ```tsx import Image from 'next/image' Character portrait ``` ### Font Optimization ```tsx import { Inter } from 'next/font/google' const inter = Inter({ subsets: ['latin'] }) export default function RootLayout({ children }) { return ( {children} ) } ``` ### Code Splitting ```tsx import dynamic from 'next/dynamic' const HeavyComponent = dynamic(() => import('@/components/HeavyComponent'), { loading: () =>

Loading...

, ssr: false, // Client-only if needed }) ``` ## Validation Checklist When reviewing Next.js skills, check for: - [ ] Uses App Router (app/ directory), not Pages Router - [ ] Server Components for data fetching - [ ] Server Actions for mutations - [ ] Proper 'use client' boundaries - [ ] generateMetadata for SEO - [ ] useRouter from 'next/navigation' - [ ] Modern caching with fetch options - [ ] loading.tsx and error.tsx files - [ ] Type-safe environment variables - [ ] Image and font optimization - [ ] No deprecated APIs (getServerSideProps, etc.) - [ ] Proper middleware usage - [ ] Correct import paths ## Quick Reference ### Must Use (Modern) - [OK] `app/` directory - [OK] Server Components (default) - [OK] Server Actions ('use server') - [OK] `generateMetadata` - [OK] `useRouter` from 'next/navigation' - [OK] `revalidatePath`/`revalidateTag` ### Must Avoid (Deprecated) - [ERROR] `pages/` directory - [ERROR] `getServerSideProps` - [ERROR] `getStaticProps` - [ERROR] `getInitialProps` - [ERROR] API routes for mutations - [ERROR] `useRouter` from 'next/router' - [ERROR] `next/head`