Not so quick with the Gatsby site optimizations

Updated on Read in 7 mins

This is the post where I talk about neglecting the site and pledge to blog more… wait, wasn’t I suppose to do this on January 1st?

Jokes aside, the last year and half I haven’t felt like writing. Between my “real” job, open source projects, COVID-19, buying and selling a house during a global pandemic, and moving the twins to a new school — where does someone find the time?

With life stabilized, I had time to dust the cobwebs off this site.

Build process

In the time that I ported over from Jekyll to Gatsby, GitHub released Actions to build, test, and deploy code. Migrating my build process over to GitHub Actions seemed obvious as I was hitting the limits of Travis CI’s free tier.

GitHub Actions migration

The process of updating involved creating a new YAML config file and adding environment variables. The cherry on top was leveraging an action to cache Gatsby’s .cache and public folders between builds.

    runs-on: ubuntu-latest
      - name: Caching Gatsby
        id: gatsby-cache-build
        uses: actions/cache@v2
          path: |
          key: ${{ runner.os }}-gatsby-build-${{ github.run_id }}
          restore-keys: |
            ${{ runner.os }}-gatsby-build-            

Building the site from a cold start and no cache takes about 35 minutes to chug through 1,426 pages and resize/optimize 13,194 images. Each cached builds after takes ~1 minute to finish — that’s what I call an improvement!

Deploy to Netlify GitHub Action log.

Upgrade Gatsby to 3.x

Gatsby v3 added all sorts of improvements to the local development experience, which naturally I wanted to take part in. Unfortunately I had to wait for a few community plugins to be updated as the old Gatsby v2 versions broke my builds.

Plugin: gatsby-remark-source-name

Coming from Jekyll I’m used to organizing Markdown files into different collections like posts, pages, and comments. With Gatsby I place each in their own folder with a matching name in gatsby-config.js as shown below:

