The page builder
How a Sanity type becomes a Vue component, and why adding one requires three files.
The idea
Most CMS integrations model a page as a document with fields: a hero image, headline, three feature slots. That buys you a fast first page and an unmaintainable twentieth, because every new layout is a new document type and every layout tweak is a deploy.
Nuxion models a page as an ordered array of blocks. A renderer walks the array, checks each block's _type value in a registry, and mounts the matching component. Editors compose; the system supplies the layout primitives. Reordering a page is content editing.
Blocks are imported with defineAsyncComponent, so a page with four blocks ships four blocks of JavaScript — not the whole library.
Adding a block
A Sanity schema
studio/schemas/web/custom/custom.hero.ts— the fields an editor sees, plus the shared section options every block includes, so layout controls stay consistent.TypeScript npm run cssvars:generateA Vue component
`
app/components/custom/CustomHero.vue` — props are typed from the generated Sanity types, so the schema and the component cannot drift.Vue npm run fonts:generateOne line of registration
app/components/custom/index.ts— The key is the Sanity_type, exactly.TypeScript 'custom.hero': { element: defineAsyncComponent(() => import('./CustomHero.vue')), props: { ...data }, },
Naming
One convention, applied everywhere: nuxion.hero → NuxionHero.vue → nuxion.hero.ts. Every dot- or hyphen-separated segment is capitalised and joined, so custom.logo-strip → CustomLogoStrip.vue. The namespace is yours to pick; custom. is a suggestion, not a rule — and it's the same rule Sanity's TypeGen uses, so the generated TypeScript type matches too. Once you know a block's Sanity type, you know both filenames without looking.
The scaffolder
You rarely write those three files by hand.
npm run scaffold:component -- custom.pricing-tableThat generates the schema, the component, and every registration, then regenerates the TypeScript types. --kind navigation and --kind footer produce navigation bars and footer blocks instead, wired into the right slots — those used to be hand-threaded across three files.
Who owns width
Blocks provide vertical rhythm and content. Horizontal width and gutter belong to the section wrapper, which offers three named intents rather than a padding input:
- Contained — a max width with a gutter. The default.
- Full — full-width band, content still inset.
- Bleed — edge-to-edge, for media, carousels and maps.
Three named choices, not a padding input. It is why blocks from differentsources line up on the left edge without anyone coordinating.