How to (server side) cache an API endpoint
Answered
json posted this in #help-forum
jsonOP
I'm using nextjs 15.1.3 app router and I cannot seem to get a working cache system.
I have a simple router like
I've tried using the canary "use cache" But i always get errors along the lines of
All I want to do is this route being 100% cached and then something along the lines of
I have a simple router like
/api/photos/tags
which looks likeimport { NextResponse } from "next/server";
import { getPhotoMetadata } from "@/lib/photos/utils";
import kv from "@/lib/kv";
export async function GET() {
try {
const keys = await kv.keys("*"); // my expensive function that I don't want to be constantly calling but I also want to be able to invalidate on the fly
const allTags = new Set<string>();
for (const key of keys) {
const metadata = await getPhotoMetadata(key);
if (metadata && Array.isArray(metadata.tags)) {
metadata.tags.forEach((tag: string) => allTags.add(tag));
}
}
const tagsArray = Array.from(allTags);
return NextResponse.json(tagsArray);
} catch (error) {
console.error("Error fetching tags:", error);
return NextResponse.json(
{ error: "Failed to fetch tags" },
{ status: 500 },
);
}
}
I've tried using the canary "use cache" But i always get errors along the lines of
Error while saving cache key: ["development","80f78954169d898ec9a7d8a9b46a5a7ef69cc25873",["$T",{"params":"$undefined"}]] [Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
and I can't seem to disable that being added to the cache key. All I want to do is this route being 100% cached and then something along the lines of
revalidateTag("photo_tags")
to have this route's cache be invalidated. But the important thing is that this cache is SERVER SIDE so I can't just apply that on the fetch()
side of calling the APIAnswered by B33fb0n3
the
Make sure you are using
You can also use
But then you can't revalidate it by tag. Only by
'use cache'
is up to know pretty unstable and may not be implemented in all features of nextjs.Make sure you are using
unstable_cache
as it's waaaay more than use cache
. Then wrap your whole GET route inside an unstable cache and you are fine.You can also use
export const dynamic = 'force-static'
But then you can't revalidate it by tag. Only by
revalidatePath
7 Replies
@json I'm using nextjs 15.1.3 app router and I cannot seem to get a working cache system.
I have a simple router like `/api/photos/tags` which looks like
ts
import { NextResponse } from "next/server";
import { getPhotoMetadata } from "@/lib/photos/utils";
import kv from "@/lib/kv";
export async function GET() {
try {
const keys = await kv.keys("*"); // my expensive function that I don't want to be constantly calling but I also want to be able to invalidate on the fly
const allTags = new Set<string>();
for (const key of keys) {
const metadata = await getPhotoMetadata(key);
if (metadata && Array.isArray(metadata.tags)) {
metadata.tags.forEach((tag: string) => allTags.add(tag));
}
}
const tagsArray = Array.from(allTags);
return NextResponse.json(tagsArray);
} catch (error) {
console.error("Error fetching tags:", error);
return NextResponse.json(
{ error: "Failed to fetch tags" },
{ status: 500 },
);
}
}
I've tried using the canary "use cache" But i always get errors along the lines of
ts
Error while saving cache key: ["development","80f78954169d898ec9a7d8a9b46a5a7ef69cc25873",["$T",{"params":"$undefined"}]] [Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
and I can't seem to disable that being added to the cache key.
All I want to do is this route being 100% cached and then something along the lines of `revalidateTag("photo_tags")` to have this route's cache be invalidated. But the important thing is that this cache is SERVER SIDE so I can't just apply that on the `fetch()` side of calling the API
the
Make sure you are using
You can also use
But then you can't revalidate it by tag. Only by
'use cache'
is up to know pretty unstable and may not be implemented in all features of nextjs.Make sure you are using
unstable_cache
as it's waaaay more than use cache
. Then wrap your whole GET route inside an unstable cache and you are fine.You can also use
export const dynamic = 'force-static'
But then you can't revalidate it by tag. Only by
revalidatePath
Answer
@B33fb0n3 the ``'use cache'`` is up to know pretty unstable and may not be implemented in all features of nextjs.
Make sure you are using ``unstable_cache`` as it's waaaay more than ``use cache``. Then wrap your whole GET route inside an unstable cache and you are fine.
You can also use
tsx
export const dynamic = 'force-static'
But then you can't revalidate it by tag. Only by ``revalidatePath``
jsonOP
But then you can't revalidate it by tag. Only by revalidatePath
That's perfect, but from the docs it seemed that
revalidatePath
didn't work for API routes?@json > But then you can't revalidate it by tag. Only by revalidatePath
That's perfect, but from the docs it seemed that `revalidatePath` didn't work for API routes?
hmmm can you test this and share the test via [codesandbox](https://codesandbox.io/)?
@B33fb0n3 the ``'use cache'`` is up to know pretty unstable and may not be implemented in all features of nextjs.
Make sure you are using ``unstable_cache`` as it's waaaay more than ``use cache``. Then wrap your whole GET route inside an unstable cache and you are fine.
You can also use
tsx
export const dynamic = 'force-static'
But then you can't revalidate it by tag. Only by ``revalidatePath``
jsonOP
okay okay no it's all good! Thank you so much I spent like 3 hours on this ;-;
Works fine for me.
two questions for you if you don't mind as I cant seem to find it on the docs.
1. for the tags, when EITHER of them are called by revalidateTag it's revalidated right?
2. why is
import { NextResponse } from "next/server";
import { getPhotoMetadata } from "@/lib/photos/utils";
import kv from "@/lib/kv";
import { revalidateTag, unstable_cache } from "next/cache";
export const dynamic = 'force-static'
const getCachedTags = unstable_cache(
async () => {
console.log("Fetching tags from KV");
const keys = await kv.keys("*");
const allTags = new Set<string>();
for (const key of keys) {
const metadata = await getPhotoMetadata(key);
if (metadata && Array.isArray(metadata.tags)) {
metadata.tags.forEach((tag: string) => allTags.add(tag));
}
}
return Array.from(allTags);
},
[],
{ tags: ['photos', "tags"] },
);
export async function GET() {
try {
const tagsArray = await getCachedTags();
// revalidateTag("photos");
return NextResponse.json(tagsArray);
} catch (error) {
console.error("Error fetching tags:", error);
return NextResponse.json(
{ error: "Failed to fetch tags" },
{ status: 500 },
);
}
}
Works fine for me.
two questions for you if you don't mind as I cant seem to find it on the docs.
1. for the tags, when EITHER of them are called by revalidateTag it's revalidated right?
2. why is
unstable_cache
depricated if "use cache"
is in canary and does not fufill the job that unstable_cache
can do ;-;@json okay okay no it's all good! Thank you so much I spent like 3 hours on this ;-;
ts
import { NextResponse } from "next/server";
import { getPhotoMetadata } from "@/lib/photos/utils";
import kv from "@/lib/kv";
import { revalidateTag, unstable_cache } from "next/cache";
export const dynamic = 'force-static'
const getCachedTags = unstable_cache(
async () => {
console.log("Fetching tags from KV");
const keys = await kv.keys("*");
const allTags = new Set<string>();
for (const key of keys) {
const metadata = await getPhotoMetadata(key);
if (metadata && Array.isArray(metadata.tags)) {
metadata.tags.forEach((tag: string) => allTags.add(tag));
}
}
return Array.from(allTags);
},
[],
{ tags: ['photos', "tags"] },
);
export async function GET() {
try {
const tagsArray = await getCachedTags();
// revalidateTag("photos");
return NextResponse.json(tagsArray);
} catch (error) {
console.error("Error fetching tags:", error);
return NextResponse.json(
{ error: "Failed to fetch tags" },
{ status: 500 },
);
}
}
Works fine for me.
two questions for you if you don't mind as I cant seem to find it on the docs.
1. for the tags, when EITHER of them are called by revalidateTag it's revalidated right?
2. why is `unstable_cache` depricated if `"use cache"` is in canary and does not fufill the job that `unstable_cache ` can do ;-;
for the tags, when EITHER of them are called byyea, it's OR. So ['users', 'user-12345']. RevalidateTag for 'users' would revalidate it. RevalidateTag for 'user-12345' would revalidate it as well
why is unstable_cache depricatedbefore next15 exists this was the solution to use the data cache for third party data. Me personally thought that this api will be implemented and stable, but it looks like the nextjs team thought different and added the
'use cache'
directive in next15jsonOP
before next15 exists this was the solution to use the data cache for third party data. Me personally thought that this api will be implemented and stable, but it looks like the nextjs team thought different and added the 'use cache' directive in next15;-; yay
anywho tysm for the help, works perfectly now!
This is what a backend dev gets when he tries to redo his portfolio xD
happy to help