Skip to content

Serve SPA fallback with 200 for routable paths in prod static serving - #6996

Open
FarhanAliRaza wants to merge 3 commits into
reflex-dev:mainfrom
FarhanAliRaza:claude/dynamic-routes-prod-404-exwen5
Open

Serve SPA fallback with 200 for routable paths in prod static serving#6996
FarhanAliRaza wants to merge 3 commits into
reflex-dev:mainfrom
FarhanAliRaza:claude/dynamic-routes-prod-404-exwen5

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

In self-hosted prod (reflex run --env prod), a direct load of a valid dynamic-route
URL (e.g. /articles/7) returned HTTP 404 with the SPA-fallback body, making valid
dynamic URLs indistinguishable from genuinely unknown paths — bad for SEO, uptime
monitors, and anything trusting status codes.

PrecompressedStaticFiles now accepts a route matcher: when Starlette's html-mode
404.html fallback is hit for a path that matches the app's route table, it is served
with status 200; unroutable paths keep the 404 status. The backend-mounted frontend
passes app.router directly; for the standalone prod static server (frontend-only
mode), the compiler persists the route table to .web/routes.json at compile time and
the mount builds a matcher from it, falling back to the previous behavior when no
manifest exists (the manifest lives outside build/client, so it is not publicly
served). Configured frontend_path prefixes are restored before matching since the
mount strips them from request paths.

Covered by new unit tests (routable → 200 incl. precompressed sidecars, unknown → 404,
real files unaffected, manifest loading, frontend_path handling) and an extended
tests/integration/test_precompressed_frontend.py with an articles/[id] page.

closes #6983

Review in cubic

Direct loads of valid dynamic-route URLs (e.g. /articles/7) in self-hosted
prod returned HTTP 404 with the SPA fallback body, making them
indistinguishable from genuinely unknown paths (bad for SEO, uptime
monitors, and anything trusting status codes).

PrecompressedStaticFiles now accepts a route matcher: when the html-mode
404.html fallback is hit for a path that matches the app's route table, it
is served with status 200; unknown paths keep the 404 status.

The backend-mounted frontend passes app.router directly. For the
standalone prod static server (frontend-only mode), the compiler now
persists the route table to .web/routes.json at compile time and the mount
builds a matcher from it, falling back to the previous behavior when no
manifest exists. Configured frontend_path prefixes are restored before
matching since the mount strips them from request paths.

Fixes reflex-dev#6983
@FarhanAliRaza
FarhanAliRaza requested a review from a team as a code owner August 28, 2026 20:58
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR updates production static serving so registered SPA routes receive HTTP 200 while unknown paths retain HTTP 404.

  • Persists compiled page routes in a generated manifest for standalone frontend serving.
  • Supplies route matchers to backend-mounted and standalone static servers.
  • Preserves precompressed fallback handling and configured frontend path prefixes.
  • Adds unit and integration coverage for dynamic routes, unknown routes, manifests, compression, and path prefixes.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
reflex/utils/precompressed_staticfiles.py Uses the route matcher to change only routable SPA fallback responses from 404 to 200 while retaining sidecar handling.
reflex/utils/exec.py Loads the compiled route manifest and adapts route matching for synthetic 404 routes and frontend path prefixes.
reflex/compiler/compiler.py Emits the registered page-route manifest into the generated web workspace.
reflex/app.py Centralizes deduplicated page routes and passes the application router to the mounted frontend.
tests/units/utils/test_precompressed_staticfiles.py Adds fallback-status, compression, and real-file regression tests; the previously requested helper docstring is present.

Reviews (3): Last reviewed commit: "Cover corrupt routes manifest in get_rou..." | Re-trigger Greptile

Comment thread tests/units/utils/test_precompressed_staticfiles.py
@codspeed-hq

codspeed-hq Bot commented Aug 28, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 32 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing FarhanAliRaza:claude/dynamic-routes-prod-404-exwen5 (319b3eb) with main (394e525)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 139ba3c7b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/reflex-base/src/reflex_base/constants/base.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread reflex/utils/precompressed_staticfiles.py
Comment thread tests/units/utils/test_precompressed_staticfiles.py
… test coverage

- Exclude the compiler's synthetic 404 page route from the SPA-fallback
  status matcher in get_frontend_mount, so a literal /404 request keeps
  its 404 status (with regression tests for both the manifest-built and
  explicitly passed routers).
- Add news fragments for reflex and reflex-base.
- Assert Vary: Accept-Encoding on the 200 SPA-fallback responses in the
  gzip test cases.
- Add the missing docstring on the _articles_router test helper and fix
  the _page_routes property docstring lint.
@masenf

masenf commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

seems like this one supersedes #6469 ? can you check to see if there is any overlap or anything missed in the earlier PR, integrate it here, then close the old one

Ports the one test scenario from PR reflex-dev#6469 that the superseding
implementation did not already cover: a routes.json that fails to parse
must disable the SPA-fallback router (return None) rather than raise.
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.

Prod mode serves HTTP 404 status for direct loads of valid dynamic-route URLs (SPA fallback body renders fine)

2 participants