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 ≥ 15react ≥ 19node ≥ 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.tsx
UI for the matching route segment.
app/layout.tsx
Persistent shell. Wraps children. Inherited by sub-routes.
app/loading.tsx
Suspense fallback for the segment.
app/error.tsx
Error boundary. Client component.
app/not-found.tsx
Renders when notFound() is thrown.
app/template.tsx
Like layout but re-instantiated per navigation. Resets state.
app/route.ts
REST-style route handler. Exports GET/POST/…
app/[slug]/page.tsx
Dynamic segment.
app/[...slug]/page.tsx
Catch-all segment.
app/[[...slug]]/page.tsx
Optional catch-all.
app/(group)/x/page.tsx
Route group — URL skips (group).
app/x/@slot/page.tsx
Parallel route slot. Pair with parent layout.tsx.
app/(.)x/page.tsx
Intercepting 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 client
Render a server component that renders a client child with props.
Never import “use client” modules into a server-only API call
Bundle 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 (
Pre-rendered HTML / payload. Static by default; dynamic on cookies / headers / search params.
Request Memoisation
Same fetch in one render is deduped.
Router Cache
Client-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 fn
Marks 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 write
Bust caches so the UI sees the new data.
FormData / Zod validation
Inputs 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 params
In Next 15+, route params are async.
REST endpointsRoute handlers
app/api/x/route.ts: export async function GET(req)
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.
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.