Problem
Repro: https://stackblitz.com/edit/ykdufm36
A minimal fsRoutes project (@funstack/static 1.3.0) with a single dynamic segment. src/pages/[lang]/layout.tsx is a client component printing useLocation().pathname and params.lang; src/pages/[lang]/page.tsx is a server component printing params.lang, with generateStaticParams returning en and ja.
Open /ja, then click the en link:
before click: URL: /ja / params.lang: ja | page params.lang: ja
after click: URL: /en / params.lang: ja | page params.lang: ja
The URL changes but params does not, in the layout and in the page. Loading /en directly is correct, which makes it easy to miss.
This matches the note in File-System Routing, so the symptom itself is known. It lands hard when the dynamic segment sits at the root of the tree, though. In an i18n site every route lives under a segment like [lang] above, so switching language keeps rendering the previous locale, and nothing fails anywhere to signal it. And there is no supported way to read the live params instead, since useRouteParams() takes a route definition object and with fsRoutes the definitions are built inside the library.
Cause
Opening /ja gives the browser /ja's HTML and its RSC payload. The payload is fetched exactly once, by prodMain in packages/static/src/client/entry.tsx; nothing ever fetches one again. Clicking en swaps the URL on the client and loads no new document, so no new HTML or payload arrives either: after the navigation the browser still holds only /ja's payload. /en's payload does exist in the build output (funstack__/fun__rsc-payload/<hash>.txt), but nothing can reach it, since each HTML carries only its own payload URL and there is no index from URL to payload.
Both stale values on screen come from that one payload, but not for the same reason.
First, the layout. buildRouteDefinitions in packages/static/src/fs-routes/runtime.tsx writes the params into the route definition as element props while the payload is being built:
definition.component = createElement(Component, { params });
The layout is a client component, so it is shipped to the browser and does re-render on navigation, which is why its pathname updates. The props it re-renders with are the baked ones, and nothing replaces them. Fresh params for the layout would not require /en's payload at all: the router can supply them from the current match, and already does so when component is a component type rather than an element.
Then the page. As a server component it ran at build time and the payload holds its rendered output rather than the component. There is nothing in the browser to re-render with different params, and server components cannot run there. Producing /en's text requires /en's payload.
In short, the two values are equally stale but not for equal reasons. The page's ja is forced by static hosting: no implementation fixes it without loading /en's payload. The layout's ja is not forced; it follows from runtime.tsx passing an element, and could change with static hosting left exactly as it is. Two questions, then:
- Would taking client components out of this limitation be in scope for
fsRoutes? That half does not seem bound by the one-payload constraint.
- Independently of that, could there be a supported way to read the live params under
fsRoutes?
Problem
Repro: https://stackblitz.com/edit/ykdufm36
A minimal
fsRoutesproject (@funstack/static1.3.0) with a single dynamic segment.src/pages/[lang]/layout.tsxis a client component printinguseLocation().pathnameandparams.lang;src/pages/[lang]/page.tsxis a server component printingparams.lang, withgenerateStaticParamsreturningenandja.Open
/ja, then click theenlink:The URL changes but
paramsdoes not, in the layout and in the page. Loading/endirectly is correct, which makes it easy to miss.This matches the note in File-System Routing, so the symptom itself is known. It lands hard when the dynamic segment sits at the root of the tree, though. In an i18n site every route lives under a segment like
[lang]above, so switching language keeps rendering the previous locale, and nothing fails anywhere to signal it. And there is no supported way to read the live params instead, sinceuseRouteParams()takes a route definition object and withfsRoutesthe definitions are built inside the library.Cause
Opening
/jagives the browser/ja's HTML and its RSC payload. The payload is fetched exactly once, byprodMaininpackages/static/src/client/entry.tsx; nothing ever fetches one again. Clickingenswaps the URL on the client and loads no new document, so no new HTML or payload arrives either: after the navigation the browser still holds only/ja's payload./en's payload does exist in the build output (funstack__/fun__rsc-payload/<hash>.txt), but nothing can reach it, since each HTML carries only its own payload URL and there is no index from URL to payload.Both stale values on screen come from that one payload, but not for the same reason.
First, the layout.
buildRouteDefinitionsinpackages/static/src/fs-routes/runtime.tsxwrites the params into the route definition as element props while the payload is being built:The layout is a client component, so it is shipped to the browser and does re-render on navigation, which is why its
pathnameupdates. The props it re-renders with are the baked ones, and nothing replaces them. Fresh params for the layout would not require/en's payload at all: the router can supply them from the current match, and already does so whencomponentis a component type rather than an element.Then the page. As a server component it ran at build time and the payload holds its rendered output rather than the component. There is nothing in the browser to re-render with different params, and server components cannot run there. Producing
/en's text requires/en's payload.In short, the two values are equally stale but not for equal reasons. The page's
jais forced by static hosting: no implementation fixes it without loading/en's payload. The layout'sjais not forced; it follows fromruntime.tsxpassing an element, and could change with static hosting left exactly as it is. Two questions, then:fsRoutes? That half does not seem bound by the one-payload constraint.fsRoutes?