Skip to content

Commit b8d0708

Browse files
committed
[ADR] Web URL Structure Design as an Architectural Decision
1 parent 6f648a7 commit b8d0708

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed

src/adrs/1760817947/README.md

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
---
2+
id: '1760817947'
3+
title: URL Structure Design as an Architectural Decision
4+
state: Draft
5+
created: 2025-10-18
6+
tags: [url-design, architecture, performance, seo]
7+
category: Platform
8+
---
9+
10+
# URL Structure Design as an Architectural Decision
11+
12+
## Context
13+
14+
URL design is often treated as a purely aesthetic or SEO decision, focusing on
15+
user-friendly, clean URLs like `/nike-air-zoom` or `/summer-collection` without
16+
prefixes. However, this seemingly simple choice has significant architectural
17+
implications that affect:
18+
19+
- **Backend query patterns**: Flat URLs require resolution logic to determine entity types
20+
- **Application performance**: Additional API calls to resolve ambiguous URLs
21+
- **Infrastructure costs**: Increased backend load from redundant lookups
22+
- **Cache complexity**: Need for sophisticated caching strategies
23+
- **Development effort**: Additional code to maintain slug uniqueness and resolution
24+
25+
The article "[The Hidden Cost of URL Design](https://alfy.blog/2025/10/16/hidden-cost-of-url-design.html)"
26+
presents a case study where flat URLs (`/leather-jacket`) caused 2x backend load
27+
because the system couldn't determine if a slug represented a product, category,
28+
page, or 404 without querying multiple endpoints.
29+
30+
### The Core Problem
31+
32+
With flat URLs like `/leather-jacket`, applications cannot deterministically
33+
know the entity type, requiring:
34+
35+
```javascript
36+
// Flat URL - requires resolution
37+
path = "/leather-jacket"
38+
isProduct = await checkIfProduct(slug)
39+
if (isProduct) return renderProduct()
40+
41+
isCategory = await checkIfCategory(slug)
42+
if (isCategory) return renderCategory()
43+
44+
return render404()
45+
```
46+
47+
With structured URLs like `/product/leather-jacket`, routing is immediate:
48+
49+
```javascript
50+
// Structured URL - instant routing
51+
path = "/product/leather-jacket"
52+
prefix = path.split("/")[1] // "product"
53+
// Route directly to product handler
54+
```
55+
56+
### Real-World Impact
57+
58+
The case study documented:
59+
60+
- **Every valid page**: 2 backend API calls (one succeeds, one fails)
61+
- **Every 404**: 2 wasted backend calls (both fail)
62+
- **Bot traffic**: 30% of requests generating double load
63+
- **P95 latency**: 800ms-1.2s during peak traffic
64+
- **Infrastructure costs**: 40% higher than projected
65+
66+
## Decision Drivers
67+
68+
- **Backend architecture compatibility**: Does the backend provide unified URL resolution?
69+
- **Performance requirements**: What latency and throughput is acceptable?
70+
- **SEO considerations**: Are flat URLs measurably better for search ranking?
71+
- **Development velocity**: How much time can be invested in resolution logic?
72+
- **Infrastructure budget**: Can additional caching/compute costs be justified?
73+
- **Slug collision risk**: Can uniqueness be enforced across entity types?
74+
75+
## Considered Options
76+
77+
### Option 1: Flat/Clean URLs
78+
79+
Use URLs without type prefixes: `/nike-air-zoom`, `/shoes`, `/about-us`
80+
81+
- **Good**, because URLs are aesthetically cleaner
82+
- **Good**, because some studies suggest minor SEO benefits
83+
- **Good**, because URLs feel more intuitive to users
84+
- **Bad**, because requires resolution logic to determine entity type
85+
- **Bad**, because necessitates multiple backend queries per request
86+
- **Bad**, because increases infrastructure costs significantly
87+
- **Bad**, because requires sophisticated caching strategies
88+
- **Bad**, because must enforce slug uniqueness across all entity types
89+
- **Bad**, because makes debugging and monitoring more complex
90+
91+
**When viable**:
92+
- Backend has SEF (Search Engine Friendly) URL tables for single-query resolution
93+
- Traffic volume justifies optimization investment
94+
- Team has capacity to build/maintain resolution infrastructure
95+
96+
### Option 2: Structured/Prefixed URLs
97+
98+
Use URLs with type indicators: `/product/nike-air-zoom`, `/category/shoes`, `/page/about-us`
99+
100+
- **Good**, because routing decisions are instantaneous
101+
- **Good**, because requires only one backend call per request
102+
- **Good**, because allows independent slug namespaces per type
103+
- **Good**, because simplifies caching strategies (can cache by URL pattern)
104+
- **Good**, because reduces infrastructure costs
105+
- **Good**, because makes monitoring and debugging straightforward
106+
- **Good**, because easier to implement and maintain
107+
- **Bad**, because URLs are slightly longer
108+
- **Bad**, because perceived as less clean/modern
109+
- **Bad**, because harder to change later (requires redirects)
110+
111+
**When viable**:
112+
- Backend has separate endpoints per entity type
113+
- Performance and cost efficiency are priorities
114+
- Team wants to minimize complexity
115+
116+
### Option 3: Hybrid Approach
117+
118+
Use flat URLs with query parameters for internal navigation: `/nike-air-zoom?t=p`
119+
120+
- **Good**, because external URLs remain clean for SEO/sharing
121+
- **Good**, because internal navigation can bypass server-side resolution
122+
- **Good**, because can be implemented without URL changes
123+
- **Good**, because reduces server-side resolution by 70-80%
124+
- **Bad**, because adds complexity to routing logic
125+
- **Bad**, because requires careful cache key management
126+
- **Bad**, because query parameters visible during SPA navigation
127+
- **Bad**, because still requires resolution for initial page loads
128+
129+
**When viable**:
130+
- Legacy system with established flat URLs
131+
- Cannot change existing URL structure
132+
- Most traffic is internal navigation after landing
133+
134+
## Decision Outcome
135+
136+
We **RECOMMEND** Option 2 (Structured URLs) as the default choice for new
137+
projects because it:
138+
139+
- Aligns URL structure with backend capabilities
140+
- Minimizes technical complexity and infrastructure costs
141+
- Provides deterministic routing without resolution overhead
142+
- Simplifies caching, monitoring, and debugging
143+
- Allows easier future optimization if needed
144+
145+
Teams **MAY** choose Option 1 (Flat URLs) if:
146+
147+
- They explicitly budget for resolution infrastructure
148+
- Backend provides unified URL resolution (SEF tables)
149+
- Measurable business value (SEO/brand) exceeds technical costs
150+
- Team documents the decision and trade-offs in an ADR
151+
152+
Teams **SHOULD** consider Option 3 (Hybrid) only for:
153+
154+
- Existing systems with established flat URL patterns
155+
- Migration scenarios where URL changes are too costly
156+
- Cases where internal navigation dominates traffic patterns
157+
158+
### Implementation Guidance
159+
160+
When implementing structured URLs, teams **SHOULD**:
161+
162+
1. **Choose consistent prefixes** that map to backend entity types
163+
2. **Document the URL structure** in API documentation
164+
3. **Configure CDN caching** rules based on URL prefixes
165+
4. **Set up monitoring** for each URL prefix pattern
166+
5. **Plan for future flexibility** with versioning strategy
167+
168+
When implementing flat URLs, teams **MUST**:
169+
170+
1. **Build or verify URL resolution endpoint** exists in backend
171+
2. **Implement caching layer** for resolved slugs
172+
3. **Enforce slug uniqueness** across all entity types at database level
173+
4. **Budget additional infrastructure** for resolution queries
174+
5. **Monitor resolution performance** and costs continuously
175+
6. **Document the trade-offs** accepted for business goals
176+
177+
### Advantages
178+
179+
- **Performance**: Reduces unnecessary backend calls by 50% (structured) to 100% (with caching)
180+
- **Cost efficiency**: Lower infrastructure costs from reduced query volume
181+
- **Simplicity**: Less code to write, test, and maintain
182+
- **Reliability**: Fewer moving parts means fewer failure modes
183+
- **Developer experience**: Easier onboarding and debugging
184+
185+
### Disadvantages
186+
187+
- **URL length**: Structured URLs are longer (marginal UX impact)
188+
- **Flexibility**: Harder to change URL structure after launch
189+
- **Perception**: May be perceived as less modern/clean
190+
191+
### Migration Path
192+
193+
Projects with existing flat URLs **SHOULD NOT** rush to restructure unless:
194+
195+
- Infrastructure costs are unsustainable
196+
- Performance issues are impacting users
197+
- Resolution complexity is blocking development
198+
199+
Instead, consider:
200+
201+
1. Implementing hybrid approach (Option 3) first
202+
2. Adding caching layers to reduce resolution cost
203+
3. Planning gradual migration with proper redirects
204+
4. Measuring business impact before/after changes
205+
206+
## Links
207+
208+
- [The Hidden Cost of URL Design](https://alfy.blog/2025/10/16/hidden-cost-of-url-design.html) - Original case study

0 commit comments

Comments
 (0)