Skip to content
11 changes: 11 additions & 0 deletions packages/reflex-base/src/reflex_base/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@ def _post_init(self, **kwargs):
for key, env_value in env_kwargs.items():
setattr(self, key, env_value)

# Normalize route prefixes to ensure they start with a slash.
self._normalize_paths()

# Normalize disable_plugins: convert strings and Plugin subclasses to instances.
self._normalize_disable_plugins()

Expand Down Expand Up @@ -418,6 +421,14 @@ def _normalize_disable_plugins(self):
)
self.disable_plugins = normalized

def _normalize_paths(self):
"""Ensure frontend and backend paths start with a slash if provided."""
if self.frontend_path and not self.frontend_path.startswith("/"):
self.frontend_path = f"/{self.frontend_path}"

if self.backend_path and not self.backend_path.startswith("/"):
self.backend_path = f"/{self.backend_path}"

def _add_builtin_plugins(self):
"""Add the builtin plugins to the config."""
for plugin in _PLUGINS_ENABLED_BY_DEFAULT:
Expand Down
3 changes: 0 additions & 3 deletions reflex/utils/exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from collections.abc import Sequence
from pathlib import Path
from typing import Any, NamedTuple, TypedDict
from urllib.parse import urljoin

from reflex_base import constants
from reflex_base.config import get_config
Expand Down Expand Up @@ -227,8 +226,6 @@ def run_process_and_launch_url(
if match:
if first_run:
url = match.group(1)
if get_config().frontend_path != "":
url = urljoin(url, get_config().frontend_path)

notify_frontend(url, backend_present)
if backend_present:
Expand Down
30 changes: 28 additions & 2 deletions tests/integration/tests_playwright/test_frontend_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,14 @@ def bouncer_dynamic():

@pytest.fixture(
scope="module",
params=["", "/prefix", "/prefix/nested"],
ids=["no-prefix", "single-level", "two-level"],
params=["", "/prefix", "/prefix/nested", "noslash", "noslash/nested"],
ids=[
"no-prefix",
"single-level",
"two-level",
"noslash-single-level",
"noslash-two-level",
],
)
def frontend_path(request: pytest.FixtureRequest) -> str:
"""Parametrise over no-prefix and various prefix depths.
Expand Down Expand Up @@ -450,3 +456,23 @@ def test_navigate_back_and_forth(frontend_path_app: AppHarness, page: Page):
expect(log).to_contain_text("index")
expect(log).to_contain_text("static")
expect(log).to_contain_text("dynamic-7")


def test_frontend_url_format(frontend_path_app: AppHarness, frontend_path: str):
"""Verify that the frontend_url correctly incorporates the frontend_path."""
url = frontend_path_app.frontend_url
assert url is not None

from urllib.parse import urlparse

parsed = urlparse(url)

expected_path = frontend_path
if expected_path and not expected_path.startswith("/"):
expected_path = f"/{expected_path}"
if expected_path and not expected_path.endswith("/"):
expected_path = f"{expected_path}/"
if not expected_path:
expected_path = "/"

assert parsed.path == expected_path
Loading