Replace Inline Styles in Gatsby with an External CSS File

Hero image for Replace Inline Styles in Gatsby with an External CSS File. Image by Markus Spiske.
Hero image for '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 datahref 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.map(component => {    const { type, props: thisProps } = component;    if (type === 'style') {      const link = (        <link rel="preload" as="style" href={thisProps['data-href']} />      );      const index = props.headComponents.indexOf(component);      props.headComponents.splice(index, 1, link);    }  });}

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 datahref 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

One thing to note is that rather than using the traditional rel="stylesheet" format, I'm using a combination of preload and as. If your hosting is configured for HTTP/2 then this will be interpreted as a server push and will allow the browser to fetch it earlier. If you don't use HTTP/2 then it will degrade gracefully, or you could also simply switch that const out with:

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

Fin.


Categories:

  1. CSS
  2. Development
  3. Front‑End Development
  4. Gatsby
  5. Guides
  6. Node.js
  7. Search Engine Optimisation