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 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.
There’s two different ways to approach this:
static
path. This is the easiest and possibly the more performant option, but is less flexible.static
folderIf 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.
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
.
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} />
}
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!