Documentation
Feedback
Guides
VTEX IO Apps

VTEX IO Apps
VTEX Rewriter - Troubleshooting and Common Scenarios
vtex.rewriter
Version: 1.70.0
Latest version: 1.70.0

This document addresses common problems, solutions, and specific use cases for the Rewriter.

🚨 Common Problems and Solutions

1. Routes not working after creation

Symptoms

  • Route created via GraphQL but doesn't resolve correctly
  • 404 on pages that should have internal routes
  • Redirect doesn't work

Possible Causes and Solutions

Outdated cache:


_15
// Check routesVersion configuration
_15
query GetSettings {
_15
vtexAppSettings(app: "vtex.rewriter") {
_15
routesVersion
_15
# If changed recently, wait for cache propagation
_15
}
_15
}
_15
_15
// Force cache clear by changing routesVersion
_15
mutation UpdateSettings {
_15
saveVtexAppSettings(
_15
app: "vtex.rewriter",
_15
settings: { routesVersion: 5 } # Increment current value
_15
)
_15
}

Incorrect binding:


_10
# Check route binding
_10
query CheckBinding($path: String!) {
_10
internal {
_10
get(path: $path, locator: { from: $path, binding: "CORRECT_BINDING" }) {
_10
binding
_10
from
_10
type
_10
}
_10
}
_10
}

Expired end date:


_10
# Check if endDate is not in the past
_10
query CheckExpiration($path: String!, $binding: String!) {
_10
redirect {
_10
get(path: $path, locator: { from: $path, binding: $binding }) {
_10
endDate # Should be null or future date
_10
from
_10
to
_10
}
_10
}
_10
}

2. Infinite redirect loop

Symptoms

  • Browser shows "Too many redirects"
  • Circular 301/302 error

Diagnosis


_21
# Check redirect chain manually
_21
query CheckRedirectChain($path: String!, $binding: String!) {
_21
redirect {
_21
get(path: $path, locator: { from: $path, binding: $binding }) {
_21
from
_21
to
_21
type
_21
}
_21
}
_21
}
_21
_21
# Then check the destination
_21
query CheckRedirectDestination($to: String!, $binding: String!) {
_21
redirect {
_21
get(path: $to, locator: { from: $to, binding: $binding }) {
_21
from
_21
to # If returns to origin = loop
_21
type
_21
}
_21
}
_21
}

Solution


_10
# Delete problematic redirects
_10
mutation DeleteProblematicRedirect($path: String!, $binding: String!) {
_10
redirect {
_10
delete(path: $path, locator: { from: $path, binding: $binding }) {
_10
from
_10
to
_10
}
_10
}
_10
}

3. Slow Rewriter performance

Symptoms

  • Pages loading slowly
  • Frequent timeouts
  • High CPU usage

Diagnosis via Logs


_10
// Enable debug mode
_10
process.env.VTEX_APP_LINK = 'true'
_10
_10
// Analyze performance logs
_10
console.log('Route Resolution Performance:', {
_10
redirectLookupTime: 150, // ms
_10
internalLookupTime: 200, // ms
_10
cacheHitRate: 0.85, // 85%
_10
vbaseLatency: 50 // ms
_10
})

Solutions

Optimize cache:


_10
// Increase cache size if needed
_10
const INCREASED_CACHE = 10000 // Default: 3000
_10
_10
// Check cache hit rate
_10
metrics.trackCache('vbase', cache) // Target: >90%

Reduce VBase calls:


_12
# Use listRedirects in batch instead of multiple get queries
_12
query OptimizedBulkCheck {
_12
redirect {
_12
listRedirects(limit: 1000) {
_12
routes {
_12
from
_12
to
_12
binding
_12
}
_12
}
_12
}
_12
}

4. Redirects not preserving query parameters

Problem


_10
/old-page?utm_source=email&utm_campaign=promo
_10
↓
_10
/new-page # Query params lost

Solution


_14
# Create redirect with wildcard to preserve QS
_14
mutation CreateRedirectWithQueryPreservation {
_14
redirect {
_14
save(route: {
_14
from: "/old-page",
_14
to: "/new-page", # No query string in destination
_14
type: "PERMANENT",
_14
binding: "binding-id"
_14
}) {
_14
from
_14
to
_14
}
_14
}
_14
}

Rewriter automatically preserves query parameters if the destination doesn't specify its own.

5. Duplicate hreflang tags

Symptoms

  • Multiple hreflang tags for same locale
  • SEO warnings about hreflang

Solution via Settings


_14
{
_14
"removeRepeatedHreflangTags": true,
_14
"removeVtexLocales": true, // Remove myvtex domains
_14
"customHreflang": [
_14
{
_14
"name": "store.com",
_14
"value": "en-US"
_14
},
_14
{
_14
"name": "es.store.com",
_14
"value": "es-ES"
_14
}
_14
]
_14
}

🔄 Specific Use Case Scenarios

1. Domain Migration


