From 68b09ebc369157bd9c8a66073935cef662745401 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Fri, 4 Apr 2025 11:32:20 -0400 Subject: [PATCH] properly use vercel data cache for fetch results --- app/api/hits/route.ts | 59 ++++++++++++++++++++++---------------- app/projects/page.tsx | 4 +-- components/Gist/Gist.tsx | 4 +-- components/Tweet/Tweet.tsx | 8 +++--- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/app/api/hits/route.ts b/app/api/hits/route.ts index c674817d..24ed3cde 100644 --- a/app/api/hits/route.ts +++ b/app/api/hits/route.ts @@ -1,9 +1,7 @@ import { NextResponse } from "next/server"; +import { unstable_cache as cache } from "next/cache"; import redis from "../../../lib/helpers/redis"; -export const dynamic = "force-static"; -export const revalidate = 1800; // 30 mins - export const GET = async (): Promise< NextResponse<{ total: { @@ -15,32 +13,43 @@ export const GET = async (): Promise< }>; }> > => { - // get all keys (aka slugs) - const slugs = await redis.scan(0, { - match: "hits:*", - type: "string", - // set an arbitrary yet generous upper limit, just in case... - count: 99, - }); + const { total, pages } = await cache( + async () => { + // get all keys (aka slugs) + const slugs = await redis.scan(0, { + match: "hits:*", + type: "string", + // set an arbitrary yet generous upper limit, just in case... + count: 99, + }); - // get the value (number of hits) for each key (the slug of the page) - const values = await redis.mget(...slugs[1]); + // get the value (number of hits) for each key (the slug of the page) + const values = await redis.mget(...slugs[1]); - // pair the slugs with their hit values - const pages = slugs[1].map((slug, index) => ({ - slug: slug.split(":").pop() as string, // remove the "hits:" prefix - hits: parseInt(values[index], 10), - })); + // pair the slugs with their hit values + const pages = slugs[1].map((slug, index) => ({ + slug: slug.split(":").pop() as string, // remove the "hits:" prefix + hits: parseInt(values[index], 10), + })); - // sort descending by hits - pages.sort((a, b) => b.hits - a.hits); + // sort descending by hits + pages.sort((a, b) => b.hits - a.hits); - // calculate total hits - const total = { hits: 0 }; - pages.forEach((page) => { - // add these hits to running tally - total.hits += page.hits; - }); + // calculate total hits + const total = { hits: 0 }; + pages.forEach((page) => { + // add these hits to running tally + total.hits += page.hits; + }); + + return { total, pages }; + }, + undefined, + { + revalidate: 1800, // 30 minutes + tags: ["hits"], + } + )(); return NextResponse.json({ total, pages }); }; diff --git a/app/projects/page.tsx b/app/projects/page.tsx index 00e43d46..153a529b 100644 --- a/app/projects/page.tsx +++ b/app/projects/page.tsx @@ -86,8 +86,8 @@ const getRepos = async (): Promise => { ...options, cache: "force-cache", next: { - // 10 minutes - revalidate: 600, + revalidate: 600, // 10 minutes + tags: ["github-api"], }, }); }, diff --git a/components/Gist/Gist.tsx b/components/Gist/Gist.tsx index 2047dce8..ec7a8ede 100644 --- a/components/Gist/Gist.tsx +++ b/components/Gist/Gist.tsx @@ -12,8 +12,8 @@ const Gist = async ({ id, file }: GistProps) => { const scriptResponse = await fetch(scriptUrl, { cache: "force-cache", next: { - // cache indefinitely in data store - revalidate: false, + revalidate: false, // cache indefinitely in data store + tags: ["gist"], }, }); diff --git a/components/Tweet/Tweet.tsx b/components/Tweet/Tweet.tsx index 504d3806..1f084825 100644 --- a/components/Tweet/Tweet.tsx +++ b/components/Tweet/Tweet.tsx @@ -1,4 +1,4 @@ -import { unstable_cache } from "next/cache"; +import { unstable_cache as cache } from "next/cache"; import Image from "next/image"; import { EmbeddedTweet, TweetNotFound } from "react-tweet"; import { fetchTweet as _fetchTweet } from "react-tweet/api"; @@ -12,9 +12,9 @@ export type TweetProps = Omit, "t className?: string; }; -const fetchTweet = unstable_cache(async (id: string) => _fetchTweet(id), [], { - // cache indefinitely - revalidate: false, +const fetchTweet = cache(async (id: string) => _fetchTweet(id), undefined, { + revalidate: false, // cache indefinitely + tags: ["tweet"], }); const Tweet = async ({ id, className, ...rest }: TweetProps) => {