Replace Inline Styles in Gatsby with an External CSS File

Abstract image used to represent External Stylesheets in Gatsby Rather than Inline
Image by Markus Spiske.

Gatsby can inline generated CSS in a production page's <head>, including CSS produced from CSS Modules. A module scopes class names; it does not by itself mean the stylesheet will be loaded as an external file. Inspect the generated HTML to see what your build actually delivers.

Inlining can avoid a separate stylesheet request for the initial render, but it also adds CSS to the HTML response. An external file can be cached and reused across document loads. Neither arrangement is automatically faster for every visit; compare the production output and the journeys your site needs to support.

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.

In the Gatsby build this example targets, html.js receives the generated head elements through headComponents. Generated inline stylesheet elements carry a data-href pointing to the corresponding CSS asset. Confirm that attribute and file in your production output before relying on the transformation.

Inside the html.js component, derive a new headComponents array before returning the template, then render {headComponents} in its <head>. Leave development output and style elements without an external asset unchanged:

const headComponents = process.env.NODE_ENV === 'production'
  ? props.headComponents.map(component => {
      const { type, props: thisProps } = component;
      const href = thisProps?.['data-href'];

      if (type !== 'style' || typeof href !== 'string' || href.trim() === '') {
        return component;
      }

      return <link key={component.key} rel="stylesheet" href={href} />;
    })
  : props.headComponents;

What This Does:

  1. Only transform the production output.
  2. Map over props.headComponents, returning an element for each original position.
  3. Replace a <style> only when its data-href is a nonempty string pointing to the generated stylesheet.
  4. Return every other element unchanged; no index lookup or array mutation is needed.
  5. Render the derived headComponents array in the template.

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.

Want to find out more?

If you need senior handson support with a complex React or Next.js platform, migration, performance issue, or technical SEO problem, send me the context and I'll tell you where I can help.