SVeditor Docs
Integrations

React / Next.js integration

Mount SVeditor in React and Next.js App Router with stable options, auto-save, and SSR-safe client boundaries.

React / Next.js integration

Three rules: create on the client only, keep a stable options reference, call destroy() on unmount.

Prerequisites: ESM package installed. Examples use Next.js App Router; other React frameworks can skip routing notes.

SSR and the ESM bundle

The ESM build may touch browser globals at import time.

  1. Import @wztlink1013/sveditor/style.css from a client module.
  2. Load @wztlink1013/sveditor only in the browser — next/dynamic with ssr: false or dynamic import() in useEffect.

Details: ESM integration — Next.js.

Reusable mount component

Copy this pattern into your app (for example components/sveditor-mount.tsx):

"use client";

import { useEffect, useRef } from "react";
import { SVeditor } from "@wztlink1013/sveditor";
import type { EditorOptions } from "@wztlink1013/sveditor";
import { cn } from "@/lib/utils"; // your className helper
import "@wztlink1013/sveditor/style.css";

export type SveditorMountProps = {
  className?: string;
  options: Omit<EditorOptions, "el">;
};

export function SveditorMount({ className, options }: SveditorMountProps) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;

    const editor = SVeditor.create({ el, ...options });
    return () => editor.destroy();
  }, [options]);

  return (
    <div ref={containerRef} className={cn("min-h-[640px] w-full", className)} />
  );
}

For App Router, export it through next/dynamic(..., { ssr: false }) from your page's client wrapper.

Stabilize options with useMemo

A new options object every render recreates the editor and can lose focus or re-run onFetch.

"use client";

import { useMemo } from "react";
import type { EditorOptions } from "@wztlink1013/sveditor";
import { SveditorMount } from "@/components/sveditor-mount";

type EditorInitOptions = Omit<EditorOptions, "el">;

export function DocEditor({ docId }: { docId: string }) {
  const options = useMemo<EditorInitOptions>(
    () => ({
      extensionsOptions: {
        autoSave: {
          onFetch: async () => {
            const res = await fetch(`/api/docs/${docId}`, { cache: "no-store" });
            const data = await res.json();
            return { content: data.content ?? "" };
          },
          onSaveContent: async (json) => {
            await fetch(`/api/docs/${docId}`, {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({ content: json }),
            });
          },
          debounceMs: 1000,
        },
      },
    }),
    [docId],
  );

  return <SveditorMount options={options} />;
}

Auto-save with metadata: Auto-save guide.

Server Component page

Keep the page as a Server Component; pass props into a client child:

import { DocEditor } from "@/components/doc-editor";

export default async function DocPage({
  params,
}: {
  params: Promise<{ docId: string }>;
}) {
  const { docId } = await params;
  return <DocEditor docId={docId} />;
}

Lazy load (optional)

import dynamic from "next/dynamic";

const DocEditor = dynamic(
  () => import("@/components/doc-editor").then((m) => m.DocEditor),
  { ssr: false, loading: () => <p>Loading editor...</p> },
);

Common patterns

Auto-save + metadata

extensionsOptions: {
  autoSave: {
    onFetch: () => fetchContent(docId),
    onSaveContent: (json) => saveContent(docId, json),
  },
  meta: {
    onFetchMeta: () => fetchMeta(docId),
    onSaveMeta: (meta) => saveMeta(docId, meta),
  },
}

Do not return meta from autoSave.onFetch.

Custom notifications

extensionsOptions: {
  notifications: {
    toast: myToastAdapter,
    builtinToaster: false, // set true only if Sonner is not mounted globally
  },
}

Checklist

PracticeWhy
"use client" on mount componentsSDK is browser-only
useMemo([docId, ...])Rebuild when document id changes
Import style.css once globally or in mount moduleRequired for layout
destroy() in effect cleanupPrevents leaks

Notes

  1. Never call SVeditor.create() in a Server Component.
  2. When docId changes, include it in useMemo dependencies.
  3. See Playground for a live wired example.

Next steps