January 20, 2021
Updating Static Next.js Pages Instantly
Lightning-fast pages, instantly updated
For the last few months, I've been working full-time on Give&Bake; allowing users to share their favourite recipes with their favourite people. Recipes that need to be fast, secure and always online. With static generation built-in, Next.js was the tool of choice.
Incremental Static Generation
Incremental Static Generation (ISR) is one of my favourite features of Next.js; providing all the benefits of static pages, with the ability to update pages in the background as traffic comes in.
On paper it sounds like ISR would work perfectly for Give&Bake's use-case, but there is an important caveat to consider. When a user visits a page with ISR enabled, an update will be triggered in the background for the next (it's in the name) user, not the current user.
If a user edited their recipe on Give&Bake, they wouldn't see their changes on the static recipe page until they hit refresh; a far from ideal user experience.
A Sprinkle of SWR
SWR ("stale-while-revalidate") is another great little library from the folks at Vercel, commonly used to fetch and revalidate data on the client-side.
With the initialData
option we can skip the fetching altogether, and pre-fetch via ISR (through getStaticProps
), creating a unique cache key of our post.
You can name your key however you wish. In this case, the key matches an
/api
route for consistency's sake; e.g. for other pages that might require SWR's client-side fetching.
// pages/post/[id]
import useSWR from "swr";
import { fetcher } from "@/utils/fetcher";
export const getStaticPaths = async () => {
// …custom logic to create paths for each `id`
};
export const getStaticProps = async () => {
// …custom logic to populate `id` and `initialData`
};
const PostPage = ({ id, initialData }) => {
// useSWR will:
// 1. Create a cache key for the post
// 2. Use the `initialData` and **won't** trigger a fetch
const { data, error } = useSWR("/api/post" + id, fetcher, { initialData });
const post = !error && data?.post;
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
};
export default PostPage;
On the "edit" page, we can mutate
the post's unique cache key with any updated data. On redirect back to the post page, the user will see their updated post instantly, with ISR triggered in the background for the next user.
// pages/post/edit/[id]
import { useRouter } from "next/router";
import useSWR from "swr";
import { fetcher } from "@/utils/fetcher";
const PostEditPage = ({ id, initialData }) => {
const router = useRouter();
const { mutate } = useSWR("/api/post" + id, fetcher, { initialData });
const handleSubmit = (newData) => {
// mutate the post however you wish, in this case prepend to other posts.
mutate(
"/api/post" + id,
(prevData) => ({
post: {
...newData,
...prevData,
},
}),
// Disable revalidation
false
);
// Prevent the user from navigating `back` to this page
router.replace(`/post/${id}`);
};
return <form>{/* Form Logic */}</form>;
};
export default PostEditPage;
When used together, ISR and SWR offer lightning-fast static pages, instantly updated for the current user and statically regenerated for the next.
Visit the demo to see this in action…
…alternatively, join the Give&Bake beta waitlist to try the recipe pages first-hand.