Focus:CMS

Migrate an Astro site

Astro is islands-friendly by design, which fits Focus:CMS nicely. Each editable surface becomes a React island; the rest of the page stays as plain HTML.

1. Add React + Focus:CMS

npx astro add react
npm install focus-cms @supabase/supabase-js zustand fast-json-patch

2. Mount the provider once

src/components/FocusShell.tsx:

import { FocusCMSProvider, supabaseAdapter } from "focus-cms";
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  import.meta.env.PUBLIC_SUPABASE_URL,
  import.meta.env.PUBLIC_SUPABASE_ANON_KEY,
);

export default function FocusShell({ children, contentKey }) {
  return (
    <FocusCMSProvider
      adapter={supabaseAdapter({
        client: supabase,
        siteSlug: "your-site",
        key: contentKey,
      })}
      isEditor={() => /* your check */}
      requireEditQuery={false}
    >
      {children}
    </FocusCMSProvider>
  );
}

src/layouts/Base.astro:

---
import FocusShell from "../components/FocusShell.tsx";

const contentKey = Astro.url.pathname === "/" ? "home/index"
  : `pages/${Astro.url.pathname.replace(/^\//, "").split("/")[0]}`;
---

<html>
  <body>
    <FocusShell contentKey={contentKey} client:load>
      <slot />
    </FocusShell>
  </body>
</html>

3. Editable fields

---
import { RichEditableText, EditableImage } from "focus-cms";
const { hero } = Astro.props;
---

<section>
  <RichEditableText path="heading" as="h1" client:visible>
    {hero.heading}
  </RichEditableText>
  <EditableImage path="image" src={hero.imageUrl} alt="" client:visible />
</section>

client:visible hydrates the editor primitives lazily. client:load for the provider so it's available as soon as the page loads.

4. Raw HTML pages

For Squarespace-imported pages: mount <EditableRawHtml> as a React island inside an Astro layout. Same pattern as the HTML recipe, just hosted from Astro.

5. Toolbar + cross-key edits

Copy components/editor-toolbar.tsx and lib/cross-key-store.ts from the reference theme. Mount the toolbar once in the Base layout, hydrated with client:load.

Why Astro?

If your site is mostly content (a blog, a marketing site, a docs site) and you want the smallest possible JS payload, Astro is a great fit. Focus:CMS becomes the only JS-driven surface; the rest of the page stays zero-JS by default. Display-mode Lighthouse scores stay where Astro's defaults put them.

For dynamic apps (dashboards, calculators, multi-step flows), Next.js is the better stack — see the Next.js recipe.