Replace Inline Styles in Gatsby with an External CSS File

Image by Markus Spiske.

Unless you chose to use styledcomponents or cssmodules (which you really should!), Gatsby will default to building a (potentially massive) inline style block with the entire contents of your project's CSS right in it at the head of your page.

This should not be seen as an issue: because of the way that the internal navigation with Gatsby (and React) works, the majority of the page markup is only ever loaded once per visit. Inlining makes a noticeable difference to the perceived speed that a page loads at: the browser has less to download before the page starts to render (with styles already in place), whereas an external CSS file gets downloaded separately, and generally after the page markup.

Nevertheless, there are any number of different reasons why this default behaviour might not be to your preference, and it is something that is regularly posted about on both GitHub and Stack Overflow. The good news is that it's not a difficult thing to do; I've toggled back and forth between inline and external stylesheets on my own personal website, and prefer a gentlymodified version of Glinkis's solution on GitHub.

When building, Gatsby passes everything that appears within head including the inlined CSS into html.js as a headComponents prop. This includes the source of the compiled CSS as a data-href attribute. You will also see this attribute if you view source on your production build.

So all we need to do is modify this prop before it is output into the template:

if (process.env.NODE_ENV === 'production') {  props.headComponents = props.headComponents.map(component => {    const { type, props: thisProps } = component;    const href = thisProps?.['data-href'];    if (type !== 'style' || !href) return component;    return <link rel="stylesheet" href={href} />;  });}

What This Does:

  1. Checks to make sure that this is a production build;
  2. Loops over each of the headComponents props as component;
  3. If the type is 'style', then we build a new link element using the data-href attribute to populate the href.
  4. We then work out the original index of this component within the headComponents prop (this is important: it will not necessarily be the current index of the map there are other types in there too);
  5. And slot it back into headComponents.

And that is it!


Considerations Around Performance

At the time this was written, one variation used rel="preload" with as="style". That could fetch the stylesheet early, but did not apply it. HTTP/2 server push depended on server response headers; the browser did not interpret the link as a push instruction. The corrected implementation uses rel="stylesheet" so the CSS is applied. The dedicated postscript records the later platform change.

const link = <link rel="stylesheet" href={thisProps["data-href"]} />

Fin.

Postscript

June 2026: this Gatsby CSS note belongs to an older performance and styling discussion. The HTTP/2 serverpush discussion is also historical: preloading a stylesheet never applied it, and browser support for server push was later removed. The underlying judgement still matters: choose the CSS delivery model that fits caching, critical rendering and maintainability, not the one a framework happens to make easiest.

Untangling a delivery problem?

Send the symptoms, constraints, and affected routes. I'll help identify whether the issue sits in the application, platform, content model, deployment path, or search surface.