← Journal

Moving this site onto Sanity

Sanity · Next.js · CMS

Every string on this site used to live in a component. Here is what it took to pull them out into a CMS without giving up the static build.

This site started as a handful of React components with the copy typed directly into them. That is genuinely fine until the first time you want to change a sentence from your phone.

The constraint that shaped everything

The site is a static export deployed to Cloudflare Pages. No server, no request-time rendering. That rules out the parts of the Sanity toolkit most tutorials reach for first: live content, draft previews, click-to-edit overlays. All of them need something running when the request arrives.

What it leaves is simple and, honestly, pleasant: fetch everything at build time, bake it into HTML, serve it from the edge.

src/sanity/client.tsts
export const client = createClient({
  projectId,
  dataset,
  apiVersion: "2026-09-12",
  // Every query runs once, at build time. Bypassing the CDN
  // means a rebuild always picks up the latest published content.
  useCdn: false,
  perspective: "published",
});

Typed queries, for free

Sanity TypeGen reads the schema and every GROQ query in the codebase and generates matching TypeScript types. Extract with required-field enforcement and the result types stop being a sea of nullable fields.

sh
sanity schemas extract --force --enforce-required-fields && sanity typegen generate

The payoff is that renaming a field in the Studio turns into a type error in the components, rather than a blank space on the page that nobody notices for a week.