Documentation
Feedback
Guides
VTEX IO Apps

VTEX IO Apps
Route Resolution Architecture
vtex.rewriter
Version: 1.70.0
Latest version: 1.70.0

This document covers the route identification, resolution logic, and route type handling in the VTEX Rewriter application.

📋 Table of Contents

🎯 Route Identification Flow

Identification Algorithm


_25
async function identifyRoute(ctx: Context) {
_25
const { url, binding } = ctx.state
_25
const locator = { from: url.pathname, binding: binding.id }
_25
_25
// 1. Search redirect (highest priority)
_25
const redirect = await findRedirect(locator, url.search)
_25
if (redirect) {
_25
return createRedirectRoute(redirect, url)
_25
}
_25
_25
// 2. Search internal route
_25
const internal = await findInternal(locator)
_25
if (internal) {
_25
return createInternalRoute(internal, url)
_25
}
_25
_25
// 3. Try compatibility with legacy URLs
_25
const compatibility = await findCompatibilityRoute(locator)
_25
if (compatibility) {
_25
return createCompatibilityRoute(compatibility, url)
_25
}
_25
_25
// 4. Fallback to search or 404
_25
return createFallbackRoute(url)
_25
}

Route Identification Detailed Flow

🔀 Route Resolution Types


_13
async function findRedirect(locator: RouteLocator, search?: string) {
_13
// First try with complete query string
_13
if (search) {
_13
const withQS = await redirects.get({
_13
...locator,
_13
from: `${locator.from}${search}`
_13
})
_13
if (withQS) return withQS
_13
}
_13
_13
// Then try without query string
_13
return redirects.get(locator)
_13
}


_11
async function findInternal(locator: RouteLocator) {
_11
// Direct search in index
_11
const direct = await internals.get(locator)
_11
if (direct && direct.type !== 'notFoundProduct') {
_11
return { route: direct, fromIndex: true }
_11
}
_11
_11
// Search by compatibility (legacy URLs)
_11
const compatibility = await findCompatibilityRoute(locator)
_11
return { route: compatibility, fromIndex: false }
_11
}

🎯 Internal Route Resolution

1. Redirect Resolution


_17
function resolveRedirect(redirect: Redirect, url: ParsedUrl): RedirectRoute {
_17
return {
_17
type: 'redirect',
_17
status: redirect.type === 'PERMANENT' ? 301 : 302,
_17
location: buildRedirectLocation(redirect.to, url.query),
_17
from: redirect.from
_17
}
_17
}
_17
_17
function buildRedirectLocation(to: string, query?: QueryParams): string {
_17
// Preserve query parameters if not specified in destination
_17
if (query && !to.includes('?')) {
_17
const queryString = new URLSearchParams(query).toString()
_17
return queryString ? `${to}?${queryString}` : to
_17
}
_17
return to
_17
}

2. Internal Route Resolution


_16
async function resolveInternal(internal: Internal, url: ParsedUrl): Promise<InternalRoute> {
_16
const resolver = getRouteResolver(internal.type)
_16
const href = await resolver.resolve(internal, url.query)
_16
_16
return {
_16
type: 'internal',
_16
href: href,
_16
from: internal.from,
_16
binding: internal.binding,
_16
meta: {
_16
declarer: internal.declarer,
_16
entityId: internal.id,
_16
routeType: internal.type
_16
}
_16
}
_16
}

3. Route Resolvers by Type

Product Route Resolver


_28
class ProductRouteResolver {
_28
async resolve(route: Internal, query: QueryParams): Promise<string> {
_28
const { id, binding } = route
_28
_28
// Fetch product data from catalog
_28
const product = await catalog.getProduct(id)
_28
if (!product) {
_28
throw new Error('Product not found')
_28
}
_28
_28
// Build final URL
_28
const basePath = `/produto/${product.linkText}/p`
_28
const queryString = this.buildQueryString(query)
_28
_28
return `${basePath}${queryString}`
_28
}
_28
_28
private buildQueryString(query: QueryParams): string {
_28
const params = new URLSearchParams()
_28
_28
// Preserve important parameters
_28
if (query.skuId) params.set('skuId', query.skuId)
_28
if (query.utm_source) params.set('utm_source', query.utm_source)
_28
_28
const result = params.toString()
_28
return result ? `?${result}` : ''
_28
}
_28
}

Category Route Resolver


