Skip to content
C.W.K.
Stream
Lesson 05 of 08 · published

Why App Router Wins

~22 min · RSC, streaming, Suspense, parallel routes

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

It's a different architecture, not a new folder

The App Router is built on React Server Components, Suspense, and a streaming HTTP response model. None of those existed in the Pages Router era. The five things you actually get:

  1. Server Components by default. Components render on the server and ship zero JavaScript for that node. Heavy libraries (markdown, syntax highlighting, date formatters) stay server-side.
  2. Nested layouts that persist. The dashboard shell doesn't re-render when you click between dashboard tabs. State and DOM stay alive across navigations.
  3. Streaming & Suspense. The page sends HTML in pieces — the cheap parts arrive instantly, slow data streams in. No full-page spinner.
  4. Parallel routes. Multiple route segments render at once into the same layout, each with its own loading/error boundary.
  5. Server Actions. Mutations live on the server, called directly from forms. No /api/… route table for CRUD.

What this changes for the team

You stop writing API routes for every server-side need. You stop wrapping pages in client-side data libraries that refetch on every navigation. You start composing UI from server-default leaves with islands of 'use client' where interactivity actually lives.

Start with Server Components and add the smallest Client Component boundary that needs state, events, effects, or browser APIs. Place Suspense around independently slow work and keep shared layouts at the ancestor whose state should survive navigation.

App Router features do not help merely because the files live under app/. Marking the page or root layout as client code, fetching everything at the top, and creating an API route for every internal write reproduces the old model with extra ceremony. Disable or slow browser JavaScript and observe which shell and content still arrive. Move one use client boundary upward and downward, compare the client bundle, and log layout mounts while navigating between sibling and unrelated routes.

Code

A nested layout that persists across child navigations·tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
  analytics,
}: {
  children: React.ReactNode;
  analytics: React.ReactNode; // parallel slot @analytics
}) {
  return (
    <div className="grid grid-cols-[240px_1fr_320px]">
      <Sidebar />
      <main>{children}</main>
      <aside>{analytics}</aside>
    </div>
  );
}

// app/dashboard/loading.tsx
export default function Loading() {
  return <DashboardSkeleton />;
}
Streaming with explicit Suspense islands·tsx
import { Suspense } from 'react';

export default function Page() {
  return (
    <>
      <Header /> {/* fast, server-rendered */}
      <Suspense fallback={<StatsSkeleton />}>
        <Stats />        {/* slow query, streams in */}
      </Suspense>
      <Suspense fallback={<ChartSkeleton />}>
        <RevenueChart /> {/* independent stream */}
      </Suspense>
    </>
  );
}

External links

Exercise

Pick a route in your app (or the starter) that has a slow data fetch. Add a loading.tsx and a Suspense boundary around the slow leaf. Capture before/after screenshots of the perceived load.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.