Skip to content

fix: fail the build on fs-route configurations that would render broken pages - #178

Merged
uhyo merged 2 commits into
masterfrom
claude/file-system-router-audit-6skqb1
Aug 29, 2026
Merged

fix: fail the build on fs-route configurations that would render broken pages#178
uhyo merged 2 commits into
masterfrom
claude/file-system-router-audit-6skqb1

Conversation

@uhyo

@uhyo uhyo commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary

An audit of the experimental file-system router found several inputs that build successfully but produce silently broken output. This PR validates them at build time with clear errors instead. Each case was verified against the real URLPattern behavior that @funstack/router matches with.

generateStaticParams() value validation (tree.ts)

  • Values must be non-empty strings without ./.. segments, ?, or #; only catch-all values may contain slashes (and no leading, trailing, or repeated ones). Anything else generated a page its own route could never match — e.g. { slug: "a/b" } for /blog/:slug builds blog/a/b.html, but :slug matches a single segment, so the page renders broken. A .. value could even write HTML outside the output directory.
  • Non-string values (e.g. numeric ids) are rejected: the URL would work on a hard load, but the baked params key ([5]) would never match the live key (["5"]), breaking soft navigation.
  • An empty catch-all value gets a dedicated error suggesting a page for the parent route: its zero-segment page cannot be soft-navigated to, because the live match yields no param value at all.
  • Errors name the route path and source file.

Segment validation in nextRoutes() (nextAdapter.ts)

  • Param names must match [A-Za-z0-9_$]+ — what URLPattern can express. [foo-bar] was silently parsed as param foo + literal -bar, producing a route that never matches its own generated pages.
  • A param name may be used only once per route path: within one emitted path, URLPattern construction throws at match time; across a layout boundary, the inner value shadows the outer.
  • Static directory names must not contain URLPattern metacharacters (:, *, ?, +, parens, braces, backslash), which either throw at match time (?, +) or silently match the wrong URLs.

Other fixes

  • A page or layout module without a default export now fails the build instead of silently rendering a blank page (or a pass-through layout).
  • fsRoutes.dir must be a subdirectory of the Vite root; . or ../… values previously produced malformed glob patterns (//**/…) that matched nothing.

Cleanups

  • FsRouteModule["default"] collapsed to a single ComponentType<FsRouteComponentProps> (the other union members were redundant under component contravariance), dropping two casts in runtime.tsx.
  • Removed a stale comment above filesByDir in the adapter.
  • Documented the new validation rules in FileSystemRouting.mdx and the generateStaticParams JSDoc.

Test plan

  • 14 new unit tests covering every rejected input (142 total passing)
  • pnpm typecheck, pnpm lint, pnpm format:check clean
  • Full monorepo pnpm build succeeds
  • All 17 fs-routing e2e tests pass against a production build

🤖 Generated with Claude Code

https://claude.ai/code/session_01CnAGg4Hh4Fmuu9ULDkdDd2


Generated by Claude Code

claude added 2 commits August 29, 2026 16:01
…en pages

File-system routing accepted several inputs that built successfully but
produced silently broken output. Validate them at build time instead:

- generateStaticParams() values must be non-empty strings without "."/".."
  segments, "?", or "#"; only catch-all values may contain slashes (and no
  leading/trailing/repeated ones). Anything else generated a page its own
  route could never match (URLPattern matches back decoded single
  segments), or wrote HTML outside the output directory via "..".
- An empty catch-all value gets a dedicated error: its zero-segment page
  cannot be soft-navigated to (the live match yields no param), so a
  parent-route page is suggested instead.
- nextRoutes() rejects param names URLPattern cannot express ([foo-bar]
  parses as param "foo" + literal "-bar" and never matches), a param name
  used twice on one route path (URLPattern construction fails, or the
  inner value shadows the outer), and static directory names containing
  URL-pattern metacharacters (":", "*", "?", "+", parens, braces,
  backslash), which either throw at match time or match the wrong URLs.
- A page or layout module without a default export now fails the build
  instead of silently rendering a blank page (or pass-through layout).
- fsRoutes.dir must be a subdirectory of the Vite root; other values
  produced malformed glob patterns that matched nothing.

Also simplify FsRouteModule["default"] to a single component type (extra
union members were redundant and forced casts) and drop a stale comment
in the nextRoutes adapter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CnAGg4Hh4Fmuu9ULDkdDd2
@uhyo
uhyo merged commit 13e3de2 into master Aug 29, 2026
2 checks passed
@uhyo
uhyo deleted the claude/file-system-router-audit-6skqb1 branch August 29, 2026 23:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants