How to render random or dynamic content in Gatsby

thumbnail

and fix content hydration issues

Neo
Monday, October 26, 2020

How I ran into this problem

A few weeks ago, I tried to make a simple title component that would display random content.

“No problem”, I thought. All you need to do is create an array of titles, randomly select an item, and display it.

Something like:

const RandomTitleComponent = () => {
  // Array of titles
  const titles = ['you good? 👌', 'hey there 👋', "'sup. 😎"]

  // Returns a random item from an array
  const randomItem = items => {
    return items[Math.floor(Math.random() * items.length)]
  }

  const title = randomItem(titles)

  // Return a component with a random title
  return <Title content={title} />
}

Simple right?

Yeah, well I was super wrong. Here’s why.

The hydration problem

The problem is that I wasn’t thinking about server-side rendering (SSR) and how Gatsby creates static pages.

The above code is indeed valid, and Gatsby will in fact run my random title code once, at build time in order to build the DOM for the static page.

Which means that my component will display the same title, which was randomly chosen and statically set at build time, every time it loads.

For folks using Gatsby and other SSR frameworks, this is a variation of a “rehydration” or “hydration” problem.

Since Gatsby generates static HTML pages, “rehydration” is a way of running client-side code on SSR pages in order to update the existing DOM, but not re-create it.

So what’s the fix?

Since we want to be able to “fetch” data and update our component post-build, we’ll need to make sure that Gatsby doesn’t try to reduce or “evaporate” (see what I did there?💧) our content files at build time.

So before getting to the actual fix, I’ll first talk about a few caveats regarding content storage as it might be relevant in your case.

Where to store your content

There’s two different ways to approach this:

  • Option 1: Store your data locally within your static path. This is the easiest and possibly the more performant option, but is less flexible.
  • Option 2: Store your data remotely. This option is more flexible, but component loads are obviously slower, introduces more complexity, and is more prone to other errors and bugs.

Option 1: Storing content in your static folder

If you are planing on sourcing your content locally (ie not from a remote source like an S3 bucket), you’ll want to store your content files (JSON, YAML, etc) in the static folder of your Gatsby project.

In order to keep the app bundle size down, the Gatsby docs recommend importing your static content using lifecycle hooks such as componentDidMount or useEffect.

If you choose to go the static folder route, you can import your content files like this:

useEffect( () => {
  import content from '/static/randomContent.json'
  const { titles, subtitles, body ... } = content
  // Do something with your content here, like...
  const title = randomItem(titles)
},[])

Keep in mind that files in the static folder are not minified or post-processed in any way, and that missing files won’t be detected at compile time, which could result in errors in your application later.

Option 2: Storing your content remotely

If you’re planning on changing your content frequently, but don’t want to keep changing your actual Gatsby code, you could also pull data from an external store like an S3 bucket or some other remote source.

Which might look something like this:

import fetch from 'cross-fetch'
import yaml from 'js-yaml'

// Funcion to fetch YAML from some URL
// Could easily be modified to return JSON as well
const fetchContent = async contentUrl => {
  return fetch(contentUrl)
    .then(async res => {
      return yaml.safeLoad(await res.text())
    })
    .catch(e => console.error(e))
}

const randomContent = fetchContent(
  'https://sweetBucketBro.s3-us-east-1.amazonaws.com/randomContent.yml'
)

Now that we can fetch our content for our component, let’s see the fix for our RandomTitleComponent.

The fix

Fortunately, the fix for hydrating your components is pretty simple.

You’ll need to wait until the component is rendered, then fetch and randomize your content, which can easily be accomplished using the useEffect or componentDidMount hooks.

Also, you’ll want to put in some safeguards/flags in case the content isn’t found or can’t be loaded correctly.

Thus, our fixed random title component now looks like this:

const RandomTitleComponent = () => {
  // Set default title state
  const [title, setTitle] = useState(null)

  // Called when component is done rendering
  useEffect(() => {
    const getContent = async () => {
      // Get content from static folder
      import randomContent from '/static/randomContent.json'
      // or from a remote source...
      const randomContent = fetchContent(
        'https://sweetBucketBro.s3-us-east-1.amazonaws.com/randomContent.yml'
      )
      const { titles } = content
      // Update title state
      setTitle(randomItem(titles))
    }
  }, [])

  if (!title) return <Title content='Some default title here...' />
  else return <Title content={title} />
}

That’s all there is to it!

Being able to display dynamic content within Gatsby can be really helpful in applications where you might want to show custom information for each user, or in cases where you want to run some simple A/B tests within your app.

I hope this helped and keeps others from banging their head against a wall like I did.

Until next time!

Take the Red Pill

you stay in wonderland

And I show you, how deep the rabbit hole goes.

OK!
We'll never share your email with anyone else.
Liked this post? Subscribe! I've heard my newsletter is pretty cool.
OK!