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

TopicWhat it covers
File-based routing and layoutsIn the App Router, a directory under app/ becomes a URL segment, and a page.js inside it makes that segmentlesson
Data fetching and server componentsComponents in the App Router render on the server unless a file opts in with the 'use client' directive at the toplesson
Building and deployingThe build output marks each route as static, dynamic or a server function, and shows the first-load JavaScript forlesson
Setting up a project with create-next-appRoutes are created by file names, not by registration. Only page, layout, route, loading, error, not-found and a fewlesson
Routing, links and navigationThe Link component and prefetching, the navigation hooks, route groups and catch-all segments, and when to redirectlesson
Server actions and forms in depthThe use server directive, form actions, useActionState and useFormStatus, structured errors, revalidation andlesson
Caching, revalidation and Cache ComponentsThe caching layers, when fetch is not cached, caching a whole computation with use cache, cacheLife and cacheTag, andlesson
Middleware, headers, cookies and sessionsRun code before a route renders, choose between a rewrite and a redirect, and protect a route group without trustinglesson
Styling, fonts and image optimisationCSS Modules versus global CSS versus Tailwind, self-hosted fonts with next/font, and sizing images correctly withlesson
Streaming, Suspense and partial prerenderingInstant loading UI, streaming server components with Suspense boundaries, skeletons that match the layout, and a staticlesson
Testing, error handling and observabilityComponent tests with Vitest, end-to-end flows with Playwright, error boundaries that users can recover from, andlesson

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.json

Full 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?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 11 lessons of the Next.js course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full Next.js course — it carries the worked explanations, the edge cases and the exercises behind every line here.

HTML CSS JavaScript TypeScript HTML DOM AJAX

Last refreshed 2026-09-27.