• next 16.2
  • sanity 5.31
  • react 19.2
  • 14 blocks
  • 4 build guards
  • drift: 0
  • €69 · one developer, forever

Next.js 16 · Sanity 5 · JavaScript

The boring decisions, already made.

A Next.js and Sanity starter with the plumbing wired and the sharp edges filed off. Page builder, fetch layer, draft mode, a signed revalidation webhook, a responsive system anchored to your Figma frames, and a Claude Code toolkit that cannot drift out of sync with your schemas.

€69 one-time · one developer, unlimited projects

next
16.2
react
19.2
sanity
5.31
tailwind
3.4
blocks
14
guards
4
skills
5
drift
0
  • NEXT 16.2
  • REACT 19.2
  • SANITY 5.31
  • TAILWIND 3.4
  • JAVASCRIPT
  • 14 BLOCKS
  • 4 BUILD GUARDS
  • DRIFT: 0

This is a content architecture, not a website. It decides how content is modelled, how it is fetched, where secrets live, how a design maps to CSS, and what happens when an editor renames a block. The libraries on top are defaults you can swap.

It is deliberately small. No TypeScript, no typegen, no MCP servers, no multi-language. If you want those, there is a more complete product and this page tells you where to find it.

common problems012 / 012
001  A secret with a NEXT_PUBLIC_ prefix002  An empty dataset returning 500 on every route003  Links resolved in the browser004  A block in the schema but not in the renderer005  Fluid type from a visual generator006  Renaming a block007  AI instructions describing last year's schema008  A layout that loads every block client-side009  Typography plugins at a 10px root010  One Open Graph card for the whole site011  A sitemap generated at build time012  A config option that never existed

Twelve ways a Sanity setup quietly breaks

Every one of these was in this repository, and every one is now a fixed bug, a guard that fails the build, or both. Each fix is in the code you get, with the guard that stops it coming back — this is not a list of things that happen to other people.

  1. A secret with a NEXT_PUBLIC_ prefix

    Next inlines those into every bundle that touches the module. The value ships to every visitor with no warning, no error, and no visible difference in development.

    until someone greps your bundle

  2. An empty dataset returning 500 on every route

    A missing singleton makes the root layout's metadata throw, and a throw there takes the whole site with it. It only happens on a brand-new project — which is to say, on every buyer's first run.

    your first five minutes

  3. Links resolved in the browser

    A component that turns a _ref into a path inside useEffect renders no anchor on the server. Crawlers see a page with no internal links at all, and every link costs a round-trip.

    every page, every crawl

  4. A block in the schema but not in the renderer

    It is selectable in the Studio, an editor places it on a page, and it renders nothing. No error in production — just a gap where their content should be.

    two blocks, several months

  5. Fluid type from a visual generator

    Stacked max-width media queries with independently calculated values do not meet at their boundaries. The page jumps backwards as the window gets wider, and the more breakpoints you add the worse it gets.

    3–4 hours to even find

  6. Renaming a block

    Sanity does not validate published documents against the current schema. The old block stays in every document that used it and renders nothing, forever.

    silent, and permanent

  7. AI instructions describing last year's schema

    Nothing checks them. An agent reads them as fact and generates code against types that were deleted months ago — confidently, and at speed.

    2,700 lines, in this repo

  8. A layout that loads every block client-side

    One `use client` at the top of a shared wrapper, for an animation prop nothing passes, and the entire page builder crosses the boundary with it.

    all five blocks, invisibly

  9. Typography plugins at a 10px root

    A 62.5% root makes Figma maths trivial and makes `prose` render a 10px article, because it hardcodes 1rem and sizes every descendant in em off that.

    one confusing afternoon

  10. One Open Graph card for the whole site

    The App Router merges metadata field by field but replaces openGraph wholesale. A page that sets only a title inherits the layout's entire block, title included.

    every share, same preview

  11. A sitemap generated at build time

    Publish an article and it appears on the site through revalidation — and in the sitemap at the next deploy, whenever that happens to be.

    days of missed indexing

  12. A config option that never existed

    Smooth-scroll libraries ignore keys they do not recognise. The option had been misspelled since the day it was written, so the behaviour was accidental and the code asserted the opposite.

    nobody notices, ever

already decided

Fourteen decisions you do not have to make

