javascript 42 lines · 8 steps

Caching dashboard stats in Next.js

Wrap an expensive multi-query aggregation in unstable_cache and invalidate it by tag when the underlying data changes.

Explained by highlit
1import { unstable_cache, revalidateTag } from 'next/cache'
2import { db } from '@/lib/db'
3 
4export const getDashboardStats = unstable_cache(
5 async (organizationId) => {
6 const [revenue, orders, customers] = await Promise.all([
7 db.order.aggregate({
8 where: { organizationId, status: 'paid' },
9 _sum: { total: true },
10 }),
11 db.order.groupBy({
12 by: ['status'],
13 where: { organizationId },
14 _count: { _all: true },
15 }),
16 db.customer.count({
17 where: { organizationId, deletedAt: null },
18 }),
19 ])
20 
21 return {
22 totalRevenue: revenue._sum.total ?? 0,
23 ordersByStatus: Object.fromEntries(
24 orders.map((row) => [row.status, row._count._all]),
25 ),
26 customerCount: customers,
27 computedAt: new Date().toISOString(),
28 }
29 },
30 ['dashboard-stats'],
31 { tags: ['dashboard-stats'], revalidate: 3600 },
32)
33 
34export async function markOrderPaid(orderId, organizationId) {
35 const order = await db.order.update({
36 where: { id: orderId, organizationId },
37 data: { status: 'paid', paidAt: new Date() },
38 })
39 
40 revalidateTag('dashboard-stats')
41 return order
42}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Tag-based caching lets you cache a value once and precisely invalidate it from anywhere a write occurs.
  2. 2Running independent queries with Promise.all shrinks total latency to the slowest single query.
  3. 3Pairing a cache key with a revalidate window gives you both freshness bounds and on-demand invalidation.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Caching dashboard stats in Next.js — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code