Branding that arrives before the JavaScript does
Every organisation on the platform gets its own address and its own branding. The obvious way to build that is to fetch the branding when the app starts. We did, and it was wrong in a way that took a while to name.
The flash
Fetching branding on mount produces a sequence: the app boots with defaults, asks the server who this organisation is, and repaints. On a fast connection that is a flicker. On a slow one it is two seconds of the wrong logo.
It is worst exactly where it matters most — the sign-in page, the first thing a new learner sees. They arrive at their own organisation's address and are shown somebody else's brand while the page makes up its mind.
No amount of optimising the fetch fixes this. The request cannot start until the bundle runs, and the bundle cannot run until it is downloaded. The branding is always at least one round trip behind the first paint.
Moving the work in front of the app
Every request to an organisation's address already passes through our router at the edge, which is what decides which application serves it. That router knows the hostname before any application does, and the hostname is precisely what identifies the organisation.
So it looks the organisation up and writes the result into the HTML on its way past:
<script>window.__TENANT__ = { "name": "…", "logoUrl": "…", "settings": { … } }</script>
</head>
By the time the browser parses the document, the branding is already in it. The application reads a value that is present before it starts rather than fetching one, and the first painted frame is correct. There is nothing to flash from.
What it cost
Three things, none of them free.
The HTML cannot be shared. A page carrying one organisation's name must
never be served to another, so these responses are no-store. The HTML shell is
small and the hashed assets behind it still cache normally, so the trade is
mostly fine — but it is a real one, and it is the reason this technique does not
generalise to every page on the internet.
Lookups have to be fast. A per-request database query in front of every page load is not viable. The lookup reads a key-value store at the edge, written by the platform whenever an organisation's settings change, with a short in-process cache in front of it. Which means writes have to invalidate that cache — a cache that also caches misses, so a newly-created organisation stays "not found" for the length of the TTL unless someone purges it. We learned that the direct way.
The fallback has to stay. Injection can fail: a misconfigured host, a lookup that times out, local development where there is no edge at all. The application still fetches its own configuration when the injected value is absent. The fast path is an optimisation, never a requirement — the moment it becomes a requirement, every one of those cases becomes an outage.
Where else it goes
Once the shell is being rewritten at the edge for one reason, other things want
the same treatment. Shared badge and certificate pages now get their
og: tags injected the same way, because link-preview crawlers do not run
JavaScript either — a credential pasted into Slack used to preview as the
generic application shell.
Same mechanism, same constraint: if it has to be in the HTML, it cannot wait for the bundle.