Routing in Next.js 🧭

Introduction πŸ‘‹

Next.js ships with a powerful, convention-based routing system built directly on top of your project's folder structure. Instead of manually configuring a router, you simply create folders and files, and Next.js automatically wires them into working routes. This tutorial walks through everything from the basics of file-based routing to advanced patterns like parallel and intercepting routes.

Information

This tutorial focuses on the modern app directory (App Router), introduced in Next.js 13 and now the recommended approach for new projects.

What is File-Based Routing? πŸ“

In file-based routing, the structure of your files and folders directly determines the URL structure of your application. There is no separate route configuration file to maintain β€” the file system is the router.

  • A folder defines a URL segment.
  • A special file (like page.tsx) inside that folder makes the segment publicly accessible.
  • Nesting folders creates nested URL paths.

Tip

This convention removes an entire class of routing bugs β€” if your file structure is correct, your routes are correct.

App Router πŸš€

The App Router lives inside the app/ directory and introduces support for Server Components, layouts, streaming, and colocated route files. It replaced the older pages/ directory (Pages Router) as the recommended architecture.

app
layout.tsx
page.tsx

Note

Every route in the App Router is defined by a page.tsx (or .jsx) file inside a folder.

Route Segments 🧩

Each folder in the app directory represents a route segment that maps to a corresponding segment in the URL path. A nested folder structure like app/blog/first-post maps directly to the URL /blog/first-post.

app
blog
first-post

Static Routes πŸ“„

A static route is the simplest kind of route β€” a fixed path with no dynamic parameters. You create one by adding a folder with a page.tsx file inside it.

app
about
page.tsx

app/about/page.tsx

export default function AboutPage() {
  return <h1>About Us</h1>;
}

This automatically becomes accessible at /about.

Dynamic Routes πŸ”€

When a segment's exact value isn't known ahead of time (like a blog slug or user ID), you can use a dynamic segment by wrapping the folder name in square brackets: [param].

app
blog
[slug]
page.tsx

app/blog/[slug]/page.tsx

export default function BlogPost({ params }: { params: { slug: string } }) {
  return <h1>Post: {params.slug}</h1>;
}

Example

Visiting /blog/hello-world renders this page with params.slug equal to "hello-world".

Catch-all Routes πŸ•ΈοΈ

A catch-all route matches an arbitrary number of segments using a spread-like syntax: [...param]. This is ideal for scenarios like documentation sites with deeply nested paths.

app
docs
[...slug]
page.tsx

app/docs/[...slug]/page.tsx

export default function Docs({ params }: { params: { slug: string[] } }) {
  return <p>Path: {params.slug.join("/")}</p>;
}

Important

A catch-all route requires at least one segment to match β€” /docs alone will 404 unless you also add an optional catch-all.

Optional Catch-all Routes ❓

Adding an extra pair of square brackets β€” [[...param]] β€” makes the catch-all segment optional, meaning the base route (with no extra segments) will also match.

app
docs
[[...slug]]
page.tsx
URLparams.slug
/docsundefined
/docs/a["a"]
/docs/a/b["a", "b"]

Nested Routes πŸͺ†

Routes naturally nest by nesting folders. Each level of folder nesting corresponds to a level of URL nesting, and each nested folder can have its own page.tsx, layout.tsx, and other route files.

app
dashboard
page.tsx
settings
page.tsx

This maps to /dashboard and /dashboard/settings respectively.

Route Groups πŸ—‚οΈ

Wrapping a folder name in parentheses β€” (groupName) β€” creates a route group. Route groups let you organize files logically without affecting the URL path, which is useful for grouping by feature or by layout.

app
(marketing)
(shop)

Tip

The segment (marketing) is omitted from the URL entirely β€” /about and /contact resolve as if the group folder didn't exist.

Private Folders πŸ”’

Prefixing a folder name with an underscore β€” _folderName β€” makes it a private folder, explicitly opting it out of routing. This is useful for colocating implementation details, utilities, or components without them becoming routes.

app
_components
Button.tsx

Note

The underscore prefix also prevents naming collisions with future Next.js file conventions.

Parallel Routes 🧡

