cheat sheet
next
Package-level reference for Next.js — App Router, server components, server actions, middleware, caching, and the 14 → 15 → 16 migration path.
next
What it is
next is Vercel's full-stack React framework: routing, server rendering, client hydration, API routes, middleware, image optimisation, font loading, and bundling all in one package. The App Router (default since Next 13) treats every file under app/ as a route, makes server components the default, and folds data fetching into async components directly.
Reach for Next.js when you want React + SSR + routing in a single supported toolkit, and when deployment to Vercel (or a Node-compatible host) is acceptable. Reach for Remix / React Router 7 if your data model is request-shaped, Astro if the site is mostly static, or Vite + React if you only need an SPA.
Install
Next prefers a scaffolded project.
npx create-next-app@latest my-app
Output: interactive prompts (TypeScript, ESLint, Tailwind, App Router, src/ dir, import alias); scaffolds and runs npm install.
For an existing project, install directly:
npm install next react react-dom
Output: added next + the React peers to dependencies
pnpm add next react react-dom
Output: added 3 packages
yarn add next react react-dom
Output: added next, react, react-dom
bun add next react react-dom
Output: installed next, react, react-dom
Versioning & Node support
Next ships a major roughly yearly. As of late 2025, the current line is next@15.x / next@16.x (track nextjs.org/blog for the exact current major).
- Node 18.17+ minimum; Node 20 LTS recommended.
- Pairs with
react@18.3+(15.x) andreact@19+(16.x); always pin peers in lockstep. - Dual ESM/CJS internally; user code can be ESM or CJS.
- TypeScript types bundled.
- Major bumps move quickly — read the release notes before upgrading a production app.
Package metadata
- Maintainer: Vercel
- Project home: github.com/vercel/next.js
- Docs: nextjs.org/docs
- npm: npmjs.com/package/next
- License: MIT
- First released: 2016
- Downloads: consistently top-20 on npm — millions of weekly downloads.
Peer dependencies & extras
Next declares react and react-dom as peer deps. Common extras:
@next/font(now built intonext/font) — font self-hostingnext-auth/@auth/core— authentication@next/bundle-analyzer— visualise bundle size@next/mdx— MDX supporteslint-config-next— Next-tuned ESLinttailwindcss— paired by default increate-next-app@vercel/analytics,@vercel/speed-insights— first-party telemetrydrizzle-orm/prisma— common DB layerszod/valibot— input validation for server actions and route handlers
Alternatives
| Package | Trade-off |
|---|---|
remix / react-router v7+ | Same React + SSR space. Request/response-shaped data API. Less Vercel-flavoured. |
astro | Static-first with islands. Better for content sites. |
gatsby | Older static-first React framework. Largely surpassed by Next App Router and Astro. |
redwood | Full-stack opinionated stack (GraphQL by default). Smaller community. |
solid-start | SolidJS equivalent. Smaller ecosystem, faster runtime. |
nuxt | Vue equivalent. |
sveltekit | Svelte equivalent. |
| Vite + React + custom server | Maximum flexibility, maximum operations cost. |
Real-world recipes
App Router page + async data fetching
app/page.tsx is the home route. Server components can await directly.
async function getPosts() {
const res = await fetch("https://api.example.com/posts", { next: { revalidate: 60 } });
return res.json() as Promise<{ id: string; title: string }[]>;
}
export default async function HomePage() {
const posts = await getPosts();
return (
<ul>
{posts.map((p) => <li key={p.id}>{p.title}</li>)}
</ul>
);
}
Output: page renders on the server with data already in the HTML; revalidates every 60 seconds via the built-in fetch cache.
Server action with form
Server actions are server-only functions invoked from a <form>.
// app/posts/new/page.tsx
import { revalidatePath } from "next/cache";
async function createPost(formData: FormData) {
"use server";
const title = String(formData.get("title"));
await db.posts.create({ title });
revalidatePath("/posts");
}
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" />
<button type="submit">Create</button>
</form>
);
}
Output: submits POST to a generated endpoint; on success the /posts page is revalidated and re-fetched.
Middleware for auth gating
middleware.ts runs at the edge before any route handler.
import { NextRequest, NextResponse } from "next/server";
export function middleware(req: NextRequest) {
const token = req.cookies.get("session")?.value;
if (!token && req.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", req.url));
}
return NextResponse.next();
}
export const config = { matcher: ["/dashboard/:path*"] };
Output: unauthenticated requests to /dashboard/* redirect to /login before the route runs.
Route handler (API route)
app/api/posts/route.ts for REST-style endpoints.
import { NextRequest, NextResponse } from "next/server";
export async function GET() {
const posts = await db.posts.findMany();
return NextResponse.json(posts);
}
export async function POST(req: NextRequest) {
const { title } = await req.json();
const post = await db.posts.create({ title });
return NextResponse.json(post, { status: 201 });
}
Output: GET /api/posts returns all posts; POST /api/posts creates one.
Dynamic route + generateStaticParams
// app/posts/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await db.posts.findMany({ select: { slug: true } });
return posts.map((p) => ({ slug: p.slug }));
}
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await db.posts.findUnique({ where: { slug } });
return <article><h1>{post?.title}</h1></article>;
}
Output: pre-renders each slug at build time; falls back to on-demand rendering for new posts.
Image optimisation
import Image from "next/image";
export function Hero() {
return <Image src="/hero.webp" alt="Hero" width={1200} height={600} priority />;
}
Output: Next serves an optimised, responsive image with srcset; priority preloads it.
Streaming with Suspense and loading.tsx
// app/posts/loading.tsx
export default function Loading() {
return <p>Loading posts…</p>;
}
// app/posts/page.tsx (slow data)
export default async function PostsPage() {
const posts = await getPosts();
return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}
Output: the loading UI streams immediately; the actual page replaces it once data resolves.
Production deployment
Next's deployment story splits along host capabilities:
- Vercel. First-party host. Zero config —
git pushdeploys. Edge functions, ISR, image optimisation, and analytics integrated. - Cloudflare Pages / Workers. Use
@cloudflare/next-on-pagesor the OpenNext adapter. App Router works; some Node-only APIs need shims. - AWS (SST / OpenNext). OpenNext builds a Lambda + CloudFront stack with full App Router and ISR support.
- Self-hosted Node.
next startafternext build. Configure a process manager (PM2, systemd) and a reverse proxy (Nginx, Caddy). - Standalone output.
output: 'standalone'innext.config.jsemits a minimal Node bundle for Docker images.
// next.config.js
const nextConfig = {
output: "standalone",
images: { remotePatterns: [{ protocol: "https", hostname: "cdn.example.com" }] },
};
export default nextConfig;
Output: next build writes .next/standalone/ containing a server.js entry; copy that and .next/static/ into a Docker image.
Performance tuning
Next exposes several caching surfaces; understanding them is the difference between snappy and sluggish.
- fetch cache.
fetch(url, { next: { revalidate: 60 } })— Next caches the response for 60 seconds.cache: 'no-store'disables. - Route segment config.
export const revalidate = 3600at the top of apage.tsxsets a default.export const dynamic = 'force-dynamic'opts the whole segment out of caching. - Tag-based revalidation. Tag a fetch with
next: { tags: ['posts'] }, then callrevalidateTag('posts')from a server action to invalidate. - Partial Prerendering (PPR). Mix static and dynamic in one route — the static shell ships immediately, dynamic holes stream in.
<Image>and<Script>. Use the built-ins, not raw<img>/<script>. They handle preload, priority hints, and lazy loading.- Server components by default. Push
"use client"to leaves. Client JS bundle shrinks proportionally. next/fontfor self-hosted fonts. Eliminates render-blocking external font requests and CLS.@next/bundle-analyzer. Visualise what's bloating client bundles before guessing.
Version migration guide
Next moves quickly. The headline shifts across recent majors:
| From | To | Key changes |
|---|---|---|
next@13 | next@14 | App Router stable; server actions stable ("use server"); Turbopack dev mode; smaller bundles. |
next@14 | next@15 | params and searchParams are Promises (must await). Fetch defaults moved from cache-by-default to no-cache-by-default. React 19 RC by default. cookies() / headers() are now async. |
next@15 | next@16 | React 19 GA; turbopack production builds; further server actions hardening; tightened CSP defaults. |
14 → 15 migration: async params and searchParams.
Before (14):
export default function Page({ params }: { params: { slug: string } }) {
return <h1>{params.slug}</h1>;
}
After (15):
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return <h1>{slug}</h1>;
}
Output: compiles; params is now an awaited promise.
14 → 15 migration: async cookies() and headers().
Before (14):
import { cookies } from "next/headers";
const token = cookies().get("session")?.value;
After (15):
import { cookies } from "next/headers";
const token = (await cookies()).get("session")?.value;
Output: cookies() returns a promise — await it before chaining.
Checklist:
- Run the official codemod:
npx @next/codemod@latest upgrade latest. - Manually inspect every
params/searchParamsaccess; await them. - Audit fetch calls — 15+ does NOT cache by default. Add
next: { revalidate: N }where you want caching. - Update React + React DOM versions per the Next release notes.
- Run
next buildlocally before deploying — type errors and config breaks surface here.
Security considerations
- Server actions are public endpoints. Marking a function
"use server"does NOT authenticate it. Validate the session inside every action. x-middleware-subrequestbypass (CVE-2025-29927). A crafted header could skip middleware in older 13/14 versions. Upgrade to a patched release (15.x and recent 14.x patches).revalidateandrevalidateTagare unauthenticated by default. Any code path that exposes them to user input lets attackers flush your cache. Gate behind authenticated routes.- Open redirect via
searchParams.next. A pattern likeredirect(searchParams.next)is a classic open-redirect bug. Validate against an allowlist of internal paths. - Server component data leak. Anything returned by a server component lands in the client HTML. Filter sensitive fields before returning.
- CSP. Configure
Content-Security-Policyinmiddleware.tsornext.config.js. Next 16 ships stricter defaults; older versions need manual setup. - Image optimisation as SSRF.
next/imagewill fetch arbitrary remote URLs ifremotePatternsis too loose. Restrict to known CDNs.
Testing & CI integration
Unit test with Vitest
// vitest.config.ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: { environment: "jsdom", setupFiles: ["./vitest.setup.ts"] },
});
// components/Button.test.tsx
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { Button } from "./Button";
describe("Button", () => {
it("renders", () => {
render(<Button>Click</Button>);
expect(screen.getByRole("button")).toHaveTextContent("Click");
});
});
Output: unit tests run in jsdom; works for client components.
Playwright for E2E
import { test, expect } from "@playwright/test";
test("home page", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/My App/);
});
Output: spins up next start against a built app and asserts in a real browser.
CI pipeline
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: "npm" }
- run: npm ci
- run: npm run lint
- run: npm test -- --run
- run: npm run build
Ecosystem integrations
| Package | Role |
|---|---|
next-auth / @auth/core | Authentication adapters |
drizzle-orm / prisma | Database layer |
@tanstack/react-query | Client cache for non-server-component data |
zod / valibot | Input validation for server actions |
@vercel/analytics | Web Vitals reporting |
next-intl | i18n |
next-sitemap | sitemap.xml + robots.txt generation |
mdx-bundler / @next/mdx | MDX rendering |
Troubleshooting common errors
Error: Dynamic server usage: Page used cookies() etc. — a static page tried to read request data. Mark it export const dynamic = 'force-dynamic' or move the read into a child server component.
Hydration failed because the server rendered HTML didn't match — locale, dates, or random IDs. Pin Intl options; gate variability behind useEffect.
fetch failed in next build — your build tried to fetch a server that isn't running. Either skip the call at build time (process.env.NEXT_PHASE === 'phase-production-build') or expose a static fallback.
Module not found: Can't resolve 'fs' — using a Node-only API in a client component. Move to a server component or route handler.
Error: Route Handlers cannot use cookies() outside of a server component or action — Next 15+ enforces async cookies/headers. Make the handler async and await cookies().
Image with src "..." has invalid src prop — remotePatterns doesn't cover the host. Add it to next.config.js.
Server actions are not enabled — older configs require experimental.serverActions: true in next.config.js; in current majors they are on by default.
When NOT to use this
- Pure static site. Astro / Eleventy / Hugo produce smaller, simpler builds for content-heavy sites.
- Pure SPA. Vite + React + React Router is leaner than Next for SPA-only workloads.
- Non-Vercel infra with strict constraints. Next runs on Cloudflare, AWS, and self-hosted, but each adapter has rough edges. Confirm your target platform supports the features you need (ISR, middleware, edge runtime).
- Existing Express / Fastify backend you cannot replace. Mounting Next as middleware works but loses some of its conveniences.
- Bun-only deployment. Next supports Bun for dev; production support varies. Hono / Elysia are Bun-native alternatives.
See also
- JavaScript: react-basics — components and hooks
- npm: react-dom — DOM renderer
- Concept: http — request methods, status codes, caching headers