Gatsby Code Splitting

Modern web apps are now thought of as bundles of modules interacting with each other to create functions/features. Bundling is the process of merging code into one "bundle" (think: script/file) to deliver to the user.

Code Splitting converts these 'bundles' from one file to many, which can further dramatically improve performance by lazy-loading just the parts of your site that the user needs.

Code Bundling Example (before code splitting):

Raw Code:

// /pages/Index.js
import { add } from '../components/math.js'

console.log(add(15, 15)); // 30
// /components/math.js
export function add(a, b) {
  return a + b;
}

Bundled Code:

function add(a, b) {
  return a + b;
}

console.log(add(15, 15)); // 30

As you can see, bundling is the process of merging your imported files/"modules" into one "bundle". While this is great, it can lead to long load times as your as your application grows in size with added pages and functions.

Code Splitting

You can speed up your site by only loading what the user needs rather than delivering the entire application at once.
E.g., by only loading the components that are ON the page the user is viewing.

One thing that makes Gatsby powerful is that it does this for you. Gatsby automatically and intelligently code-splits bundles when you run gatsby build. However, it does so in a specific manner. For example:

Imagine your website has two pages: a Landing Page and a Contact Page. Each page has 2 unique components; 4 in total:

-- Landing Page

  • Hero.js (Component)
  • Services.js (Component)

-- Contact Page

  • ContactInfo.js (Component)
  • ContactForm.js (Component)

In a traditional React app, a user who visits the Landing page will download a bundle containing all of the components -- Hero, Services, ContactInfo and ContactForm -- despite only needing the Hero and Services components to correctly display the landing page.

Multiply this by say, 10 pages, and you've got an issue on your hands - you're serving a 10 MB payload for a 1 MB page.

This is how Gatsby approaches code-splitting: on a page-by-page basis.

This strength of Gatsby can also be a real downside when creating a single-page site. Since Gatsby splits bundles up by page, you'll end up delivering an unnecessarily massive payload to the client, slowing down your Largest Contentful Paint / initial load times.

So...the solution?

Code Splitting Components instead of Pages

Code-splitting helps you lazy-load only what the user needs and reduce initial load times without modifying the quantity of code you've written.

The React team created React.lazy and Suspense for implementation of this but, unfortunately, neither are compatible with server side rendering/Gatsby.

Instead, they recommend using a library called Loadable Components.

Using Loadable-Components with Gatsby

Code-splitting components with Loadable-Components in Gatsby is simple. Start by installing dependencies

npm install @loadable/component
# or use yarn
yarn add @loadable/component

Then, on any page you wish to lazy-load a component:

Change:

import Hero from '../components/Hero'
import Services from '../components/Services'

export default function LandingPage() {
  return (
    <div>
      <Hero />
      <Services />
    </div>
  )
}

To:

import loadable from '@loadable/component'

import Hero from '../components/Hero'
const Services = loadable(() => import('../components/Services'))

export default function LandingPage() {
  return (
    <div>
      <Hero />
      <Services />
    </div>
  )
}

And that's all! Services.js will now be loaded in its own bundle, separate from the one containing Hero.js. As you see in this example, one method of using loadable-components for a single page site is importing content above the fold normally, and lazy loading content below it.

Bonus: Specifying a Fallback without Suspense

When you lazy-load a component, you're deferring it on the initial page load. Therefore, there will be a short period of time where your content is not visible. You can display a placeholder during that period:

import loadable from '@loadable/component'

import Hero from '../components/Hero'
const Services = loadable(() => import('../components/Services'), {
  fallback: <div>Loading...</div>,
})

export default function LandingPage() {
  return (
    <div>
      <Hero />
      <Services />
    </div>
  )
}

The word "Loading..." will now display until your component is rendered.

Did you find this article helpful?

If you did, would you take a second to share the article by clicking below? It helps our cause immensely!

Make sure to also click the follow button to get notified when new posts go live 🔔

17