Prefixing a folder with @ creates a slot, enabling parallel routes β€” multiple independent pages rendered simultaneously within the same layout. This is powerful for dashboards with distinct, independently-loadable sections.

app
@analytics
page.tsx
@team
page.tsx
layout.tsx

app/layout.tsx

export default function Layout({
  children,
  analytics,
  team,
}: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  team: React.ReactNode;
}) {
  return (
    <>
      {children}
      {analytics}
      {team}
    </>
  );
}
layout.tsx
@analytics slot
@team slot
children (default page)

Intercepting Routes βœ‹

Intercepting routes let you load a route from another part of your application within the current layout β€” commonly used for modals, like showing a photo in a lightbox while keeping the feed behind it. The convention uses dot-segment prefixes:

ConventionMeaning
(.)match a segment on the same level
(..)match a segment one level above
(..)(..)match a segment two levels above
(...)match a segment from the root
app
feed
page.tsx
(..)photo

Example

Navigating client-side from /feed to /photo/3 renders the intercepted modal version, while a hard refresh on /photo/3 renders the full standalone page.

Layouts πŸ–ΌοΈ

A layout.tsx file defines UI that is shared across multiple pages, such as navigation bars or sidebars. Layouts preserve state, remain interactive, and do not re-render on navigation between sibling pages.

app/layout.tsx

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Important

The root layout at app/layout.tsx is required and must contain <html> and <body> tags.

Nested Layouts 🧱

Layouts can be nested β€” every folder can define its own layout.tsx, which wraps its own page.tsx as well as any child layouts and pages beneath it, forming a layout hierarchy.

Root Layout
Dashboard Layout
Dashboard Page
Settings Page

Templates πŸŽ›οΈ

A template.tsx file is similar to a layout in that it wraps child pages, but with one key difference: templates create a new instance on every navigation, resetting state and re-running effects β€” unlike layouts, which persist.

app/template.tsx

export default function Template({ children }: { children: React.ReactNode }) {
  return <div className="fade-in">{children}</div>;
}

Tip

Use templates for things like per-page enter/exit animations or resetting a form's local state on every visit.

Pages πŸ“ƒ

A page.tsx file makes a route segment publicly accessible and defines the unique UI for that route. Without a page.tsx, a folder is just an organizational segment β€” it won't be reachable as a URL.

app/contact/page.tsx

export default function ContactPage() {
  return <h1>Contact Us</h1>;
}

Default Pages 🧭

A default.tsx file acts as a fallback UI for parallel route slots when Next.js can't recover an active state for that slot on a full page load β€” for example, when navigating directly to a URL that only affects one slot.

app/@team/default.tsx

export default function Default() {
  return null;
}

Loading UI ⏳

Adding a loading.tsx file automatically wraps a page (and its children) in a Suspense boundary, showing instant loading state while content streams in from the server.

app/dashboard/loading.tsx

export default function Loading() {
  return <p>Loading dashboard…</p>;
}

Best Practice

Keep loading UI lightweight β€” it should render instantly to provide immediate feedback to the user.

Error UI 🚨

An error.tsx file automatically wraps a route segment in a React Error Boundary. When a rendering error occurs, Next.js shows this fallback UI instead of crashing the whole app.

app/dashboard/error.tsx

"use client";

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <p>Something went wrong!</p>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

Important

error.tsx files must be Client Components, marked with the "use client" directive.

Global Error UI 🌍

A global-error.tsx file at the root of the app directory catches errors in the root layout itself β€” something a regular error.tsx cannot do, since it doesn't wrap the layout it lives in.

app/global-error.tsx

"use client";

export default function GlobalError({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <html>
      <body>
        <h2>Something went seriously wrong</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  );
}

Not Found Pages πŸ”

A not-found.tsx file renders when the notFound() function is called, or when a URL doesn't match any route within that segment.

app/blog/[slug]/not-found.tsx

export default function NotFound() {
  return <h2>Post not found</h2>;
}

Route Handlers πŸ› οΈ

A route.tsx (or route.ts) file lets you build API endpoints directly within the app directory, using standard Web Request and Response APIs.

app/api/hello/route.ts

