Next.js Rules
Rule
Components
- MUSTUse Server Components by default. Add
"use client"only when needed. - MUSTUse App Router metadata API for
<head>content, notnext/head. - NEVERUse async client components. Use Server Components for async operations.
- MUST
app/**/page.tsxandapp/**/layout.tsxown the route's visual shape and remain Server Components. - MUSTWhen interactivity is needed in a route, keep
page.tsx/layout.tsxserver-side and compose focused client leaves/providers inside them. - MUSTFor route-wide client state, use a scoped Context or scoped Zustand store/provider, not prop drilling through route structure.
- NEVERCreate catch-all client wrappers (
*Shell,*Wrapper,*Content,*ClientLayout,*UI) just to avoid putting"use client"in route files. - NEVERMove route structure out of
page.tsx/layout.tsxinto a client "god component".
Assets & Loading
- MUSTUse
next/fontfor fonts andnext/scriptfor third-party scripts. - MUSTUse
next/imagefor all images. - MUSTEvery
<Image>must have asizesprop. Without it, the browser requests the largest srcSet candidate (up to 3840px) regardless of viewport. Example:sizes="(max-width: 768px) 100vw, 50vw". - SHOULDAbove-the-fold images use
loading="eager"orfetchPriority="high". Useprioritysparingly.
Proxy (replaces Middleware in Next.js 16+)
- MUSTNew projects use
proxy.tsinstead ofmiddleware.ts - MUSTExport function named
proxy, notmiddleware - NOTE: Runs on Node.js only (Edge runtime not supported)
- SHOULDMigrate existing middleware.ts using codemod
// src/proxy.ts (Next.js 16+)
import { NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
// Auth check, redirects, rewrites, etc.
return NextResponse.next();
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
Migration from middleware.ts
npx @next/codemod@latest upgrade latest
Or manually:
- Rename
middleware.ts→proxy.ts - Rename exported function
middleware→proxy - Remove Edge runtime APIs (not supported in proxy)
Caching (Next.js 16+)
- MUSTUse
use cachedirective for explicit caching (opt-in model) - MUSTEnable
cacheComponents: truein next.config.ts for component caching - SHOULDUse
use cache: remotein serverless for shared cache - NEVERAccess cookies()/headers()/searchParams inside cached scope
// Explicit caching with use cache directive
async function getData(id: string) {
"use cache";
return db.query.items.findFirst({ where: eq(items.id, id) });
}
// Component-level caching
async function CachedComponent() {
"use cache";
const data = await getData();
return <div>{data.title}</div>;
}
Bundle Optimization
- MUSTNever import from barrel files for large icon/component libraries.
import { Check } from 'lucide-react'loads 1,500+ modules. Import directly:import { Check } from 'lucide-react/dist/esm/icons/check'. Or useoptimizePackageImportsin next.config:experimental: { optimizePackageImports: ['lucide-react', '@mui/material'] } - MUSTUse
next/dynamicwith{ ssr: false }for heavy client-only components (editors, charts, maps). Keeps them out of the initial bundle. - SHOULDDefer non-critical third-party scripts (analytics, tracking) by dynamically importing them with
{ ssr: false }. - SHOULDPreload heavy modules on hover/focus when a user interaction will trigger them (e.g., preload editor on "Open Editor" button hover).
Server Performance
- MUSTAuthenticate inside Server Actions — they are public endpoints. Always call
verifySession()or equivalent before any mutation. - MUSTWhen using
React.cache(), pass primitives not objects.cache(async (params: { id: string }) => ...)always misses — usecache(async (id: string) => ...). - SHOULDOnly pass fields the client component actually uses across the RSC boundary, not entire objects.
<Profile name={user.name} />not<Profile user={user} />. - SHOULDUse
after()fromnext/serverto schedule non-blocking work (logging, analytics) that runs after the response is sent.
Feature Structure
- SHOULDAdd new features under
apps/<app>/features/<feature>/. - SHOULDWithin a feature, organize by kind:
components/,hooks/,utils/,lib/,types/. - SHOULDPrefer feature-local state (Context or local Zustand) scoped to the feature tree.
- SHOULDPlace shared components in
apps/<app>/components/rather than a feature folder.