Not features — decisions. Each one is a fork you would otherwise stand at for an afternoon, and several are enforced by a script rather than left as advice.

  1. 001Server Components fetch

    Content arrives as a `data` prop from a Server Component. No loading state to design, no waterfall, no fetch layer shipped to the browser.

  2. 002References resolve in GROQ

    Shared fragments turn a link into a ready href and an image reference into a URL with real dimensions. If a component needs a useEffect to know what to render, the query is missing a projection.

  3. 003Secrets cannot reach the browser

    Public config and server secrets are separate modules, and the build scans its own client output for credentials afterwards. It fails rather than warns.

  4. 004Figma pixels divide by ten

    The root is fixed at 62.5%, so 1rem is 10px at every viewport and a 32px value from your design is text-[3.2rem]. No scale to memorise.

  5. 005Fluid type without breakpoints

    text-fluid-[32,48] is 32px at your mobile frame and 48px at your desktop one, interpolating between. A check runs the curve from 200px to 6000px and fails if it ever goes backwards.

  6. 006Publishing reaches the live site at once

    A signed webhook expires the cache the moment you publish, and every fetch carries the tag it clears. Visitors with the site open get the change through live updates too.

  7. 007Routing comes from content

    An editor sets a pathname and the route exists. Every document with one appears in the sitemap automatically, with real lastmod dates.

  8. 008The Studio keeps its own root

    A 62.5% root would shrink every Sanity UI measurement by 37%. The Studio is a separate root layout with a second lock in CSS.

  9. 009Colours are roles, and measured

    Components ask for bg-surface because it is a raised surface. Each token carries its contrast ratio, and the ones that cannot carry text are marked decorative rather than left for you to discover.

  10. 010Sections invert without a branch

    A theme field re-declares the colour properties for one subtree. Every utility inside follows on its own — no component ever learns a dark mode exists.

  11. 011A block is four files, all checked

    Schema, group, component, registry entry. The build fails if any is missing, in either direction, so a block cannot be insertable and invisible at the same time.

  12. 012The AI instructions cannot drift

    A guard resolves every schema name in the agent docs against the schemas themselves, and holds the inventories to set equality. Rename a block and the build names every file still mentioning it.

  13. 013Images keep their focal point

    Hotspot and crop travel from the Studio through to the CDN URL and the object-position, so an art-directed crop survives every breakpoint.

  14. 014Animation degrades to content

    Every animated element renders complete and correct with JavaScript off. Motion is a layer over something already in the HTML, never the thing that puts it there.

the page builder

A block is four files, and the build checks all four

Editors compose pages from blocks. The registry is a plain object keyed by schema name, so the mapping is readable at a glance — and a script asserts every block has all four pieces, because a block in the schema with no component is selectable and silent.

npm run check:blocks11 lines
$ npm run check:blocks[check-blocks] OK — 14 blocks, schema and component in step. # adding one:sanity/schemas/components/Pricing.js      the schemasanity/schemas/components/index.js        a groupcomponents/Pricing/Pricing.jsx            the componentobjects/PageBuilder.jsx                   the registry entry # or ask for it:$ /new-block pricing table

the fetch layer

Everything resolves in the query, never in a component

Shared GROQ fragments turn references and assets into finished values on the server. This is the difference between a page whose internal links exist in the HTML and one whose links appear a round-trip later, after the crawler has gone.

sanity/fragments.js14 lines
export const linkFields = groq`  _key,  label,  "href": select(    linkType == true => externalLink.href,    defined(internalLink.reference->pathname.current) =>      internalLink.reference->pathname.current,    defined(internalLink.reference->_id) => "/",   // a singleton    null  ),`; // the component receives { label, href } and renders an anchor.// it never learns what a _ref is.

the responsive system

Two numbers from Figma, and the type follows

You give it your mobile and desktop frame widths. `text-fluid-[32,48]` is then 32px at the first and 48px at the second, interpolating between with no breakpoint and no scale to learn. Below is that exact utility, resolved.

Viewporttext-fluid-[32,48]What it means
320px32.0pxbelow the mobile frame — held, not shrunk
390px32.0pxthe mobile frame. Exactly your Figma value
768px36.9pxinterpolating
1024px40.2pxinterpolating
1620px48.0pxthe desktop frame. Exactly your Figma value
1920px51.9pxthe cap
2560px51.9pxstill the cap — it never runs away

Computed from design.config.js, not measured by hand. `npm run check:fluid` asserts both anchors and walks the curve from 200px to 6000px looking for a step backwards — and it carries a negative test, so a formula that sawtooths fails the check rather than passing quietly.

guard rails

Four things the build refuses to let you ship

Documentation rots and conventions slip. These run on every build, and each one exists because the thing it prevents had already happened here.

