Prefetching lets you start React apps without loading spinners. Gatsby introduced this concept with staticQuery
, react-query and NextJS make the pattern easier 😍
CodeWithSwiz is a twice-a-week live show. Like a podcast with video and fun hacking. Focused on experiments. Join live Wednesdays and Sundays
It took two episodes to figure this out and verify it works. Here they are 👇
We're building towards a headless CMS. The component that needs prefetching is a social card.
Prefetchable component
export const SocialCardImage = ({ title }) => {
const cardQuery = useSocialCardQuery(title)
if (cardQuery.isLoading) {
return <Spinner />
}
return <Image src={cardQuery.data.url} />
}
The SocialCardImage
component uses a query to create a social card for a title. When the query isLoading
, we show a spinner, when it's done, we show the image.
We built the useSocialQuery
custom hook in a past CodeWithSwiz episode. A thin wrapper on react-query's useQuery
makes it more reusable.
export function useSocialCardQuery(title) {
return useQuery(["social-card", title], fetchSocialCard, {
staleTime: 5000,
})
}
Name the query "social-card"
, add title
as a parameter, use the fetchSocialCard
data loader, and set staleTime
to 5 seconds. Helps with query reuse and avoids refetching.
The data loader talks to my screenshot AWS Lambda to create social cards.
async function fetchSocialCard(key, title) {
if (title) {
const res = await fetch(
`https://pifc233qp6.execute-api.us-east-1.amazonaws.com/dev/social-card?title=${title}`
)
return res.json()
} else {
return null
}
}
Basic fetch()
request to a lambda. The lambda spins up a Chrome browser, loads a special page on my blog, renders the card, takes a screenshot, saves it to S3, returns a URL.
What is prefetching
The SocialCardImage
component works great. Slow, but great.
Pop it on a page, add input from a form, users get a social card. Everyone expects a spinner. Can't load something before the user says what.
What if you load this as a new page?
Fill out the form, click submit, get redirected. Would you expect loading spinners like this?
Or would you think "The site has data, why am I waiting twice?"
Granted, it's 2020 and you wouldn't expect the site to reload after a form submit. But I wanted to figure out prefetching 😇
Prefetching data with NextJS
NextJS offers two convenient ways to pre-define data for your page:
getStaticProps
for static datagetServerSideProps
for dynamic server data
One is meant for SSR – server side rendering – and the other for SSH – server side generation. Which is which feels unclear from the docs.
Both enable you to bake data into the initial HTML for your page. The difference is when this baking happens.
Both work the same way:
- NextJS starts building a page
- Calls your exported
getStaticProps
orgetServerSideProps
function - Function does stuff
- Returns a
props
object - NextJS renders your page with those props
getServerSideProps with NextJS dynamic routing
To render the SocialCardImage
with a pre-defined article title based on a URL slug, you'd write a page like this:
// pages/[slug].js
export async function getServerSideProps(context) {
const { slug } = context.params
const article = magicallyReadArticle(slug)
return {
props: {
article
}
}
}
export default ArticlePreview({ article }) {
return <SocialCardImage title={article.title} />
}
Naming your file [slug]
gives you nextjs dynamic routing – file runs for any /this-is-slug
URL and renders the ArticlePreview
page component.
NextJS runs getServerSideProps
with the routing context and you use the slug to find your article in a storage system. We used temp files but that doesn't work in production.
The function returns your article
as a prop, it gets passed into the page component, and the user gets a preview.
And they get a spinner.
Add React Query to the mix
You can fix the spinner by manually fetching the social card in getServerSideProps
. And that means duplicating your logic, dealing with checking 2 data sources, etc.
You've built the query before. You've got a component that works. Why rebuild it all?? 🤨
Here's what you do instead:
- Wrap your app in React Query cache
- Prefetch query in
getServerSideProps
- Dehydrate the full cache on page load
- Keep frontend code the same
// pages/_app.js
const queryCache = new QueryCache()
function MyApp({ Component, pageProps }) {
return (
<ReactQueryCacheProvider queryCache={queryCache}>
<Hydrate state={pageProps.dehydratedState}>
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
</Hydrate>
</ReactQueryCacheProvider>
)
}
Create a new query cache, wrap the app in <ReactQueryCacheProvider>
and <Hydrate>
. They're react-query's context providers for internal state sharing.
Then prefetch your queries:
async function prefetchQueries(article) {
const queryCache = new QueryCache()
await queryCache.prefetchQuery(
["social-card", article.title],
fetchSocialCard
)
return dehydrate(queryCache)
}
export async function getServerSideProps(context) {
const { slug } = context.params
const article = magicallyReadArticle(slug)
return {
props: {
dehydratedState: await prefetchQueries(article),
article,
},
}
}
We can't reuse useSocialCardQuery
because we're doing something different – prefetching.
Replace useQuery
with queryCache.prefetchQuery
and pass that into the dehydrate()
call from react-query. Put the full result in the dehydratedState
prop.
The <Hydrate>
component reads that from its props
<Hydrate state={pageProps.dehydratedState}>
And you get a page load with no spinners
Okay the UX around that form submit needs work but you get the idea. No spinners! 🎉
Why I like this
Way easier to use than Gatsby's useStaticQuery
. Same code works with both static data and dynamic updates later.
Data baking in Gatsby is more suited to pages that don't turn into single-page-apps. This works for both 😍
Cheers,
~Swizec
Continue reading about Prefetch data with React Query and NextJS – CodeWithSwiz 8, 9
Semantically similar articles hand-picked by GPT-4
- Towards a Gatsby+Suspense proof-of-concept
- Building a small CMS with NextJS, pt2 – CodeWithSwiz
- Your first NextJS app – CodeWithSwiz
- Exploring NextJS with a headless CMS, pt4 – CodeWithSwiz
- Async React with NextJS 13
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️