Skip to content

File-Based Routing Compared

Compare file-based routing conventions on clarity, nesting, and escape hatches, pick on axes, not logos.

5 min read
File-Based Routing
Next.js
Remix
Astro
React Router
SPA

React 19 is the runtime. Routing is still how users move. Most modern React stacks sell file-based routing as the default, drop a file, get a URL - and then bury the real differences in undocumented folder conventions.

I care about three axes: convention clarity, nested UI ownership, and escape hatches when the URL shape refuses to match the filesystem.

What "file-based" actually promises

A file-based router maps paths on disk to URL segments, usually with special filenames for layouts, loading UI, and dynamic params. The promise is:

  • New routes are discoverable by browsing a folder
  • Colocation of UI + data for that URL
  • Less hand-maintained route config

The lie is that every framework means the same thing by those words.

The cast (mid-2025)

I am comparing conventions, not declaring a winner:

SystemMental modelDynamic / catch-all flavor
Next.js App Routerapp/ tree; page, layout, loading, error, route[id], [...slug], route groups (marketing)
Remix (flat / folder routes)URL-ish filenames or folders; loaders/actions beside UI$id, splats per Remix conventions
React Router framework modeSimilar loader/action story; file conventions via RR/Vite pluginsParam syntax per chosen convention
Astrosrc/pages/ file = route; islands opt into interactivity[id], rest params, content-first
TanStack Router (file mode)Optional file generation into a typed route treeStrong types; config still first-class

April's framework comparison post picked on navigation/data/deploy. This post zooms into how the files teach the URL.

Axis 1: Convention clarity

Ask: can a new hire predict the URL from the path in under ten seconds?

Next App Router is powerful and dense. Route groups (shop) disappear from the URL; parallel routes and intercepting routes add concepts you only need for specific UX. Great when you live in that model daily. Costly when half the team still thinks in pages/.

Remix / RR tend to keep filenames closer to URLs (invoices.$id.tsx reads like /invoices/:id). That clarity is a feature for HTTP-shaped brains.

Astro is the clearest for content sites: blog/post.md/blog/post. The trade-off is that app-like nested authenticated chrome is not its center of gravity.

Decision rule: if your product is mostly documents and marketing, prefer the boring pages directory. If your product is nested app chrome with independent loading states, prefer a router that treats layouts as first-class

  • and budget time to learn its special files.

Axis 2: Nested UI ownership

File-based routing pays off when layouts nest and each segment can own loading and error UI.

text
app/ layout.tsx → shell invoices/ layout.tsx → invoices chrome page.tsx → /invoices [invoiceId]/ page.tsx → /invoices/:invoiceId loading.tsx → segment-level pending

That structure is the App Router pitch. Remix nested routes express the same idea with outlet composition. Either way, the failure mode is the same: a single giant layout that re-fetches the world on every child navigation because nobody drew segment boundaries.

tsx
// app/invoices/layout.tsx, illustrative ownership boundary export default function InvoicesLayout({ children, }: { children: React.ReactNode; }) { return ( <div className="invoices-shell"> <InvoicesNav /> <main>{children}</main> </div> ); }

If every child page re-implements the nav, you do not have nested routing, you have copy-paste with a fancy folder name.

Axis 3: Escape hatches

Pure convention breaks. You will need at least one of:

  • Route groups / pathless layouts (organize without changing URLs)
  • Optional / repeating params without inventing ghost folders
  • Programmatic route trees when generated docs or multi-tenant paths explode
  • Typed route APIs when stringly-typed hrefs become a bug factory
ts
// When files fight the product, illustrative escape // Prefer a typed route helper over sprinkling '/invoices/' + id everywhere export const href = { invoice: (id: string) => `/invoices/${id}` as const, invoiceEdit: (id: string) => `/invoices/${id}/edit` as const, };

If your app's URL space is a graph (wizards, modals-as-routes, multi-panel tools), file-based routing still works, but only if you admit config/helpers beside the files. Dogma that "everything must be a file" produces empty directories and magical filenames nobody can pronounce.

Decision criteria

SituationLean
Content + docs + light interactivityAstro pages (or Next app used calmly)
Nested app UI + loader/action mental modelRemix / React Router framework
RSC + platform features as defaultNext App Router
Extreme type-safe navigation as the product needTanStack Router (dedicated deep dive)
Tiny SPA, few routesExplicit route config may beat folders

Failure modes

Folder archaeology. Route groups, parallel routes, and intercepting routes stacked without a README, only the author can add a page.

URL and filesystem divergence nobody documented. "Why is this not the path?" becomes the onboarding ritual.

Layouts that own too much data. Nested routes with a root layout that blocks on the union of all child fetches.

Migrating pages/app/ as a moral crusade. Migrate by surface area that benefits; dual trees are allowed while you learn.

Assuming file-based means zero config forever. Generated routes, redirects, and typed links always return.

Continuity

File conventions are the map. TanStack Router covers when type-safe routes are worth the overhead over framework defaults, and how that choice sits next to the React Router / Next / Remix landscape from April.

Takeaway

File-based routing is a clarity bet: predictable URLs, nested ownership, and documented escape hatches. Pick the convention your team can read at a glance - special filenames only help until nobody remembers what they mean.