export async function GET() {
  return Response.json({ message: "Hello, world!" });
}

Caution

A folder cannot contain both a page.tsx and a route.tsx at the same segment level β€” they would conflict.

Metadata Routes 🏷️

Next.js supports special files that generate metadata assets, like sitemaps, robots rules, and app icons, without needing to hand-craft static files.

FilePurpose
sitemap.tsgenerates sitemap.xml
robots.tsgenerates robots.txt
icon.tsxgenerates the app favicon
opengraph-image.tsxgenerates social share images
manifest.tsgenerates the web app manifest

Route Organization 🧭

Beyond individual conventions, Next.js gives you several tools β€” route groups, private folders, and colocated components β€” to keep large projects maintainable without affecting the resulting URL structure.

app
(marketing) β€” grouped, no URL impact
(shop) β€” grouped, no URL impact
_lib β€” private, excluded from routing

Navigation Overview 🧷

Client-side navigation is handled primarily through the <Link> component and the useRouter hook, both of which enable fast, prefetched transitions between routes without a full page reload.

components/Nav.tsx

import Link from "next/link";

export default function Nav() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
    </nav>
  );
}

Tip

<Link> automatically prefetches linked routes in the background when they enter the viewport, making navigation feel instant.

Route Configuration βš™οΈ

Individual route segments can export special variables to configure their runtime behavior, such as caching, revalidation, and rendering strategy.

ExportPurpose
dynamiccontrol static vs dynamic rendering
revalidateset ISR revalidation interval
fetchCachecontrol fetch request caching
runtimechoose Node.js or Edge runtime

app/blog/page.tsx

export const revalidate = 60;
export const dynamic = "force-static";

Route Resolution 🧠

When a request comes in, Next.js resolves it by matching the URL against the folder structure, prioritizing more specific (static) segments over dynamic ones.

  1. Static segments are matched first (e.g. /blog/featured).
  2. Dynamic segments are matched next (e.g. /blog/[slug]).
  3. Catch-all segments are matched last (e.g. /blog/[...slug]).

Route Matching 🎯

Understanding matching priority helps avoid ambiguity when multiple segment types could apply to the same URL.

URLMatched Segment
/blog/featuredblog/featured/page.tsx (static)
/blog/hello-worldblog/[slug]/page.tsx (dynamic)
/blog/a/b/cblog/[...slug]/page.tsx (catch-all)

Route Segment Config πŸ”§

Route segment config options let individual pages, layouts, or route handlers opt out of shared behavior. These are declared as simple exported constants at the top of a route file.

OptionValues
dynamic"auto" | "force-dynamic" | "force-static" | "error"
dynamicParamstrue | false
revalidatefalse | 0 | number
runtime"nodejs" | "edge"

Best Practices βœ…

  • Use route groups to organize large apps by feature or team without polluting URLs.
  • Keep loading.tsx files lightweight so streaming feels instantaneous.
  • Colocate route-specific components inside private folders (_components) to avoid accidental routing.
  • Prefer <Link> over manual anchor tags for prefetching and client-side transitions.
  • Use parallel routes sparingly β€” reserve them for genuinely independent UI regions.

Common Mistakes ⚠️

  • Forgetting that route groups ((name)) do not appear in the URL, and expecting them to.
  • Placing both page.tsx and route.tsx in the same segment, causing a conflict.
  • Using [...slug] when [[...slug]] was actually needed for an optional base route.
  • Forgetting the "use client" directive on error.tsx files.

Frequently Asked Questions πŸ’¬

Question

Can I mix the Pages Router and App Router in the same project?

Answer

Yes β€” Next.js supports incremental adoption, letting pages/ and app/ coexist during migration.

Question

Do route groups affect data fetching or caching?

Answer

No β€” route groups are purely organizational and have zero runtime or caching impact.

Question

What's the difference between layout.tsx and template.tsx?

Answer

Layouts persist state across navigations; templates create a fresh instance on every navigation.

Summary πŸ“Œ

Summary

Mastering these conventions unlocks the full power of the Next.js App Router β€” from simple static pages to sophisticated, streaming, multi-slot applications.