{"record":{"id":"98e197cf340bcbfb","repo":"remix-run/react-router","slug":"route-segment-segment-for-routeid-cannot","errorCode":null,"errorMessage":"Route segment \"${segment}\" for \"${routeId}\" cannot contain \"${char}\".\nIf this is something you need, upvote this proposal for React Router https://github.com/remix-run/react-router/discussions/9822.","messagePattern":"Route segment \"(.+?)\" for \"(.+?)\" cannot contain \"(.+?)\"\\.\nIf this is something you need, upvote this proposal for React Router https://github\\.com/remix-run/react-router/discussions/9822\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/react-router-fs-routes/flatRoutes.ts","lineNumber":363,"sourceCode":"  | \"ESCAPE\"\n  // we hit a `(` and are now in an optional segment until we hit a `)` or an escape sequence\n  | \"OPTIONAL\"\n  // we previously were in a opt fional segment and hit a `[` and are now in an escape sequence until we hit a `]` - take characters literally and skip isSegmentSeparator checks - afterwards go back to OPTIONAL state\n  | \"OPTIONAL_ESCAPE\";\n\nexport function getRouteSegments(routeId: string): [string[], string[]] {\n  let routeSegments: string[] = [];\n  let rawRouteSegments: string[] = [];\n  let index = 0;\n  let routeSegment = \"\";\n  let rawRouteSegment = \"\";\n  let state: State = \"NORMAL\";\n\n  let pushRouteSegment = (segment: string, rawSegment: string) => {\n    if (!segment) return;\n\n    let notSupportedInRR = (segment: string, char: string) => {\n      throw new Error(\n        `Route segment \"${segment}\" for \"${routeId}\" cannot contain \"${char}\".\\n` +\n          `If this is something you need, upvote this proposal for React Router https://github.com/remix-run/react-router/discussions/9822.`,\n      );\n    };\n\n    if (rawSegment.includes(\"*\")) {\n      return notSupportedInRR(rawSegment, \"*\");\n    }\n\n    if (rawSegment.includes(\":\")) {\n      return notSupportedInRR(rawSegment, \":\");\n    }\n\n    if (rawSegment.includes(\"/\")) {\n      return notSupportedInRR(segment, \"/\");\n    }\n\n    routeSegments.push(segment);","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/remix-run/react-router/blob/1fd704a7dabcbe3ae09d7387b460e6acaba30ec1/packages/react-router-fs-routes/flatRoutes.ts#L345-L381","documentation":"Thrown by getRouteSegments() (used by flatRoutes) when a raw filename segment contains a character React Router file-system routing doesn't support outside escape sequences: `*`, `:`, or `/`. The error names the segment, the routeId, and the offending character, and links to the upstream proposal (discussion #9822) for tracking. Escape sequences `[...]` are the supported way to include otherwise-special characters.","triggerScenarios":"A route filename like app/routes/users:$id.tsx, app/routes/post*.tsx, or app/routes/a/b.tsx where the raw segment (before bracket-escaping) contains `*`, `:`, or `/`. The pushRouteSegment helper scans rawSegment for these characters and calls notSupportedInRR.","commonSituations":"Coming from Next.js / Express conventions and naming files with `:param` or `*` splats. Using `/` inside a filename to denote nesting instead of `.` (the RR convention). Forgetting that `[param]` is the escape mechanism.","solutions":["Use bracket-escape syntax for parameters: rename `users:$id.tsx` → `users.$id.tsx` (RR param convention) or `users.[].tsx` style per docs.","Replace `*` splats with RR's catch-all convention (`$.tsx`).","Use `.` (or `\\` on Windows normalized) to nest instead of `/` in filenames.","If you genuinely need a literal `*`, `:`, or `/` in a segment, wrap it in brackets per the escape rules — but first check discussion #9822 for current support."],"exampleFix":"// before\n//   app/routes/users:$id.tsx          → segment contains ':'\n//   app/routes/post*.tsx               → segment contains '*'\n// after\n//   app/routes/users.$id.tsx           → /users/:id\n//   app/routes/post.$.tsx              → /post/* (splat)","handlingStrategy":"validation","validationCode":"// prebuild filename linter\nimport { basename } from 'node:path';\nimport { globSync } from 'node:fs';\nfor (const file of globSync('app/routes/**')) {\n  for (const segment of basename(file).split('.')) {\n    if (['*',':','/'].some((c) => segment.includes(c)) && !/^\\[.*\\]$/.test(segment)) {\n      console.warn(`${file}: segment '${segment}' contains an unsupported character`);\n    }\n  }\n}","typeGuard":"function isSupportedRouteSegment(rawSegment: string): boolean {\n  return !['*',':','/'].some((c) => rawSegment.includes(c));\n}","tryCatchPattern":null,"preventionTips":["Use the RR param/splat conventions ($param, $) instead of :param / *.","Nest with `.` not `/` in filenames.","Lint route filenames in CI to catch unsupported characters early."],"tags":["fs-routes","flat-routes","filename","routing-convention"],"backgroundTag":null,"analyzedSha":"1fd704a7dabcbe3ae09d7387b460e6acaba30ec1","analyzedAt":"2026-08-12T13:54:57.804Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}