Next.js App Router: File-Based Routing Meets Server Components
Once you’ve built a few components with plain React, the next question is usually: how do multiple pages, navigation, and data fetching fit together? Next.js answers that with the App Router — a routing system built around the app/ directory that also happens to be the primary way most developers use React Server Components in practice.
File-Based Routing in app/
Instead of configuring routes in a router library, the App Router derives routes from your folder structure inside app/. Each folder is a URL segment, and a special page.js (or .tsx) file inside a folder makes that segment a visitable route.
app/
├── layout.js # Root layout — wraps every page
├── page.js # Route: /
├── about/
│ └── page.js # Route: /about
├── blog/
│ ├── layout.js # Layout for all /blog/* routes
│ ├── page.js # Route: /blog
│ └── [slug]/
│ └── page.js # Route: /blog/my-first-post (dynamic segment)
└── dashboard/
├── layout.js
├── page.js # Route: /dashboard
└── settings/
└── page.js # Route: /dashboard/settings
A folder named in square brackets, like [slug], defines a dynamic segment — app/blog/[slug]/page.js matches /blog/anything-here, and the value is available to the page as a parameter.
// app/blog/[slug]/page.js
export default async function BlogPost({ params }) {
const { slug } = await params;
const post = await getPostBySlug(slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}
This is a Server Component by default — no "use client" directive — so it can await data directly, the same way described in the React Server Components post.
Layouts and Nesting
A layout.js file wraps every page.js beneath it in the folder tree, and — crucially — it doesn’t re-render when you navigate between sibling pages. That makes layouts the natural place for navigation bars, sidebars, or anything that should persist across route changes.
// app/dashboard/layout.js
export default function DashboardLayout({ children }) {
return (
<div className="dashboard">
<nav>
<a href="/dashboard">Overview</a>
<a href="/dashboard/settings">Settings</a>
</nav>
<main>{children}</main>
</div>
);
}
Layouts nest: the root app/layout.js wraps everything (it’s the one place you must render <html> and <body>), app/dashboard/layout.js wraps everything under /dashboard, and so on. Navigating from /dashboard to /dashboard/settings only re-renders the page.js content — the dashboard nav in the layout stays mounted.
How This Builds on Server Components
Before the App Router, Next.js’s Pages Router (pages/ directory) rendered everything as a client-oriented React tree, with data fetched via functions like getServerSideProps. The App Router instead treats every component in app/ as a Server Component unless you opt out. That means:
page.jsandlayout.jsfiles can beasyncfunctions that fetch data directly withawait, with no separate data-fetching API required.- Only the leaf components you explicitly mark
"use client"— a like button, a form, a dropdown — ship JavaScript to the browser. - Next.js streams the server-rendered HTML as it becomes ready, so a slow data fetch in one part of the page doesn’t block the rest from showing up (using
loading.jsfiles or<Suspense>).
A loading.js file dropped next to a page.js is shown automatically while that page’s data is being fetched:
app/dashboard/
├── layout.js
├── page.js # slow data fetch inside
└── loading.js # shown instantly while page.js awaits its data
// app/dashboard/loading.js
export default function Loading() {
return <p>Loading dashboard…</p>;
}
Client Components Still Fit In
Interactive pieces work exactly as described in the Server Components post — add "use client" at the top of the file:
// app/dashboard/settings/RefreshButton.js
"use client";
import { useState } from "react";
export default function RefreshButton() {
const [refreshing, setRefreshing] = useState(false);
return (
<button
disabled={refreshing}
onClick={() => {
setRefreshing(true);
location.reload();
}}
>
{refreshing ? "Refreshing…" : "Refresh"}
</button>
);
}
You’d import and render <RefreshButton /> from a server-rendered page.js just like any other component — Next.js takes care of drawing the line between what’s sent to the browser and what stays on the server.
When to Reach for the App Router
- Starting a new Next.js project today — the App Router is the recommended default, with the older Pages Router kept mainly for existing codebases.
- You want nested layouts that persist across navigation without re-fetching or re-mounting shared UI.
- You want server-side data fetching co-located with the component that needs it, instead of a separate
getServerSidePropsfunction.
The App Router is what turns React Server Components from a rendering concept into an actual application structure — routes, layouts, and streaming all fall out of the same app/ directory convention.