DS DevShelfHub Projects · AI tools
Cheatsheets / Next.js
Cheatsheet · Dev tooling

Next.js: App Router, Server Components and Actions Reference Guide

By DevShelfHub

App Router, Server Components, Server Actions, data fetching, dynamic routes, Middleware, Image, and deployment — Next.js 14/15 reference for full-stack React applications with TypeScript and Tailwind.

90 items 7 min App Router RSC Actions

Start hereQuick start · 6 you’ll reach for daily

Scaffoldnpx create-next-app@latest
Pageapp/path/page.tsx
Layoutapp/path/layout.tsx
Client"use client"
Server fn"use server"
Cache bustrevalidateTag("…")

Target versions · paceVersions

Targets: next ≥ 15 react ≥ 19 node ≥ 20

Sheet pins to Next 15+ with the App Router. params and searchParams are Promises in 15 — always await. Caching defaults changed: fetch is uncached by default; opt in via next.cache / tags / revalidate. The legacy Pages Router still works and lives under pages/; this sheet covers the App Router.

scaffold · run · deploySetup

bash
# New app — App Router + TS by default
npx create-next-app@latest my-app --typescript --app --tailwind --eslint

# Daily-loop
cd my-app
npm run dev                            # next dev on :3000
npm run build && npm run start          # prod build + start
npm run lint                            # eslint
npx tsc --noEmit                        # type-check

# Env vars
echo "DATABASE_URL=..." >> .env.local   # never commit .env.local
# Prefix public vars with NEXT_PUBLIC_ to expose them to the browser bundle.

# Deploy targets
vercel                                  # zero-config on Vercel
docker build -t my-app .                # standalone output with Node

where things liveCommon imports

import Link from "next/link"Client-side navigation. Prefetches in viewport.
import Image from "next/image"Image optimisation. Required width/height or fill.
import Script from "next/script"Strategy-aware 3rd-party scripts.
import { redirect, notFound, permanentRedirect, RedirectType } from "next/navigation"Server-side navigation helpers.
import { useRouter, usePathname, useSearchParams, useParams } from "next/navigation"Client-side hooks. App-Router-flavoured.
import { headers, cookies, draftMode } from "next/headers"Server-only readers (async in 15+).
import { revalidatePath, revalidateTag, unstable_cache } from "next/cache"Cache controls.
import { NextResponse, type NextRequest } from "next/server"Middleware + route handler types.
import dynamic from "next/dynamic"Code-split a client component, optionally SSR-off.
import type { Metadata } from "next"Type for generateMetadata.

file conventionsApp Router

app/page.tsxUI for the matching route segment.
app/layout.tsxPersistent shell. Wraps children. Inherited by sub-routes.
app/loading.tsxSuspense fallback for the segment.
app/error.tsxError boundary. Client component.
app/not-found.tsxRenders when notFound() is thrown.
app/template.tsxLike layout but re-instantiated per navigation. Resets state.
app/route.tsREST-style route handler. Exports GET/POST/…
app/[slug]/page.tsxDynamic segment.
app/[...slug]/page.tsxCatch-all segment.
app/[[...slug]]/page.tsxOptional catch-all.
app/(group)/x/page.tsxRoute group — URL skips (group).
app/x/@slot/page.tsxParallel route slot. Pair with parent layout.tsx.
app/(.)x/page.tsxIntercepting route — modal-over-page pattern.

RSC defaultServer vs Client components

Server (default)No useState, no DOM, can await, runs only on server.
"use client"Top-of-file marker. Component (and everything it imports) goes to the browser bundle.
async function Page()Allowed only in server components.
Pass server data to clientRender a server component that renders a client child with props.
Never import “use client” modules into a server-only API callBundle bloat + leaked secrets.
import "server-only"Hard error if imported from a client component. Use to guard secrets.
import "client-only"Mirror — hard error if imported on the server.
javascript
// app/posts/[slug]/page.tsx — server component by default
import { notFound } from "next/navigation";
import { Suspense } from "react";

type Params = { params: Promise<{ slug: string }> };

// Build-time static params (SSG)
export async function generateStaticParams() {
  const slugs = await fetch("https://api.example.com/posts").then(r => r.json());
  return slugs.map((s: string) => ({ slug: s }));
}

// SEO
export async function generateMetadata({ params }: Params) {
  const { slug } = await params;
  return { title: `Post: ${slug}` };
}

// The page itself
export default async function Page({ params }: Params) {
  const { slug } = await params;
  const post = await fetch(`https://api.example.com/posts/${slug}`, {
    next: { revalidate: 60 },          // ISR — refetch at most every 60s
  }).then(r => r.ok ? r.json() : null);

  if (!post) notFound();

  return (
    

{post.title}

Loading comments…

}>
); }

