---
url: /routing/rationale.md
description: >-
  Understanding why directory-based routing scales better than file-based
  routing for organizing large applications with clear navigation,
  colocalization, and visual hierarchy.
---

At first glance, directory-based routing looks more verbose than file-based alternatives.
`api/users/[id]/index.ts` vs `api/users/[id].ts` - the extra folder seems unnecessary.
It isn't, and the reason becomes obvious as your project grows.

## The File-Based Routing Problem

In file-based routing, route handlers and helper files live side by side:

```
api/
  users/
    index.ts           -> Handler for /users
    [id].ts            -> Handler for /users/:id
    schema.ts          -> Validation schemas... for which route?
    auth.ts            -> Authorization... for which endpoint?
    utils.ts           -> Helpers... used by what?
```

Which files are route handlers? Which are helpers? Is `schema.ts` a route at `/users/schema`
or a shared validation file? You can't tell without opening each file or relying on team conventions.

## Directory-Based Clarity

With directory-based routing, the rule is simple: **only `index.ts` is a route handler**.
Everything else in the folder is a helper for that route.

```
api/
  users/
    index.ts           -> Handler for /users
    schema.ts          -> Obviously a helper for /users

    [id]/
      index.ts         -> Handler for /users/:id
      permissions.ts   -> Obviously a helper for this endpoint

      posts/
        index.ts       -> Handler for /users/:id/posts
        formatter.ts   -> Obviously post-specific logic
```

No conventions to memorize, no ambiguity. The folder tree is your API map -
every folder with an `index.ts` is a route, everything else is support code.

This scales naturally:

```
api/
  products/
    index.ts
    [id]/
      index.ts
      cache.ts
      pricing.ts
      reviews/
        index.ts
        moderation.ts
        [reviewId]/
          index.ts
          flags.ts
```

Each route's complexity is isolated in its own folder.
New developers understand the structure immediately.
Six months later, you can still navigate it without re-reading the codebase.

### Helpers shared by several routes

The same rule answers this one: nothing is scanned except `index.*` and `use.ts`,
so everything else is yours to place. Two spots follow from the tree itself:

```
api/
  helpers.ts             -> used across the whole api tree
  products/
    pricing.ts           -> used by products and everything under it
    index.ts
    [id]/
      index.ts
      cache.ts           -> used by this route only
```

Put it in the deepest folder that covers every route using it, and move it up when that stops being true.
A helper's position then tells you its reach, the same way a route's position tells you its URL.

For code with no natural place in the route tree - a mailer, a queue client, a domain model -
use the folder root and reach it through `~/`, which is what the examples in these docs do with `~/types/`.

One more step up: code shared by *several source folders* goes outside `src/` entirely and is reached through `@/`.
Same idea, one level higher - the deepest place that covers everyone who needs it.
[Path mappings ›](/essentials/project-structure.md#path-mappings)

## The Trade-off

You create a folder even when it only contains `index.ts`. That's the entire cost.

In return: zero ambiguity, natural colocalization, room to grow without restructuring,
and a folder tree that directly mirrors your API surface:

```sh
$ tree -d src/front/api
src/front/api/
└── shop
    ├── cart
    ├── [category]
    │   └── {productId}
    ├── checkout
    │   ├── confirm
    │   ├── payment
    │   └── shipping
    ├── orders
    │   └── [orderId]
    └── products
        └── {category}
```
