Moving this site onto Sanity
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.
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.
sanity schemas extract --force --enforce-required-fields && sanity typegen generateThe 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.