| /** | |
| * Shared routing patterns for determining App Router vs Pages Router routing decisions. | |
| * This module centralizes pattern definitions to ensure consistency between | |
| * app-router-gateway.ts and render-page.ts | |
| */ | |
| // Define which routes should use App Router (without locale prefix) | |
| const APP_ROUTER_ROUTES = new Set([ | |
| '/_not-found', | |
| '/404', | |
| // Add more routes as you migrate them | |
| ]) | |
| /** | |
| * Determines if a given path should be handled by the App Router | |
| * @param path - The request path to check | |
| * @param pageFound - Whether a page was found by the findPage middleware | |
| * @returns true if the path should use App Router, false for Pages Router | |
| */ | |
| export function shouldUseAppRouter(path: string, pageFound: boolean = true): boolean { | |
| // Strip locale prefix before checking | |
| const strippedPath = stripLocalePrefix(path) | |
| // Check exact matches on the stripped path for specific App Router routes | |
| if (APP_ROUTER_ROUTES.has(strippedPath)) { | |
| return true | |
| } | |
| // Special case: paths ending with /empty-categories should always 404 via App Router | |
| // This handles translation tests where certain versioned paths should not exist | |
| if (strippedPath.endsWith('/empty-categories')) { | |
| return true | |
| } | |
| // For 404 migration: If no page was found, use App Router for 404 handling | |
| if (!pageFound) { | |
| return true | |
| } | |
| return false | |
| } | |
| /** | |
| * Checks if a path looks like a "junk path" that should be handled by shielding middleware | |
| * These are paths like WordPress attacks, config files, etc. that need specific 404 handling | |
| */ | |
| export function isJunkPath(path: string): boolean { | |
| // Common attack patterns and junk paths that should be handled by shielding | |
| const junkPatterns = [ | |
| /^\/wp-/, // WordPress paths: /wp-content, /wp-login.php, etc. | |
| /^\/[^/]*\.php$/, // PHP files in root: xmlrpc.php, wp-login.php (but not /en/delicious-snacks/donuts.php) | |
| /^\/~\w+/, // User directory paths: /~root, /~admin, etc. | |
| /\/\.env/, // Environment files: /.env, /.env.local, etc. | |
| /\/package(-lock)?\.json$/, // Node.js package files | |
| /^\/_{2,}/, // Multiple underscores: ///, /\\, etc. | |
| /^\/\\+/, // Backslash attacks | |
| ] | |
| return junkPatterns.some((pattern) => pattern.test(path)) | |
| } | |
| /** | |
| * Checks if a path contains a version identifier (e.g., enterprise-server@3.16, enterprise-cloud@latest) | |
| * This helps distinguish versioned docs paths from regular paths that should potentially use App Router | |
| */ | |
| export function isVersionedPath(path: string): boolean { | |
| const strippedPath = stripLocalePrefix(path) | |
| // Check for version patterns: plan@release | |
| // Examples: enterprise-server@3.16, enterprise-server@latest, enterprise-cloud@latest, free-pro-team@latest | |
| const versionPattern = | |
| /(enterprise-server@[\d.]+|enterprise-server@latest|enterprise-cloud@latest|free-pro-team@latest)/ | |
| return versionPattern.test(strippedPath) | |
| } | |
| /** | |
| * Checks if a versioned path contains invalid segments that should result in 404 | |
| * These should be routed to App Router for proper 404 handling | |
| */ | |
| export function isInvalidVersionedPath(path: string): boolean { | |
| const strippedPath = stripLocalePrefix(path) | |
| // Check for obviously invalid paths that should 404 | |
| // Example: /enterprise-server@latest/ANY/admin or /enterprise-server@12345/anything | |
| return ( | |
| strippedPath.includes('/ANY/') || | |
| /enterprise-server@12345/.test(strippedPath) || | |
| // Add other invalid patterns as needed | |
| false | |
| ) | |
| } | |
| /** | |
| * Decodes a URL path, handling URL-encoded characters like %40 -> @ | |
| */ | |
| export function decodePathSafely(path: string): string { | |
| try { | |
| return decodeURIComponent(path) | |
| } catch { | |
| // If decoding fails, use original path | |
| return path | |
| } | |
| } | |
| /** | |
| * Strips the locale prefix from the path if present | |
| * e.g., /en/search -> /search | |
| * e.g., /en/enterprise-server@3.17 -> /enterprise-server@3.17 | |
| */ | |
| export function stripLocalePrefix(path: string): string { | |
| const decodedPath = decodePathSafely(path) | |
| const localeMatch = decodedPath.match(/^\/([a-z]{2})(\/.*)?$/) | |
| if (localeMatch) { | |
| return localeMatch[2] || '/' | |
| } | |
| return decodedPath | |
| } | |