diff --git a/src/app.rs b/src/app.rs index 5daa6f3..d415eb0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -54,9 +54,32 @@ pub fn shell(options: LeptosOptions) -> impl IntoView { pub fn App() -> impl IntoView { provide_meta_context(); + // ONE site resource and ONE for the whole app, both living + // outside the router. Two dueling Title components (a static + // SITE_NAME fallback here + a per-page override) turned every SPA + // navigation into a remount race that the compile-time fallback + // kept winning - redoal.com's tab flipped to "uhhm" on the first + // client-side nav. App never remounts, so this Title never + // unmounts; the Suspense makes SSR await the resolved title so the + // served <head> is right too. Pages read the same resource via + // context instead of fetching their own copy. + let site = Resource::new(|| (), |_| get_site()); + provide_context(site); + view! { <Stylesheet id="leptos" href="/pkg/portal.css"/> - <Title text=SITE_NAME/> + <Suspense fallback=|| ()> + {move || { + site.get() + .map(|res| { + let title = res + .ok() + .and_then(|s| s.title) + .unwrap_or_else(|| SITE_NAME.to_string()); + view! { <Title text=title/> } + }) + }} + </Suspense> <Router> <Routes fallback=|| view! { <NotFound/> }> <Route path=path!("") view=QuestionPage/> @@ -86,21 +109,11 @@ fn QuestionPage() -> impl IntoView { |(path, chain)| get_question(path, chain), ); let user = Resource::new(|| (), |_| current_user()); - let site = Resource::new(|| (), |_| get_site()); + // App's shared site resource (see App) - one fetch per session, + // and the Title tied to it survives navigation. + let site = expect_context::<Resource<Result<SiteConfig, ServerFnError>>>(); view! { - // The content-declared site title lives OUTSIDE the question - // Suspense: tied to the per-page resource it unmounted on every - // SPA navigation and lost the leptos_meta race to App's - // compile-time SITE_NAME fallback (redoal.com flashing to - // "uhhm" on nav - and staying there). Out here it mounts once - // and covers every branch, NotFound included. - {move || { - site.get() - .and_then(|r| r.ok()) - .and_then(|s| s.title) - .map(|t| view! { <Title text=t/> }) - }} <Suspense fallback=|| { view! { <main class="loading">