Tutorial•Mar 10, 2024•3 min read
Next.js App Router: Complete Guide
Everything you need to know about the App Router. From Server Components to streaming, caching strategies, and migration tips from Pages Router.
BD
Bang Den
Frontend Engineer & Designer
Table of Contents
The New Mental Model
The App Router fundamentally changes how we think about React applications. Server Components are now the default—and that's a good thing.
Server vs Client Components
Server Components (Default)
TypeScript (TSX)
1// app/posts/page.tsx2// This runs ONLY on the server3async function PostsPage() {4 const posts = await db.posts.findMany(); // Direct DB access!56 return (7 <div>8 {posts.map(post => (9 <PostCard key={post.id} post={post} />10 ))}11 </div>12 );13}
Client Components
TypeScript (TSX)
1'use client';23import { useState } from 'react';45export function LikeButton({ postId }: { postId: string }) {6 const [likes, setLikes] = useState(0);78 return (9 <button onClick={() => setLikes(l => l + 1)}>10 ❤️ {likes}11 </button>12 );13}
Data Fetching Patterns
Parallel Data Fetching
TypeScript (TSX)
1async function Dashboard() {2 // These run in parallel!3 const [user, posts, analytics] = await Promise.all([4 getUser(),5 getPosts(),6 getAnalytics(),7 ]);89 return (10 <div>11 <UserProfile user={user} />12 <PostsList posts={posts} />13 <AnalyticsChart data={analytics} />14 </div>15 );16}
Sequential with Suspense
TypeScript (TSX)
1import { Suspense } from 'react';23export default function Page() {4 return (5 <div>6 <h1>Dashboard</h1>78 <Suspense fallback={<UserSkeleton />}>9 <UserProfile />10 </Suspense>1112 <Suspense fallback={<PostsSkeleton />}>13 <RecentPosts />14 </Suspense>15 </div>16 );17}
Route Handlers (API Routes)
TYPESCRIPT
1// app/api/posts/route.ts2import { NextResponse } from 'next/server';34export async function GET() {5 const posts = await db.posts.findMany();6 return NextResponse.json(posts);7}89export async function POST(request: Request) {10 const body = await request.json();11 const post = await db.posts.create({ data: body });12 return NextResponse.json(post, { status: 201 });13}
Caching Strategies
Static vs Dynamic
TypeScript (TSX)
1// Force static generation2export const dynamic = 'force-static';34// Force dynamic rendering5export const dynamic = 'force-dynamic';67// Revalidate every 60 seconds8export const revalidate = 60;
On-Demand Revalidation
TYPESCRIPT
1// app/api/revalidate/route.ts2import { revalidatePath, revalidateTag } from 'next/cache';34export async function POST(request: Request) {5 const { path, tag } = await request.json();67 if (path) {8 revalidatePath(path);9 }1011 if (tag) {12 revalidateTag(tag);13 }1415 return Response.json({ revalidated: true });16}
Metadata API
TypeScript (TSX)
1// app/blog/[slug]/page.tsx2import { Metadata } from 'next';34export async function generateMetadata({ params }): Promise<Metadata> {5 const post = await getPost(params.slug);67 return {8 title: post.title,9 description: post.excerpt,10 openGraph: {11 images: [post.coverImage],12 },13 };14}
Loading & Error States
TypeScript (TSX)
1// app/posts/loading.tsx2export default function Loading() {3 return <PostsSkeleton />;4}56// app/posts/error.tsx7'use client';89export default function Error({ error, reset }) {10 return (11 <div>12 <h2>Something went wrong!</h2>13 <button onClick={() => reset()}>Try again</button>14 </div>15 );16}
Conclusion
The App Router is a paradigm shift. Embrace Server Components, use streaming wisely, and your apps will be faster than ever.