fix: add cacheLife("max") to build-time cached functions and clarify throw-vs-catch caching rule

- Add `cacheLife("max")` to `getLocalImage` (OG image) and `getStaticRoutes` (sitemap) so entries are held as long as possible during prerender instead of expiring at the default TTL
- Update AGENTS.md to distinguish build-time reads (catch inside the cached function, return a fallback, use a short `cacheLife` on the error path) from request-time reads (may throw from the cached function since they never run during prerender)
- Expand the `getCachedComments` comment to explain why throwing is only safe there: a throw inside `"use cache"` during prerender fails the build even if the caller catches it
This commit is contained in:
jake committed 2026-10-03 09:55:33 -04:00
1 parent d3b2d07de7
commit 3baffbbd29
4 files changed
+7 -2

No files matched your search

+1 -1
View File
@@ -137,7 +137,7 @@ export const createComment = async (data: {
- Schema in `lib/db/schema.ts`; migrations are generated into `drizzle/` with `pnpm db:generate` and applied with `pnpm db:migrate`. Drizzle ORM is a pinned `1.0.0-rc` prerelease, so check its docs for v1 APIs (e.g. `defineRelations`).
- `cacheComponents` is enabled. Cache expensive or remote reads with `"use cache"` and an explicit `cacheLife(...)` (add `cacheTag(...)` when something needs to invalidate it). Examples: `app/projects/github.ts`, `components/third-party/tweet.tsx`.
- Use `"use cache: remote"` for reads that run at request time (inside a dynamic hole, e.g. after `headers()`/`connection()`) so the entry is shared across server instances; plain `"use cache"` is fine for anything that ends up in a prerendered shell. Example: `lib/data/comments.ts`.
- Don't catch-and-return-a-fallback inside a cached function, or the failure gets cached; let it throw and catch in an uncached wrapper.
- A throw inside a `"use cache"` function fails the build if it happens during prerender, even when a caller catches it. For data read at build time (`app/projects/github.ts`, `lib/data/stats.ts`), catch inside the cached function and return a fallback; to avoid pinning a failure for long, call a shorter `cacheLife()` on the failure path (see `components/third-party/tweet.tsx`). Only request-time reads (`lib/data/comments.ts`) may throw from the cached function and catch in an uncached wrapper, which keeps errors out of the cache entirely.
- After mutations, call `updateTag()` (server actions) or `revalidateTag()` (route handlers), and only expire tags that some `cacheTag()` actually sets.
```typescript
+2
View File
@@ -1,6 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { cacheLife } from "next/cache";
import { notFound } from "next/navigation";
import { ImageResponse } from "next/og";
@@ -31,6 +32,7 @@ const loadGoogleFont = async (font: string, weight: number): Promise<ArrayBuffer
const getLocalImage = async (src: string): Promise<ArrayBuffer | string> => {
"use cache";
cacheLife("max");
// https://stackoverflow.com/questions/5775469/whats-the-valid-way-to-include-an-image-with-no-src/14115340#14115340
const NO_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";
+2
View File
@@ -1,12 +1,14 @@
import path from "node:path";
import type { MetadataRoute } from "next";
import { cacheLife } from "next/cache";
import { glob } from "tinyglobby";
import { getFrontMatter } from "@/lib/posts";
const getStaticRoutes = async (): Promise<string[]> => {
"use cache";
cacheLife("max");
return glob("**/page.{tsx,mdx}", {
cwd: path.join(process.cwd(), "app"),
+2 -1
View File
@@ -17,7 +17,8 @@ export const commentsTag = (pageSlug: string) => `comments-${pageSlug}`;
/**
* Shared across server instances since it's read at request time (inside dynamic holes), not at prerender. Throws on
* failure so an error is never cached.
* failure so an error is never cached -- only safe because it never runs during prerender, where a throw inside
* "use cache" fails the build even if caught.
*/
const getCachedComments = async (pageSlug: string): Promise<CommentWithUser[]> => {
"use cache: remote";