TechLead

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 UI
layout.tsx - Shared layout
loading.tsx - Loading UI
error.tsx - Error boundary
not-found.tsx - 404 page
template.tsx - Re-rendered layout
route.ts - API endpoint
default.tsx - Parallel route fallback

Basic 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

πŸ“– Next.js Routing Documentation β†’

βœ… 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