Replies: 1 comment 1 reply
|
There's a documented pattern for exactly this in the router-context guide, and it sidesteps the three approaches you listed because it doesn't try to make export const Route = createRootRoute({
component: () => {
const matches = useRouterState({ select: (s) => s.matches })
const matchWithTitle = [...matches].reverse().find((d) => d.context.getTitle)
const title = matchWithTitle?.context.getTitle() || 'My App'
return (
<html>
<head><title>{title}</title></head>
<body>{/* ... */}</body>
</html>
)
},
})The key difference from This is also why it avoids your two failure modes: it goes through the router's own |
Uh oh!
There was an error while loading. Please reload this page.
Problem
In a SPA using the recommended TanStack Query integration, loaders typically call
queryClient.ensureQueryData()without awaiting so navigation stays instant, and components read the data withuseSuspenseQuery. In this setuphead({ loaderData })can't produce a dynamic title:loaderDatais empty whenhead()runs, andhead()is never re-invoked once the data lands in the cache.Current options all have drawbacks:
useEffect+document.titleafter Suspense resolves → works, but bypassesHeadContent, and only by relying on the (undocumented) fact thatHeadContentwon't rewrite an unchanged title.<title>hoisting → conflicts with the<title>thatHeadContentrenders (duplicate tags, first-in-document wins).Proposal
First-class support for a two-phase title: a static fallback shown at navigation, replaced when async data resolves. Possible shapes:
head()when an unawaited/deferred promise returned from the loader resolves (Await-awarehead), oruseHead()hook or<RouteHead title={...} />component that participates inHeadContent's dedupe/override chain.Related
document.titleworkaroundheadrunning before the loader finishes (the awaited case)All reactions