skip to content

A renamed dynamic route folder broke internal links that only failed at runtime. How do you make a file-derived route table refactor-safe?

level: seniorimportance: should knowfreq 48%

answer

  1. the table exists only as directories
  2. a rename is a move, not a reference
  3. generate a checked surface from the tree
  4. build paths, never write them
  5. content links need a crawl, not types

basics

~20 s

Generate types from the tree and stop writing paths by hand: a route-pattern union, typed parameters and a path-building helper turn a rename into a compile error. Add a generated route listing and a pipeline link check.

solid answer

~50 s

The failure is structural: the route table is implicit, so a rename is a directory move no compiler can connect to the string literals pointing at it. The fix is to give the tree a checked surface. Most file-based routers can **generate types from the route tree** — a union of valid route patterns and, per route, the names and kinds of its parameters — and expose a **path-building helper** that takes a pattern plus its parameters and returns the URL. Route through a helper call instead of a literal and a rename fails type-checking wherever the old route was used. Then cover the gaps: a lint rule banning raw path literals, a committed **generated route listing** so route changes show up in review, and a pipeline link check for paths living in content. Typed parameters still arrive as untrusted strings, so validate at the route boundary regardless.

go deeper

for a junior

Recall the shape of the problem: URLs written as plain strings have no connection to the folders that serve them, so a route rename breaks links that nothing checks until someone clicks.

for a middle

Explain the mechanism of the fix: types generated from the route tree, a path-building helper that takes typed parameters, and why that moves the failure from runtime to compile time.

for a senior

Demonstrate coverage thinking: name what types catch, what a committed route listing catches in review, what only a link check over a build catches, and what only keeping the old URL alive can handle.

for a principal

Own the erosion problem. Decide where the rules are enforced, who owns a route rename as an interface change, and how the organisation reads its route table without walking a directory tree.

## Why this class of break exists Every benefit of deriving routes from the filesystem comes with the same liability: **the route table has no declaration a tool can hold on to**. Renaming a folder is a file move; the compiler sees a module path change, not a URL change. Meanwhile the URL appears throughout the codebase as ordinary strings — in link targets, redirect destinations, tests, emails, content, analytics configuration. None of those strings has a typed relationship to the directory that serves them, so the rename compiles, ships and fails on the first click. The goal is not to abandon the convention; it is to **re-derive a checked surface from the same tree**, so the implicit table is available to tools even though no human wrote it. ## The layers that actually help 1. **Generated route types.** A build step walks the tree and emits a union of every route pattern it found. A value of that union type cannot hold a path the tree does not serve, so a removed or renamed route becomes a compile error rather than a 404. 2. **Typed parameters per route.** The same generation step knows which segments are variable, what they are called and whether a segment yields one value or a list. A route module then reads its parameters through a typed shape instead of indexing an untyped bag. 3. **A path-building helper.** Pass the route pattern and its parameters; get a correctly escaped URL back. This is what makes a rename fail *at the call site*, and it also removes hand-built string concatenation, which is where encoding bugs live. 4. **A lint rule against raw internal path literals.** Types only help where the helper is used, so the rule is what keeps the escape hatch from becoming the default. 5. **A committed route listing.** A generated file enumerating the served routes, checked into the repository, turns "which URLs does this app serve?" into a file you can read and, more usefully, makes every route addition or removal a visible line in a diff. 6. **A link check in the pipeline.** Paths embedded in content, configuration or documentation are invisible to types. A crawler over a built preview, or a checker that validates each internal link against the generated listing, catches those. | Where the URL lives | Caught by | Not caught by | |---|---|---| | a link in a component | typed helper, lint rule | a route listing alone | | a redirect or rewrite entry | link check against the listing | types, if the entry is plain data | | a path inside authored content | crawl or link check | types and lint | | a path in an external system | nothing automatic | everything — this is why the old URL must keep working | ## What types cannot give you Two honest limits are worth stating in an interview, because they separate someone who has run this from someone repeating advice. - **A typed parameter is still untrusted input.** The type says a value exists and is a string, or that a tail is a list of strings. It says nothing about whether an identifier is real, whether a list is the length your code assumes, or whether a tail is safe to join into a path. Validation belongs at the route boundary and stays there. - **Nothing inside the repository protects links you do not own.** Bookmarks, search results and other systems keep pointing at the old URL, so keeping it working is a separate obligation that the type system will never remind you about. ## How to run the rename itself When the tooling is in place the change becomes mechanical, and that is the point: 1. Move the directory and regenerate the route types and listing. 2. Fix the type errors — with a helper in use, they are exactly the call sites that pointed at the old route. 3. Read the generated listing's diff as the review artefact: it states in plain text which URLs appeared and disappeared. 4. Run the link check over a build to catch paths in content and configuration. 5. Decide explicitly what happens to the old URL for outside callers, and make that decision visible in the change rather than implicit in its absence. ## The team-level habit The tooling degrades quietly, because a single hand-written path literal costs nothing on the day it is written. Three habits keep it alive: the lint rule runs in the pipeline rather than in an editor; the generated listing is committed so its diff is reviewable; and a route rename is treated as an interface change with an owner, not as a tidy-up. Without them you still have a file-based router — you have just gone back to finding broken links by clicking on them.

  • Why is a path-building helper worth more than a checked list of route constants?
    Constants cover the pattern but not the values, so parameters get concatenated by hand — the place encoding bugs and malformed URLs appear. A helper takes the pattern plus its typed parameters and returns a correctly built path, so both halves of the URL are checked at once.
  • The route types are generated. What still has to be validated at runtime?
    Everything about the values. Types describe what the router guarantees — a parameter exists, a tail is a list of strings — not whether an identifier resolves, whether the list is the length the code assumes, or whether a tail is safe to use in a path. Validate at the route boundary.
  • What do you gain by committing a generated route listing rather than producing it on demand?
    Reviewability. A committed listing makes every added, renamed or removed URL a line in the diff, so a route change cannot slip through as an unremarkable directory move, and anyone can read the route table without running a build.
  • How do you keep the discipline from decaying once the tooling exists?
    Enforce it where it cannot be skipped: the lint rule banning raw internal paths runs in the pipeline, the generated listing is committed so drift shows up in review, and route renames are treated as interface changes with a named owner rather than routine cleanup.

saying these in an interview costs you the question

  • Says a search-and-replace over path strings is enough
  • Believes typed parameters make route input trusted
  • Assumes a compiler can see string paths pointing at a folder
  • Expects the router to fail the build on a broken internal link
  • Ignores paths embedded in content and configuration
  • Adds a path helper but leaves raw literals unpoliced