Skip to content

ServerlessReact.dev

Student Login
  • Welcome to the workshop
Before the workshop
The workshop
Before you head out

Dynamic static-first data

Now here's where the modern JAMStack gets fun 👉 dynamic static-first data. How do you ensure visitors see fresh data on statically generated pages?

With NextJS, you can avoid this issue with getServerSideProps. That leads to load on your servers. And with how NextJS caches pages, you might still show stale data.

And sometimes you want to start with a short static preview then expand to more.

Combining useStaticQuery with useQuery

This is the achilles' heel for Gatsby. Combining useQuery and useStaticQuery is the best approach I've found for dynamically refetching data.

But it's clunky.

You can use the React Query or the Apollo Client approach. Both are clunky in different ways.

Exercise

Continue with your Gatsby project in exercise-5, or jump to mine in exercise-6.

Grab your <LaunchList> component, use the static query to load an initial list of 10 launches. Display it during the loading phase of useQuery.

Use the useQuery approach to replace the static list with new data when it's ready.

The same principle applies whether you're using GraphQL or REST for in-browser data. You'll need to keep both GraphQL queries, they're different.

Solution

Find mine in the exercise-6-solution branch.

NextJS + React Query rehydration

The fantastic integration between NextJS and React Query makes dynamic static-first data far less clunky than Gatsby.

Use the same query in your page and in getServerSideProps, hydrate the cache, and React Query handles the rest.

Exercise

Take your NextJS project from exercise-5, or jump into mine at exercise-6.

Install react-query and add it to your _app.js

// pages/_app.js
import { QueryCache, ReactQueryCacheProvider } from "react-query"
import { Hydrate } from "react-query/hydration"
const queryCache = new QueryCache();
function MyApp({ Component, pageProps }) {
return (
<ReactQueryCacheProvider queryCache={queryCache}>
<Hydrate state={pageProps.dehydratedState}>

You'll use dehydratedState to share query cache with the browser app.

Change your launches.js page to prefetch the query.

// pages/launches.js
import { QueryCache } from "react-query"
import { dehydrate } from "react-query/hydration"
async function getLaunches(key, limit) {
const res = await fetch(
`https://api.spacex.land/rest/launches-past?limit=${limit}`
)
return res.json()
}
export async function getServerSideProps() {
const queryCache = new QueryCache()
await queryCache.prefetchQuery(["launches-past", 10], getLaunches)
return {
props: {
dehydratedState: dehydrate(queryCache),
},
}
}

You can now re-use your query on the frontend.

// pages/launches.js
const Launches = ({ limit = 10 }) => {
const { isLoading, error, data } = useQuery(
["launches-past", limit],
getLaunches,
{
staleTime: 5 * 60 * 1000,
}
)
if (isLoading) {
// render loading
} else if (error) {
// render error
} else {
// render list
}
}

Look at your network log. Notice there's no additional API request or loading state on the frontend.

Even if you render this as a child component on a different page. As long as the query key matches and the data's not stale.

Solution

Find mine in the exercise-6-solution branch.

Bonus exercise

Try this approach with your bonus exercise solution for rocket pages.

Did you enjoy this chapter?

Previous:
Baked-in static data
Next:
How was it?
Created bySwizecwith ❤️