---
title: "04 — Routing Foundations — The File System Is the Router"
uid: routing-foundations
tags: ["roadmap:nextjs", "file-system", "dynamic-routes", "routing", "app-router", "nextjs"]
excerpt: "The file system IS the router — directories and files map directly to URL segments, so the whole route tree is visible at a glance in the project explorer."
date: 2026-08-13T03:28:02+0000
source: https://www.aveshina.my.id/en/blog/routing-foundations
---

The foundation everything else in the framework builds on turned out to be visible from the file explorer. The model that finally clicked: **the file system is the router.** Directories and files under app/ (or the legacy pages/) map directly to URL segments, so the application's entire route tree is visible at a glance in the project explorer [1][2]. There is no central route configuration file to maintain — the directory structure *is* the configuration.

That single decision — file-system routing — is what makes Next.js feel like "just make files and they're routes" instead of "declare routes, then wire them up."

## The core vocabulary

The Next.js router has a small set of terms, and getting them straight up front saves confusion later [3]:

- **Route** — a specific URL path that maps to a component, e.g. /blog/my-first-post.
- **Route segment** — one part of the URL path. In /blog/my-first-post, both blog and my-first-post are segments. Each segment corresponds to one folder in the project.
- **File-system routing** — the convention that the directory structure inside app/ or pages/ directly defines the routes [1][2].
- **Dynamic route** — a segment that takes a parameter, written as a folder named [slug]. The bracket notation marks it as dynamic; the value is filled in at request time.
- **Index route** — the route served at a directory's root, represented by a page.js (App Router) or index.js (Pages Router) inside a folder.
- **Layout** — a component that wraps multiple pages, providing shared UI like headers and footers across routes.
- **Link component** — next/link, used for client-side navigation between routes, which is faster than a full page load via <a>.

## How files become URLs

The mapping is mechanical. Given this structure under app/:

```
app/
├── page.tsx              →  /
├── blog/
│   ├── page.tsx          →  /blog
│   └── [slug]/
│       └── page.tsx      →  /blog/:slug
└── about/
    └── page.tsx          →  /about
```

Each nested folder is a route segment, and each page.tsx makes that segment publicly accessible. A folder without a page.tsx is just an organizational unit — it doesn't become a route on its own, but it can still hold a layout.tsx that wraps its children. The [slug] folder is the dynamic case: visiting /blog/hello-world and /blog/second-post both hit the same page.tsx, with hello-world or second-post available as a params prop.

```figure
<svg viewBox="0 0 700 280" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="File system mapping to URLs. Left: a file tree with app/page.tsx, app/blog/page.tsx, app/blog/[slug]/page.tsx, app/about/page.tsx. Right: the resulting URLs /, /blog, /blog/hello, /about. Arrows connect each file to its URL.">
  <defs>
    <marker id="rarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-monospace, monospace" text-rendering="geometricPrecision" font-size="11">

    <!-- File tree header -->
    <text x="180" y="24" font-family="ui-sans-serif" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">file system</text>
    <text x="520" y="24" font-family="ui-sans-serif" font-size="12" font-weight="700" fill="#052e16" text-anchor="middle">URL</text>

    <!-- Row 1: root -->
    <rect x="40" y="42" width="280" height="32" rx="6" fill="#e0e7ff" stroke="#6366f1" stroke-width="1"/>
    <text x="56" y="62" fill="#1e1b4b">app/page.tsx</text>
    <path d="M322,58 L418,58" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#rarrow)"/>
    <rect x="420" y="42" width="200" height="32" rx="6" fill="#dcfce7" stroke="#16a34a" stroke-width="1"/>
    <text x="520" y="62" fill="#052e16" text-anchor="middle">/</text>

    <!-- Row 2: /blog -->
    <rect x="40" y="92" width="280" height="32" rx="6" fill="#e0e7ff" stroke="#6366f1" stroke-width="1"/>
    <text x="56" y="112" fill="#1e1b4b">app/blog/page.tsx</text>
    <path d="M322,108 L418,108" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#rarrow)"/>
    <rect x="420" y="92" width="200" height="32" rx="6" fill="#dcfce7" stroke="#16a34a" stroke-width="1"/>
    <text x="520" y="112" fill="#052e16" text-anchor="middle">/blog</text>

    <!-- Row 3: dynamic -->
    <rect x="40" y="142" width="280" height="32" rx="6" fill="#fce7f3" stroke="#db2777" stroke-width="1"/>
    <text x="56" y="162" fill="#500724">app/blog/[slug]/page.tsx</text>
    <path d="M322,158 L418,158" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#rarrow)"/>
    <rect x="420" y="142" width="200" height="32" rx="6" fill="#fce7f3" stroke="#db2777" stroke-width="1"/>
    <text x="520" y="162" fill="#500724" text-anchor="middle">/blog/hello, /blog/xyz …</text>

    <!-- Row 4: about -->
    <rect x="40" y="192" width="280" height="32" rx="6" fill="#e0e7ff" stroke="#6366f1" stroke-width="1"/>
    <text x="56" y="212" fill="#1e1b4b">app/about/page.tsx</text>
    <path d="M322,208 L418,208" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#rarrow)"/>
    <rect x="420" y="192" width="200" height="32" rx="6" fill="#dcfce7" stroke="#16a34a" stroke-width="1"/>
    <text x="520" y="212" fill="#052e16" text-anchor="middle">/about</text>

    <text x="350" y="258" font-family="ui-sans-serif" font-size="10" font-style="italic" fill="#64748b" text-anchor="middle">folders = segments, page.tsx = the route becomes public</text>
  </g>
</svg>
```

