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

Open
#6,983 2 comments 0 reactions 1 assignee View on GitHub

@FarhanAliRaza is already working on this.

Since Aug 28, 2026.

Assessment

This issue has not been assessed yet.

Description

Describe the bug

In self-hosted prod (reflex run --env prod), a direct load of any dynamic-route URL returns HTTP status 404 with the SPA-fallback HTML as the body; the page then renders correctly client-side. Valid dynamic URLs and genuinely unknown paths are indistinguishable at the HTTP layer (byte-identical bodies, same status). Static routes behave differently: they 307-redirect to .../ and then 200.

Filing this as a behavior/docs question rather than a straight bug: is the 404 status for valid dynamic routes intended (accepted SPA-fallback tradeoff to be documented as a known limitation), or should the status code be fixed? Bad for SEO, uptime monitors, and anything that trusts status codes.

To Reproduce

App with pages /, /other, /articles/[id], /posts/[[...splat]]:

reflex run --env prod --frontend-port 8604 --backend-port 8604
curl -sS -o /dev/null -w '%{http_code}\n' http://localhost:8604/            # 200
curl -sS -o /dev/null -w '%{http_code}\n' -L http://localhost:8604/other    # 307 -> 200
curl -sS -o /dev/null -w '%{http_code}\n' http://localhost:8604/articles/7  # 404 (renders fine in browser)
curl -sS -o /dev/null -w '%{http_code}\n' http://localhost:8604/posts/a/b   # 404 (renders fine in browser)
curl -sS -o /dev/null -w '%{http_code}\n' http://localhost:8604/definitely-not-a-page  # 404, same body

Browser check (document response status): /articles/7 -> status 404, renders the correct article page.

Observed behavior

All dynamic-route direct loads return status 404 with a body byte-identical to .web/build/client/404.html; true 404s return the same thing.

Expected behavior

Valid, routable URLs return 200 (the prod backend knows the route table — app.router(path) — so the static mount could serve the fallback with 200 for routable paths and reserve 404 for genuinely unknown ones); or the current behavior is documented as a known limitation of the SPA-fallback serving model.

Root cause (from source)

Prod mode mounts the built frontend with Starlette StaticFiles(html=True) (get_frontend_mount -> PrecompressedStaticFiles), and the build step copies the react-router SPA fallback (__spa-fallback.html) over 404.html (build.py: path_ops.cp(spa_fallback, static_dir / "404.html")). Starlette's html=True semantics serve 404.html with status 404 for any path that maps to no file; dynamic routes are never prerendered to files, so they always take this path. Static pages exist as <route>/index.html dirs, hence the 307 -> 200.

Specifics (please complete the following information):

  • Python Version: 3.11
  • Reflex Version: 0.9.9a1
  • OS: linux
  • Browser (Optional): Chromium

Additional context

  • Pre-existing: identical curl/browser matrix on 0.9.8 — NOT a 0.9.9 regression.
  • Closely related to #6463 (soft 404: 200 for non-existent routes) — same underlying "SPA fallback does double duty" design; this is the mirror-image symptom on the self-hosted StaticFiles(html=True) path, and a route-table-aware fix would resolve both.
  • Found during 0.9.9a1 pre-release testing; repro app and status matrices are on branch claude/reflex-prerelease-testing-t0sd90 under prerelease-testing/2026-08-27-v0.9.9a1/routing/.
Dominant language
Python
Stars
28.9k
Forks
1.8k
Avg merge
1d 20h
Merged PRs (30d)
173

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from reflex-dev/reflex

All issues in reflex-dev/reflex

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.