Focus:CMS

Migrate a React (Vite / CRA) site

Single-page React apps can host Focus:CMS as an in-app overlay. The trade-off vs Next.js: no static export, so SEO depends on whatever SSR layer you already have.

1. Install dependencies

npm install focus-cms @supabase/supabase-js zustand fast-json-patch

2. Mount the provider

// src/App.tsx
import { FocusCMSProvider, supabaseAdapter } from "focus-cms";
import { supabase } from "./lib/supabase";

export default function App() {
  const adapter = supabaseAdapter({
    client: supabase,
    siteSlug: "your-site",
    key: deriveKeyFromUrl(location.pathname),
    editorId: currentUser?.id,
  });

  return (
    <FocusCMSProvider adapter={adapter} isEditor={() => !!currentUser} requireEditQuery={false}>
      <Routes />
    </FocusCMSProvider>
  );
}

function deriveKeyFromUrl(pathname: string): string {
  if (pathname === "/" || !pathname) return "home/index";
  const first = pathname.replace(/^\/+/, "").split("/")[0];
  return `pages/${first}`;
}

3. Wrap fields

import { RichEditableText, EditableImage } from "focus-cms";

<RichEditableText path={`${path}.heading`} as="h1">
  {block.heading}
</RichEditableText>

<EditableImage path={`${path}.imageUrl`} src={block.imageUrl} alt="" />

4. Mount the toolbar

import { EditorToolbar } from "./components/editor-toolbar";

<>
  <Routes />
  <EditorToolbar />
</>

Copy components/editor-toolbar.tsx from the reference theme. It hosts Save / Discard, the Settings panel, the Layers / Components palette, and the Navigation manager.

5. Cross-key edits

Non-page surfaces (nav, footer, site meta) use the cross-key store:

import { useCrossKeyStore } from "./lib/cross-key-store";

const pushPatch = useCrossKeyStore((s) => s.pushPatch);
pushPatch("nav/primary", { op: "add", path: "/items", value: items });

Copy lib/cross-key-store.ts from the reference theme.

6. Note: no EditableRawHtml here

EditableRawHtml depends on a server-rendered HTML stream that the editor hydrates. SPAs don't have that pattern — content comes through JSX, not raw HTML. Use the structured editors (RichEditableText, EditableImage) for SPA content.

If you have legacy HTML pages you want editable, consider migrating those specific pages to a static Next.js shell (HTML recipe) and serving them alongside the SPA on a subdomain or path.