// excerpt from gatsby-config.js
plugins: [
    resolve: 'gatsby-source-filesystem',
    options: {
      name: 'posts',
      path: `${__dirname}/src/posts`,
    resolve: 'gatsby-source-filesystem',
    options: {
      name: 'pages',
      path: `${__dirname}/src/pages`,
    resolve: 'gatsby-source-filesystem',
    options: {
      name: 'comments',
      path: `${__dirname}/src/comments`,

The gatsby-remark-source-name plugin then adds these as a sourceName field to Remark nodes. Allowing for an easy way to filter on collection type in my GraphQL queries:

# query for all "posts" Markdown files
return graphql(`
    posts: allMarkdownRemark(
      filter: {
        fields: { sourceName: { eq: "posts" } }
    ) {
      edges {
        node {
          frontmatter {
            # Assumes you're using title in your frontmatter

This broke with Gatsby v3 and threw the following error:

TypeError: Cannot destructure property 'createNodeField' of 'boundActionCreators' as it is undefined.

Turns out a plugin isn’t needed and you can achieve the same result by adding the following to gatsby-node.js.

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === 'MarkdownRemark') {
    const fileNode = getNode(node.parent)
      name: 'sourceName',
      value: fileNode.sourceInstanceName,

Plugin: gatsby-source-github-api

Not sure why gatsby-source-github-api had problems with Gatsby 3.x, but this was the error message I encountered:


"gatsby-source-github-api" threw an error while running the sourceNodes lifecycle:

Cannot destructure property 'createNode' of 'boundActionCreators' as it is undefined.

   8 | ) => {
   9 |   console.log(boundActionCreators)
> 10 |   const { createNode } = boundActionCreators;
     |           ^
  11 |   return new Promise((resolve, reject) => {
  12 |     // we need a token to use this plugin
  13 |     if (token === undefined) {

File: node_modules\gatsby-source-github-api\gatsby-node.js:10:11

A quick update of the npm package and all was well with the world.

Migrating Gatsby from v2 to v3

For everything that wasn’t related to outdated community plugins, I followed the official migration guide. A couple of find/replace commands across my project and I was done.

The other big change came from migrating gatsby-image to gatsby-plugin-image. This involved updating dependencies, fixing GraphQL queries, and replacing components.

Running npx gatsby-codemods gatsby-plugin-image did a lot of this work for me. Gatsby’s CLI tool is pretty good at letting you know if you’re using the deprecated gatsby-image component. It even let me know about instances where I should add alt attributes, which is good for accessibility.

Passing Core Web Vitals

Perfect 100 Lighthouse scores for

Only took me a year of tinkering to get 100’s in Google Lighthouse for performance, accessibility, best practices, and SEO. Shipping less JavaScript by installing gatsby-plugin-preact certainly helped improve performance scores too.

But on some of my heavier pages with tons of DOM elements, 3rd party embeds from Twitter, and large animated GIFs — perf scores still take a hit.

Gatsby supports both WebP and AVIF image formats now, so I’ll need to experiment with those to see if I can shave even more time off. Just as long as my build times don’t increase too much…

Accessibility improvements

  • Adjusted the :focus and :hover styling of grid and list entries to render an outline around each when selected.
  • Bumped up the padding around footer links so they’re easier to tap.

Dark mode updates

Flipping between light and dark themes on the site worked before, but it had room to improve. For starters it wasn’t checking if a user had color theme preference (light or dark) set in their operating system.

To remedy this I installed gatsby-plugin-use-dark-mode to add a custom React Hook for use in my “dark mode toggle” component.

As an added benefit it also injects a small bit of JavaScript that helps with the dreaded flash of default theme plaguing sites with dark modes.

Table of contents updates

I’m still using a feature of Gatsby’s Markdown transformer to automatically create table of contents from headings. But instead of dropping it into a collapsible <details> element I styled the unordered list as a true sidebar.

Could just be me, but putting it off to the side has a nicer flow than before.

Post template with optional table of contents pushed to the left of main content

Visual refinements

And because I can’t leave anything alone I started tinkering with the design of a few things.

Home page

Replaced the glitched image of me on the home page with a faux Polaroid frame and fixed it’s alignment with the rest of the page.

Home template with intro and Polaroid photo

Archive pages

Changed the styling of the related tags component from a multi-column to an inline list to save space. I also reused this component on archive pages to help surface related topics that might have been buried before.

Related topics inline list component

Added the featured posts component (used on the home page) to all archive pages… if applicable.

Featured posts component

Works page

Pulled in more GitHub repositories under the Open source contributions section, added fork counts, and switched to a staggered card layout to add interest.

GitHub repositories arranged in a grid of cards

Speaking of open source, I switched this repository to private on GitHub. Way too many lazy developers were reusing my content with minimal changes. I have nothing against people forking my work and making it their own. But replacing my name and social media links with your own and calling it a day is weak.

I may make it public again if I have time to submodule out the content, but that might be too much pain for what it’s worth.

Experimenting with ads

To help offset the small costs of keeping this site up I’m experimenting with Google AdSense again. I was ready to ditch them once I saw how bad they degraded page speed performance.

After messing around with an intersection observer, I was able to keep things in check by waiting to load the Adsense scripts. I’m sure this JavaScript I added to Gatsby’s html.js file could be improved on. But for now it’s doing the trick and I’m pennies richer for it.

    __html: `
      (function(window, document) {
        function loadAds() {
          // Load Google AdSense
          var ad = document.createElement('script');
          ad.type = 'text/javascript';
          ad.async = true;
          ad.dataset.adClient = 'ca-pub-xxxxxxxxxxxxxxxx';
          ad.src = '';
          var sc = document.getElementsByTagName('script')[0];
          sc.parentNode.insertBefore(ad, sc);

        var lazyLoad = false;
        function onLazyLoad() {
          if (lazyLoad === false) {
            lazyLoad = true;
            console.log("advertisements loaded");

          let observer = new IntersectionObserver((entries, observer) => {
            entries.forEach(entry => {
          }, { rootMargin: "0px 0px 500px 0px" });
          document.querySelectorAll('.adsbygoogle').forEach(ad => { observer.observe(ad) });
      })(window, document);

1 mention

  1. Gallo Chingon

    Would you ever make a boilerplate Gatsby version/theme of this site available on GitHub?


How I'm using Jekyll in 2016

Documenting my approach to using Jekyll, how that’s evolved over the years, and web development learnings I’ve picked up along the way.