Introduced as stable in Next.js 13.4, the App Router represents a major leap forward compared to the traditional Pages Router. Built on React Server Components (RSC), this new architecture enhances performance, enables more complex UI patterns, and completely refreshes the developer experience.
In this article, we’ll cover a comparison between Pages Router and App Router, file conventions, Suspense and Streaming, Parallel Routes and Intercepting Routes, the <Link>
component, and middleware—all the practical points you should know.
1. Comparison: Pages Router vs App Router
Let’s start by comparing the legacy Pages Router with the new App Router to get the big picture.
Feature | Pages Router | App Router |
---|---|---|
Directory | pages/ |
app/ |
Component Type | Client Components by default | Server Components by default (use client to switch) |
Data Fetching |
getStaticProps / getServerSideProps
|
Direct fetch calls on the server |
Layouts |
_app.tsx / _document.tsx
|
layout.tsx (nested layouts supported) |
Routing | Flat, file-based | Folder hierarchy-based, supports Route Groups |
Delayed UI | Manual implementation |
Suspense + loading.tsx + Streaming |
Error Handling |
_error.tsx / 404.tsx
|
error.tsx / not-found.tsx
|
API Routes | pages/api/*.ts |
route.ts |
Complex UI / Modals | Implemented with state logic and conditions | Officially supported with Parallel Routes / Intercepting Routes |
SEO / Metadata | next/head |
metadata API with type safety |
👉 App Router evolves toward clear separation of responsibilities and official support for complex UI patterns.
2. App Router File Conventions
In App Router, placing special files inside the app/
directory defines routing and UI behavior.
File | Purpose |
---|---|
page.tsx |
The main page component, mapped to the URL. |
layout.tsx |
Shared layout. Nested and persistent across page transitions. |
template.tsx |
Similar to layout.tsx but re-mounts on navigation (e.g., resets forms). |
loading.tsx |
Loading UI during data fetching. Works with Suspense automatically. |
error.tsx |
Error boundary for the route. |
not-found.tsx |
Custom 404 UI for the route. |
route.ts |
Defines API routes by exporting functions for HTTP methods. |
default.tsx |
Default UI for Parallel Routes when nothing is selected. |
(group) directory |
Grouping for organizational purposes without affecting the URL. |
[param] directory |
Dynamic routes, e.g., /users/[id] . |
👉 The ability to separate loading, error, and not-found handling per route is a huge step forward in clarity and maintainability.
3. Suspense and Streaming
Suspense
<Suspense>
displays a fallback UI while data is being fetched or components are being lazily loaded.
<Suspense fallback={<p>Loading comments...</p>}>
<Comments />
</Suspense>
👉 While fetching, it shows “Loading comments...,” then seamlessly swaps in the final UI when ready.
Streaming
Streaming sends HTML to the client as soon as each part is ready, rather than waiting for the entire page.
This eliminates the dreaded “blank screen wait,” leading to a much smoother UX.
4. Parallel Routes and Intercepting Routes
Parallel Routes
Allows rendering multiple routes simultaneously.
Example: In an email app, render “inbox” and “details” side by side.
// layout.tsx
export default function Layout({ inbox, detail }: { inbox: React.ReactNode, detail: React.ReactNode }) {
return (
<div className="grid grid-cols-2">
<div>{inbox}</div>
<div>{detail}</div>
</div>
);
}
Intercepting Routes
Intercepts navigation and replaces it with an alternative view, such as a modal.
Example: Show a product detail page inside a modal when clicked.
👉 This combines SPA-style modal navigation with full-page rendering when accessed directly.
5. <Link>
Component and Prefetching
The <Link>
component enables fast client-side navigation in Next.js.
How It Works
- When a
<Link>
enters the viewport, Next.js prefetches the resources for the target page. - On click, the preloaded resources are used instantly.
- Page navigation completes nearly instantly.
👉 Thanks to prefetching, navigation feels like instant page switching.
6. Middleware
Overview
middleware.ts
runs immediately after a request reaches the server, before rendering a page or API route.
Common Use Cases
-
Authentication check: Redirect unauthenticated users to
/login
. -
Locale detection: Redirect based on
Accept-Language
headers. - A/B testing: Split traffic between variants.
- Access control: Block requests based on IP or user-agent.
Example
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("token");
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"],
};
👉 This lets you enforce authentication and redirects before any page logic runs.
Conclusion
-
App Router advancements
- Nested layouts, clearer file conventions, and Streaming/Suspense for improved UX.
- Complex UI officially supported with Parallel Routes and Intercepting Routes.
-
<Link>
advantages- Prefetching enables instant-feeling client-side navigation.
-
Middleware power
- Perform auth checks, redirects, and locale detection right after the request arrives.
👉 For new projects, App Router is strongly recommended. For existing projects, migrate step by step to avoid risks.
Top comments (0)