diff --git a/AGENTS.md b/AGENTS.md index 28424303..5acc2c4a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/app/notes/[slug]/opengraph-image.tsx b/app/notes/[slug]/opengraph-image.tsx index 38278c8b..282ee6f2 100644 --- a/app/notes/[slug]/opengraph-image.tsx +++ b/app/notes/[slug]/opengraph-image.tsx @@ -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 => { "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="; diff --git a/app/sitemap.ts b/app/sitemap.ts index 897270c9..dc91c2a2 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -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 => { "use cache"; + cacheLife("max"); return glob("**/page.{tsx,mdx}", { cwd: path.join(process.cwd(), "app"), diff --git a/lib/data/comments.ts b/lib/data/comments.ts index c7a9305a..cf5bf9f7 100644 --- a/lib/data/comments.ts +++ b/lib/data/comments.ts @@ -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 => { "use cache: remote";