The App Router file conventions, caching behavior, and server action patterns you actually need day-to-day. No fluff. Covers Next.js 15 defaults (fetch is uncached, params are async, etc.). Keep this tab open.
| Name | Syntax | Description |
|---|---|---|
| page.tsx | export default function Page({ params, searchParams }) {} | Unique UI for a route. Makes the segment publicly accessible. Server Component by default. |
| layout.tsx | export default function Layout({ children }) {} | Shared UI that wraps child segments. Persists across navigations — state is preserved. |
| loading.tsx | export default function Loading() {} | Instant loading UI via React Suspense. Shown while the page segment streams in. |
| error.tsx | 'use client'; export default function Error({ error, reset }) {} | Error boundary for a segment. Must be a Client Component. Call reset() to retry. |
| not-found.tsx | export default function NotFound() {} | Rendered when notFound() is called in the segment. Scoped 404 page. |
| route.tsx | export async function GET(request: Request) {} | API route handler. Export named functions for HTTP methods (GET, POST, PUT, DELETE). |
| template.tsx | export default function Template({ children }) {} | Like layout, but re-mounts on every navigation. Useful for enter/exit animations. |
| default.tsx | export default function Default() {} | Fallback for parallel route slots when the slot URL doesn't match after a soft navigation. |
| middleware.ts | export function middleware(request: NextRequest) {} | Runs before every matched request. Redirect, rewrite, or set headers. Lives at project root. |
| [slug] / [...slug] | app/blog/[slug]/page.tsx app/docs/[...slug]/page.tsx | Dynamic segments. [slug] matches one segment. [...slug] catches all remaining segments. |
| (group) | app/(marketing)/about/page.tsx | Route group. Organizes routes without affecting the URL. Useful for separate layouts. |
| @slot | app/@analytics/page.tsx → layout gets { analytics } prop | Parallel route slot. Renders multiple pages in the same layout simultaneously. |
| (.)intercepting | app/@modal/(.)photo/[id]/page.tsx | Intercepting route. (.) same level, (..) one up. Classic pattern: modal on click, full page on refresh. |
| generateStaticParams | export async function generateStaticParams() { return [{ slug: '...' }] } | Pre-renders dynamic routes at build time. Replaces getStaticPaths from the Pages Router. |
| generateMetadata | export async function generateMetadata({ params }) { return { title: '...' } } | Dynamic <head> metadata based on route params or fetched data. Async, runs on the server. |
layout → template → error → loading → not-found → pageSpecial files are rendered in a fixed hierarchy. Layouts wrap everything below, templates re-mount on nav, error boundaries catch throws, loading shows during streaming, and page is the leaf.
Tips
[param] | [...catchAll] | [[...optionalCatchAll]]Brackets in folder names create dynamic segments. In Next.js 15, params is a Promise that must be awaited. Catch-all routes capture arbitrarily deep paths.
Tips
(groupName) for route groups | _folderName for privateRoute groups let you apply different layouts to different sections without affecting URLs. Prefix folders with _ to exclude them from routing entirely.
Tips
async function + await fetch() in RSCServer Components are async. Fetch data directly in the component body with async/await. No hooks, no loading state management, no client-side waterfall.
Tips
export async function generateStaticParams()Pre-renders dynamic routes at build time. Return an array of param objects matching the dynamic segment name. Replaces getStaticPaths from the Pages Router.
Tips
Promise.all([fetchA(), fetchB()])Sequential awaits create data fetching waterfalls. Use Promise.all to kick off independent requests simultaneously and cut total load time.
Tips
fetch(url, { cache, next: { revalidate, tags } })Next.js extends fetch with caching controls. In Next.js 15, fetch is uncached by default (breaking change from 14). Use force-cache or revalidate to opt in.
Tips
revalidatePath(path) | revalidateTag(tag)Purge cached data immediately after a mutation or via a webhook. revalidatePath invalidates an entire route, revalidateTag invalidates all fetches sharing that tag.
Tips
export const dynamic | revalidate | runtime | fetchCacheExport config constants from page.tsx or layout.tsx to control caching and rendering behavior at the segment level. Acts as a blanket override for all fetches in the segment.
Tips
'use server' at file top or inline in functionServer Actions are async functions that run exclusively on the server. Mark them with 'use server' either per-file or per-function. They work as form actions with progressive enhancement.
Tips
const [state, formAction, isPending] = useActionState(action, initialState)Full-stack form pattern: validate with Zod on the server, return field errors, and display them on the client. Works without JavaScript (progressive enhancement).
Tips
onClick={() => serverAction(data)} outside of formsServer Actions aren't limited to forms. Call them from click handlers, effects, or any client-side code. Wrap in startTransition for non-blocking updates.
Tips
export const metadata = {} | export async function generateMetadata()Export metadata (static) or generateMetadata (dynamic) from page.tsx or layout.tsx. Next.js automatically generates the correct <head> tags. Metadata merges down the layout tree.
Tips
opengraph-image.tsx | sitemap.ts | robots.tsDrop specially-named files into route folders for automatic OG images, sitemaps, and robots.txt. Next.js picks them up by convention and serves them at the right paths.
Tips
middleware.ts + matcher configMiddleware runs before every matched request. Use it for coarse auth checks (redirecting unauthenticated users). Always verify authorization again in Server Components close to the data.
Tips
generateStaticParams + revalidate + revalidateTagBuild static pages at deploy time, serve them from cache, and refresh individual pages on a timer or on demand. Best of both worlds: static speed with dynamic freshness.
Tips
@slot + (.)intercept patternThe classic Instagram/Twitter pattern: clicking a photo opens a modal (intercepted route), refreshing the page shows the full photo page (direct route). Requires a parallel route slot + intercepting route.
Tips
Check auth directly in the Server Component. redirect() throws internally (never returns), so code below it only runs for authenticated users. This is server-side only — no client bundle impact.
The production-ready pattern: Zod validates on the server, field errors flow back to the form via useActionState, and the cache is refreshed after the write. Works without JS (progressive enhancement).
fetch() is uncached by default in Next.js 15 — your pages may be slower than expected if you're upgrading from 14
Next.js 14 cached fetch by default (force-cache). In 15, fetch is uncached unless you explicitly opt in with { cache: 'force-cache' } or { next: { revalidate: N } }. Audit your fetches after upgrading — add caching where the data doesn't change often.
'use client' doesn't mean "client only" — it marks the boundary where Server Components stop and Client Components begin
A 'use client' component is still pre-rendered on the server (SSR), then hydrated on the client. It just can't do server-only things (direct DB access, fs, etc.). You CAN pass Server Components as children of Client Components — they stay on the server.
params and searchParams are now Promises in Next.js 15 — forgetting to await them is a runtime error
In page.tsx and layout.tsx, params and searchParams are async. Write: const { slug } = await params. The old synchronous access (params.slug) will throw. This also applies to generateMetadata and generateStaticParams context.
error.tsx cannot catch errors thrown in the layout of the same segment — it only catches errors in page.tsx and children
error.tsx wraps the page, not the layout. If your layout throws, the error bubbles up to the parent segment's error.tsx. For root layout errors, use app/global-error.tsx (which must define its own <html> and <body>).
Middleware (proxy.ts in v16) runs on the Edge runtime — you cannot use Node.js APIs like fs, Prisma, or heavy libraries
Keep middleware thin: check cookies, redirect, set headers. Move heavy logic (DB queries, auth token verification with libraries) into Server Components or Route Handlers that run on the Node.js runtime. Use a lightweight JWT decode for token checks in middleware.
Go beyond the cheatsheet with hands-on lessons and challenges.