Engineering

Building with the Next.js App Router

Server components, streaming, and metadata done right. A pragmatic tour of the patterns we reach for on every App Router project.

RRG Tech2 min read

The App Router changed how we structure Next.js apps. After shipping several production sites on it, these are the patterns we keep coming back to.

Server components are the default — keep it that way

The biggest mental shift is that components render on the server unless you opt out. Push 'use client' as far down the tree as possible so the interactive bits are small islands in an otherwise static page.

// app/dashboard/page.tsx — a Server Component, no client JS shipped.
import { getStats } from '@/lib/stats';
import { LiveChart } from './live-chart'; // 'use client' lives here, not here.
 
export default async function Dashboard() {
  const stats = await getStats(); // runs on the server
  return (
    <section>
      <h1>Overview</h1>
      <LiveChart initial={stats} />
    </section>
  );
}

Warning

A 'use client' directive is contagious downward: every component imported by a client component also becomes client code. Audit your boundaries.

Metadata belongs in the framework

Hand-writing <head> tags is error prone. The Metadata API gives you typed, per-route metadata that merges across layouts.

import type { Metadata } from 'next';
 
export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug);
  return {
    title: post.title,
    description: post.description,
    alternates: { canonical: `/blog/${post.slug}` },
    openGraph: { type: 'article', title: post.title },
  };
}

Choosing a rendering strategy

Not every route wants the same strategy. A rough guide:

StrategyWhen to useTrade-off
Static (SSG)Content known at build timeRebuild to update
ISRMostly static, occasional updatesSlightly stale until revalidate
Dynamic (SSR)Per-request, personalizedSlower TTFB, no full-page cache

Note

For a blog, static generation with a long revalidate is almost always the right call: instant loads, great SEO, and content still refreshes on a schedule.

A checklist before you ship

  • Server components by default, client islands where needed
  • generateStaticParams for known dynamic routes
  • Typed metadata via generateMetadata
  • Lighthouse run on a production build
  • Real-device check on a mid-range phone

Done well, the App Router gives you fast pages, clean data flow, and SEO that mostly takes care of itself. The constraints are the feature.