When a server-rendered page hydrates in the browser, what decides how much translation data the visitor downloads?
answer
- the browser starts with nothing
- what ships depends on how catalogues split
- one artifact per language, active one only
- the route already names the language
- mismatch shows as a text flash
basics
~20 sHow the translation catalogues are split. Ship them all in one bundle and every visitor downloads every language; split one artifact per language and only the active one loads; scope them to the route and only that page's strings load.
solid answer
~50 sThe server rendered the HTML with the full catalogue available to it; the browser starts with nothing but what the page ships. So the question is what the client bundle contains. If every language's strings sit in the main bundle, each visitor downloads nine languages they cannot read, and the cost grows with each language added. If the catalogues are separate artifacts keyed by language, only the active one loads — and because the language is readable from the route before render, the right one can be requested alongside the page rather than after it. Scoping further, to the strings a given route actually uses, is smaller still but requires the build to attribute strings to routes. The practical failure mode is a mismatch: the browser hydrates with a different catalogue than the server rendered with, and the translated text flashes back to the default.
go deeper
Remember that the browser only has the strings the page shipped; the server's catalogue does not travel with the HTML.
Explain the three splits — everything in one bundle, one artifact per language, per-route subsets — and what each costs in payload, switch behaviour and build complexity.
Diagnose the translated-to-default flash: check that both renders resolve the language from the same input and that the active catalogue is requested with the page rather than after it.
Decide how far to push the splitting given the language count and payload budget, and whether a language change is modelled as a navigation or an in-place swap — the choice shapes routing, caching and the data layer together.
A page that is rendered on the server and then made interactive in the browser is rendered **twice** against the same component tree. The two renders do not share memory, and that is what makes translation data a delivery problem rather than a lookup problem. ## Two renders, two sources of strings On the server the whole catalogue is typically in process memory — reading one more language costs nothing. In the browser, the only strings available are the ones the page shipped. Every string the client render needs must have arrived, in the right language, before that render runs. The HTML already contains the translated text, but hydration re-derives it; if the client resolves a key differently than the server did, the visible text changes after load. ## The three shapes | Shape | What ships to the browser | Switch cost | Build cost | |---|---|---|---| | **One bundle, all languages** | every language's strings, to everyone | instant, already present | trivial | | **One artifact per language** | only the active language | fetch the other catalogue | small — split by language key | | **Per-route subsets per language** | only this route's strings, in this language | fetch per route and language | real — the build must attribute strings to routes | The first is the default that costs nothing to set up and quietly gets worse with every language a product adds: at ten languages, roughly ninety percent of the translation payload is dead weight for any given visitor, downloaded and parsed on every cold load. The second is the common landing place. The catalogue becomes a dimension of the asset graph keyed by language, and because the language is part of the route, it is known **before** the page renders — so the right catalogue can be requested with the page's other assets rather than discovered mid-render. That ordering is the whole benefit; fetched late, it reintroduces the flash it was meant to prevent. The third is the smallest payload and the most machinery. Splitting strings by route means the build must know which route uses which keys, which is easy for statically referenced keys and hard for keys built at runtime. ## Why the text flashes A translated-to-default flash after load almost always means the client render resolved keys against a different catalogue than the server did. Common causes: - the active language was derived from the URL on the server but from a different source in the browser, and the two disagreed; - the catalogue for the active language had not arrived when hydration ran, so lookups fell back to the default; - only a subset of keys was shipped, and the route used one that was not in it. The general rule is that the language must be resolved from the same input on both sides. When the language is a route segment, both sides can read it from the URL, which is the most reliable arrangement available. ## Switching language at runtime With per-language artifacts, switching is asynchronous: the new catalogue has to be fetched before the interface can re-render in the new language. Meta-frameworks differ here — some treat a language change as a plain navigation to the other language's URL, which lets the normal route machinery fetch what the new page needs; others swap the catalogue in place and re-render. The navigation form is usually simpler to reason about, because the address already changes anyway. Either way, budget for the moment between the two languages: the interface is showing one language, the catalogue for the other is in flight, and something has to be on screen. Keeping the previous language visible until the new catalogue resolves reads better than blanking the page or briefly showing raw keys. ## Rules of thumb 1. Do not ship languages nobody on that page will read; the waste scales with every language added. 2. Resolve the active language from the same input on both renders — the route segment is the easiest such input. 3. Make sure the active catalogue is requested with the page, not after it, or the saving is paid back as a flash. 4. Treat per-route string splitting as an optimisation to reach for when payload measurements justify the build complexity, not as the starting point.
- Why is having the language in the route segment an advantage for loading the right catalogue?Because it is known from the address before rendering starts, on either side. The server reads it from the path, and the browser reads the same value from the same path, so both renders resolve keys against the same catalogue — and the correct catalogue can be requested alongside the page's other assets instead of being discovered part-way through the render.
- If the translated HTML already arrived from the server, why does the browser need the catalogue at all?The server sent finished text, but the client render re-derives it and every later interaction produces new text — a validation message, a formatted count, a newly opened panel. Without the catalogue those come out in the default language or as raw keys, and the initial hydration itself may replace the server's text with fallbacks.
saying these in an interview costs you the question
- Assumes the browser inherits the server's catalogue automatically
- Ships every language to every visitor and calls it simple
- Thinks translated server HTML means no client strings are needed
- Ignores that a late catalogue fetch causes a visible text flash
- Derives the language from different sources on server and client