A Complete Guide to Routing in Next.js (With Detailed Examples)

Routing is a fundamental concept in modern web development, and few frameworks make it as intuitive and powerful as Next.js. Unlike traditional React applications that rely on third-party libraries, Next.js provides a built-in, file-based routing system that simplifies navigation and improves developer productivity.



This guide explores how routing works in Next.js, covering both the modern App Router and the legacy Pages Router, with practical examples and detailed comments.


Understanding File-Based Routing

In Next.js, your folder structure directly maps to URLs. This means:

/app/about/page.js → /about

Let’s see a simple example:

// File: app/page.js
// This is the homepage ("/")

export default function HomePage() {
  return (
    <div>
      {/* Main heading */}
      <h1>Welcome to Home Page</h1>

      {/* Simple paragraph */}
      <p>This route is automatically mapped from app/page.js</p>
    </div>
  )
}

The App Router: The Modern Standard

The App Router lives inside the /app directory and is now the recommended approach.


Basic Routes

// File: app/about/page.js
// This creates the "/about" route automatically

export default function AboutPage() {
  return (
    <div>
      <h1>About Page</h1>
      {/* Explaining routing */}
      <p>This page is created using file-based routing.</p>
    </div>
  )
}

Nested Routing

// File: app/dashboard/settings/page.js
// URL: /dashboard/settings

export default function SettingsPage() {
  return (
    <div>
      <h1>Settings</h1>

      {/* This page is nested inside dashboard */}
      <p>This is a nested route inside dashboard.</p>
    </div>
  )
}

Dynamic Routes

Dynamic routes allow us to create reusable templates.

// File: app/blog/[slug]/page.js
// Example URL: /blog/hello-world

export default function BlogPost({ params }) {
  // params.slug contains the dynamic part of the URL
  const { slug } = params

  return (
    <div>
      <h1>Blog Post</h1>

      {/* Display dynamic slug */}
      <p>Slug: {slug}</p>

      {/* In real apps, you would fetch data based on slug */}
    </div>
  )
}

Catch-All Routes

// File: app/docs/[...slug]/page.js
// Matches multiple URL segments like /docs/a/b/c

export default function DocsPage({ params }) {
  const { slug } = params

  return (
    <div>
      <h1>Docs Page</h1>

      {/* slug is an array here */}
      <p>Path: {slug.join(" / ")}</p>
    </div>
  )
}

Layouts (Shared UI)

// File: app/layout.js
// This wraps ALL pages in the app

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {/* Global navigation */}
        <nav>
          <a href="/">Home</a> | <a href="/about">About</a>
        </nav>

        {/* Render page content here */}
        {children}

        {/* Footer */}
        <footer>
          <p>My App Footer</p>
        </footer>
      </body>
    </html>
  )
}

Nested Layout Example

// File: app/dashboard/layout.js
// This layout ONLY applies to /dashboard routes

export default function DashboardLayout({ children }) {
  return (
    <div>
      <h2>Dashboard Layout</h2>

      {/* Sidebar */}
      <aside>Sidebar Menu</aside>

      {/* Main content */}
      <main>{children}</main>
    </div>
  )
}

Loading and Error UI

// File: app/loading.js
// Shows while page is loading

export default function Loading() {
  return <p>Loading...</p>
}
// File: app/error.js
// Handles runtime errors

"use client" // required for error boundaries

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>

      {/* Show error message */}
      <p>{error.message}</p>

      {/* Retry button */}
      <button onClick={() => reset()}>
        Try Again
      </button>
    </div>
  )
}

Navigation in Next.js

Using Link Component

// File: app/page.js

import Link from "next/link"

export default function HomePage() {
  return (
    <div>
      <h1>Home</h1>

      {/* Client-side navigation (no page reload) */}
      <Link href="/about">Go to About</Link>
    </div>
  )
}

Programmatic Navigation

// Example using router

"use client"

import { useRouter } from "next/navigation"

export default function GoToPage() {
  const router = useRouter()

  function handleClick() {
    // Navigate to /about when button is clicked
    router.push("/about")
  }

  return (
    <button onClick={handleClick}>
      Go to About
    </button>
  )
}

Pages Router (Legacy)


Basic Example

// File: pages/index.js

export default function Home() {
  return <h1>Home Page</h1>
}

Dynamic Route Example

// File: pages/blog/[slug].js

import { useRouter } from "next/router"

export default function Blog() {
  const router = useRouter()

  // Extract slug from URL
  const { slug } = router.query

  return (
    <div>
      <h1>Blog Page</h1>
      <p>Slug: {slug}</p>
    </div>
  )
}

API Route Example

// File: pages/api/hello.js

export default function handler(req, res) {
  // Handle GET request
  if (req.method === "GET") {
    return res.status(200).json({
      message: "Hello from API"
    })
  }

  // Handle unsupported methods
  res.status(405).json({
    error: "Method not allowed"
  })
}

Best Practices

  • Use the App Router for new projects

  • Keep routes organized using folders

  • Use dynamic routes for reusable pages

  • Avoid duplicating layouts

  • Handle loading and error states properly


Conclusion

Routing in Next.js is designed to be simple yet powerful. With features like file-based routing, dynamic segments, layouts, and API routes, developers can build scalable applications efficiently.

The App Router represents the future of Next.js development, offering better structure, improved performance, and a more intuitive developer experience.

By mastering these routing concepts and practicing with real examples, you can build modern web applications that are both maintainable and performant.

Comments