An SEO approach to async components with loadable-components
Tags: gatsby, react
Published: 2020-05-15 15:09:02

---
title: An SEO approach to async components with loadable-components
published: true
date: 2020-04-23 16:00:14 UTC
tags: gatsby,react
canonical_url: https://blog.logrocket.com/seo-approach-async-loadable-components/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/o05n5igy3v9l1ktk9ttp.jpeg
---
**Written by [Yomi Eluwande](https://blog.logrocket.com/author/yeluwande/)**✏️

Have you ever viewed the page source for a web page while loading components asynchronously? If you have, there’s a possibility you may have noticed the actual content is not being rendered.

This is because the components are being loaded asynchronously and on the client side, which can be bad for SEO because crawlers will be unable to get the actual content when indexing the site.

This article will provide a method with which you can get the best of both worlds by using [loadable-components](https://github.com/gregberge/loadable-components) to asynchronously load components.

We’ll be working with an existing Gatsby project in this article. The project already uses the loadable component, and we’ll see how to implement it better for SEO purposes.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Problem statement

We have a Gatsby site that uses dynamic data from [Contentful](https://www.contentful.com/), a content management system to render information and modules. The Gatsby site is a blog that shows all the posts from Contentful. The posts are written in a rich text editor, and we use a rich text renderer in the codebase to parse rich text to React components.

However, we’d also like to be able to display other things apart from blog posts on the site. To that end, we created a new [content type](https://www.contentful.com/help/content-modelling-basics/) in Contentful: custom blocks . A custom block, as the name suggests, would allow us to render custom blocks (content that is not necessarily a blog post) on the website.

This is where the challenge lies. As opposed to blog posts, which are always rendered in article format, custom blocks may need to be rendered by different and multiple React components depending on design. For example, there’s a React component for a pricing information custom block on Contentful, a React component for an FAQ custom block on Contentful, etc.

So, to implement that, there’s a file below that uses the custom block’s name to render its own component — i.e., if the custom block’s name matches any of the keys in `CUSTOM_BLOCKS`, then the corresponding component will be rendered.

```jsx
// blocks.js
import TestPage from './TestPage'
import PricingInfo from './PricingInfo'
import FAQInfo from './FAQInfo'

const CUSTOM_BLOCKS = {
  TestPage: TestPage,
  PricingInfo: PricingInfo,
  FAQInfo: FAQInfo,
}
export default CUSTOM_BLOCKS
```

The custom blocks can then be used in a code snippet like the one below, where the `CustomBlockComponent` is only returned if there’s a corresponding match with `customBlock.name`.

```jsx
// CustomBlock.js
import CUSTOM_BLOCKS from './blocks'

const CustomBlock = ({ customBlock }) => {
  const CustomBlockComponent = CUSTOM_BLOCKS[customBlock.name]
  if (!CustomBlockComponent) {
    return null
  }
  return <CustomBlockComponent customBlock={customBlock} />
}

export default CustomBlock
```

With this current implementation, we’re loading all the custom blocks and their components all at once, even though we don’t need them. Right now, it’s just two custom blocks, but imagine if it were a whole lot more than that.

## Using loadable-components

A case like this is where `loadable/component` comes in. It allows us to only load the components when they are needed, i.e., asynchronously. Let’s add `loadable/component` to the first code snippet shared above.

```jsx
// blocks.js
import loadable from '@loadable/component'

const CUSTOM_BLOCKS = {
  TestPage: loadable(() => import('./TestPage')),
  PricingInfo: loadable(() => import('./PricingInfo')),
  FAQInfo: loadable(() => import('./FAQInfo')),
}
export default CUSTOM_BLOCKS
```

All the custom blocks are being loaded asynchronously, so they’ll only be loaded when needed, which in turn results in the code being optimized for performance.

This is the reason why we have chosen to use loadable-components in our project, and it seems to solve the problem we initially had. However, importing the components with loadable means the content of the custom block will not be pre-rendered into the static HTML.

As an example, in the page source below, I’m expecting the `Date One` text to be in the source, but it’s not. The `Date One` text is inside one of the custom block files above, and it needs some JavaScript to be evaluated, hence, it’s not showing up.

![Page Source Missing Async Components](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/page-source-missing-components.png?resize=730%2C446&ssl=1)

This is what we’ll try to solve in this article: how to load the components asynchronously and also make sure that content gets rendered in the static HTML.

## Configuring loadable-components

We can solve this by making some additional configurations to how `loadable/component` is set up. We already have `loadable/component` installed in the codebase, but we need to make some configurations. First, install the dependencies below.

```jsx
yarn add -D @loadable/babel-plugin @loadable/webpack-plugin babel-preset-gatsby
```

The next thing is to add a custom Babel plugin to the project. To do that, we’ll need to modify the `.babelrc.js` file. In the plugins array, add the line below:

```jsx
// .babelrc.js
{
  "plugins": [
    ...
    "@loadable/babel-plugin",
    ...
  ]
}
```

Next, we’ll add a custom webpack plugin to the `gatsby-node.js` file.

```jsx
// gatsby-node.js
const LoadablePlugin = require('@loadable/webpack-plugin')

exports.onCreateWebpackConfig = ({ stage, actions }) => {
  actions.setWebpackConfig({
    plugins: [new LoadablePlugin()],
  })
}
exports.onCreateBabelConfig = ({ actions }) => {
  actions.setBabelPlugin({
    name: `@loadable/babel-plugin`,
  })
}
```

The final step in all of this is making sure that the content of the custom block is pre-rendered with the static HTML. One way to do that is by using the `fallback` prop of `loadable/components`.

## Pre-rendering custom block elements in static HTML

The `fallback` prop determines what to show while the component is being loaded asynchronously. This is what will be used to make sure asynchronous components get rendered to the static HTML. How?

So, for asynchronous components, the following happens:

1. Static HTML is rendered
2. React components are hydrated into the static HTML
3. Because of the asynchronous components taking time to resolve, the current DOM is destroyed and only created again when it’s done loading

We can then take advantage of step two to get and save the current static HTML and then use that as a fallback. That’s exactly what’s being done in the code snippet below. If you recall above, the `CustomBlock.js` file simply checks whether a custom block component exists and then returns it.

Now it’s doing a whole more than that:

- Setting an `id` to `CustomBlock__`, plus whatever the current custom block name is
- Adding a fallback prop, which is set to be HTML gotten from the `getRenderedContent()` function
- Lastly, the `getRenderedContent` function checks whether an element with an ID exists in the HTML and, if yes, returns it

```jsx
// CustomBlock.js
import * as React from 'react'

import CUSTOM_BLOCKS from './blocks'</p>

<p>const getRenderedContent = customBlockName => {
  if (typeof window === 'undefined') return ''
  const element = window.document.querySelector(
    <code>#CustomBlock__${customBlockName}</code>
  )
  return element ? element.innerHTML : ''
}
const CustomBlock = ({ customBlock }) => {
  const CustomBlockComponent = CUSTOM_BLOCKS[customBlock.name]
  if (!CustomBlockComponent) {
    return null
  }
  return (
    <section id={<code>CustomBlock__${customBlock.name}</code>}>
      <CustomBlockComponent
        customBlock={customBlock}
        fallback={
          <div
            dangerouslySetInnerHTML={{
              __html: getRenderedContent(customBlock.name),
            }}
          />
        }
      />
    </section>
  )
}
export default CustomBlock
```

It’s a bit of a hack, but then we get to see the content of the asynchronous components in the page source, and that’s good for SEO.

Now we can build the site and run it in production with the commands below:

```jsx
yarn build && serve public
```

The dates are now coming up in the page source, which means the custom block elements are now being pre-rendered which in turn means crawlers can successfully crawl this page.

![Page Source With Async Components](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/page-source-components.png?resize=730%2C447&ssl=1)

## Conclusion

To simulate what I’ve explained in this article, there’s a [GitHub](https://github.com/yomete/loadable-components-seo) repository that contains the codebase for the Gatsby project above. It also contains an exported Contentful space so you can set that up (by importing into a new space) and connect to the Gatsby project.

* * * 
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/eq752g8qhbffxt3hp9t4.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://www2.logrocket.com/react-performance-monitoring)
* * *

The post [An SEO approach to async components with loadable-components](https://blog.logrocket.com/seo-approach-async-loadable-components/) appeared first on [LogRocket Blog](https://blog.logrocket.com).