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_15query 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_15mutation UpdateSettings {_15 saveVtexAppSettings(_15 app: "vtex.rewriter", _15 settings: { routesVersion: 5 } # Increment current value_15 )_15}
Incorrect binding:
_10# Check route binding_10query 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_10query 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_21query 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_21query 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_10mutation 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_10process.env.VTEX_APP_LINK = 'true'_10_10// Analyze performance logs_10console.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_10const INCREASED_CACHE = 10000 // Default: 3000_10_10// Check cache hit rate_10metrics.trackCache('vbase', cache) // Target: >90%
Reduce VBase calls:
_12# Use listRedirects in batch instead of multiple get queries_12query 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_14mutation 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_15mutation 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_30const 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_30await 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_30const 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_30await client.mutate({_30 mutation: SAVE_MANY_REDIRECTS,_30 variables: { routes: redirects }_30})
3. Multilingual URL Implementation
_49# Primary route in Portuguese_49mutation 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_49mutation 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_23mutation 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
_39async 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_36async 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
_40async 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
_53async 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
_51async 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.