Routing and Resolution
Date: 2026-08-16
Every web system has to decide which code handles a given URL. There are two answers — the code declares the URLs, or the content does — and which one a platform picked explains most of what it feels like to work in.
Routing and resolution is the mechanism by which a request URL is matched to the code that produces a response.
The distinction worth holding:
ROUTE-CENTRIC CONTENT-CENTRIC
───────────── ───────────────
code declares the URLs content IS the URL
/products/:id /content/en/shoes
↓ ↓
a handler function read the node's
↓ own type
it fetches content ↓
find a renderer
for that type
In one line: route-centric asks “what handles this pattern?”; content-centric asks “what is the thing at this path, and what renders that kind of thing?”
The three shapes
Route-centric — a table of patterns, declared in code.
// Express
app.get('/products/:id', (req, res) => {
const product = db.find(req.params.id)
res.render('product', product)
})The URL space is fixed by code. A new URL is a deploy.
Convention-centric — the filesystem is the route table.
app/products/[id]/page.tsx → /products/:id
app/about/page.tsx → /about
Same model, less typing. The URL space is still fixed by code, so a new URL is still a deploy — Next.js, Astro, SvelteKit.
Content-centric — the content declares its own type, and the type selects the renderer.
GET /content/site/en/shoes.html
↓
node at /content/site/en/shoes
↓ the node's own property says
sling:resourceType = "mysite/page"
↓
render /apps/mysite/page/page.html
The URL space is fixed by content. A new URL is an author clicking create — no deploy, no developer. This is AEM’s Sling, and it’s the model most CMSes approximate — Adobe Experience Manager.
The pattern you’ve already met
WordPress’s template hierarchy is content-centric resolution with a fallback chain, and it’s the clearest small example of the idea:
request resolves to a "product" post
↓ try, in order
single-product.php
single.php
singular.php
index.php ← always exists
Nothing declared that route. The content’s type selected the renderer, and the chain guarantees a result. That’s resolution, not routing.
What changes downstream
The choice is not stylistic. It decides who can do what:
| Route-centric | Content-centric | |
|---|---|---|
| A new page | a deploy | an author saves |
| A new page type | a deploy | a deploy |
| URL shape follows | the code’s design | the content tree |
| Renaming content | URL unaffected | URL moves |
| Who owns the URL space | engineering | editorial |
| The sitemap comes from | the route table | the tree |
The rename row is the one that bites. If the URL is a path into the content tree, moving or renaming content moves the URL — so every reorganisation is a redirect exercise, and forgetting it silently destroys accumulated link equity — Redirects and Link Equity, Technical SEO.
Platforms that know this decouple the two: Shopify freezes a product’s handle at creation so renaming the title never changes the URL, which is a deliberate patch on exactly this problem — Shopify.
The row that gets missed: a new page type is a deploy in both models. Content-centric buys authors freedom over instances, never over kinds. Teams often expect otherwise and are disappointed.
Hybrids, which is most real systems
Almost nothing is pure. Shopify is the clean example:
FIXED ROUTE TABLE CONTENT PICKS RENDERER
/products/:handle → the product's assigned
template decides:
product.json
product.bundle.json
Routing gets you to the product; resolution decides how it renders. Most mature platforms land here, because the two models are good at different halves of the job.
The deeper idea
This is dispatch — the general question of who decides which code runs.
CALLER DECIDES THE THING DECIDES
if (type == 'a') shape.draw()
drawA()
else if (b) ↑ polymorphism
drawB()
Content-centric resolution is polymorphism applied to URLs: the resource carries its own type, and the type selects the behaviour. That’s why adding a new content type doesn’t require touching a central registry — the same reason adding a subclass doesn’t require touching a switch statement.
Route tables are the switch statement. They’re explicit, greppable and easy to reason about, and they centralise every change — Coupling and Cohesion.
Choosing
- Who needs to create URLs? If non-developers publish pages daily, content-centric or a CMS. If the URL space is small and stable, a route table is simpler and clearer
- How many content types? Few types, many instances favours content-centric. Many types, few instances favours explicit routes
- Is the URL structure a product decision or an editorial one? Content-centric hands it to editorial, permanently
- What’s the caching key? Route-centric caches by pattern; content-centric caches by path, which is finer-grained and harder to invalidate in bulk — Caching Strategies
The failure mode of route-centric is a bottleneck: every new landing page is a ticket.
The failure mode of content-centric is that nobody knows what the URL space contains, because it grew by authoring rather than by design. A sitemap becomes a discovery exercise rather than a fact.