Route Resolution
This page explains how URLs are resolved to content, including context handling, alias resolution, and optional segments.
Resolution Flow Overview
When a request comes in for a URL like /uk/en/jackets/parka, Foir resolves it through several steps:
Request: GET /uk/en/jackets/parka
│
▼
┌────────────────────────────────────┐
│ 1. Load Route Tree (cached) │
│ Pattern: /:country/:locale/... │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 2. Match Path to Expectations │
│ uk → country context │
│ en → locale context │
│ jackets → collection entity │
│ parka → product entity │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 3. Batch Fetch Entities │
│ Single query for all 4 │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 4. Validate Memberships │
│ Is parka in jackets? │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 5. Return Resolved Content │
│ Entity + contexts + parents │
└────────────────────────────────────┘Context Entities
Context entities are special entity types used for URL prefixes like markets, countries, and locales.
Context Configuration
Each context is configured with:
| Property | Description | Example |
|---|---|---|
entityModelKey | Entity model for context values | locale, country |
canonicalField | Field for canonical URL value | naturalKey |
defaultRecordKey | Default when omitted from URL | en for locale |
validValues | All valid context values + aliases | {en, fr, de, gb, uk, ...} |
Valid Values Loading
When the route tree is cached, valid values for each context are loaded:
Context: locale
├── Natural Keys: en, fr, de, es, ...
└── Aliases: english → en, french → fr, ...
Context: country
├── Natural Keys: gb, us, de, fr, ...
└── Aliases: uk → gb, britain → gb, ...This allows the router to distinguish between context segments and entity segments.
Alias Resolution
Entities can have aliases - alternative URLs that resolve to the same content.
Alias Configuration
// Country entity: 'gb'
{
naturalKey: 'gb',
alias: ['uk', 'britain'],
aliasStrategy: 'redirect' // or 'canonical-tag'
}Alias Strategies
| Strategy | Behavior | Use Case |
|---|---|---|
redirect | 301 redirect to canonical URL | SEO - consolidate link equity |
canonical-tag | Serve with <link rel="canonical"> | A/B testing, soft migration |
Resolution Example
Request: /uk/en/jackets/parka
│
▼
┌────────────────────────────────────┐
│ Is 'uk' a valid country? │
│ → YES (alias for 'gb') │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ aliasStrategy = 'redirect' │
│ → 301 to /gb/en/jackets/parka │
└────────────────────────────────────┘Optional Context Segments
Context segments can be optional when a default value is configured.
How It Works
If a context has defaultRecordKey set, the segment can be omitted from the URL:
Route Tree: /:country/:locale/:collection/:product
With defaults:
- country.defaultRecordKey = 'gb'
- locale.defaultRecordKey = 'en'
Request: /jackets/parka
│
▼
┌────────────────────────────────────┐
│ 'jackets' not a valid country │
│ → Use default 'gb' │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 'jackets' not a valid locale │
│ → Use default 'en' │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 'jackets' matches collection │
│ 'parka' matches product │
└────────────────────────────────────┘
│
▼
Resolved: /gb/en/jackets/parka (with defaults applied)Supported URL Patterns
| URL | Resolved As |
|---|---|
/gb/en/jackets/parka | Explicit country and locale |
/gb/jackets/parka | Explicit country, default locale |
/jackets/parka | Default country and locale |
/en/jackets/parka | Default country, explicit locale (if ‘en’ not a country) |
Membership Validation
After fetching entities, memberships are validated to ensure the URL is valid.
Parent Memberships
Products belong to collections via parentMemberships:
// Product: parka
{
parentMemberships: {
'shopify-collection': ['jackets-id', 'outerwear-id', 'sale-id']
}
}For /jackets/parka to resolve, parka must have jackets in its parent memberships.
Validation Flow
Target: parka (product)
Check 1: Is 'jackets' in parka.parentMemberships['collection']?
→ YES ✓
All checks pass → Route resolves successfullyPerformance
Route resolution is optimized for speed:
| Step | Typical Time |
|---|---|
| Route tree cache hit | 0-1ms |
| Path matching | 1-2ms |
| Batch entity fetch | 10-20ms |
| Membership validation | 1-2ms |
| Total (uncached) | ~30ms |
| Total (cached) | ~5-10ms |
Caching
- Route tree: Cached per-project, 5 minute TTL
- Context valid values: Loaded with route tree
- Entity lookups: Batched in single query