From e8a8fef69af157c07c53691e4364bc68efc8b7a5 Mon Sep 17 00:00:00 2001 From: timon0305 Date: Thu, 12 Feb 2026 18:04:14 +0100 Subject: [PATCH 1/2] fix: Handle periods in dynamic route segments for production mode (#5803) --- reflex/app.py | 9 ++++++ reflex/route.py | 48 +++++++++++++++++++++++++++++++ reflex/utils/frontend_skeleton.py | 26 +++++++++++++---- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index 4ff412ef863..a4699b44c63 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -1156,6 +1156,8 @@ def _compile( ReflexRuntimeError: When any page uses state, but no rx.State subclass is defined. FileNotFoundError: When a plugin requires a file that does not exist. """ + from reflex import route as route_module + from reflex.utils import frontend_skeleton from reflex.utils.exceptions import ReflexRuntimeError self._apply_decorated_pages() @@ -1534,6 +1536,13 @@ def _submit_work_without_advancing( raise FileNotFoundError(msg) output_mapping[path] = modify_fn(file_content) + # Update package.json with dynamic route patterns for sirv --ignores + routes = list(self._unevaluated_pages.keys()) + dynamic_route_prefixes = route_module.get_dynamic_route_prefixes(routes) + if dynamic_route_prefixes: + console.debug(f"Updating package.json with dynamic route prefixes: {dynamic_route_prefixes}") + frontend_skeleton.initialize_package_json(dynamic_route_prefixes) + with console.timing("Write to Disk"): for output_path, code in output_mapping.items(): compiler_utils.write_file(output_path, code) diff --git a/reflex/route.py b/reflex/route.py index 761ec8f974d..c9c69e288ce 100644 --- a/reflex/route.py +++ b/reflex/route.py @@ -132,6 +132,54 @@ def replace_brackets_with_keywords(input_string: str) -> str: ) +def get_dynamic_route_prefixes(routes: list[str]) -> list[str]: + """Get route prefixes for dynamic routes to use with sirv --ignores. + + sirv treats URLs with extensions (periods) as asset requests, not SPA routes. + This function extracts route patterns that have dynamic segments, so they can + be excluded from asset detection using sirv's --ignores flag. + + Example: + >>> get_dynamic_route_prefixes(["/posts/[slug]", "/data/[version]/info"]) + ['^/posts/', '^/data/'] + + Args: + routes: List of route strings. + + Returns: + List of regex patterns for sirv --ignores flag. + """ + prefixes = [] + for route in routes: + # Check if route has dynamic segments + if get_route_args(route): + # Extract the prefix up to the first dynamic segment + parts = route.split("/") + prefix_parts = [] + for part in parts: + # Stop at first dynamic segment + if ( + constants.RouteRegex.ARG.match(part) + or constants.RouteRegex.OPTIONAL_ARG.match(part) + or part == constants.RouteRegex.SPLAT_CATCHALL + ): + break + prefix_parts.append(part) + + # Build prefix pattern (e.g., "/posts" -> "^/posts/") + prefix = "/".join(prefix_parts) + # Ensure prefix starts with / for absolute path matching + if not prefix.startswith("/"): + prefix = "/" + prefix + if prefix and prefix != "/": + # Add trailing slash and anchor to start for sirv pattern + pattern = f"^{prefix}/" + if pattern not in prefixes: + prefixes.append(pattern) + + return prefixes + + def route_specificity(keyworded_route: str) -> tuple[int, int, int]: """Get the specificity of a route with keywords. diff --git a/reflex/utils/frontend_skeleton.py b/reflex/utils/frontend_skeleton.py index 96ced280fe2..8f4ecd7bee4 100644 --- a/reflex/utils/frontend_skeleton.py +++ b/reflex/utils/frontend_skeleton.py @@ -167,12 +167,24 @@ def _update_react_router_config(config: Config, prerender_routes: bool = False): return f"export default {json.dumps(react_router_config)};" -def _compile_package_json(): +def _compile_package_json(dynamic_route_prefixes: list[str] | None = None): + """Compile package.json with optional dynamic route handling. + + Args: + dynamic_route_prefixes: List of route prefix patterns for sirv --ignores. + """ + # Build prod command with --ignores flags for dynamic routes + prod_command = constants.PackageJson.Commands.PROD + if dynamic_route_prefixes: + # Add --ignores flag for each dynamic route prefix + ignores = " ".join(f"--ignores '{prefix}'" for prefix in dynamic_route_prefixes) + prod_command = f"{prod_command} {ignores}" + return templates.package_json_template( scripts={ "dev": constants.PackageJson.Commands.DEV, "export": constants.PackageJson.Commands.EXPORT, - "prod": constants.PackageJson.Commands.PROD, + "prod": prod_command, }, dependencies=constants.PackageJson.DEPENDENCIES, dev_dependencies=constants.PackageJson.DEV_DEPENDENCIES, @@ -180,10 +192,14 @@ def _compile_package_json(): ) -def initialize_package_json(): - """Render and write in .web the package.json file.""" +def initialize_package_json(dynamic_route_prefixes: list[str] | None = None): + """Render and write in .web the package.json file. + + Args: + dynamic_route_prefixes: List of route prefix patterns for sirv --ignores. + """ output_path = get_web_dir() / constants.PackageJson.PATH - output_path.write_text(_compile_package_json()) + output_path.write_text(_compile_package_json(dynamic_route_prefixes)) def _compile_vite_config(config: Config): From 2c1a45df52f2b411213925bed8508cca02cfd414 Mon Sep 17 00:00:00 2001 From: Timon <59460476+timon0305@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:10:31 +0100 Subject: [PATCH 2/2] Update reflex/route.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- reflex/route.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reflex/route.py b/reflex/route.py index c9c69e288ce..5e6382d052d 100644 --- a/reflex/route.py +++ b/reflex/route.py @@ -161,6 +161,8 @@ def get_dynamic_route_prefixes(routes: list[str]) -> list[str]: if ( constants.RouteRegex.ARG.match(part) or constants.RouteRegex.OPTIONAL_ARG.match(part) + or constants.RouteRegex.STRICT_CATCHALL.match(part) + or constants.RouteRegex.OPTIONAL_CATCHALL.match(part) or part == constants.RouteRegex.SPLAT_CATCHALL ): break