_15
# Create bulk redirects to new domain
_15
mutation MigrateDomain {
_15
redirect {
_15
saveMany(routes: [
_15
{
_15
from: "/category/electronics",
_15
to: "https://newdomain.com/electronics",
_15
type: "PERMANENT",
_15
binding: "old-binding-id",
_15
origin: "domain-migration"
_15
}
_15
# ... more redirects
_15
])
_15
}
_15
}

2. Category URL Restructuring

Scenario: Change from /category/electronics to /electronics


_30
// 1. First create new internal routes
_30
const newRoutes = categories.map(cat => ({
_30
from: `/$\{cat.slug\}`,
_30
type: 'category',
_30
id: cat.id,
_30
declarer: 'vtex.catalog-graphql',
_30
binding: bindingId
_30
}))
_30
_30
// 2. Save new routes
_30
await Promise.all(newRoutes.map(route =>
_30
client.mutate({
_30
mutation: SAVE_INTERNAL_ROUTE,
_30
variables: { route }
_30
})
_30
))
_30
_30
// 3. Create redirects from old URLs
_30
const redirects = categories.map(cat => ({
_30
from: `/category/$\{cat.slug\}`,
_30
to: `/$\{cat.slug\}`,
_30
type: 'PERMANENT',
_30
binding: bindingId,
_30
origin: 'url-restructure'
_30
}))
_30
_30
await client.mutate({
_30
mutation: SAVE_MANY_REDIRECTS,
_30
variables: { routes: redirects }
_30
})

3. Multilingual URL Implementation


_49
# Primary route in Portuguese
_49
mutation CreatePrimaryRoute {
_49
internal {
_49
save(route: {
_49
from: "/produto/smartphone",
_49
type: "product",
_49
id: "123",
_49
declarer: "vtex.store",
_49
binding: "pt-binding",
_49
alternates: [
_49
{ binding: "en-binding", path: "/product/smartphone" },
_49
{ binding: "es-binding", path: "/producto/smartphone" }
_49
]
_49
}) {
_49
from
_49
alternates { binding, path }
_49
}
_49
}
_49
}
_49
_49
# Alternative routes
_49
mutation CreateAlternativeRoutes {
_49
internal {
_49
saveMany(routes: [
_49
{
_49
from: "/product/smartphone",
_49
type: "product",
_49
id: "123",
_49
declarer: "vtex.store",
_49
binding: "en-binding",
_49
alternates: [
_49
{ binding: "pt-binding", path: "/produto/smartphone" },
_49
{ binding: "es-binding", path: "/producto/smartphone" }
_49
]
_49
},
_49
{
_49
from: "/producto/smartphone",
_49
type: "product",
_49
id: "123",
_49
declarer: "vtex.store",
_49
binding: "es-binding",
_49
alternates: [
_49
{ binding: "pt-binding", path: "/produto/smartphone" },
_49
{ binding: "en-binding", path: "/product/smartphone" }
_49
]
_49
}
_49
])
_49
}
_49
}

4. Seasonal Campaign with Temporary Redirects


_23
# Redirects to Black Friday landing page
_23
mutation CreateCampaignRedirects {
_23
redirect {
_23
saveMany(routes: [
_23
{
_23
from: "/oferta",
_23
to: "/black-friday-2024",
_23
type: "TEMPORARY",
_23
binding: "binding-id",
_23
endDate: "2024-12-01T23:59:59.999Z", # Expires after campaign
_23
origin: "black-friday-campaign"
_23
},
_23
{
_23
from: "/promocao",
_23
to: "/black-friday-2024",
_23
type: "TEMPORARY",
_23
binding: "binding-id",
_23
endDate: "2024-12-01T23:59:59.999Z",
_23
origin: "black-friday-campaign"
_23
}
_23
])
_23
}
_23
}

📊 Monitoring and Metrics

1. Health Check Dashboard


_39
async function getRewriterHealth() {
_39
const [redirectsCount, internalsCount, cacheStats] = await Promise.all([
_39
// Total active redirects
_39
client.query({
_39
query: gql`
_39
query GetRedirectsCount \{
_39
redirect \{
_39
listRedirects(limit: 1) \{
_39
routes \{ from \}
_39
\}
_39
\}
_39
\}
_39
`
_39
}),
_39
_39
// Total internal routes
_39
client.query({
_39
query: gql`
_39
query GetInternalsCount \{
_39
internal \{
_39
listInternals(limit: 1) \{
_39
routes \{ from \}
_39
\}
_39
\}
_39
\}
_39
`
_39
}),
_39
_39
// Performance stats (via custom metrics)
_39
getCacheMetrics()
_39
])
_39
_39
return {
_39
redirectsCount: redirectsCount.data.redirect.listRedirects.routes.length,
_39
internalsCount: internalsCount.data.internal.listInternals.routes.length,
_39
cacheHitRate: cacheStats.hitRate,
_39
avgResponseTime: cacheStats.avgResponseTime
_39
}
_39
}

2. Automated Alerts


