← Cheatsheets
Tags: nextjs, react, app-router, pages-router,
server-components, middleware, caching
Last updated: 2026-06-26
Next.js Cheatsheet
Quick Reference
| Concept | App Router | Pages Router |
| Pages |
app/page.tsx |
pages/index.tsx |
| Layouts |
app/layout.tsx |
_app.tsx + per-page |
| Data fetching |
async components, fetch() |
getServerSideProps,
getStaticProps |
| Dynamic routes |
app/[id]/page.tsx |
pages/[id].tsx |
| API routes |
app/api/route.ts |
pages/api/*.ts |
| Middleware |
middleware.ts (root) |
middleware.ts (root) |
| Metadata |
export const metadata |
<Head> component |
| Loading UI |
loading.tsx (Suspense) |
Manual <Suspense> |
App Router vs Pages Router
Pages Router (Classic)
// pages/posts/[id].tsx
export async function getServerSideProps(ctx) {
const post = await fetch(...).then(r => r.json());
return { props: { post } };
}
export default function Post({ post }) {
return <h1>{post.title}</h1>;
}
App Router (Modern)
// app/posts/[id]/page.tsx
export default async function Post({ params }) {
const post = await fetch(...).then(r => r.json());
return <h1>{post.title}</h1>;
}
Key Differences
- App Router: React Server Components by default; add
"use client" to opt into client-side.
- App Router: file-based conventions (
page,
layout, loading, error,
not-found). Pages Router: pages/ folder +
custom _app, _document.
- App Router: nested layouts, streaming, and Suspense built-in.
- Pages Router: mature ecosystem, simpler mental model, no RSC
complexity.
Server Components & Client Components
Server Component (Default)
// No "use client" = server component
// Can be async, access DB directly, keep secrets
export default async function ServerComp() {
const data = await db.query("SELECT ...");
return <div>{data}</div>;
}
Client Component
"use client";
import { useState } from "react";
export default function ClientComp() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
{count}
</button>
);
}
Server-Only Imports
import "server-only"; // Build error if imported client-side
import "client-only"; // Build error if imported server-side
When to Use Client Components
useState, useEffect,
useReducer, useContext
- Event listeners (
onClick, onChange)
- Browser APIs (
window, document,
localStorage)
- Custom hooks that depend on any of the above
Routing
App Router File Conventions
app/
├── layout.tsx # Shared layout (wraps children)
├── page.tsx # Route UI
├── loading.tsx # Suspense fallback
├── error.tsx # Error boundary
├── not-found.tsx # 404 UI
├── route.ts # API endpoint
├── template.tsx # Re-mounts on navigate
└── default.tsx # Parallel route fallback
Dynamic Routes
// app/blog/[slug]/page.tsx
export default async function Post({ params }) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}
// Catch-all: [...slug] → /shop/a, /shop/a/b
// Optional: [[...slug]] → also matches /shop
Navigation
import Link from "next/link";
// App Router
import { useRouter } from "next/navigation";
// Pages Router
import { useRouter } from "next/router";
<Link href="/about">About</Link>
const router = useRouter();
router.push("/dashboard");
router.replace("/login");
router.refresh(); // Re-fetch server components
router.back();
Data Fetching & Caching
fetch() in App Router
// Static (cached forever — default)
const res = await fetch("https://api.example.com/data");
// Revalidate every 60 seconds
const res = await fetch("https://api.example.com/data", {
next: { revalidate: 60 }
});
// Dynamic (no cache)
const res = await fetch("https://api.example.com/data", {
cache: "no-store"
});
// On-demand revalidation
import { revalidateTag, revalidatePath } from "next/cache";
revalidateTag("posts");
revalidatePath("/blog/[slug]");
Static Generation (Pages Router)
export async function getStaticProps() {
return { props: { data }, revalidate: 3600 }; // ISR
}
export async function getStaticPaths() {
return { paths: [...], fallback: "blocking" };
}
Server Actions (App Router)
// app/actions.ts
"use server";
import { revalidatePath } from "next/cache";
export async function createPost(formData: FormData) {
const title = formData.get("title");
await db.insert({ title });
revalidatePath("/posts");
}
// Client usage
<form action={createPost}>
<input name="title" />
<button type="submit">Create</button>
</form>
Middleware
// middleware.ts (at project root, NOT in app/)
import { NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("token")?.value;
if (!token && request.nextUrl.pathname
.startsWith("/dashboard")) {
return NextResponse.redirect(
new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"],
};
Caching Strategies
| Cache | Default | Control |
| Full Route | Static (build time) |
dynamic = "force-dynamic" |
| Data (fetch) | Cached forever |
cache: "no-store" or
revalidate: N |
| Client Router | 30s in-memory |
router.refresh() |
| ISR | Revalidate after TTL |
revalidate in fetch/gsP |
Route Segment Config
// app/page.tsx
export const dynamic = "force-dynamic"; // Always SSR
export const dynamic = "force-static"; // Static (default)
export const revalidate = 60; // ISR every 60s
export const runtime = "edge"; // Edge Runtime
Layouts & Templates
Root Layout (Required)
// app/layout.tsx
export const metadata = {
title: "My App",
description: "Built with Next.js",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<main>{children}</main>
</body>
</html>
);
}
Nested Layouts
// app/dashboard/layout.tsx
// Wraps all /dashboard/* pages — state persists
export default function DashboardLayout({ children }) {
return (
<div><Sidebar />{children}</div>
);
}
Template (vs Layout)
Like layout but re-mounts on every navigation. Use
for fresh state on each visit (page view tracking, resetting forms).
Metadata & SEO
// Static
export const metadata = {
title: "About",
openGraph: { images: ["/og.png"] },
};
// Dynamic
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return { title: post.title };
}
Tips
- Prefer Server Components by default — add
"use client" only where you need interactivity.
- Use
loading.tsx for instant
Suspense-based loading states without manual wrappers.
- Middleware runs on the Edge — keep it fast and
avoid heavy imports.
- For forms, use Server Actions instead of API
routes — fewer network hops, automatic CSRF protection.
- Use
revalidateTag for on-demand cache
invalidation instead of time-based revalidation where possible.