Partial Pre-rendering in listing page
Answered
Asian paper wasp posted this in #help-forum
Asian paper waspOP
TL;DR
Any suggestions on reducing the number of DB queries while making use of Partial Pre-rendering (PPR)?
Context:
I have two (set of) pages.
1.
2.
Both pages have
Issue:
In the blog listing page, I'm doing:
The issue is, that for every blog listed, it has to query the DB once. So now I have 5 blogs on the listing page, and I need to do 5 DB queries to retrieve the corresponding view count instead of just 1 like the following.
Yet, for PPR to work, the dynamic part has to be wrapped in
Any suggestions on reducing the number of DB queries while making use of Partial Pre-rendering (PPR)?
Context:
I have two (set of) pages.
1.
/blog - the blog listing page2.
/blog/:slug - the blog page itselfBoth pages have
ViewCount which displays the no. of views of the corresponding blog. And I want to use Partial Pre-render (PPR) for it.Issue:
In the blog listing page, I'm doing:
// app/blog/page.tsx
const blogs = await getBlogs();
{blogs.map(blog => (
<Card> // the part that gets pre-rendered
// ...
<Suspense> // the dynamic part
<ViewCount blogId={blog.id} />
</Suspense>
</Card>
)}export const ViewCount: FC<ViewCountProps> = async ({ blogId, ...props }) => {
noStore();
const metadata = await prisma.blogMetadata.findUnique({
where: { id: blogId },
});
return (
<Typography startDecorator={<Eye />} {...props}>
{numberFormatter.format(metadata?.view ?? 0)} views
</Typography>
);
}; The issue is, that for every blog listed, it has to query the DB once. So now I have 5 blogs on the listing page, and I need to do 5 DB queries to retrieve the corresponding view count instead of just 1 like the following.
const metadata = await prisma.blogMetadata.findMany({
where: { id: { in: blogIds } },
});Yet, for PPR to work, the dynamic part has to be wrapped in
Suspense, so I can't just do the above in app/blog/page.tsx and pass the data as props to ViewCount.Answered by Siberian Flycatcher
Hey 👋
Try fetching metadata for all posts in every
This way, you should be able to get everything you need with 1 db query.
P.S. The React
Try fetching metadata for all posts in every
ViewCount and dedupe it with cache. This way, you should be able to get everything you need with 1 db query.
// app/blog/page.tsx
const blogs = await getBlogs();
{blogs.map(blog => (
<Card> // the part that gets pre-rendered
// ...
<Suspense> // the dynamic part
<ViewCount blogId={blog.id} allIds={blogs.map(blog => blog.id}} />
</Suspense>
</Card>
)}import { cache } from 'react'
const getAllViews = cache(async (ids) => {
return await prisma.blogMetadata.findMany({
where: { id: { in: ids } },
});
})
export const ViewCount: FC<ViewCountProps> = async ({ blogId, allIds, ...props }) => {
noStore();
const allViews = await getAllViews(allIds)
const thisPostViews = allViews[blogId];
return (
<Typography startDecorator={<Eye />} {...props}>
{thisPostViews} views
</Typography>
);
};P.S. The React
cache function doesn't cache your query. It dedupes it during one request. So, the "views" data should still be fresh for every request.9 Replies
@Ray Click to see attachment
Asian paper waspOP
I don't want the count to be cached. The entire point of using PPR is to have the rest of the part statically generated (or "cached" in a sense) while having a fully dynamic island.
it only cache for current request
nvm. I just read clearly your use case
I am attempting something similar here, when I have a ViewCounter (I am happy with the multiple requests) if I use a setup like this, adding
unstable_noStore seems to break the partial pre-rendering of the blog listing page? Have you had anything similar?Siberian Flycatcher
Hey 👋
Try fetching metadata for all posts in every
This way, you should be able to get everything you need with 1 db query.
P.S. The React
Try fetching metadata for all posts in every
ViewCount and dedupe it with cache. This way, you should be able to get everything you need with 1 db query.
// app/blog/page.tsx
const blogs = await getBlogs();
{blogs.map(blog => (
<Card> // the part that gets pre-rendered
// ...
<Suspense> // the dynamic part
<ViewCount blogId={blog.id} allIds={blogs.map(blog => blog.id}} />
</Suspense>
</Card>
)}import { cache } from 'react'
const getAllViews = cache(async (ids) => {
return await prisma.blogMetadata.findMany({
where: { id: { in: ids } },
});
})
export const ViewCount: FC<ViewCountProps> = async ({ blogId, allIds, ...props }) => {
noStore();
const allViews = await getAllViews(allIds)
const thisPostViews = allViews[blogId];
return (
<Typography startDecorator={<Eye />} {...props}>
{thisPostViews} views
</Typography>
);
};P.S. The React
cache function doesn't cache your query. It dedupes it during one request. So, the "views" data should still be fresh for every request.Answer
Asian paper waspOP
Saw a similar pattern in Leerobs' blog. I guess that's the reason.
Let me give it a try
Let me give it a try
Asian paper waspOP
Adding som context in case anyone is interested in this in the future.
The key is how
That means, in the listing page, which mounts multiple
And when we switch to
The key is how
React.cache works.React will invalidate the cache for all memoized functions for each server request.https://react.dev/reference/react/cache#caveats
That means, in the listing page, which mounts multiple
ViewCount, only 1 DB query will be executed because we are calling getAllViews multiple times within the same /blog page (hence one GET /blog server request). So the cache is working.And when we switch to
/blog/:slug, or refresh the current page, we are making a new server request, so React will invalidate the getAllViews cache and run a DB query once more.