## Dynamic routes — the parameterized case

Dynamic routes are where the file-system convention starts paying for itself. Instead of declaring a route with a parameter object in a config file, I just name a folder [slug] and read the parameter from the page's props:

```
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
  return <h1>Post: {params.slug}</h1>;
}
```

The bracket syntax generalizes. [slug] matches one segment; [...slug] is a catch-all that grabs the rest of the path as an array; [[...slug]] is an optional catch-all that also matches the parent path itself. These three forms cover most parameterized-route needs without ever touching a route table.

## Linking between routes

Navigation between routes uses the Link component from next/link, which performs client-side navigation — it prefetches the target route and updates without a full page reload [3]. For a user clicking around a site, this is the difference between SPA-like fluidity and the flicker of traditional multi-page navigation. A plain <a> tag works but loses the prefetch and the no-reload transition.

## How I use this

The file-system model changed how I think about route design. When I'm planning a feature, I sketch the URL structure first, then create the matching folders — the routes exist the moment the files do, with no wiring step. Dynamic routes handle every parameterized case I've needed, and the catch-all variants cover the rest. The mental overhead is low precisely because the file system is already a tree, and URLs are already a tree; Next.js just makes them the same tree.

## References

[1] Vercel, "Routing — Pages Router," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/pages/building-your-application/routing](https://nextjs.org/docs/pages/building-your-application/routing)

[2] Vercel, "Defining routes," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/14/app/building-your-application/routing/defining-routes](https://nextjs.org/docs/14/app/building-your-application/routing/defining-routes)

[3] Vercel, "Project structure and organization," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/getting-started/project-structure](https://nextjs.org/docs/app/getting-started/project-structure)

[4] "Next.js 15 routing explained for beginners," YouTube, 2024. [Video]. Available: [https://www.youtube.com/watch?v=qivq_vDYFGk](https://www.youtube.com/watch?v=qivq_vDYFGk)

[5] "Next.js 15 tutorial — Dynamic routes," YouTube, 2024. [Video]. Available: [https://www.youtube.com/watch?v=k9g6aVLH3p4](https://www.youtube.com/watch?v=k9g6aVLH3p4)

```quiz
Q: In Next.js App Router, how does a folder named `blog` with a `page.tsx` inside it become a route?
- It's declared in a central routes config file
- The file system is the router — folders map to URL segments, page.tsx makes the segment public
correct: 1
explain: Next.js uses file-system routing. The directory structure under app/ directly defines routes; no separate route configuration file is maintained.

Q: What does a folder named `[slug]` represent?
- A private folder ignored by the router
- A dynamic route segment — the bracket notation marks it as parameterized
correct: 1
explain: Square brackets denote a dynamic segment. The value is filled in at request time and available as a params prop on the page.

Q: What's the difference between `next/link` and a plain `<a>` tag for internal navigation?
- Nothing — they behave identically
- next/link does client-side navigation with prefetching, avoiding a full page reload
correct: 1
explain: The Link component prefetches the target route and transitions without a full reload, giving SPA-like fluidity. A plain <a> triggers a full page load.

Q: A folder without a `page.tsx` inside it…
- becomes a route that 404s
- is just an organizational unit — it doesn't become a public route on its own
correct: 1
explain: A folder needs a page.tsx to become a publicly accessible route. Without one, it's purely for organizing layouts or nested structure.
```
