Tags: web-dev concept

Routing

Date: 2026-08-19


A router does three things: match a URL to code, nest layouts so shared chrome doesn’t re-render, and give each route a place to declare the data it needs. The third is what separates a modern router from a switch statement over location.pathname.


Framework routing is the mapping from URL to component tree, plus whatever the framework hangs off that mapping. The general URL-to-handler question is Routing and Resolution; this is what frameworks specifically add on top.

File-based versus config-based

The same route table, declared two ways:

FILE-BASED                              CONFIG-BASED

routes/                                 [
  _index.tsx           →  /               {index: true, file: 'home.tsx'},
  products._index.tsx  →  /products       {path: 'products', children: [
  products.$handle.tsx →  /products/:h       {index: true, file: 'list.tsx'},
  account.tsx          →  /account          {path: ':handle', file: 'detail.tsx'},
  account.orders.tsx   →  /account/orders ]},
  $.tsx                →  catch-all       ]

File-based is discoverable — the URL structure is the directory listing, and a new route needs no registration step. Config-based is greppable, refactorable and can be generated. Most frameworks now do file-based by default with an escape into config, which is the right default: convention until you need something conventions can’t express.

Nested routes are the actual feature

A URL is a path, and each segment can own a layout. Matching /account/orders/1234 renders four things nested inside each other:

root layout          nav, footer, providers          does not re-render
 └ account layout    sidebar, logged-in user         does not re-render
    └ orders layout  filters, order count            does not re-render
       └ order       the order itself                ← only this changes

The payoff is that navigation is partial. Moving between two orders re-runs one route’s data loading and leaves the sidebar’s alone. In a flat router every navigation is a page, and shared chrome is re-created each time — which is why “keep the video playing while the user browses” is trivially possible in one model and a hack in the other.

Nesting is by URL by default, and needs an opt-out. A login page usually sits at /account/login but must not render inside the logged-in account shell. Frameworks spell this differently — a trailing underscore, a route group in brackets, an explicit layout: null — but every file-based router needs the concept, and forgetting it is the most common file-routing bug.

Route-level data

Once a route exists as a first-class object, it can own more than a component: the data it needs, its mutations, its metadata, its error boundary, its cache headers. That co-location is what makes the parallel loading in Data Fetching Patterns possible, because all of a URL’s data requirements are knowable before rendering starts.

Matching, and its footguns

Two different rules, and knowing which you’re in matters. Nested and file-based routers rank by specificity — static segments before dynamic, dynamic before catch-all — regardless of declaration order. Middleware-style routers (Express and its descendants) match strictly in the order routes were registered, so a catch-all registered early shadows everything after it. Three reliable traps either way:

  • Catch-alls swallowing real routes. $.tsx matching before a later static route, usually because the framework’s ordering isn’t what the author assumed
  • Trailing slashes. /products and /products/ as separate URLs is a duplicate-content problem before it’s a routing one — Canonicalisation
  • Case sensitivity. Paths are case-sensitive, and marketing links are not careful

Client-side navigation is an emulation

Intercepting a link click and swapping the view means the framework has taken over things the browser did for free, and each has to be re-implemented: scroll restoration, focus moving to the new content, the announcement screen readers expect on page change, and the back button — The History API. Routers handle the first and the last well, and the middle two are the two most common accessibility defects in single-page applications, because nothing visibly breaks — see The Accessibility Tree.

The compensating win is preloading. A router knows the route table, so it can fetch the code and data for a link before it’s clicked, which is the largest perceived-performance improvement available for the cost — Resource Hints.