The Shift from Pages to App Router
When Next.js introduced the App Router in version 13, it wasn't just a new routing system — it was a complete rethinking of how React applications should be built. After working with both Pages Router and App Router across multiple production projects, I'm convinced the App Router is the future.
Server Components by Default
The biggest paradigm shift is that every component is a Server Component by default. This means:
- Components render on the server, sending only HTML to the client
- Zero JavaScript shipped for components that don't need interactivity
- Direct access to databases, file systems, and server-only APIs
// This component ships ZERO JS to the browser
async function ProductList() {
const products = await db.product.findMany();
return (
<ul>
{products.map((p) => (
<li key={p.id}>{p.name} - ${p.price}</li>
))}
</ul>
);
}
Only add "use client" when you actually need browser APIs, event handlers, or hooks like useState.
File-Based Routing That Makes Sense
The App Router's file conventions are intuitive and powerful:
app/
layout.tsx → Root layout (wraps everything)
page.tsx → Home page (/)
loading.tsx → Loading UI (automatic Suspense)
error.tsx → Error boundary
not-found.tsx → 404 page
blog/
page.tsx → /blog
[slug]/
page.tsx → /blog/:slug
Each route can have its own layout.tsx that persists across navigations, loading.tsx for automatic loading states, and error.tsx for error boundaries — all without writing a single line of configuration.
Data Fetching: Simple and Powerful
Gone are the days of getServerSideProps and getStaticProps. In the App Router, you just use async/await:
// Server Component - fetches data at request time
async function BlogPost({ params }) {
const { slug } = await params;
const post = await getPost(slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
Static vs Dynamic
Next.js automatically determines whether a page should be static or dynamic:
- Static (SSG): Pages with no dynamic data are pre-rendered at build time
- Dynamic (SSR): Pages using
cookies(),headers(), orsearchParamsrender at request time - ISR: Use
revalidateto rebuild static pages on a schedule
// Static by default
async function About() {
return <h1>About Us</h1>;
}
// Dynamic because of searchParams
async function Search({ searchParams }) {
const { q } = await searchParams;
const results = await search(q);
return <Results data={results} />;
}
Parallel Routes and Intercepting Routes
Two features that unlock complex UI patterns:
Parallel Routes
Render multiple pages simultaneously in the same layout:
app/
@modal/
login/page.tsx
@sidebar/
page.tsx
layout.tsx → Receives both as props
export default function Layout({ children, modal, sidebar }) {
return (
<div>
<aside>{sidebar}</aside>
<main>{children}</main>
{modal}
</div>
);
}
Intercepting Routes
Show a modal when navigating from within your app, but a full page when accessed directly:
app/
feed/
page.tsx
@modal/
(..)photo/[id]/page.tsx → Intercepts as modal
photo/
[id]/page.tsx → Full page on direct access
This is how Instagram-style photo modals work — modal when clicking from the feed, full page when sharing a URL.
Middleware and Edge Runtime
Run code before a request is completed, at the edge, close to your users:
// middleware.ts
export function middleware(request) {
// Redirect, rewrite, or modify headers
if (!isAuthenticated(request)) {
return NextResponse.redirect("/login");
}
}
Combined with Edge Runtime, your middleware runs in milliseconds worldwide.
Performance Wins I've Seen
In real projects, switching to App Router gave me:
- 40-60% reduction in client-side JavaScript bundle
- Faster TTFB thanks to streaming SSR
- Better Core Web Vitals — especially LCP and INP
- Simpler code — fewer abstractions, less state management
When to Use App Router
Use App Router for:
- New projects (no question)
- Projects that benefit from Server Components (most of them)
- Apps with complex layouts and nested routing
- SEO-critical applications
Stick with Pages Router for:
- Existing large codebases (migrate incrementally)
- Teams that need time to learn the new mental model
My Advice
Start with Server Components everywhere. Only add "use client" when the compiler tells you to. You'll be surprised how little client-side JavaScript you actually need.
The App Router isn't just a better way to route — it's a better way to build React applications. The combination of Server Components, streaming, and file-based conventions creates a developer experience that's both simpler and more powerful.
The future of React development runs on the server first.