fetch in server componentsData fetching

const data = await fetch(url)Top-level await in server components.
fetch(url, { cache: "force-cache" })Force the Data Cache.
fetch(url, { cache: "no-store" })Always fresh.
fetch(url, { next: { revalidate: 60 } })ISR-style time-based revalidation.
fetch(url, { next: { tags: ["posts"] } })Tag for targeted revalidateTag.
Parallel fetch with Promise.allAvoid waterfalls: const [a, b] = await Promise.all([…]).
unstable_cache(fn, key, { revalidate, tags })Cache an arbitrary async function.
generateStaticParams()Pre-render specific dynamic segments at build.
dynamic = "force-dynamic" / "force-static"Per-segment overrides. Export at module scope.
runtime = "edge" | "nodejs"Pick the runtime per segment / handler.

data · full route · routerCaching layers

Data CachePer-fetch cache. Controls: cache, next.revalidate, next.tags.
Full Route CachePre-rendered HTML / payload. Static by default; dynamic on cookies / headers / search params.
Request MemoisationSame fetch in one render is deduped.
Router CacheClient-side cache of recent route segments. Eviction tied to navigation history.
revalidatePath("/posts")Invalidate a route segment.
revalidateTag("posts")Invalidate every fetch carrying that tag.
router.refresh()Re-fetch the current route on the client.
Default is uncached in 15+. Memorise this: a plain fetch in a server component is not cached unless you say so. Opt in with next.revalidate or cache: "force-cache".

mutate without an APIServer Actions

"use server" at top of file or fnMarks code as a server action.
<form action={myAction}>Native form submit calls the action.
useActionState(action, initial)React 19 hook for pending state + result.
useFormStatus()Inside a submit button to read pending state.
return { error: … }Validation results travel back to the form.
redirect(url)Throw-based redirect from a server action.
revalidateTag / revalidatePath after a writeBust caches so the UI sees the new data.
FormData / Zod validationInputs arrive as FormData. Parse + validate explicitly.
javascript
// app/posts/actions.ts — server actions
"use server";
import { revalidatePath, revalidateTag } from "next/cache";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { z } from "zod";

const Schema = z.object({
  title: z.string().min(1).max(200),
  body:  z.string().min(1),
});

export async function createPost(_prev: unknown, formData: FormData) {
  const parsed = Schema.safeParse({
    title: formData.get("title"),
    body:  formData.get("body"),
  });
  if (!parsed.success) {
    return { error: parsed.error.flatten().fieldErrors };
  }

  const session = (await cookies()).get("session")?.value;
  if (!session) return { error: { auth: ["Sign in required"] } };

  const post = await db.posts.create({ data: parsed.data });

  revalidateTag("posts");                       // bust cached fetches tagged "posts"
  revalidatePath("/posts");                     // and the /posts route segment
  redirect(`/posts/${post.slug}`);
}

navigate & redirectRouting helpers

<Link href="/x">Prefetches in viewport. Client-side transition.
<Link href="/x" prefetch={false}>Skip prefetch on busy pages.
useRouter().push("/x")Programmatic navigation (client).
usePathname() / useSearchParams() / useParams()Read URL bits in client components.
redirect("/login")Server-side redirect by throw.
permanentRedirect("/new")308 instead of 307.
notFound()Render the closest not-found.tsx.
params = await paramsIn Next 15+, route params are async.

REST endpointsRoute handlers

app/api/x/route.ts: export async function GET(req)REST endpoint. Other verbs: POST, PATCH, DELETE.
return Response.json({…}) / NextResponse.json({…})JSON response.
return new Response("ok", { status: 200 })Plain Web Response.
const data = await req.json() / req.formData()Inputs.
export const runtime = "edge"Run on the edge runtime.
export const dynamic = "force-dynamic"Skip caching for this handler.
Streaming with ReadableStreamReturn a streaming Response for SSE / token-by-token.

edge runtime gateMiddleware

middleware.ts at project rootSingle file. Runs before matched routes.
export const config = { matcher: […] }Limit which paths trigger it.
NextResponse.next()Continue to the route.
NextResponse.redirect(url) / .rewrite(url)Bounce or silently rewrite.
req.cookies.get / set / deleteCookie manipulation.
req.geo / req.ipEdge-runtime request metadata.
No Node APIs (fs, crypto.createHash)Edge runtime — Web APIs only.
javascript
// middleware.ts — runs in the edge runtime before every matched request
import { NextResponse, type NextRequest } from "next/server";

