logoalt Hacker News

JanSchu05/05/20250 repliesview on HN

Why can’t I just write <include src="header.html"> and be done with it?

We almost could. Chrome shipped a draft of HTML Imports back in 2014. You’d do exactly that, the browser would fetch the fragment, parse it, and make it available for insertion. The idea died for three reasons that still apply today:

Execution‑order and performance hazards. Images, scripts, and styles are fire‑and‑forget: the preload scanner sees a URL, starts the fetch, and the parser keeps streaming. With HTML fragments you need the full subtree before you can finish parsing the parent document (otherwise IDs, custom‑element upgrades, <script defer>, etc. fire in the wrong order). That either stalls the parser—horrible for TTFB—or forces async insertion, which produces layout shifts. Everyone hated both outcomes.

Security and isolation. If an imported fragment can run scripts it becomes an XSS foot‑gun; if it can’t run scripts it breaks a surprising amount of markup (think onerror, custom elements with module scripts, CSP inheritance, etc.). The platform already has an “HTML that can’t run scripts” container: it’s called an iframe. Anything more permissive lands in a swamp of half‑trusted execution.

The “circular dependency” tar‑pit. Templates inherit CSS scopes, custom element registries, and base URLs from the document that instantiates them. Once you let HTML pull in more HTML, those scopes can nest arbitrarily—and can link back to parents. The HTML spec team tried to spec out the edge‑cases and basically threw up their hands. (There’s a famous TAG thread titled “HTML Imports considered harmful” that reads like war diaries.)

Meanwhile developers solved the “shared header” problem higher up the stack—SSI, PHP include, SSG partials, React components, you name it—so browser vendors didn’t see a payoff big enough to justify the complexity. The attitude became: “composition is a build‑time concern, not a runtime primitive.”

Could it ever come back? Maybe, but the bar is higher now that everyone has a build step. A proposal would need to:

Stream (no parser‑blocking)

Sandbox (no ambient script execution)

Deduplicate (avoid circular fetch hell)

Play nicely with CSP, SRI, origin isolation, and the module graph

That starts to look a lot like… <iframe src="header.html" loading="eager">, which we already have—just not the ergonomic sugar we wish for.

So the short answer is: HTML includes are easy in user‑land but devilishly hard to make safe, fast, and spec‑compliant in the browser itself.