Getting Started with Next.js App Router

A practical introduction to the Next.js App Router, covering server components, data fetching, and layouts.

Server Components by Default

The App Router renders components on the server by default, which reduces the JavaScript shipped to the browser and lets you fetch data directly inside components without a separate API round-trip.

File-Based Routing

app/
  layout.tsx
  page.tsx
  products/
    page.tsx
    [id]/
      page.tsx
  api/
    products/
      route.ts

A Server Component Fetching Data

// app/products/page.tsx
async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    next: { revalidate: 60 },
  });
  return res.json();
}

export default async function ProductsPage() {
  const products = await getProducts();

  return (
    <ul>
      {products.map((p: Product) => (
        <li key={p.id}>{p.name} — ${p.price}</li>
      ))}
    </ul>
  );
}

Client Components When You Need Interactivity

'use client';
import { useState } from 'react';

export default function AddToCartButton({ productId }: { productId: string }) {
  const [loading, setLoading] = useState(false);

  async function handleClick() {
    setLoading(true);
    await fetch('/api/cart', { method: 'POST', body: JSON.stringify({ productId }) });
    setLoading(false);
  }

  return <button onClick={handleClick} disabled={loading}>Add to Cart</button>;
}

Layouts and Nested Routing

// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Header />
        {children}
        <Footer />
      </body>
    </html>
  );
}

Route Handlers (API Routes)

// app/api/products/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  const products = await db.products.findMany();
  return NextResponse.json(products);
}

export async function POST(request: Request) {
  const body = await request.json();
  const product = await db.products.create({ data: body });
  return NextResponse.json(product, { status: 201 });
}

Server vs Client: When to Use Which

Use Server Components for Use Client Components for
Data fetching Event handlers (onClick, onChange)
Static or rarely-changing content Browser APIs (localStorage, geolocation)
Reducing client JS bundle size State (useState, useReducer)

Conclusion

The App Router’s server-first model takes some adjustment if you’re used to the Pages Router, but it removes a lot of client-side data-fetching boilerplate once you’re comfortable with the server/client component split. Start by keeping components server-rendered by default, and only add 'use client' where actual interactivity is needed.