Statically Rendered, Instantly Fresh
Static pages that update the moment I publish. How a Sanity webhook triggers on-demand revalidation so a fully static Next.js site never serves stale content.

Static sites are fast because the work happened at build time — and stale for the same reason. The classic fix is to rebuild the whole site on every edit, which is slow and wasteful for a typo. This blog is fully statically rendered and still updates the instant I publish, because a Sanity webhook tells Next.js to revalidate exactly the pages that changed. Fast by default, fresh on demand. Part of the Building with AI series.
Static first, for speed
Every post page is generated ahead of time. Next.js pre-renders the known routes at build, so a reader gets finished HTML from the edge with no database call in the request path. The list of routes to build is, like everything else, a read through the typed service layer:
1import { getAllSlugs, getPostBySlug } from "@repo/service";
2
3export async function generateStaticParams() {
4 const slugs = await getAllSlugs();
5 return slugs.map((s) => ({ slug: s.current }));
6}
7
8export const revalidate = false; // static until something tells us otherwisePublish fires a webhook
The freshness comes from a signal, not a schedule. Sanity is configured to POST to a route in the app whenever a blog_post is published. That handler doesn't rebuild the site — it invalidates just the affected cache tags, so the next request to those pages regenerates and everything else stays untouched.
1import { revalidateTag } from "next/cache";
2import { parseBody } from "@sanity/webhook";
3
4export async function POST(req: Request) {
5 const { isValidSignature, body } = await parseBody(req, process.env.SANITY_WEBHOOK_SECRET);
6 if (!isValidSignature) return new Response("Invalid signature", { status: 401 });
7
8 revalidateTag("post:" + body.slug);
9 revalidateTag("post-list");
10 return Response.json({ revalidated: true });
11}The signature check on the way in is not optional — the highlighted revalidateTag is a public endpoint doing real work, so it verifies Sanity's secret before it trusts the payload. An unsigned request gets a 401 and nothing moves.
Tags tie data to pages
The reason a single publish can refresh the right pages is that the fetches are tagged. When the service layer reads a post, it tags that cache entry with the post's slug and a shared post-list tag. Invalidating post:my-slug drops exactly the detail page; invalidating post-list refreshes the home and archive cards. No full rebuild, no guesswork.
Static isn't the opposite of fresh. Cache invalidation just needs a trigger, and publishing is the perfect one.
Why this fits the rest of the build
This is the same shape as every other decision in the series: derive from one source, and let a signal — not a human — keep things in sync. The metadata generates itself, the sitemap is a query, and now the cache heals itself on publish. An agent can add a page or a fetch without learning a bespoke deploy dance, because freshness is a property of the tagging convention, not a manual step.
Serve static, invalidate on publish, and you get edge speed with live-CMS freshness. The reader never waits on the database, and I never wait on a rebuild. Last in the series, an honest accounting of the whole thing — 24 Days, ~1,250 Commits: What the Agents Got Right and Wrong.
