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.
$.tsxmatching before a later static route, usually because the framework’s ordering isn’t what the author assumed - Trailing slashes.
/productsand/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.