App Router & File-based Routing
Master the App Router, layouts, pages, and file-based routing system
The App Router
Next.js 13+ introduced the App Router, a new paradigm for building React applications with
nested layouts, React Server Components, and streaming. The App Router uses the app/
directory and is the recommended approach for new projects.
π Special Files
page.tsx - Unique page UIlayout.tsx - Shared layoutloading.tsx - Loading UIerror.tsx - Error boundarynot-found.tsx - 404 pagetemplate.tsx - Re-rendered layoutroute.ts - API endpointdefault.tsx - Parallel route fallbackBasic Routing
Routes are derived from the folder structure. Static folders map to URL segments, and brackets create dynamic or catch-all segments.
// File-based routing - folders become URL segments
app/
βββ page.tsx β /
βββ about/
β βββ page.tsx β /about
βββ blog/
β βββ page.tsx β /blog
β βββ [slug]/
β βββ page.tsx β /blog/:slug
βββ products/
β βββ page.tsx β /products
β βββ [...categories]/
β βββ page.tsx β /products/* (catch-all)
βββ (marketing)/ β Route group (no URL impact)
βββ about/
βββ contact/
Layouts
Layouts wrap pages and persist between navigations. A root layout is required, and nested layouts let you share UI within a section.
// app/layout.tsx - Root layout (required)
import './globals.css';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<nav>{/* Global navigation */}</nav>
<main>{children}</main>
<footer>{/* Global footer */}</footer>
</body>
</html>
);
}
// app/dashboard/layout.tsx - Nested layout
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex">
<aside className="w-64">
{/* Dashboard sidebar */}
</aside>
<div className="flex-1">{children}</div>
</div>
);
}
// Layouts persist across navigation
// State is preserved when navigating between pages
// Only the page content re-renders
Dynamic Routes
Dynamic segments read parameters from the URL, and generateStaticParams can prebuild
known paths at build time.
// app/blog/[slug]/page.tsx - Dynamic segment
interface PageProps {
params: Promise<{ slug: string }>;
}
export default async function BlogPost({ params }: PageProps) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}
// Generate static paths at build time
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
// app/products/[...categories]/page.tsx - Catch-all
// Matches: /products/a, /products/a/b, /products/a/b/c
interface PageProps {
params: Promise<{ categories: string[] }>;
}
export default async function Products({ params }: PageProps) {
const { categories } = await params;
// categories = ['a', 'b', 'c'] for /products/a/b/c
return <div>Categories: {categories.join(' > ')}</div>;
}
// app/[[...slug]]/page.tsx - Optional catch-all
// Also matches the root: /
Loading & Error States
Add route-level loading UI and error boundaries to improve UX during async rendering and to recover from runtime errors.
// app/dashboard/loading.tsx - Loading UI
export default function Loading() {
return (
<div className="flex items-center justify-center h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500" />
</div>
);
}
// app/dashboard/error.tsx - Error boundary
'use client'; // Error boundaries must be client components
import { useEffect } from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<div className="p-8 text-center">
<h2 className="text-2xl font-bold text-red-600">Something went wrong!</h2>
<button
onClick={reset}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
>
Try again
</button>
</div>
);
}
// app/not-found.tsx - Custom 404
import Link from 'next/link';
export default function NotFound() {
return (
<div className="text-center py-20">
<h2 className="text-4xl font-bold">404</h2>
<p>Page not found</p>
<Link href="/" className="text-blue-500">Go home</Link>
</div>
);
}
Route Groups
Route groups organize folders without changing URLs, making it easy to separate app sections with different layouts.
// Group routes without affecting URL structure
// Use parentheses: (groupName)
app/
βββ (marketing)/ # No URL impact
β βββ about/
β β βββ page.tsx β /about
β βββ blog/
β β βββ page.tsx β /blog
β βββ layout.tsx # Shared marketing layout
βββ (shop)/
β βββ products/
β β βββ page.tsx β /products
β βββ cart/
β β βββ page.tsx β /cart
β βββ layout.tsx # Shared shop layout
βββ layout.tsx # Root layout
// Each group can have its own layout!
// Useful for different sections with different designs
Navigation
Use Link for client-side navigation and the router for programmatic redirects or refreshes.
// Use the Link component for client-side navigation
import Link from 'next/link';
export default function Nav() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/blog/hello-world">Blog Post</Link>
{/* Prefetch is automatic for links in viewport */}
<Link href="/dashboard" prefetch={false}>
Dashboard
</Link>
{/* Replace history instead of push */}
<Link href="/login" replace>Login</Link>
</nav>
);
}
// Programmatic navigation
'use client';
import { useRouter } from 'next/navigation';
export default function Form() {
const router = useRouter();
const handleSubmit = async () => {
await saveData();
router.push('/success'); // Navigate
router.replace('/login'); // Replace
router.back(); // Go back
router.forward(); // Go forward
router.refresh(); // Refresh server components
};
}
Parallel Routes
Parallel routes render multiple segments in the same layout, useful for dashboards, tabs, and complex layouts with independent areas.
// Show multiple pages simultaneously in the same layout
// Use @folder convention
app/
βββ layout.tsx
βββ page.tsx
βββ @dashboard/
β βββ page.tsx
βββ @analytics/
βββ page.tsx
// app/layout.tsx
export default function Layout({
children,
dashboard,
analytics,
}: {
children: React.ReactNode;
dashboard: React.ReactNode;
analytics: React.ReactNode;
}) {
return (
<div>
{children}
<div className="grid grid-cols-2 gap-4">
{dashboard}
{analytics}
</div>
</div>
);
}
// Great for dashboards, modals, and conditional rendering
β Routing Best Practices
- β’ Use layouts to share UI between routes
- β’ Add loading.tsx for better UX during navigation
- β’ Implement error.tsx boundaries at strategic points
- β’ Use route groups to organize without affecting URLs
- β’ Generate static params for known dynamic routes
- β’ Use the Link component for all navigation