I have a problem with inconsistent behavior between the site generated by gatsby develop
and gatsby build
. The result is a site that works in development but not production.
A summary of my site:
A simple blog-like site (personal profiles instead of blog posts). The index page is a list of people, and each item in that list links to that person's profile page.
I'm using Gatsby to build the site. My data (the personal profiles) are entries hosted on the Contentful headless CMS. I'm using the gatsby-source-contentful source plugin.
High level description of the problem
I cannot shuffle the order of the profile list items on the index page. The only behavior where my site goes beyond any basic gatsby tutorial is that I want to randomize the list of profiles on my index page (to give everyone a fair chance at being listed at the top).
gatsby build
generates a static index page with the list in one permutation.
When loaded in a browser the ThumbList
component re-shuffles those items to another permutation on render and some sub-elements are not properly managed by react and stay stuck as other elements shift. This leads, for example, to profile images paired with the wrong name.
The code
The following code is somewhat summarized for readability.
src/pages/index.js:
import React from "react"
import Layout from "../components/layout"
import ThumbList from "../components/thumbList"
import { graphql } from "gatsby"
export default ({data}) => {
// people are called "creators" in the app
const creatorData = data.allContentfulCreator.edges
const shuffledData = shuffle(creatorData.slice(0))
return (
<Layout>
<ThumbList data={shuffledData} />
</Layout>
)
}
const shuffle = (a) => {
// Fisher-Yates randomized array in-place shuffle algo
// ...
return a
}
export const query = graphql`
{
allContentfulCreator {
edges {
node {
id
slug
name
bio {
id
bio
}
mainImage {
file {
url
}
}
}
}
}
}
`
src/components/thumbList.js:
import React from "react"
import { Link } from "gatsby"
// A list of creator profile links, with name and picture thumbnail
export default ({data}) => {
return (
<div>
<ul>
{
data.map(({node}) => {
const creator = node
const link = "/" + creator.slug
const image = "https:" + creator.mainImage.file.url
return (
<li key={creator.id}>
<Link to={link}>
{creator.name}
</Link>
<img src={image} />
</li>
)
})
}
</ul>
</div>
)
}
The result of gatsby build
is an index.html
containing:
<ul>
<li>
<a href="/alice">
Alice
</a>
<img src="cdn.com/alice.jpg">
</li>
<li>
<a href="/bob">
Bob
</a>
<img src="cdn.com/bob.jpg">
</li>
<li>
<a href="/eve">
Eve
</a>
<img src="cdn.com/eve.jpg">
</li>
</ul>
However, when viewing the index page in the browser (via gatsby serve
or a deployed version of the site) the live react ThumbList
component again shuffles the data in its render method.
The result re-rendered html:
<ul>
<li>
<a href="/alice">
Bob
</a>
<img src="cdn.com/alice.jpg">
</li>
<li>
<a href="/bob">
Eve
</a>
<img src="cdn.com/bob.jpg">
</li>
<li>
<a href="/eve">
Alice
</a>
<img src="cdn.com/eve.jpg">
</li>
</ul>
Here only the text nodes are rearranged to match the new order (confirmed by console logging the array order), but the links and image elements remain stuck where they were in the static build. Now the names, images, and links are scrambled.
Two other things to note:
- All works fine with
gatsby develop
. I guess it's because in development index.html is generated without its static content in the body - allowing react complete control over the DOM from the start with no static scaffolding to confuse it. - Using the react inspector I see that the virtual DOM and the real DOM have gotten out of sync. React thinks it has correctly shuffled the list items. Inspector shows something like:
(very abbreviated for readability)
<ul>
<li key="165e2405">
<GatsbyLink to="/bob">
Bob
</GatsbyLink>
<img src="cdn.com/bob.jpg"></img>
</li>
<li key="067f9afc">
<GatsbyLink to="/eve">
Eve
</GatsbyLink>
<img src="cdn.com/eve.jpg"></img>
</li>
<li key="ca4b82bf">
<GatsbyLink to="/alice">
Alice
</GatsbyLink>
<img src="cdn.com/alice.jpg"></img>
</li>
</ul>
My questions
- Is this just an un-Gatsby-like approach? This description of a "Hybrid app page" seems to imply that you can either have static or dynamic components. I suppose I'm trying to have it both ways: I want the
profiles fetched from contentful via graphql during build so it can be available via static HTML + pre-built json data files (e.g.
/static/d/556/path---index-6a9-L7r5Sntxcv3RUIoHYIR3Qqm9Jmg.json
), but then I want to dynamically shuffle that data and rearrange the DOM on render. Is that not possible with Gatsby? Do I need to give up the pre-fetched data during build and just consider that a dynamic component and fetch the data via the Contentful API incomponentDidMount
? - If this approach should be OK, what am I doing wrong?
- If this approach is not idomatic, is there a way to modify (shuffle) the data queried via graphql at build time? I'd actually be happier if the data only shuffled at build time and did not re-shuffle at run-time in the browser - I could just automate the site to rebuild every hour or so and the site could be more static to the client.
setTimeout
and invoked that function in the React lifecycle methodcomponentWillMount
? Just an idea, but that combo could create a timed shuffle of the nodes right before the user loads them, regardless of how they are stored in the database. Yeah, it's client-side but such a small action that I can't imagine much of a perf hit. Another option would be to do a lambda function, do the shuffle server side after the page loads, this would be the hybrid app option. Seems overkill but you could do it that way too. – serraosays