Refreshing Server-Side Props

In a Next.js application I'm working on, I have an "admin" page, which lets me manage registered users:

One of my favourite features about Next.js is that individual routes can opt-in to server-side rendering. While I tend to be a pretty big advocate for static generation, this is a perfect use-case for server-side rendering; I can fetch and inject the database data on first render, simplifying my front-end code.

At least, that's what I thought… And then I hit a snag. 😬

Here's my problem: in my dashboard, I'm able to edit users, to bestow purchases or update their name:

Notice how the updated name isn't shown in the table? This is because the page doesn't know that the underlying data has changed. After the page is server-rendered, those props are immutable.

How do we tell Next.js to re-fetch the data, on demand, without doing a hard refresh of the whole page?

In this short tutorial, I'll share the Nifty Trick I learned to solve this problem. We'll also learn how Next.js works under-the-hood, and cover a couple related problems + solutions.

Allons-y!

Here's the solution, for busy beavers looking for a copy-paste win:

js

The refreshData function would be called whenever you want to pull new data from the backend. It'll vary based on your usecase. As an example, here's how you'd refresh the data right after modifying a user:

js

But why does this work? Why are we involving the router in this process, and what the heck is it doing?

In fact, this solution requires a bit of context-setting. Let's talk a bit about how server-rendered routes have a secret alter-ego 🦸🏾‍♀️

When we think about getServerSideProps, we typically imagine a flow that looks like this:

  • User follows a link from Google (or wherever) to our site.
  • Next.js calls our getServerSideProps method, and uses it to generate an HTML file.
  • The user receives that HTML file, and React rehydrates on the client.

(If you're not sure what I mean by “React rehydrates”, I wrote a blog post about rehydration!)

This is the meat-and-potatoes of Next.js server routes. This is the clerical work that Clark Kent does in his day-job.

But there's another way that Next.js uses your getServerSideProps—from the client.

Consider a different scenario:

  • User is already on your site, and they click a Next.js Link to navigate to the server-rendered page.
  • Next.js calls your getServerSideProps method on the server, but instead of generating an HTML file, it sends the data as JSON to the client.
  • React uses that data as the initial props when rendering the new page, in-browser.

One of the things that makes Next.js so cool is that the server-rendering code can also be used as a sort-of API. We can have lightning-quick client-side routing with Next because your server-rendered routes can hop into a phone booth, spin into a costume, and become an API endpoint. Server-render by day, JSON-sender by night.

Our solution works because we're performing a client-side transition to the same route. router.asPath is a reference to the current path. If we're on /admin-panel, we're telling Next to do a client-side redirect to /admin-panel, which causes it to re-fetch the data as JSON, and pass it to the current page as props. 🧨

If you're wondering about router.replace: it's like router.push, but it doesn't add an item to the history stack. We don't want this to "count" as a redirect, so that the browser's "Back" button still works as we intend.

In short: Next.js doesn't have a "refetchProps" method, but we can leverage the client-side navigation behaviour to achieve the same goal. 💯

When you call this method, there will be no indication in your UI that a re-fetch is happening. This is fine in some cases, but we can't assume a fast network; even if your getServerSideProps call is Blazing Fast™, a user in the woods with 1 bar of 3G will still be stuck waiting for a hot minute.

We need a loading state! Here's how we can create one:

js

We have a new React state variable, isRefreshing. We set it to true when we make the request, and it gets set back to false when the component re-renders with the new data.

Instead of firing on every single render, we put it in an effect hook, and track our theData prop (theData is a placeholder for whatever your server data looks like). When the server returns fresh data, the hook will fire, and our loading state will terminate. And it protects us against "incidental" renders, if some other bit of state happens to change while we're waiting for data.

Here's what this looks like. Keep an eye on the top-right corner to see the loading indicator:

In my admin-dashboard case, I don't need to make any "special" modifications to the server-rendered data. A straightforward refresh is all I'm looking for.

But what if I wanted to change the data in some way? Maybe I want to do an "optimistic update", to show the data in its new state before the server has confirmed it?

In this case, we'd have to transfer the props into state, so that it could be mutated like any other React state:

js

Now, you may be thinking: isn't copying props into state an anti-pattern? Don't the React docs tell us not to do this exact thing?

Not exactly. The thing we want to avoid is duplicating the source of truth. If multiple components define the same bit of state, that's usually a sign of a problem.

In this case, we only have a single source of truth, and it's at the very top of our React tree. This smells like Spring Breeze fabric softener to me. A code scent, not a code smell.

It's recommended to prefix the prop with initial (eg. initialUsers instead of users), so that it's clear that the props serve as an initial value, and not a continued source of truth.

Before I discovered the router-refresh trick, my game-plan looked something like this:

  1. Pull the database calls out of getServerSideProps and into a function, getUsers. Call that function in getServerSideProps.
  2. Create an API route that also uses the getUsers data, and returns it as JSON.
  3. In the page component, use a library like SWR to track the data. It'll be initialized from the server-side props, but connect to the new API route for subsequent data-fetches.
  4. Use SWR to mutate the data as needed.

This path felt terribly overengineered for me, in my specific situation; I didn't want to have two separate mechanisms for fetching users! And while SWR is a great library—I'm using it in my app to manage authentication—it feels a bit heavy in this situation.

But, in other situations, I think that this would be the right approach. For example, if you have complex data-fetching or data-mutating requirements, or if you already have an API you could interact with directly on the client.

This is my very-first tutorial on Next.js! 🍾

I've been using Next.js to build a custom platform for my upcoming course, CSS for JavaScript Developers. It's a course specifically for JS developers who struggle with CSS. I know from experience that CSS becomes a lot more fun once you get comfortable with it, and being able to switch effortlessly between JS and CSS is absolutely wonderful. Follow the link to learn more!

This leads me to a bit of a conundrum, though. I have no idea how many of you, my dear readers, use Next.js, or have any interest in it at all! Cast a vote in this poll, and let me know how you feel:

Special thanks to Brandon for the router-based solution! Brandon's working on Blitz.js, an exciting framework built on top of Next.js that aims to recreate the Rails experience, and I'm super excited to see where it goes 💯

A front-end web development newsletter that sparks joy

My goal with this blog is to create helpful content for front-end web devs, and my newsletter is no different! It includes to upcoming posts and access to special bonus goodies. No spam, unsubscribe at any time.