_14
class CategoryRouteResolver {
_14
async resolve(route: Internal, query: QueryParams): Promise<string> {
_14
const { id } = route
_14
_14
// Fetch category tree
_14
const category = await catalog.getCategory(id)
_14
const categoryTree = await catalog.getCategoryTree(id)
_14
_14
// Build hierarchical path
_14
const path = categoryTree.map(c => c.linkText).join('/')
_14
_14
return `/categoria/${path}`
_14
}
_14
}

🔀 Redirect Processing Flow

🔄 Compatibility Handling

The compatibility layer handles legacy URL patterns and transforms them to current routing standards.

Legacy Pattern Detection


_18
// Legacy patterns are detected through regex matching and catalog API lookups
_18
async function findCompatibilityRoute(locator: RouteLocator) {
_18
// Check various legacy patterns
_18
const patterns = [
_18
productLegacyPattern,
_18
categoryLegacyPattern,
_18
brandLegacyPattern
_18
]
_18
_18
for (const pattern of patterns) {
_18
const match = await pattern.test(locator.from)
_18
if (match) {
_18
return await pattern.resolve(match, locator.binding)
_18
}
_18
}
_18
_18
return null
_18
}

🚀 Auto-Cache Integration

The route resolution system now includes an auto-cache mechanism that automatically saves compatibility routes to VBase, improving future performance.

Auto-Cache in Route Resolution


_68
async function findInternal(locator: RouteLocator, workspace: string) {
_68
// 1. Try direct VBase lookup first (fast)
_68
const vbaseStart = Date.now()
_68
const direct = await internals.get(locator)
_68
const vbaseElapsed = Date.now() - vbaseStart
_68
_68
if (direct && direct.type !== 'notFoundProduct') {
_68
// Cache hit - log performance metrics
_68
metrics.batch('route-performance-tracking', undefined, {
_68
route_path: locator.from,
_68
access_type: 'cache_hit',
_68
latency_ms: vbaseElapsed,
_68
vbase_ms: vbaseElapsed,
_68
compatibility_ms: 0,
_68
total: 1,
_68
})
_68
return { route: direct, fromIndex: true }
_68
}
_68
_68
// 2. Cache miss - try compatibility layer (slower)
_68
const compatStart = Date.now()
_68
const compatibility = await findCompatibilityRoute(locator)
_68
const compatElapsed = Date.now() - compatStart
_68
_68
if (compatibility) {
_68
const totalLatency = vbaseElapsed + compatElapsed
_68
_68
// Log cache miss metrics
_68
metrics.batch('route-performance-tracking', undefined, {
_68
route_path: locator.from,
_68
access_type: 'cache_miss_first_time',
_68
latency_ms: totalLatency,
_68
vbase_ms: vbaseElapsed,
_68
compatibility_ms: compatElapsed,
_68
will_be_cached: 1,
_68
total: 1,
_68
})
_68
_68
// Auto-cache for production only
_68
if (workspace === 'master' && compatibility.type !== 'notFoundProduct') {
_68
saveCompatibilityRouteInBackground(compatibility, internals, redirects)
_68
}
_68
}
_68
_68
return { route: compatibility, fromIndex: false }
_68
}
_68
_68
// Background save function (non-blocking)
_68
function saveCompatibilityRouteInBackground(
_68
compatibility: Internal,
_68
internals: any,
_68
redirects: any
_68
) {
_68
const tomorrow = new Date()
_68
tomorrow.setDate(tomorrow.getDate() + 1)
_68
_68
const routeWithExpiry = {
_68
...compatibility,
_68
endDate: tomorrow.toISOString(), // 24h TTL
_68
}
_68
_68
// Fire-and-forget async save
_68
setImmediate(() => {
_68
internals.save(routeWithExpiry, redirects).catch(() => {
_68
// Silent error handling - don't impact user response
_68
})
_68
})
_68
}

Performance Evolution Example


_11
Request 1: /produto/smartphone/p
_11
├── VBase lookup: 15ms (miss)
_11
├── Compatibility check: 520ms (Catalog API)
_11
├── Total: 535ms
_11
└── 🚀 Auto-save to VBase (background)
_11
_11
Request 2: /produto/smartphone/p (same route)
_11
├── VBase lookup: 10ms (hit!)
_11
├── Compatibility check: 0ms (skipped)
_11
├── Total: 10ms
_11
└── ✨ 98% improvement!

This auto-cache system transforms the rewriter from a 20% cache hit rate to 95%+ over time, with each compatibility route lookup automatically becoming a fast VBase hit for future requests.


_10

See also
Vtex.rewriter
VTEX IO Apps
VTEX App Store
VTEX IO Apps
Was this helpful?