Next.js: File-Based Routing Deep Dive

A calm, practical deep dive into Next.js file-based routing — covering static routes, dynamic routes, catch-all segments, nested routing, parallel routes, and scalable folder structures.

Next.js uses your folder structure as your routing system.
This can feel simple at first (“a folder becomes a URL”), but under the surface, file-based routing is one of the most expressive tools in modern web development.

In this chapter, we’ll take a calm, structured look at how routing works in the App Router, and how to use it to build clear, scalable, real-world applications.

No complicated abstractions — just practical, predictable patterns.

Why file-based routing works so well

Traditional routing systems use code-based configuration:

<Route path="/dashboard/settings" component={Settings} />

This becomes difficult to maintain in large apps.

File-based routing removes this complexity:

app/dashboard/settings/page.tsx → /dashboard/settings

The benefits:

  • Routing is visible from your folder tree.
  • URLs and UI structure stay in sync.
  • Refactoring routes is safer and easier.
  • Layouts and routing structure compose naturally.

Instead of managing a router, you simply build your UI tree.

1. Static routes

The simplest form:

app/
  page.tsx          → /
  about/
    page.tsx        → /about
  contact/
    page.tsx        → /contact

Static pages are ideal for:

  • Marketing pages
  • Product pages
  • Documentation sections
  • Help center articles

They produce fast, predictable URLs with no configuration.

2. Dynamic routes ([param])

Dynamic routes map patterns like /blog/:slug or /products/:id.

Example:

app/
  blog/
    [slug]/
      page.tsx      → /blog/:slug

Inside the page:

export default function Page({ params }) {
  return <h1>{params.slug}</h1>;
}

You can capture any type of segment:

  • /products/123
  • /blog/how-to-use-nextjs
  • /user/john-doe

Dynamic routes keep your folder structure clean even as your content grows.

3. Catch-all routes ([...param])

Sometimes you don’t know how many segments a URL will have.

A catch-all route:

app/
  docs/
    [...slug]/
      page.tsx      → /docs/* (any depth)

Examples:

  • /docs
  • /docs/getting-started
  • /docs/api/v1/errors
  • /docs/anything/here/or/deeper

params.slug becomes an array:

// /docs/api/v1
params.slug // ["api", "v1"]

This is powerful for:

  • Documentation hierarchies
  • Nested categories
  • Custom router imports
  • Legacy URL migrations

4. Optional catch-all routes ([[...param]])

If you want the root segment AND deeper segments handled by the same route:

app/
  shop/
    [[...filters]]/
      page.tsx    → /shop and /shop/* both match

Examples:

  • /shop
  • /shop/category/shoes
  • /shop/brand/nike

params.filters:

  • undefined for /shop
  • Array for deeper URLs

Useful for:

  • Filter pages
  • Search results
  • Multi-level browsing

5. Nested routing and nested layouts

One of the App Router’s biggest strengths is deeply nested routing without losing layout structure.

app/
  dashboard/
    layout.tsx         → wraps all dashboard pages
    page.tsx           → /dashboard
    settings/
      page.tsx         → /dashboard/settings
    analytics/
      page.tsx         → /dashboard/analytics

Each section inherits the layout above it.

This gives you:

  • Persistent navigation
  • Shared sidebar/header
  • State that doesn’t reset on navigation

Perfect for dashboards and SaaS apps.

6. Route groups ((group))

Groups allow you to organize folders without affecting the URL.

app/
  (marketing)/
    about/
      page.tsx     → /about
    contact/
      page.tsx     → /contact

The (marketing) folder is ignored in the URL.

This is extremely helpful for:

  • separating marketing vs. app code
  • grouping authenticated vs. public pages
  • grouping experiments or feature flags
  • keeping a clean top-level app/ structure

Another example:

app/
  (app)/
    dashboard/
      page.tsx     → /dashboard
  (auth)/
    login/
      page.tsx     → /login

Your filesystem can stay tidy without affecting your URLs.

7. Parallel routes (@slot)

Parallel routes let you render multiple segments at the same URL.

Example folder:

app/
  dashboard/
    layout.tsx
    @feed/
      page.tsx
    @stats/
      page.tsx

At /dashboard, both feed and stats content render into different layout slots.

Use cases:

  • Dashboards showing multiple panels
  • Split-screen UI
  • Multi-feed interfaces

This is one of the more advanced App Router capabilities, but it enables powerful, modular UI.

8. Intercepting routes ((.), (..), (...))

Intercepting lets you temporarily replace part of the URL structure during navigation.

For example:

app/
  inbox/
    page.tsx
    (.)message/[id]/page.tsx

This allows:

  • Opening a message as a modal over /inbox
  • Without navigating away from the inbox route
  • While preserving browser navigation semantics

Ideal for:

  • Overlays
  • Modals
  • Side panels
  • Quick previews

A surprisingly elegant pattern once understood.

9. API routes with route.ts

Next.js 14 allows defining API endpoints inside the routing tree.

app/
  api/
    users/
      route.ts → /api/users

Example:

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

Benefits:

  • API lives next to the UI segment that uses it
  • Easier to maintain
  • No external server required for simple needs

For larger apps, you may still prefer dedicated backend services — but route handlers are great for small to medium projects.

10. Designing a scalable folder structure

A healthy folder tree should feel calm and predictable.

A recommended structure:

app/
  (public)/
    about/
    pricing/
  (auth)/
    login/
    signup/
  (dashboard)/
    layout.tsx
    page.tsx
    settings/
    analytics/
components/
lib/
types/

Guidelines:

  • Group by feature, not by file type
  • Group related routes with route groups
  • Avoid deeply nested complexity early on
  • Split components out of routes once they grow
  • Keep UI segments small and focused

Routing should feel like a map — easy to trace, easy to modify.

11. Practical tips

  • Start simple. Add complexity only when needed.
  • Prefer route groups to keep app/ tidy.
  • Move shared UI into layouts early.
  • Don’t overuse dynamic routing — static is easier to cache and maintain.
  • Use catch-all routes sparingly; they’re powerful but easy to misuse.
  • Keep routing aligned with your product’s mental model.

The goal is not cleverness — it’s clarity.

Summary

Next.js file-based routing is more than a folder convention.
It is a structural foundation that shapes how your UI grows, how your team collaborates, and how your application scales.

By combining static routes, dynamic segments, nested layouts, route groups, and advanced patterns like parallel and intercepting routes, you can express almost any modern web application with a clean, predictable filesystem.

In the next chapter, we’ll shift focus to a core part of the App Router:
Server Components and Client Components — what runs where, and why it matters.

Understanding that boundary unlocks the full power of Next.js.

Keep Reading

Follow the engineering thread

Get the next practical Birdor note, or browse the archive for related systems, tooling, and architecture work.

Join newsletter Browse articles