Partial Prerendering in Next.js
When I was working on my latest side project where I was building a platform for users who want to start writing their own blogs, I had a problem with the article page, because I wanted to statically generate them (SSG), but also I wanted it to fetch the comments dynamically. You can't simply put a comments section inside the page that uses generateStaticParams because in this scenario the application couldn't be built. I did a research on how to handle this and the newest version of Next.js (while I'm writing this post, it's still an experimental feature) introduces something called Partial Prerendering which allows you to put dynamic content on statically generated pages!
How it works?
- A static route shell is served immediately, this makes the initial load fast.
- The shell leaves holes where dynamic content (that might be slower) will be streamed in to minimize the perceived overall page load time.
- The async holes are loaded in parallel, reducing the overall load time of the page.
Before we start
As I mentioned before, while I'm writing this post, Partial Prerendering is marked as experimental feature, so we need to enable it in next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
ppr: true,
},
};
export default nextConfig;
Let's code it!
For the purpose of this tutorial, we will be using the code from my latest project. The code below showcases the static generation of article pages using generateStaticParams and hole for dynamic elements wrapped with Suspense.
...
export const generateStaticParams = async () => {
const posts = await getAllPosts();
return posts.map((post) => ({
params: { slug: post.slug },
}));
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPostBySlug(params.slug);
return {
title: post.title,
description: post.description,
};
}
export default async function PostPage({ params: { slug } }: Props) {
const post = await getPostBySlug(slug);
if (!post) {
return notFound();
}
const { title, profiles, created_at, categories, content } = post;
return;
<section className="mx-auto mb-20 max-w-4xl px-6">
...
<article className="prose prose-invert max-w-none">
<MDXRemote source={content} />
</article>
<Suspense fallback={<LoadingIndicator />}>
<CommentsContainer id={post.id} />
</Suspense>
</section>;
}
Comments component
Now we can simply fetch our dynamic data any way we want, I used react-query
...
export const CommentsContainer = async ({ id }: { id: number }) => {
const queryClient = new QueryClient();
const session = await getServerSession(authOptions);
await queryClient.prefetchQuery({
queryKey: ["comments", id],
queryFn: () => getCommentsByPostID(id),
});
return (
<div className="mt-20 border-t pt-5">
<h2 className="mb-5 text-2xl font-bold">Comments</h2>
<CommentsList id={id} />
{session && <AddCommentForm post_id={id} />}
</div>
);
};
After our application build is successfully done, we can see that the article pages are marked as Partial Prerender
Conclusion
Partial Prerendering is a powerful feature, and I am sure that it won't be experimental soon, it allows you to dynamically fetch data in static pages. I encourage you to check it out! Happy coding! 🚀