Skip to main content
The default integration path uses 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

These three handlers are the easy half — they just work as-is on any web-standard server.

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 publishSecret under NODE_ENV=production is a 401, whose reason names PUBLISH_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 as x-publish-token.
  • A publish that would remove every page is a 409 unless the body carries allowDelete: true. An empty pages array is almost never somebody deleting their site; it is a client publishing what it thinks it has after its own state failed to load. Pass getPages so the refusal can say what it protected and so maxPagesRemoved — 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.
The rule itself is exported as 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:
  1. 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 calls enableDraftMode() on your adapter; you decide what that means.
  2. 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.
  3. Build the redirect response — same idea. The SDK gives you the destination URL and the cookies; you return a framework-appropriate Response.
The interface you implement is small:

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/draft route validates the secret and sets it
  • The /api/editor/draft/disable route clears it
  • Your page render code reads it and switches between published and draft data sources
The enableDraftMode / disableDraftMode adapter callbacks are usually no-ops on non-Next.js frameworks — the createRedirect callback does the real work by attaching the cookie.
Open-redirect protection is enforced inside the SDK, not in your adapter. createDraftEnableHandlerCore validates the ?secret= against process.env.DRAFT_MODE_SECRET and resolves the ?redirect= against an internal-paths-only allowlist before it calls adapter.createRedirect. Don’t second-guess the URL it gives you — just turn it into a framework-appropriate response. If you bypass the core handler and roll your own validation, you must reject anything that isn’t an internal path starting with /. See the Next.js Integration warning for the full reasoning.

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/pages endpoint instead, and shows the editor overlay.
The SDK gives you two helpers for this.

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():
That’s the whole API — no framework coupling. Both functions accept an optional { 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:
The function returns either 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)

The Remix / Nuxt / Hono versions look almost identical — same three calls, same fallback chain. (On Astro the integration does this for you, through 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:
The CLI doesn’t care which framework you’re on. It writes 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

Same curl 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.js createSitePage 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:
  1. 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.
  2. 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.
  3. 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.ts is 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.