createEditorApiHandler and createSitePage — both of which import from next/headers and next/server. If your site is on Remix, SvelteKit, Hono, Cloudflare Workers, or any other framework that gives you web-standard Request and Response objects — Astro excepted, which has its own integration — you can implement the same contract by hand using the SDK’s framework-agnostic /core primitives instead. Same /api/editor/* URL paths, same wire format, same end state — your site appears in the editor’s dashboard, edits round-trip through Draft Mode (or the framework’s equivalent), and the rest of the docs apply.
On Astro, stop here — there is a package.
@avocadostudio-ai/astro is an Astro integration that mounts all five routes, injects the preview bridge and handles the prerendering problem for you. Nothing on this page applies; do not implement the contract by hand.Next.js and Astro are the tested frameworks today. The /core primitives below are framework-agnostic by design, but the adapters shipped in the box are the Next.js ones in @avocadostudio-ai/site-sdk/draft and @avocadostudio-ai/site-sdk/routes, plus the Astro integration above. On Remix, SvelteKit, Hono or Workers you’ll be a first mover. The patterns on this page are real and the primitives work, but expect to find gaps — tell us about them.What you have to implement
The contract the editor speaks is five HTTP routes mounted under/api/editor/*. On Next.js the SDK’s catch-all handler implements all five for you. Off Next.js, you implement them yourself, but the SDK gives you most of the logic via the /core exports — you only have to provide a small adapter that translates between your framework’s request/cookie/redirect primitives and the SDK’s web-standard ones.
The SDK exports come from three subpaths:
1. Blocks and pages (no adapter needed)
createBlocksHandler, createPagesHandler, and createPublishHandler already accept and return web-standard Request and Response objects. They have no Next.js dependency — you can mount them on any framework that lets you wire a (request: Request) => Response handler to a route.
Hono / Cloudflare Workers example
Two things createPublishHandler refuses
This route overwrites the site’s content, so it fails closed in two states, and a hand-wired mount meets both.
- No
publishSecretunderNODE_ENV=productionis a 401, whosereasonnamesPUBLISH_TOKEN. The option used to be optional, and every integration that read it from an environment variable nobody set was serving an endpoint that replaced a site’s pages for any caller. In development the handler stays open — publishing to your own machine is the point — and warns once on the first request. When a secret is set, the caller sends it asx-publish-token. - A publish that would remove every page is a 409 unless the body carries
allowDelete: true. An emptypagesarray is almost never somebody deleting their site; it is a client publishing what it thinks it has after its own state failed to load. PassgetPagesso the refusal can say what it protected and somaxPagesRemoved— a tighter bound than “not all of them” — can be enforced at all. Without a baseline the empty publish is still refused: not knowing what is there is not a reason to overwrite it with nothing. A site that is already empty may publish empty, so a first publish never trips this.
checkDestructivePublish from @avocadostudio-ai/site-sdk/routes, so a route you write entirely by hand can apply the same one.
2. Draft Mode routes (needs an adapter)
The Draft Mode entry / exit routes need to do three framework-specific things:- Toggle draft mode — on Next.js this is
(await draftMode()).enable(). On other frameworks, draft mode is usually a cookie you set; there’s no global “enable” function. The SDK callsenableDraftMode()on your adapter; you decide what that means. - Set cookies on the response — every framework handles this differently. The SDK passes a list of cookies to your adapter; you attach them to whatever response object you return.
- Build the redirect response — same idea. The SDK gives you the destination
URLand the cookies; you return a framework-appropriateResponse.
SvelteKit example
What enableDraftMode actually means off Next.js
Next.js has a global draftMode() API that flips a server-side flag, and next/headers reads it back from inside your page render. No other framework has this. On every other framework, the closest equivalent is “set a cookie that your page-render code checks.”
The __draft_enabled=1 cookie in the SvelteKit example above is illustrative — pick whatever name and shape works for your stack. The SDK doesn’t care what your draft flag looks like; it only cares that:
- The
/api/editor/draftroute validates the secret and sets it - The
/api/editor/draft/disableroute clears it - Your page render code reads it and switches between published and draft data sources
enableDraftMode / disableDraftMode adapter callbacks are usually no-ops on non-Next.js frameworks — the createRedirect callback does the real work by attaching the cookie.
3. Page render: switching between published and draft
This is the part that lives outside the editor API routes. When a user visits/pricing on your site:
- Published mode (no draft cookie): your page handler reads from your CMS / file system / database and renders normally.
- Draft mode (your draft cookie is set, OR
?session=…&siteId=…query params from the editor iframe): your page handler reads from the orchestrator’s/draft/pagesendpoint instead, and shows the editor overlay.
Helper A: fetchEditorPage and fetchEditorSlugs (no adapter needed)
These are plain fetch() wrappers around the orchestrator’s draft endpoints. Use them anywhere you can call await fetch():
{ orchestratorUrl } override if you don’t want to set the env var.
Helper B: resolveDraftContextCore (needs an adapter)
This is the helper that figures out whether you’re in draft mode by looking at cookies, query params, and env defaults — the same logic createSitePage uses internally on Next.js. It needs a tiny adapter so it can read your framework’s cookies:
null (you’re in published mode) or { session, siteId, editorOrigin } (you’re in draft mode and should call fetchEditorPage with these values).
The request has to say it came from the editor. A configured defaultSiteId is the site’s identity, not evidence about the caller, so it no longer resolves a context on its own: resolveDraftContextCore returns null unless the request carries siteId, session, editorOrigin or __editor in the query, the draft cookies your adapter reads, your framework’s draft-mode flag, or a valid secret. Every real editor entry point sends one of those. What this changes is the anonymous request — a plain curl of your dev server is a visitor now, and renders published content. Before, it took the draft path, where an unknown slug is “draft unavailable” at HTTP 200 rather than a 404 — and on the Next.js factory it also cost every page its title, description and social card, because an editor render emits noindex and nothing else.
Putting it together (SvelteKit pseudo-code)
Astro.locals.avocado.getDraftPage().)
4. Register the site
This step is framework-independent. From your project directory, run the same registration CLI that the Next.js path uses:DRAFT_MODE_SECRET, ORCHESTRATOR_URL, NEXT_PUBLIC_DEFAULT_SITE_ID, NEXT_PUBLIC_SITE_NAME, NEXT_PUBLIC_EDITOR_ORIGIN to your .env.local. The NEXT_PUBLIC_* variable names are vestigial from the Next.js convention — your framework will read them just fine, or you can rename them on the way in (the SDK doesn’t actually require those specific names; only DRAFT_MODE_SECRET is non-negotiable).
After it succeeds, the site appears in the editor’s dashboard the next time you open or refresh http://localhost:4100.
5. Verify the contract
Samecurl checks as the Next.js walkthrough — the contract is identical regardless of which framework implements it. See the Verify the contract step for the five curl commands that should all pass against your routes.
What you don’t get on the non-Next.js path
The Next.jscreateSitePage helper does several things automatically that you’ll have to do by hand:
None of these are blockers — they’re “you have to write the integration glue, but the building blocks exist.” The SDK source under
packages/site-sdk/src/create-site-page.tsx is the reference implementation; on a non-Next.js framework, you’re translating it into your framework’s idioms.
Compared to the alternatives
If this looks like a lot of work, here are your other options:- Wrap your app in a thin Next.js shell that proxies to your existing backend. Use the standard Next.js Integration. Most people who try the non-Next.js path end up here anyway, and it is the only fully supported shape.
- Hand it to your own coding agent. The coding agent workflow puts Claude Code, Codex or Cursor to work inside your repository, where it already knows your render path and your data layer. The prompt template still assumes Next.js, but an agent that can read this page can usually adapt it; results vary.
- Ask for an official adapter for your framework. Astro got one that way. If we hear the same request for Remix, SvelteKit or Nuxt often enough, those become candidates for first-class support and stop being “first mover” territory.
When something doesn’t fit
The/core exports are real and tested (the Next.js shims in the SDK use them as their own implementation), but the patterns on this page are illustrative — they haven’t been verified against every framework’s quirks. If you hit something:
- Get in touch with the framework, the version, and the exact symptom. A concrete report on a framework nobody has wired yet is the fastest way to get it onto the tested list.
- The Next.js adapter at
packages/site-sdk/src/draft-routes.tsis 30 lines and is the reference for what a framework adapter looks like. If you write one for your stack, we would like to see it — a working adapter is what moves a framework out of “first mover” territory.