Skip to content

Fix outstanding frontend_path issues#6328

Open
masenf wants to merge 5 commits intomainfrom
masenf/fx-frontend-path
Open

Fix outstanding frontend_path issues#6328
masenf wants to merge 5 commits intomainfrom
masenf/fx-frontend-path

Conversation

@masenf
Copy link
Copy Markdown
Collaborator

@masenf masenf commented Apr 14, 2026

Summary

Fixes outstanding issues with the frontend_path config option and adds comprehensive integration tests to prevent regressions.

When frontend_path is set (e.g. frontend_path="/app"), the Reflex app is served from a subpath instead of the root. This PR ensures that assets, stylesheets, links, redirects, uploads, on_load events, and 404 pages all work correctly under a configured frontend_path.

Changes

  • Integration tests (tests/integration/tests_playwright/test_frontend_path.py): Comprehensive playwright tests covering links (static and dynamic routes), redirects (event handler and on_load), assets (local and shared), uploaded files (rx.get_upload_url), CSS url() references, on_load event firing, and 404 pages. Tests are parametrized over dev/prod mode and with/without frontend_path.
  • Shared asset fix (reflex/assets.py): rx.asset(shared=True) now calls prepend_frontend_path on its return value, matching the existing behavior for local assets.
  • testing.py: Fix AppHarnessProd to locate 404.html under the frontend_path subdirectory when set, matching real prod deployment behavior.
  • CI workflow (.github/workflows/integration_app_harness.yml): Run playwright tests in a separate job from other integration tests. The pytest-playwright plugin keeps an asyncio event loop running on the main thread for the session, which is incompatible with pytest-asyncio tests in the same process.

Closes

Supersedes

Test plan

  • uv run pytest tests/integration/tests_playwright/test_frontend_path.py -v passes in both dev and prod modes, with and without frontend_path
  • Existing integration tests still pass: uv run pytest tests/integration --ignore=tests/integration/tests_playwright -v
  • Playwright tests are isolated from pytest-asyncio tests in CI

masenf added 3 commits April 13, 2026 16:36
* also initialize vite.config.js during compile, in case user has set a
  different runtime frontend path
* remove assetsDir workaround (base handles this case)
Add `prepend_frontend_path` to `rx.Config` to make it easy to handle the
contract in current and future APIs.

Use `prepend_frontend_path` in:
  * rx.get_upload_url
  * rx.asset
  * vite config
  * react router config
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 14, 2026

Merging this PR will not alter performance

βœ… 9 untouched benchmarks


Comparing masenf/fx-frontend-path (12cfd39) with main (4c53848)

Open in CodSpeed

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 14, 2026

Greptile Summary

This PR fixes frontend_path subpath deployment by replacing the Vite assetsDir hack with the proper base config option, centralising path-prefix logic in a new Config.prepend_frontend_path() helper, and wiring it into rx.asset (local), the Starlette static-file mount, the React Router basename, and the AppHarnessProd test harness.

  • P1 β€” shared assets still broken: rx.asset(shared=True) returns an unprefixed URL (line 132 of reflex/assets.py). With frontend_path set, these URLs will 404 because the Starlette mount only handles paths under /prefix/.

Confidence Score: 4/5

Safe to merge for apps using local assets only; shared assets (rx.asset shared=True) will break in subpath deployments until the one-line fix is applied.

One P1 gap remains: shared/library assets are not prefixed, making them silently broken when frontend_path is configured. All other changes are clean refactors with correct equivalent behaviour and good integration-test coverage.

reflex/assets.py β€” the shared-asset return path (line 132) needs prepend_frontend_path.

Important Files Changed

Filename Overview
reflex/assets.py Fixes local assets with frontend_path, but shared assets (shared=True) still return an unprefixed URL at line 132 β€” they will 404 when frontend_path is set.
packages/reflex-base/src/reflex_base/config.py Adds prepend_frontend_path() helper that consistently builds prefixed paths; logic is correct and all call-sites use it properly.
packages/reflex-base/src/reflex_base/compiler/templates.py Switches Vite config from the assetsDir hack to the proper top-level base option, which is the correct way to handle subpath deployment.
reflex/utils/exec.py Static files mount now points at the correct subdirectory (static/prefix/) and uses prepend_frontend_path for the mount path; looks correct.
reflex/testing.py AppHarnessProd now derives the correct 404 page path and frontend_url from prepend_frontend_path, fixing prod-mode test harness for subpath apps.
tests/integration/tests_playwright/test_frontend_path.py Comprehensive new integration tests for dev/prod Γ— with/without prefix; CSS background-image assertion is too weak to catch a known Vite absolute-URL limitation.
reflex/app.py Adds a vite config recompilation step during compilation (and increments the progress step counter to 8) so runtime config changes are reflected immediately.
reflex/utils/build.py Only adds a clarifying comment; the actual file-moving logic is unchanged.
tests/units/test_prerequisites.py Unit test expectations updated from assetsDir to base to match the new Vite config format.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Config.frontend_path = '/prefix'"] --> B["Config.prepend_frontend_path('/') β†’ '/prefix/'"]
    B --> C["vite.config.js: base: '/prefix/'"]
    B --> D["react-router.config.js: basename: '/prefix/'"]
    B --> E["Starlette Mount('/prefix/')"]
    B --> F["rx.asset(local) β†’ '/prefix/image.png'"]
    E --> G["StaticFiles(dir=static/prefix/)"]
    C --> H["Vite build: rewrites JS/CSS asset refs"]
    H --> I["build.py: mv static/* β†’ static/prefix/"]
    I --> G
    J["rx.asset(shared=True)"] --> K["'/external/mod/img.png' ← NO prefix ❌"]
    K --> L["Browser requests /external/mod/img.png"]
    L --> M["Starlette 404 β€” not under /prefix/"]
Loading

Comments Outside Diff (1)

  1. reflex/assets.py, line 132 (link)

    P1 Shared assets still missing frontend_path prefix

    rx.asset(shared=True) returns a URL without the frontend_path prefix. With frontend_path = "prefix" the Starlette static mount serves from static/prefix/ at /prefix/, so a browser request to /external/module/image.png will always 404 β€” it never matches the /prefix/ mount. Only local assets are fixed by this PR.

Reviews (1): Last reviewed commit: "consolidate boilerplate logic in test_fr..." | Re-trigger Greptile

masenf added 2 commits April 14, 2026 18:09
playwright uses a session scope fixture which creates an event loop for the
session, so subsequent tests using pytest_asyncio fixture cannot start their
own loop and fail
include updated test case
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant