Skip to content

fix: prevent root wildcard static routes from overriding mount routes#1686

Closed
raunak-rpm wants to merge 2 commits intoelysiajs:mainfrom
raunak-rpm:fix/spa-fallback-1515
Closed

fix: prevent root wildcard static routes from overriding mount routes#1686
raunak-rpm wants to merge 2 commits intoelysiajs:mainfrom
raunak-rpm:fix/spa-fallback-1515

Conversation

@raunak-rpm
Copy link

@raunak-rpm raunak-rpm commented Jan 19, 2026

Summary

Fixes issue #1515 - Cannot implement SPA fallback routing with mounted APIs

When using static Response/HTMLBundle on root wildcard routes (e.g., GET /*), Bun's nativeStaticResponse optimization would bypass Elysia's dynamic router, causing mounted API routes to return HTML instead of JSON.

⚠️ Dependency

This PR requires PR #1685 to be merged first. PR #1685 fixes route specificity in the dynamic router (compose.ts), which is necessary for this fix to work properly. Without #1685, requests would still be incorrectly routed even after bypassing Bun's static table.

Root Cause

Static responses are added directly to Bun's static route table, which has highest priority and bypasses Elysia's dynamic router entirely. A root wildcard in the static table matches everything including mount routes.

Solution

Modified: src/adapter/bun/index.ts

  • During listen(), collect mount prefixes by scanning router.history for routes with hooks.config.mount flag
  • In createStaticRoute(), skip root wildcard paths (/*, /*/, /, ``) when mounts exist
  • This forces root wildcards through the dynamic router where route specificity (from PR fix: route specificity for wildcard vs mounted routes (#1682, #1515) #1685) is properly handled

Why This Approach

  • Minimal performance impact: Only scans routes once during .listen(), not per-request
  • Surgical fix: Only affects root wildcards when mounts exist
  • Preserves optimization: Specific static routes still go to Bun's fast static table
  • Backwards compatible: Does not break existing apps without mounts

Test Coverage

Added comprehensive tests in test/core/spa-fallback.test.ts covering:

  • Root wildcard with single/multiple mounts
  • Registration order independence (wildcard before/after mount)
  • POST requests to mounted routes
  • Specific wildcard paths (/public/*) still work
  • Normal wildcard behavior preserved without mounts
  • Nested mount paths

All tests pass with PR #1685 merged.

Verification

const app = new Elysia()
    .mount('/api', backend)
    .get('/*', staticHtmlResponse)

// Before fix:
// GET /api/users → HTML (bug)

// After fix:
// GET /api/users → JSON ✅
// GET /about → HTML ✅

Fixes #1515
Depends on: #1685

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced route resolution during startup to properly handle mount route prefixes, ensuring mounted routes take precedence over wildcard static routes and preventing route conflicts.
  • Tests

    • Added comprehensive test suite for SPA fallback routing, covering single/multiple mounts, route ordering, POST requests, nested paths, and wildcard behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

…elysiajs#1515)

When using static Response/HTMLBundle on root wildcard routes (e.g., GET /*),
Bun's nativeStaticResponse optimization would add them to Bun's static route
table, which has highest priority and bypasses Elysia's dynamic router.

This caused SPA fallback patterns with mounted APIs to fail:
  app.mount('/api', backend)
     .get('/*', staticHtmlResponse)

The /api/* routes would return HTML instead of API responses because the /*
wildcard in Bun's static table matched everything.

Solution:
- During listen(), collect mount prefixes by scanning router.history for
  routes with hooks.config.mount flag
- In createStaticRoute(), skip root wildcard paths (/* or /) when mounts exist
- This forces root wildcards through the dynamic router where the route
  specificity fix from elysiajs#1682 correctly handles priority

Production-grade approach:
- Minimal performance impact: only scans during .listen() once
- Surgical fix: only affects root wildcards when mounts exist
- Preserves optimization: specific static routes still go to Bun table
- Backwards compatible: doesn't break existing apps without mounts

Fixes elysiajs#1515
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

Walkthrough

The PR updates the Bun adapter to record mounted route prefixes at startup and prevents root-level wildcard static routes from being registered when mounts exist, so mounted routes are not shadowed by generic wildcard static handlers.

Changes

Cohort / File(s) Summary
Adapter Route Guard Logic
src/adapter/bun/index.ts
During adapter startup, scans app.router.history for mount entries and collects mount prefixes; adds a guard that skips registering root wildcard static routes (/*, /*/, /, empty) when any mount prefixes are present, changing route registration order to favor mounted paths.
SPA Fallback Routing Tests
test/core/spa-fallback.test.ts
Adds comprehensive tests covering SPA fallback vs mounted APIs: single/multiple mounts, registration ordering, POST handling, nested mounts (e.g., /api/v1), mixed wildcard scenarios, and baseline behavior when no mounts exist. Tests instantiate servers and assert API vs SPA fallback responses.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I catalogued each mount and way,

So wildcards no longer steal the day.
APIs stand firm, the SPA can play,
Hops of joy along the routing way! 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: preventing root wildcard static routes from overriding mount routes, which is the core fix implemented in src/adapter/bun/index.ts.
Linked Issues check ✅ Passed The PR fully addresses the requirements from issue #1515: root wildcard paths are skipped when mounts exist, enabling SPA fallback without breaking mounted API routes, and comprehensive tests validate the fix across multiple scenarios.
Out of Scope Changes check ✅ Passed All changes are within scope: modifications to src/adapter/bun/index.ts implement the routing fix, and tests in test/core/spa-fallback.test.ts validate the SPA fallback scenarios described in the linked issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Also update tests to use actual fetch() instead of .handle() since the fix
targets Bun's static route table optimization.
@raunak-rpm
Copy link
Author

Closing this PR as the fix has been merged into PR #1685 which now addresses both #1682 and #1515 together.

@raunak-rpm raunak-rpm closed this Jan 19, 2026
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.

Cannot implement SPA fallback routing: HTML bundle serialized as JSON in onError

2 participants