export function middleware(req: NextRequest) {
  // 1 · Auth gate
  const session = req.cookies.get("session")?.value;
  if (!session && req.nextUrl.pathname.startsWith("/dashboard")) {
    const url = req.nextUrl.clone();
    url.pathname = "/login";
    url.searchParams.set("from", req.nextUrl.pathname);
    return NextResponse.redirect(url);
  }

  // 2 · Header shaping
  const res = NextResponse.next();
  res.headers.set("X-Frame-Options", "DENY");
  res.headers.set("Strict-Transport-Security", "max-age=31536000");
  return res;
}

export const config = {
  // Only run on these paths — skip _next, static, images
  matcher: ["/((?!_next|favicon.ico|.*\\..*).*)"],
};

build & shipDeployment

VercelZero-config target. Edge + Node functions, ISR, ImageOptim.
output: "standalone" (next.config)Minimal Node bundle for Docker / self-host.
output: "export" (next.config)Pure static export. No server features.
images: { remotePatterns: […] }Allow-list of remote image hosts.
env.NEXT_PUBLIC_*Only these env vars leak to the client bundle.
experimental.ppr / serverActions.bodySizeLimitOpt-in flags in next.config.ts.
@next/bundle-analyzerVisualise route bundles. Find bloat.

fetch · form action · revalidateEnd-to-end · List + create

Server-rendered list, native form posting to a server action, tag-based cache invalidation. The shape that replaces most fetch-then-mutate React patterns.

javascript
// app/items/page.tsx — list + create with a server action
import { createItem } from "./actions";
import { revalidateTag } from "next/cache";

async function getItems() {
  return fetch("https://api.example.com/items", {
    next: { tags: ["items"], revalidate: 30 },
  }).then(r => r.json());
}

export default async function Page() {
  const items: { id: number; name: string }[] = await getItems();
  return (
    
    {items.map(i =>
  • {i.name}
  • )}
); } // app/items/actions.ts "use server"; import { revalidateTag } from "next/cache"; export async function createItem(formData: FormData) { const name = String(formData.get("name") ?? ""); if (!name) return; await fetch("https://api.example.com/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), }); revalidateTag("items"); }

Best practiceGood to know

Keep server components server by default. Only flip on "use client" when you need state, effects, browser APIs, or event handlers. Smaller bundle, faster TTI.
Tag your fetches. fetch(url, { next: { tags: ["posts"] } }) lets one revalidateTag("posts") invalidate every relevant cache — even those generated by other routes.
Parallel-fetch in server components. Two sequential await fetch(…) calls are a waterfall. const [a, b] = await Promise.all([…]) halves the latency.

Common trapsWatch out for

Secrets in client bundles. Any env var prefixed NEXT_PUBLIC_ ships to the browser. Use import "server-only" on modules that read secrets so an accidental client import errors at build time.
params is a Promise in 15+. const { slug } = params destructures from a Promise → undefined. You need const { slug } = await params.
Forgetting revalidateTag / revalidatePath. The mutation succeeds, the UI shows stale data, the bug looks intermittent. After every write in a server action, invalidate the relevant cache.

Go deeperSee also

Next.js FAQ

What is Next.js used for?

Next.js is a React framework for building full-stack web applications. It provides file-based routing, server-side rendering, static generation, and API routes out of the box. The App Router (Next 13+) adds React Server Components, Server Actions, and granular cache control.

What is the difference between Server Components and Client Components in Next.js?

Server Components (the default in the App Router) render on the server and send HTML to the browser — no JS bundle, direct database access, zero client-side state. Client Components (marked with 'use client') hydrate in the browser and can use hooks, event handlers, and browser APIs.

How does caching work in Next.js 15?

Next.js 15 made fetch uncached by default. Opt in with { next: { revalidate: N } } for time-based revalidation or { next: { tags: ['tag'] } } for on-demand revalidation via revalidateTag(). The full-route cache is keyed by route and can be cleared with revalidatePath().

What are Next.js Server Actions?

Server Actions are async functions marked 'use server' that run on the server but can be called from Client Components or form actions. They replace the need for a separate API route for mutations — form submission, database writes, and authentication flows work without boilerplate fetch code.

Is Next.js free to use?

Yes. Next.js is open source under the MIT license. You can deploy it on any Node.js host, Docker, or edge runtime. Vercel, the company that maintains Next.js, offers paid managed hosting, but the framework itself is free and not locked to Vercel.

What is the difference between the Pages Router and the App Router in Next.js?

The App Router (introduced in Next.js 13) uses React Server Components by default, supports nested layouts, Server Actions for mutations, and streaming with Suspense. The Pages Router is the original file-based routing system using getServerSideProps and getStaticProps. New projects should use the App Router; the Pages Router remains fully supported for existing apps.