@sp-stage/sdk and @sp-stage/admin exactly like an Astro site. The third package, @sp-stage/next, supplies the Next-shaped glue: the anchor helpers as React props, <StageAdmin> for the /admin route, renderers for pages and article bodies, the media slot, the preview canvas resolvers, and the on-demand publish handler. Everything on the other Developers pages applies; this section covers what differs.
The one design decision
The admin edits by writing directly into the canvas frame’s DOM: contenteditable text, grafted adds. Hydrated React would clobber those writes. So a Next site is split:
The routers coexist natively. The canvas is an internal editor surface no visitor sees. A pure pages-router site works too; every pages-shaped helper remains exported.
Zero JS means: anything a script was going to supply is missing
Two ways a canvas silently renders wrong. Check both on every route you add.1
Data a client effect seeds
A component driven by a store that a
useEffect fills renders nothing on the canvas. Pass the data in as props instead. PageRenderer and ArticleBody spread a context prop into every renderer for exactly this. The article canvas is the easy one to miss: wrap the article view in the site’s own shell and hand it the globals, or posts render with no footer while every page has one.2
State a script was going to reveal
An image that fades in from
opacity-0 in an onLoad handler stays transparent. A motion library’s initial={{ opacity: 0 }} is inlined as a style and never animates. Reveal them with canvas-scoped CSS (.stage-canvas img.opacity-0.transition-opacity { opacity: 1 }) rather than per component. Scope tightly: overlays meant to stay hidden until JS runs must stay hidden.The three laws
- No time-based ISR on Stage pages. Stage’s semantic is live equals last publish.
export const revalidate = Nwould regenerate in the background and leak saved-but-unpublished edits. Only the publish handler regenerates. - The canvas is zero-JS. Components render complete without scripts. Anything a script reveals needs a canvas-scoped CSS rule or a props-passed data path.
/adminloads zero site CSS. Site styles enter through the site layout group, the browse canvas and the pages_app, never the root layout. An unlayered Tailwind preflight beats the admin’s layered design system regardless of order, and every admin button flattens to transparent.
Requirements
- Next 15 or later. Site routes in the App Router; the canvas as one pages-router file.
- One React 19 everywhere. The admin shell is React 19; a React 18 host breaks it with an invalid hook call.
transpilePackages: ["@sp-stage/admin", "@sp-stage/sdk", "@sp-stage/next"]in the Next config.- Same origin for
/admin,/preview/*,/browse/*and the site. - The admin shell’s two font files copied once from the package into
public/fonts/.
The template
The Next client template is a minimal App Router site plus the pages-router canvas, a referenceHero, a site shell that takes globals as props, the blog, the browse canvas, the admin route with a reference schema, and the publish handler. Start a new Next site by cloning and renaming it. Its checklist:
- Rename the package and replace workspace dependencies with exact-pinned registry versions of the three packages.
- Delete the in-repo source aliases from the Next config and
tsconfig. - Create the org, fill the env.
- Seed pages and globals, or migrate from an existing CMS.
- Rebrand the tokens, build the components, register each type, grow the schema.
- Keep the blog base path identical in three places: the schema, the browse canvas, and the
pages/preview/<base>/directory (plusexpandPathsin the publish handler). - Run the canvas sweep on every route: footer present, nothing stuck at opacity 0.
- Deploy, then flip the org to revalidate publish mode with its secret.
Next steps
Wiring
Every file, with the code.
Edit anchors
The same attributes, as React props.