_36
// Function to automatically detect issues
_36
async function checkForIssues() {
_36
const issues = []
_36
_36
// 1. Check expired redirects
_36
const expiredRedirects = await findExpiredRedirects()
_36
if (expiredRedirects.length > 0) {
_36
issues.push({
_36
type: 'expired_redirects',
_36
count: expiredRedirects.length,
_36
items: expiredRedirects
_36
})
_36
}
_36
_36
// 2. Check potential loops
_36
const potentialLoops = await detectPotentialLoops()
_36
if (potentialLoops.length > 0) {
_36
issues.push({
_36
type: 'potential_loops',
_36
count: potentialLoops.length,
_36
chains: potentialLoops
_36
})
_36
}
_36
_36
// 3. Check performance
_36
const slowRoutes = await findSlowResolvingRoutes()
_36
if (slowRoutes.length > 0) {
_36
issues.push({
_36
type: 'performance_issues',
_36
count: slowRoutes.length,
_36
routes: slowRoutes
_36
})
_36
}
_36
_36
return issues
_36
}

3. Usage Reports


_40
async function generateUsageReport(startDate: string, endDate: string) {
_40
// Simulated report based on logs/metrics
_40
return {
_40
period: { startDate, endDate },
_40
redirects: {
_40
total: 1250,
_40
by_type: {
_40
PERMANENT: 1100,
_40
TEMPORARY: 150
_40
},
_40
by_origin: {
_40
'admin-interface': 800,
_40
'bulk-import': 300,
_40
'api-integration': 150
_40
},
_40
top_sources: [
_40
{ from: '/old-category', hits: 5000 },
_40
{ from: '/old-product', hits: 3200 }
_40
]
_40
},
_40
internals: {
_40
total: 8500,
_40
by_type: {
_40
product: 6000,
_40
category: 2000,
_40
brand: 400,
_40
search: 100
_40
},
_40
cache_performance: {
_40
hit_rate: 0.92,
_40
avg_resolution_time: 45 // ms
_40
}
_40
},
_40
performance: {
_40
avg_response_time: 120, // ms
_40
p95_response_time: 250, // ms
_40
error_rate: 0.001 // 0.1%
_40
}
_40
}
_40
}

🔧 Maintenance Scripts

1. Cleanup Expired Redirects


_53
async function cleanupExpiredRedirects() {
_53
const limit = 100
_53
let next = null
_53
let cleanedCount = 0
_53
_53
do {
_53
const { data } = await client.query({
_53
query: gql`
_53
query GetRedirects($limit: Int!, $next: String) \{
_53
redirect \{
_53
listRedirects(limit: $limit, next: $next) \{
_53
routes \{
_53
from
_53
to
_53
binding
_53
endDate
_53
\}
_53
next
_53
\}
_53
\}
_53
\}
_53
`,
_53
variables: { limit, next }
_53
})
_53
_53
const expired = data.redirect.listRedirects.routes.filter(route =>
_53
route.endDate && new Date(route.endDate) < new Date()
_53
)
_53
_53
if (expired.length > 0) {
_53
await client.mutate({
_53
mutation: gql`
_53
mutation CleanupExpired($paths: [String!]!, $locators: [RouteLocator!]!) \{
_53
redirect \{
_53
deleteMany(paths: $paths, locators: $locators)
_53
\}
_53
\}
_53
`,
_53
variables: {
_53
paths: expired.map(r => r.from),
_53
locators: expired.map(r => ({ from: r.from, binding: r.binding }))
_53
}
_53
})
_53
_53
cleanedCount += expired.length
_53
}
_53
_53
next = data.redirect.listRedirects.next
_53
} while (next)
_53
_53
console.log(`Cleaned up $\{cleanedCount\} expired redirects`)
_53
return cleanedCount
_53
}

2. Data Integrity Validation


_51
async function validateDataIntegrity() {
_51
const issues = []
_51
_51
// Check orphaned internal routes (without corresponding entity)
_51
const { data } = await client.query({
_51
query: gql`
_51
query GetAllInternals \{
_51
internal \{
_51
listInternals(limit: 1000) \{
_51
routes \{
_51
from
_51
type
_51
id
_51
binding
_51
\}
_51
\}
_51
\}
_51
\}
_51
`
_51
})
_51
_51
for (const route of data.internal.listInternals.routes) {
_51
if (route.type === 'product') {
_51
// Check if product exists in catalog
_51
const productExists = await checkProductExists(route.id)
_51
if (!productExists) {
_51
issues.push({
_51
type: 'orphaned_product_route',
_51
route: route.from,
_51
productId: route.id,
_51
binding: route.binding
_51
})
_51
}
_51
}
_51
_51
if (route.type === 'category') {
_51
// Check if category exists
_51
const categoryExists = await checkCategoryExists(route.id)
_51
if (!categoryExists) {
_51
issues.push({
_51
type: 'orphaned_category_route',
_51
route: route.from,
_51
categoryId: route.id,
_51
binding: route.binding
_51
})
_51
}
_51
}
_51
}
_51
_51
return issues
_51
}


This guide covers the most common troubleshooting scenarios and advanced use cases for the Rewriter. For situations not covered here, consult the technical documentation or contact the development team.

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