Performance
Inlining the stylesheet
2 min readStatic Core
A linked stylesheet costs a second request before a browser can paint anything at all. On a site whose CSS is a handful of kilobytes, that round trip is a larger share of the wait than the bytes it fetches — so the stylesheet goes into the document.
The round trip is the cost, not the bytes
A page that links a stylesheet cannot render until the stylesheet arrives. The browser parses the document, finds the link, opens a request, and waits: on a connection with 80ms of latency that wait is 80ms of nothing on screen, whether the file is two kilobytes or two hundred.
Twelve kilobytes of CSS transfers in a fraction of the time it takes to ask for it. When the payload is that much smaller than the round trip that fetches it, the fetch is the whole cost, and the fix is not to make the file smaller but to stop asking for it separately.
So the build reads the compiled stylesheet once, never writes it to the output directory, and hands it to the layout, which renders it into a style block in every page. A cold visit paints on the first response.
What it costs on the second page
The trade is real and worth stating plainly rather than burying: a repeat visitor downloads the same CSS again on every page, because it is no longer a file with a name and a cache entry of its own. Twenty pages of this site each carry the same twelve kilobytes.
That is a bad trade for an application a reader spends an hour inside, and a good one for a site where most visits are one page arrived at from a search result. It is worth re-running the arithmetic when either the stylesheet or the session length grows — which is why it is a single configuration flag and not an assumption spread through the code.
Everything else the build produces still goes the other way. Assets declared in the config are content-hashed before any page renders, so pages link the exact file the build produced and it can be cached immutably. Inlining is the one deliberate exception.
The trap is escaping
A templating layer escapes text children, and it is right to: that is what keeps a stray angle bracket in a product description from opening a tag. Applied to CSS it is exactly wrong. An escaped child combinator becomes an entity, the selector stops matching, and the page renders with most of its rules quietly inert.
CSS is not markup, so the stylesheet is rendered with the escape hatch every templating layer keeps for the purpose. This is the one place in the codebase where the rule against hand-rolling escaping inverts, and it carries a comment saying so, because it looks exactly like the mistake it is not.
The failure mode is easy to miss in review and impossible to miss in a browser, which is a good combination. Half a stylesheet applied looks like a broken site, not like a subtle regression.
Key takeaways
- On a small stylesheet, the request costs more than the file does
- Inlining trades repeat-visit caching for a faster first paint — measure which one your visitors actually have
- Never let a templating layer escape CSS: a child combinator turned into an entity silently stops matching