From a8010c18bc08c60c6b2e2136e1918d185deaa6c3 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 13 Aug 2025 14:13:47 -0700 Subject: [PATCH 1/4] use sirv with spa fallback --- pyi_hashes.json | 1 - .../web/utils/client_side_routing.js | 45 ------------ reflex/app.py | 14 ++-- reflex/components/core/client_side_routing.py | 70 ------------------- reflex/constants/installer.py | 4 +- 5 files changed, 9 insertions(+), 125 deletions(-) delete mode 100644 reflex/.templates/web/utils/client_side_routing.js delete mode 100644 reflex/components/core/client_side_routing.py diff --git a/pyi_hashes.json b/pyi_hashes.json index 5f879fd0800..ba394b89fe5 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -14,7 +14,6 @@ "reflex/components/core/__init__.pyi": "007170b97e58bdf28b2aee381d91c0c7", "reflex/components/core/auto_scroll.pyi": "10c4cf71d0d0c1d46a8e1205bd119c11", "reflex/components/core/banner.pyi": "3c07547afc4f215aefd5e5afa409aa25", - "reflex/components/core/client_side_routing.pyi": "57a7917e993f625c623bcef50b60b804", "reflex/components/core/clipboard.pyi": "a844eb927d9bc2a43f5e88161b258539", "reflex/components/core/debounce.pyi": "055da7aa890f44fb4d48bd5978f1a874", "reflex/components/core/helmet.pyi": "43f8497c8fafe51e29dca1dd535d143a", diff --git a/reflex/.templates/web/utils/client_side_routing.js b/reflex/.templates/web/utils/client_side_routing.js deleted file mode 100644 index eb3eaf76737..00000000000 --- a/reflex/.templates/web/utils/client_side_routing.js +++ /dev/null @@ -1,45 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import { useLocation, useNavigate } from "react-router-dom"; - -/** - * React hook for use in NotFound page to enable client-side routing. - * - * Uses React Router to redirect to the provided URL when loading - * the NotFound page (for example as a fallback in static hosting situations). - * - * @returns {boolean} routeNotFound - true if the current route is an actual 404 - */ -export const useClientSideRouting = () => { - const [routeNotFound, setRouteNotFound] = useState(false); - const didRedirect = useRef(false); - const location = useLocation(); - const navigate = useNavigate(); - - useEffect(() => { - if (!didRedirect.current) { - // have not tried redirecting yet - didRedirect.current = true; // never redirect twice to avoid navigation loops - - // attempt to redirect to the route in the browser address bar once - const path = window.location.pathname; - const search = window.location.search; - - // Use navigate instead of replace - navigate(path + search, { replace: true, state: { fromNotFound: true } }) - .then(() => { - // Check if we're still on a NotFound route - // Note: This depends on how your routes are set up - if (location.pathname === path) { - setRouteNotFound(true); // Mark as an actual 404 - } - }) - .catch(() => { - setRouteNotFound(true); // navigation failed, so this is a real 404 - }); - } - }, [location, navigate]); - - // Return the reactive bool, to avoid flashing 404 page until we know for sure - // the route is not found. - return routeNotFound; -}; diff --git a/reflex/app.py b/reflex/app.py index 675764b8f7b..7d24c451ba9 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -67,10 +67,6 @@ connection_toaster, ) from reflex.components.core.breakpoints import set_breakpoints -from reflex.components.core.client_side_routing import ( - default_404_page, - wait_for_client_redirect, -) from reflex.components.core.sticky import sticky from reflex.components.core.upload import Upload, get_upload_dir from reflex.components.radix import themes @@ -775,8 +771,10 @@ def add_page( if route == constants.Page404.SLUG: if component is None: - component = default_404_page - component = wait_for_client_redirect(self._generate_component(component)) + from reflex.components.el.elements import span + + component = span("404: Page not found") + component = self._generate_component(component) title = title or constants.Page404.TITLE description = description or constants.Page404.DESCRIPTION image = image or constants.Page404.IMAGE @@ -1312,7 +1310,9 @@ def memoized_toast_provider(): self.head_components, html_lang=self.html_lang, html_custom_attrs=( - {**self.html_custom_attrs} if self.html_custom_attrs else {} + {"suppressHydrationWarning": "true", **self.html_custom_attrs} + if self.html_custom_attrs + else {"suppressHydrationWarning": "true"} ), ) ) diff --git a/reflex/components/core/client_side_routing.py b/reflex/components/core/client_side_routing.py deleted file mode 100644 index 046381c812c..00000000000 --- a/reflex/components/core/client_side_routing.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Handle dynamic routes in static exports via client-side routing. - -Works with /utils/client_side_routing.js to handle the redirect and state. - -When the user hits a 404 accessing a route, redirect them to the same page, -setting a reactive state var "routeNotFound" to true if the redirect fails. The -`wait_for_client_redirect` function will render the component only after -routeNotFound becomes true. -""" - -from __future__ import annotations - -from reflex import constants -from reflex.components.component import Component -from reflex.components.core.cond import cond -from reflex.vars.base import Var - -route_not_found: Var = Var(_js_expr=constants.ROUTE_NOT_FOUND) - - -class ClientSideRouting(Component): - """The client-side routing component.""" - - library = "$/utils/client_side_routing" - tag = "useClientSideRouting" - - def add_hooks(self) -> list[str | Var]: - """Get the hooks to render. - - Returns: - The useClientSideRouting hook. - """ - return [f"const {constants.ROUTE_NOT_FOUND} = {self.tag}()"] - - def render(self) -> str: - """Render the component. - - Returns: - Empty string, because this component is only used for its hooks. - """ - return "" - - -def wait_for_client_redirect(component: Component) -> Component: - """Wait for a redirect to occur before rendering a component. - - This prevents the 404 page from flashing while the redirect is happening. - - Args: - component: The component to render after the redirect. - - Returns: - The conditionally rendered component. - """ - return cond( - route_not_found, - component, - ClientSideRouting.create(), - ) - - -def default_404_page() -> Component: - """Render the default 404 page. - - Returns: - The 404 page component. - """ - import reflex as rx - - return rx.el.span("404: Page not found") diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index a0f689d8eda..21dd2fd2454 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -106,7 +106,7 @@ class Commands(SimpleNamespace): DEV = "react-router dev --host" EXPORT = "react-router build" - PROD = "serve ./build/client" + PROD = "sirv ./build/client --single __spa-fallback.html --host" PATH = "package.json" @@ -127,7 +127,7 @@ def DEPENDENCIES(cls) -> dict[str, str]: "react-router": cls._react_router_version, "react-router-dom": cls._react_router_version, "@react-router/node": cls._react_router_version, - "serve": "14.2.4", + "sirv-cli": "3.0.1", "react": cls._react_version, "react-helmet": "6.1.0", "react-dom": cls._react_version, From c0479d3e07d15b248daa56c7f5013c6555af3f18 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 13 Aug 2025 14:32:32 -0700 Subject: [PATCH 2/4] don't get rid of 404 --- reflex/.templates/web/app/routes.js | 1 - reflex/constants/installer.py | 2 +- reflex/utils/build.py | 4 ++++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/reflex/.templates/web/app/routes.js b/reflex/.templates/web/app/routes.js index bc4c0310d45..e7ebae7cfcb 100644 --- a/reflex/.templates/web/app/routes.js +++ b/reflex/.templates/web/app/routes.js @@ -2,7 +2,6 @@ import { route } from "@react-router/dev/routes"; import { flatRoutes } from "@react-router/fs-routes"; export default [ - route("404", "routes/[404]._index.jsx", { id: "404" }), ...(await flatRoutes({ ignoredRouteFiles: ["routes/\\[404\\]._index.jsx"], })), diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index 21dd2fd2454..9c9883d91b9 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -106,7 +106,7 @@ class Commands(SimpleNamespace): DEV = "react-router dev --host" EXPORT = "react-router build" - PROD = "sirv ./build/client --single __spa-fallback.html --host" + PROD = "sirv ./build/client --single 404.html --host" PATH = "package.json" diff --git a/reflex/utils/build.py b/reflex/utils/build.py index f1174ecb3bb..1e9bf542272 100644 --- a/reflex/utils/build.py +++ b/reflex/utils/build.py @@ -200,6 +200,10 @@ def build(): ) processes.show_progress("Creating Production Build", process, checkpoints) _duplicate_index_html_to_parent_dir(wdir / constants.Dirs.STATIC) + path_ops.cp( + wdir / constants.Dirs.STATIC / "__spa-fallback.html", + wdir / constants.Dirs.STATIC / "404.html", + ) def setup_frontend( From 1ed1b942ae20f9c70bd201f1d2c3516369dd43d8 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 13 Aug 2025 14:34:06 -0700 Subject: [PATCH 3/4] define spa fallback --- reflex/constants/base.py | 2 ++ reflex/utils/build.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/reflex/constants/base.py b/reflex/constants/base.py index a63a7c60fe7..c6359f1c1b2 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -174,6 +174,8 @@ class ReactRouter(Javascript): rf"(?:{DEV_FRONTEND_LISTENING_REGEX}|{PROD_FRONTEND_LISTENING_REGEX})(.*)" ) + SPA_FALLBACK = "__spa-fallback.html" + # Color mode variables class ColorMode(SimpleNamespace): diff --git a/reflex/utils/build.py b/reflex/utils/build.py index 1e9bf542272..fb77db39376 100644 --- a/reflex/utils/build.py +++ b/reflex/utils/build.py @@ -201,7 +201,7 @@ def build(): processes.show_progress("Creating Production Build", process, checkpoints) _duplicate_index_html_to_parent_dir(wdir / constants.Dirs.STATIC) path_ops.cp( - wdir / constants.Dirs.STATIC / "__spa-fallback.html", + wdir / constants.Dirs.STATIC / constants.ReactRouter.SPA_FALLBACK, wdir / constants.Dirs.STATIC / "404.html", ) From 26c6c5dc25bd239307a3b955a6510453b92d37ce Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 13 Aug 2025 15:15:52 -0700 Subject: [PATCH 4/4] bruh --- reflex/testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/testing.py b/reflex/testing.py index 4c8e5a295d0..2b229461d35 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -1008,7 +1008,7 @@ def _run_frontend(self): / reflex.constants.Dirs.STATIC ) error_page_map = { - 404: web_root / "404" / "index.html", + 404: web_root / "404.html", } with Subdir404TCPServer( ("", 0),