npm run check12 lines
$ npm run check [check-fluid]    OK — anchors exact, curve monotonic, 200→6000px[check-blocks]   OK — 14 blocks, schema and component in step.[check-toolkit]  OK — 15 instruction files, every schema reference resolves.✓ Compiled successfully[check-secrets]  OK — scanned 119 files, no credentials found. # and when something is wrong:[check-toolkit] FAILED — 4 stale inventory listing(s):  CLAUDE.md:111  blocks inventory is out of date    no longer exists: Accordion
⌘J⌘K
  • app
  • (pages)
  • (admin)/admin
  • api/revalidate
  • sitemap.js robots.js
  • components
  • objects
  • sanity
  • env.js / env.server.js
  • fragments.js
  • schemas
  • design.config.js
  • theme.config.js
  • seed
  • scripts
  • docs
readme.md
  1. 1# What you actually get
  2. 2 
  3. 3Source you unzip and own. No CLI to install, no licence check phoning home, no build step between you and the source.
  4. 4 
  5. 5## What's inside
  6. 6 
  7. 7- app/ — routes — public site and Studio, separate root layouts
  8. 8- (pages)/ — the site. Sets the fluid class
  9. 9- (admin)/admin/ — Sanity Studio, isolated from the site's styles
  10. 10- api/revalidate/ — signed publish webhook
  11. 11- sitemap.js robots.js — generated from Sanity, not from a config file
  12. 12- components/ — fourteen page-builder blocks, plus header and footer
  13. 13- objects/ — shared UI — Container, Figure, RichText, PageBuilder
  14. 14- sanity/ — client, live, queries, fragments, structure
  15. 15- env.js / env.server.js — the public/secret split, enforced by the build
  16. 16- fragments.js — link, image, video and rich text projections
  17. 17- schemas/ — documents, blocks, field objects, singletons
  18. 18- design.config.js — the four numbers the responsive system derives from
  19. 19- theme.config.js — the palette, both themes, single source of truth
  20. 20- seed/ — this site, as importable content
  21. 21- scripts/ — setup wizard and the four build guards
  22. 22- docs/ — eleven documents, one per subsystem
main · updated today14 blocks · 4 guards · 0 drift

scope

What this is not

Written down so you can decide before paying rather than after. None of these are coming later.

  1. 001Not TypeScript

    JavaScript throughout, deliberately. If strict types are a requirement, this is not your starter — and that is a better outcome for both of us than a regret in three weeks.

  2. 002No typegen or MCP servers

    Sanity typegen depends on TypeScript. MCP servers are a different product at a different price, and this page tells you which one.

  3. 003Single locale

    No i18n. Multi-language is genuinely large and half of it is worse than none of it.

  4. 004No component library

    No UI kit to theme, no design system to learn. Fourteen blocks that show the conventions, and then it is your code.

  5. 005No auth or billing

    No dashboard scaffolding, no user accounts, no payments. This is a content architecture.

  6. 006Not a no-code builder

    You write real code on top of it. There is no drag-and-drop and no visual editor.

honestly

Where this sits

There is a more complete product in this niche. It is good, it costs several times this, and it is aimed at a different buyer. Pretending otherwise would waste your afternoon and my reputation.

A free starterThis · €69The €549 option
Shows how the pieces connectyesyesyes
Decides how you fetch and modelnoyesyes
Guards that fail the buildnofouryes
Agent instructionsnoskills + commandsskills + MCP + llms.txt
TypeScript strictsometimesnoyes
Sanity typegennonoyes
Multi-languagenonono
Award-winning showcasenonoyes
Pricefree€69€549

If the right-hand column is what you need, buy that one. This is the smaller, cheaper, honest option — and the rows where it says no are the reason it costs what it costs.

  • one price
  • one developer
  • unlimited projects
  • no attribution
  • full source
  • €69

One price, one developer, forever

Delivered as a ZIP of the full source. Commit it as your project's first commit and keep an untouched starter branch beside it — each update is a new ZIP merged through that branch, so Git shows you exactly what changed. Sold through Gumroad, which handles the payment, the tax and the download.

Next.js 16 · Sanity 5available now

every licence includes

  1. 001Full source, no build step, no licence check
  2. 002Unlimited projects, personal and commercial
  3. 003Client work, and the site you deliver is theirs
  4. 004No attribution, no badge, no backlink
  5. 005Updates for as long as it is maintained
  6. 006The full source as a ZIP, re-downloadable from your purchase
  7. 007The demo is the product — see all of it before you buy

and deliberately does not

  1. 001TypeScript — this is JavaScript, deliberately
  2. 002Typegen, MCP servers, llms.txt
  3. 003Multi-language — single locale by design
  4. 004A UI kit or component library to theme
  5. 005Auth, billing or dashboard scaffolding

before you buy

Sixteen questions, answered plainly