Next.js cheat sheet
A scannable Next.js reference: 19 short snippets across 11 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| File-based routing and layouts | In the App Router, a directory under app/ becomes a URL segment, and a page.js inside it makes that segment | lesson |
| Data fetching and server components | Components in the App Router render on the server unless a file opts in with the 'use client' directive at the top | lesson |
| Building and deploying | The build output marks each route as static, dynamic or a server function, and shows the first-load JavaScript for | lesson |
| Setting up a project with create-next-app | Routes are created by file names, not by registration. Only page, layout, route, loading, error, not-found and a few | lesson |
| Routing, links and navigation | The Link component and prefetching, the navigation hooks, route groups and catch-all segments, and when to redirect | lesson |
| Server actions and forms in depth | The use server directive, form actions, useActionState and useFormStatus, structured errors, revalidation and | lesson |
| Caching, revalidation and Cache Components | The caching layers, when fetch is not cached, caching a whole computation with use cache, cacheLife and cacheTag, and | lesson |
| Middleware, headers, cookies and sessions | Run code before a route renders, choose between a rewrite and a redirect, and protect a route group without trusting | lesson |
| Styling, fonts and image optimisation | CSS Modules versus global CSS versus Tailwind, self-hosted fonts with next/font, and sizing images correctly with | lesson |
| Streaming, Suspense and partial prerendering | Instant loading UI, streaming server components with Suspense boundaries, skeletons that match the layout, and a static | lesson |
| Testing, error handling and observability | Component tests with Vitest, end-to-end flows with Playwright, error boundaries that users can recover from, and | lesson |
Quick snippets
File-based routing and layouts
Nested layouts that survive navigation
// app/docs/layout.js — wraps every route under /docs
export default function DocsLayout({ children }) {
return (
<div className="docs">
<DocsSidebar />
{children}
</div>
)
}Full lesson: File-based routing and layouts →
Data fetching and server components
Server by default, client on request
// app/guides/page.js — async server component, no loading spinner needed
import { db } from '@/lib/db'
export default async function Guides() {
const guides = await db.guide.findMany({ orderBy: { title: 'asc' } })
return <GuideList guides={guides} />
}
Server by default, client on request
'use client' // only what needs interactivity
import { useState } from 'react'
export default function SearchBox({ initial = '' }) {
const [q, setQ] = useState(initial)
return <input value={q} onChange={e => setQ(e.target.value)} />
}
Mutations with server actions
// app/guides/new/page.js
import { createGuide } from '../actions'
export default function NewGuide() {
return (
<form action={createGuide}>
<input name="title" required />
<button type="submit">Create</button>
</form>
)
}Full lesson: Data fetching and server components →
Building and deploying
What the build tells you
npx create-next-app@latest my-app --ts --app --eslint
npm run dev # development server with fast refresh
npm run build # production build; reports route types and sizes
npm start # serve the production build on a Node server
# a self-contained server bundle for containers
# next.config.js -> output: 'standalone'Full lesson: Building and deploying →
Setting up a project with create-next-app
create-next-app and its flags
npx create-next-app@latest my-app \
--ts --app --eslint --tailwind --src-dir --turbopack \
--import-alias "@/*"
cd my-app
npm run dev # http://localhost:3000
npm run build # production build into .next/
npm start # serve the build (never in development)
The folder layout
my-app/ (with --src-dir)
src/app/
layout.tsx required root layout: renders html and body
page.tsx the / route
globals.css imported once by the root layout
favicon.ico
public/ served from /, copied unprocessed
next.config.ts framework configuration
tsconfig.json paths: { "@/*": ["./src/*"] }
eslint.config.mjs
package.jsonFull lesson: Setting up a project with create-next-app →
Routing, links and navigation
Route groups, catch-all segments and redirects
app/
(marketing)/
about/page.tsx -> /about (group name is not in the URL)
pricing/page.tsx -> /pricing
(shop)/
layout.tsx -> shell shared by every route in the group
docs/[...slug]/page.tsx -> /docs/a/b catch-all
shop/[[...filters]]/page.tsx -> /shop and /shop/a optional catch-all
old/page.tsx -> redirect('/new')Full lesson: Routing, links and navigation →
Server actions and forms in depth
Actions and form actions
// app/guides/new/page.tsx — works before any JavaScript has loaded
import { createGuide } from '../actions'
export default function NewGuide() {
return (
<form action={createGuide}>
<input name="title" />
<button type="submit">Create</button>
</form>
)
}
Revalidation and optimistic UI
// app/guides/actions.ts
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
export async function addComment(guideId, formData) {
await db.comment.create({ data: { guideId, body: String(formData.get('body') ?? '') } })
revalidateTag('guide-' + guideId) // only the data this write affected
revalidatePath('/guides/' + guideId) // the route and its cache
}Full lesson: Server actions and forms in depth →
Caching, revalidation and Cache Components
Invalidating on demand
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
import { db } from '@/lib/db'
export async function publishGuide(id) {
await db.guide.update({ where: { id }, data: { published: true } })
revalidateTag('guides') // every entry tagged 'guides'
revalidatePath('/guides') // the route and everything cached below it
// revalidatePath('/guides', 'page') // only that page, not nested segments
}Full lesson: Caching, revalidation and Cache Components →
Middleware, headers, cookies and sessions
Sessions and protecting a route group
// lib/session.ts
import 'server-only'
import { cache } from 'react'
import { cookies } from 'next/headers'
export const getSession = cache(async () => {
const token = (await cookies()).get('session')?.value
if (!token) return null
return verifyToken(token) // signature check, no round trip
})
Sessions and protecting a route group
// app/(app)/layout.tsx — protect every route in the group
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/session'
export default async function AppLayout({ children }) {
const session = await getSession()
if (!session) redirect('/login')
return <AppShell user={session.user}>{children}</AppShell>
}Full lesson: Middleware, headers, cookies and sessions →
Styling, fonts and image optimisation
CSS Modules, global CSS and Tailwind
// app/guides/page.tsx
import styles from './guides.module.css' // class names are scoped to this file
export default function Guides({ open }) {
return (
<>
<h1 className={styles.heading}>Guides</h1>
<div className={['card', open ? 'card-open' : ''].join(' ')} />
</>
)
}
CSS Modules, global CSS and Tailwind
/* app/guides/guides.module.css */
.heading { font-size: 2rem; letter-spacing: -0.02em; }
.card { border: 1px solid var(--border); border-radius: 8px; }Full lesson: Styling, fonts and image optimisation →
Streaming, Suspense and partial prerendering
loading.tsx and Suspense boundaries
// app/guides/loading.tsx — the whole segment gets an instant fallback
export default function Loading() {
return <Skeleton rows={5} />
}Full lesson: Streaming, Suspense and partial prerendering →
Testing, error handling and observability
Unit and component tests
// tests/GuideCard.test.tsx
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import GuideCard from '@/components/GuideCard'
describe('GuideCard', () => {
it('links to the guide and shows its title', () => {
render(<GuideCard guide={{ slug: 'intro', title: 'Intro' }} />)
expect(screen.getByRole('link', { name: /intro/i }))
.toHaveAttribute('href', '/guides/intro')
})
})
End-to-end flows and error boundaries
// e2e/guides.spec.ts
import { expect, test } from '@playwright/test'
test('a visitor can open a guide', async ({ page }) => {
await page.goto('/guides')
await page.getByRole('link', { name: 'Intro' }).click()
await expect(page).toHaveURL(/\/guides\/intro$/)
await expect(page.getByRole('heading', { level: 1 })).toBeVisible()
})
End-to-end flows and error boundaries
// app/guides/error.tsx — must be a client component
'use client'
export default function Error({ error, reset }) {
return (
<div role="alert">
<p>We could not load the guides.</p>
<button onClick={reset}>Try again</button>
{process.env.NODE_ENV !== 'production' && <pre>{error.message}</pre>}
</div>
)
}Full lesson: Testing, error handling and observability →
FAQ
Is this Next.js cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
HTML CSS JavaScript TypeScript HTML DOM AJAX
Last refreshed 2026-09-27.