{"record":{"id":"587350bf1e711669","repo":"expo/expo","slug":"input-should-not-end-with","errorCode":null,"errorMessage":"Input should not end with `/`","messagePattern":"Input should not end with `/`","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@expo/cli/src/start/server/metro/createServerComponentsMiddleware.ts","lineNumber":644,"sourceCode":"    if (error instanceof TypeError) {\n      throw Error(`Invalid URL: ${fileURL}`, { cause: error });\n    }\n    throw error;\n  }\n};\n\nconst encodeInput = (input: string) => {\n  if (input === '') {\n    return 'index.txt';\n  }\n  if (input === 'index') {\n    throw new Error('Input should not be `index`');\n  }\n  if (input.startsWith('/')) {\n    throw new Error('Input should not start with `/`');\n  }\n  if (input.endsWith('/')) {\n    throw new Error('Input should not end with `/`');\n  }\n  return input + '.txt';\n};\n\nfunction wrapBundle(str: string) {\n  // Skip the metro runtime so debugging is a bit easier.\n  // Replace the __r() call with an export statement.\n  // Use gm to apply to the last require line. This is needed when the bundle has side-effects.\n  return str.replace(/^(__r\\(.*\\);)$/gm, 'module.exports = $1');\n}\n","sourceCodeStart":626,"sourceCodeEnd":655,"githubUrl":"https://github.com/expo/expo/blob/b09195aac27c9e4a63daaf12dcb6f00b6b2e08ee/packages/@expo/cli/src/start/server/metro/createServerComponentsMiddleware.ts#L626-L655","documentation":"Input validation in `encodeInput`: route inputs must not end with `/`. A trailing slash would append `.txt` to form a path like `foo/.txt` under `_flight/<platform>/`, which is invalid and would collide or be lost during static export.","triggerScenarios":"Thrown at createServerComponentsMiddleware.ts:643 when `exportRoutesAsync` passes a `buildConfig` entry `input` that ends with `/` to `encodeInput`. Reached during `expo export` with static RSC routes.","commonSituations":"An expo-router version that leaves a trailing slash on route inputs; a catch-all or nested route whose input normalization drops the last segment but keeps the separator; a redirect targeting a directory-style URL.","solutions":["Update `expo-router` and `@expo/cli` to matching versions.","Inspect generated routes/redirects for trailing slashes and strip them.","Clear the export cache (`expo export -c`).","Report with the route tree if it reproduces on a clean project."],"exampleFix":"// before — input retains a trailing slash from the route matcher\nconst input = route.input; // e.g. 'about/'\n\n// after — strip trailing slashes before encoding\nconst input = route.input.replace(/\\/+$/, ''); // 'about'","handlingStrategy":"validation","validationCode":"function normalizeRouteInput(input: string): string {\n  return input.replace(/\\/+$/, '');\n}\n// const safe = normalizeRouteInput(route.input);","typeGuard":"function isValidRouteInput(input: string): boolean {\n  return !input.endsWith('/') && !input.startsWith('/') && input !== 'index';\n}","tryCatchPattern":null,"preventionTips":["Never emit trailing slashes on route inputs.","Validate generated inputs against a single `isValidRouteInput` helper before export.","Test catch-all and nested routes specifically — they are the most likely to leak a trailing slash."],"tags":["static-export","rsc","expo-router","validation","routing"],"backgroundTag":null,"analyzedSha":"b09195aac27c9e4a63daaf12dcb6f00b6b2e08ee","analyzedAt":"2026-08-12T15:59:58.304Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}