Next.js (App Router)
What the App Router actually changes, when to use server components vs client components, and the data fetching model.
The App Router is React Server Components plus file-based routing plus a new caching model. Server Components render on the server, ship zero JS for that subtree, and can await data directly. Client Components are the old React you know: state, effects, event handlers, ship JS to the browser.
The default
Every component is a Server Component unless you write "use client" at the top of the file. That directive marks a client boundary. Everything imported below that boundary becomes client too. So put the directive on the leaf interactive component, not the page.
Data fetching
Server Components can be async. Just await your data:
export default async function Page({ params }: { params: { id: string } }) {
const post = await db.post.findUnique({ where: { id: params.id } });
return <article>{post.title}</article>;
}No useEffect, no useState, no loading state ladder. The request streams from the server with the data already inline.
fetch is augmented: by default it deduplicates calls within a single render, caches across requests until you opt out with cache: 'no-store', and can be tagged for on-demand revalidation. Other data sources (Prisma, raw DB) need unstable_cache or explicit revalidation calls.
Routing primitives
page.tsx: the route's UI.layout.tsx: persistent wrapper, does not re-render on route changes within its scope.loading.tsx: Suspense fallback for the segment.error.tsx: error boundary for the segment.route.ts: HTTP handlers for the path (replaces API routes).- Parallel routes (
@slot), intercepting routes ((.)photo/[id]), route groups ((marketing)) for advanced layouts.
Server actions
Mutations without writing an API route. Function with "use server" directive, called from a form or a client component, runs on the server, can revalidate paths or tags afterwards.
async function createPost(formData: FormData) {
"use server";
await db.post.create({ data: { title: formData.get("title") as string } });
revalidatePath("/posts");
}The caching model (Next.js 15+)
Next.js 14 cached fetch by default and surprised everyone. Next.js 15 reversed: fetch is uncached by default, GET route handlers are uncached, page rendering is dynamic by default if it reads dynamic APIs. Opt into caching explicitly with cache: 'force-cache', unstable_cache, or static generation.
Learn more
- DocsNext.js App Router DocsNext.js
- Docs
- Article