静态渲染

PreviousNext

一个支持 RSC/SSR 的最小化、记忆化、只读版本的 Plate。

<PlateStatic> 是一个快速、只读的 React 组件,用于渲染 Plate 内容,针对服务端React Server Component(RSC)环境进行了优化。它避免了客户端编辑逻辑,并对节点渲染进行记忆化处理,相比在只读模式下使用 <Plate> 具有更好的性能。

它是 serializeHtml 用于 HTML 导出的核心部分,非常适合任何需要非交互式、展示性 Plate 内容视图的服务端或 RSC 场景。

主要优势

  • 服务端安全: 无浏览器 API 依赖;可在 SSR/RSC 中运行。
  • 无 Plate 编辑器开销: 排除了选区或事件处理器等交互功能。
  • 记忆化渲染: 使用 _memo 和结构检查,仅重新渲染已更改的节点。
  • 部分重渲染: 文档某一部分的更改不会强制全部重新渲染。
  • 轻量级: 由于省略了交互式编辑器代码,包体积更小。

何时使用 <PlateStatic>

  • 使用 HTML 序列化 生成 HTML。
  • 在 Next.js 中显示服务端渲染的预览(特别是使用 RSC 时)。
  • 构建包含只读 Plate 内容的静态站点。
  • 优化性能关键的只读视图。
  • 渲染 AI 流式内容。

Kit 用法

安装

启用静态渲染最快的方式是使用 BaseEditorKit,它包含预配置的基础插件,可与服务端渲染无缝配合。

import { BaseAlignKit } from './plugins/align-base-kit';
import { BaseBasicBlocksKit } from './plugins/basic-blocks-base-kit';
import { BaseBasicMarksKit } from './plugins/basic-marks-base-kit';
import { BaseCalloutKit } from './plugins/callout-base-kit';
import { BaseCodeBlockKit } from './plugins/code-block-base-kit';
import { BaseColumnKit } from './plugins/column-base-kit';
import { BaseCommentKit } from './plugins/comment-base-kit';
import { BaseDateKit } from './plugins/date-base-kit';
import { BaseFootnoteKit } from './plugins/footnote-base-kit';
import { BaseFontKit } from './plugins/font-base-kit';
import { BaseLineHeightKit } from './plugins/line-height-base-kit';
import { BaseLinkKit } from './plugins/link-base-kit';
import { BaseListKit } from './plugins/list-base-kit';
import { MarkdownKit } from './plugins/markdown-kit';
import { BaseMathKit } from './plugins/math-base-kit';
import { BaseMediaKit } from './plugins/media-base-kit';
import { BaseMentionKit } from './plugins/mention-base-kit';
import { BaseSuggestionKit } from './plugins/suggestion-base-kit';
import { BaseTableKit } from './plugins/table-base-kit';
import { BaseTocKit } from './plugins/toc-base-kit';
import { BaseToggleKit } from './plugins/toggle-base-kit';
 
export const BaseEditorKit = [
  ...BaseBasicBlocksKit,
  ...BaseCodeBlockKit,
  ...BaseTableKit,
  ...BaseToggleKit,
  ...BaseTocKit,
  ...BaseMediaKit,
  ...BaseCalloutKit,
  ...BaseColumnKit,
  ...BaseMathKit,
  ...BaseDateKit,
  ...BaseLinkKit,
  ...BaseMentionKit,
  ...BaseBasicMarksKit,
  ...BaseFontKit,
  ...BaseListKit,
  ...BaseAlignKit,
  ...BaseLineHeightKit,
  ...BaseCommentKit,
  ...BaseSuggestionKit,
  ...MarkdownKit,
  ...BaseFootnoteKit,
];
import { BaseAlignKit } from './plugins/align-base-kit';
import { BaseBasicBlocksKit } from './plugins/basic-blocks-base-kit';
import { BaseBasicMarksKit } from './plugins/basic-marks-base-kit';
import { BaseCalloutKit } from './plugins/callout-base-kit';
import { BaseCodeBlockKit } from './plugins/code-block-base-kit';
import { BaseColumnKit } from './plugins/column-base-kit';
import { BaseCommentKit } from './plugins/comment-base-kit';
import { BaseDateKit } from './plugins/date-base-kit';
import { BaseFootnoteKit } from './plugins/footnote-base-kit';
import { BaseFontKit } from './plugins/font-base-kit';
import { BaseLineHeightKit } from './plugins/line-height-base-kit';
import { BaseLinkKit } from './plugins/link-base-kit';
import { BaseListKit } from './plugins/list-base-kit';
import { MarkdownKit } from './plugins/markdown-kit';
import { BaseMathKit } from './plugins/math-base-kit';
import { BaseMediaKit } from './plugins/media-base-kit';
import { BaseMentionKit } from './plugins/mention-base-kit';
import { BaseSuggestionKit } from './plugins/suggestion-base-kit';
import { BaseTableKit } from './plugins/table-base-kit';
import { BaseTocKit } from './plugins/toc-base-kit';
import { BaseToggleKit } from './plugins/toggle-base-kit';
 
export const BaseEditorKit = [
  ...BaseBasicBlocksKit,
  ...BaseCodeBlockKit,
  ...BaseTableKit,
  ...BaseToggleKit,
  ...BaseTocKit,
  ...BaseMediaKit,
  ...BaseCalloutKit,
  ...BaseColumnKit,
  ...BaseMathKit,
  ...BaseDateKit,
  ...BaseLinkKit,
  ...BaseMentionKit,
  ...BaseBasicMarksKit,
  ...BaseFontKit,
  ...BaseListKit,
  ...BaseAlignKit,
  ...BaseLineHeightKit,
  ...BaseCommentKit,
  ...BaseSuggestionKit,
  ...MarkdownKit,
  ...BaseFootnoteKit,
];

添加 Kit

import { createSlateEditor } from 'platejs';
import { PlateStatic } from 'platejs/static';
import { BaseEditorKit } from '@/components/editor/editor-base-kit';
 
const editor = createSlateEditor({
  plugins: BaseEditorKit,
  value: [
    { type: 'h1', children: [{ text: 'Server-Rendered Title' }] },
    { type: 'p', children: [{ text: 'This content is rendered statically.' }] },
  ],
});
 
// Render statically
export default function MyStaticPage() {
  return <PlateStatic editor={editor} />;
}
import { createSlateEditor } from 'platejs';
import { PlateStatic } from 'platejs/static';
import { BaseEditorKit } from '@/components/editor/editor-base-kit';
 
const editor = createSlateEditor({
  plugins: BaseEditorKit,
  value: [
    { type: 'h1', children: [{ text: 'Server-Rendered Title' }] },
    { type: 'p', children: [{ text: 'This content is rendered statically.' }] },
  ],
});
 
// Render statically
export default function MyStaticPage() {
  return <PlateStatic editor={editor} />;
}

示例

查看完整的服务端静态渲染示例:

import * as React from 'react';
 
import { cva } from 'class-variance-authority';
import fs from 'node:fs/promises';
import path from 'node:path';
import { type Value, normalizeStaticValue } from 'platejs';
import { createStaticEditor, serializeHtml } from 'platejs/static';
 
import { BaseEditorKit } from '@/components/editor/editor-base-kit';
import {
  EditorClient,
  EditorViewClient,
  ExportHtmlButton,
  HtmlIframe,
} from '@/components/editor/slate-to-html';
import { alignValue } from '@/registry/examples/values/align-value';
import { basicBlocksValue } from '@/registry/examples/values/basic-blocks-value';
import { basicMarksValue } from '@/registry/examples/values/basic-marks-value';
import { columnValue } from '@/registry/examples/values/column-value';
import { dateValue } from '@/registry/examples/values/date-value';
import { discussionValue } from '@/registry/examples/values/discussion-value';
import { equationValue } from '@/registry/examples/values/equation-value';
import { fontValue } from '@/registry/examples/values/font-value';
import { indentValue } from '@/registry/examples/values/indent-value';
import { lineHeightValue } from '@/registry/examples/values/line-height-value';
import { linkValue } from '@/registry/examples/values/link-value';
import { listValue } from '@/registry/examples/values/list-value';
import { mediaValue } from '@/registry/examples/values/media-value';
import { mentionValue } from '@/registry/examples/values/mention-value';
import { tableValue } from '@/registry/examples/values/table-value';
import { tocPlaygroundValue } from '@/registry/examples/values/toc-value';
import { createHtmlDocument } from '@/lib/create-html-document';
import { EditorStatic } from '@/components/ui/editor-static';
 
const getCachedTailwindCss = React.cache(async () => {
  const cssPath = path.join(process.cwd(), 'public', 'tailwind.css');
 
  return await fs.readFile(cssPath, 'utf8');
});
 
export default async function SlateToHtmlBlock() {
  const createValue = (): Value =>
    normalizeStaticValue([
      ...basicBlocksValue,
      ...basicMarksValue,
      ...tocPlaygroundValue,
      ...linkValue,
      ...tableValue,
      ...equationValue,
      ...columnValue,
      ...mentionValue,
      ...dateValue,
      ...fontValue,
      ...discussionValue,
      ...alignValue,
      ...lineHeightValue,
      ...indentValue,
      ...listValue,
      ...mediaValue,
    ]);
 
  const editor = createStaticEditor({
    plugins: BaseEditorKit,
    value: createValue(),
  });
 
  const tailwindCss = await getCachedTailwindCss();
  const katexCDN = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.css" integrity="sha384-9PvLvaiSKCPkFKB1ZsEoTjgnJn+O3KvEwtsz37/XrkYft3DTk2gHdYvd9oWgW3tV" crossorigin="anonymous">`;
 
  // const cookieStore = await cookies();
  // const theme = cookieStore.get('theme')?.value;
  const theme = 'light';
 
  // Get the editor content HTML using EditorStatic
  const editorHtml = await serializeHtml(editor, {
    editorComponent: EditorStatic,
    props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } },
  });
 
  // Create the full HTML document
  const html = createHtmlDocument({
    editorHtml,
    katexCDN,
    tailwindCss,
    theme,
  });
 
  return (
    <div className="grid grid-cols-3 px-4">
      <div className="p-2">
        <h3 className={headingVariants()}>Editor</h3>
        <EditorClient value={createValue()} />
      </div>
 
      <div className="p-2">
        <h3 className={headingVariants()}>EditorView</h3>
        <EditorViewClient value={createValue()} />
      </div>
 
      <div className="relative p-2">
        <h3 className={headingVariants()}>HTML Iframe</h3>
        <ExportHtmlButton
          className="absolute top-10 right-0"
          html={html}
          serverTheme={theme}
        />
        <HtmlIframe
          className="h-[7500px] w-full"
          html={html}
          serverTheme={theme}
        />
      </div>
    </div>
  );
}
 
const headingVariants = cva(
  'group mt-8 scroll-m-20 font-heading font-semibold text-xl tracking-tight'
);
import * as React from 'react';
 
import { cva } from 'class-variance-authority';
import fs from 'node:fs/promises';
import path from 'node:path';
import { type Value, normalizeStaticValue } from 'platejs';
import { createStaticEditor, serializeHtml } from 'platejs/static';
 
import { BaseEditorKit } from '@/components/editor/editor-base-kit';
import {
  EditorClient,
  EditorViewClient,
  ExportHtmlButton,
  HtmlIframe,
} from '@/components/editor/slate-to-html';
import { alignValue } from '@/registry/examples/values/align-value';
import { basicBlocksValue } from '@/registry/examples/values/basic-blocks-value';
import { basicMarksValue } from '@/registry/examples/values/basic-marks-value';
import { columnValue } from '@/registry/examples/values/column-value';
import { dateValue } from '@/registry/examples/values/date-value';
import { discussionValue } from '@/registry/examples/values/discussion-value';
import { equationValue } from '@/registry/examples/values/equation-value';
import { fontValue } from '@/registry/examples/values/font-value';
import { indentValue } from '@/registry/examples/values/indent-value';
import { lineHeightValue } from '@/registry/examples/values/line-height-value';
import { linkValue } from '@/registry/examples/values/link-value';
import { listValue } from '@/registry/examples/values/list-value';
import { mediaValue } from '@/registry/examples/values/media-value';
import { mentionValue } from '@/registry/examples/values/mention-value';
import { tableValue } from '@/registry/examples/values/table-value';
import { tocPlaygroundValue } from '@/registry/examples/values/toc-value';
import { createHtmlDocument } from '@/lib/create-html-document';
import { EditorStatic } from '@/components/ui/editor-static';
 
const getCachedTailwindCss = React.cache(async () => {
  const cssPath = path.join(process.cwd(), 'public', 'tailwind.css');
 
  return await fs.readFile(cssPath, 'utf8');
});
 
export default async function SlateToHtmlBlock() {
  const createValue = (): Value =>
    normalizeStaticValue([
      ...basicBlocksValue,
      ...basicMarksValue,
      ...tocPlaygroundValue,
      ...linkValue,
      ...tableValue,
      ...equationValue,
      ...columnValue,
      ...mentionValue,
      ...dateValue,
      ...fontValue,
      ...discussionValue,
      ...alignValue,
      ...lineHeightValue,
      ...indentValue,
      ...listValue,
      ...mediaValue,
    ]);
 
  const editor = createStaticEditor({
    plugins: BaseEditorKit,
    value: createValue(),
  });
 
  const tailwindCss = await getCachedTailwindCss();
  const katexCDN = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.css" integrity="sha384-9PvLvaiSKCPkFKB1ZsEoTjgnJn+O3KvEwtsz37/XrkYft3DTk2gHdYvd9oWgW3tV" crossorigin="anonymous">`;
 
  // const cookieStore = await cookies();
  // const theme = cookieStore.get('theme')?.value;
  const theme = 'light';
 
  // Get the editor content HTML using EditorStatic
  const editorHtml = await serializeHtml(editor, {
    editorComponent: EditorStatic,
    props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } },
  });
 
  // Create the full HTML document
  const html = createHtmlDocument({
    editorHtml,
    katexCDN,
    tailwindCss,
    theme,
  });
 
  return (
    <div className="grid grid-cols-3 px-4">
      <div className="p-2">
        <h3 className={headingVariants()}>Editor</h3>
        <EditorClient value={createValue()} />
      </div>
 
      <div className="p-2">
        <h3 className={headingVariants()}>EditorView</h3>
        <EditorViewClient value={createValue()} />
      </div>
 
      <div className="relative p-2">
        <h3 className={headingVariants()}>HTML Iframe</h3>
        <ExportHtmlButton
          className="absolute top-10 right-0"
          html={html}
          serverTheme={theme}
        />
        <HtmlIframe
          className="h-[7500px] w-full"
          html={html}
          serverTheme={theme}
        />
      </div>
    </div>
  );
}
 
const headingVariants = cva(
  'group mt-8 scroll-m-20 font-heading font-semibold text-xl tracking-tight'
);

手动用法

创建 Slate 编辑器

使用 createSlateEditor 初始化 Slate 编辑器实例,配置所需的插件和组件。这类似于为交互式 <Plate> 组件使用 usePlateEditor

lib/plate-static-editor.ts
import { createSlateEditor } from 'platejs';
// Import your desired base plugins (e.g., BaseHeadingPlugin, MarkdownPlugin)
// Ensure you are NOT importing from /react subpaths for server environments.
 
const editor = createSlateEditor({
  plugins: [
    // Add your list of base plugins here
    // Example: BaseHeadingPlugin, MarkdownPlugin.configure({...})
  ],
  value: [ // Example initial value
    {
      type: 'p',
      children: [{ text: 'Hello from a static Plate editor!' }],
    },
  ],
});
lib/plate-static-editor.ts
import { createSlateEditor } from 'platejs';
// Import your desired base plugins (e.g., BaseHeadingPlugin, MarkdownPlugin)
// Ensure you are NOT importing from /react subpaths for server environments.
 
const editor = createSlateEditor({
  plugins: [
    // Add your list of base plugins here
    // Example: BaseHeadingPlugin, MarkdownPlugin.configure({...})
  ],
  value: [ // Example initial value
    {
      type: 'p',
      children: [{ text: 'Hello from a static Plate editor!' }],
    },
  ],
});

定义静态节点组件

如果您的交互式编辑器使用客户端组件(例如带有 use client 或事件处理器的组件),您必须创建静态的、服务端安全的等效组件。这些组件应该渲染纯 HTML,不包含浏览器特定的逻辑。

components/ui/paragraph-node-static.tsx
import React from 'react';
import type { SlateElementProps } from 'platejs/static';
 
export function ParagraphElementStatic(props: SlateElementProps) {
  return (
    <SlateElement {...props}>
      {props.children}
    </SlateElement>
  );
}
components/ui/paragraph-node-static.tsx
import React from 'react';
import type { SlateElementProps } from 'platejs/static';
 
export function ParagraphElementStatic(props: SlateElementProps) {
  return (
    <SlateElement {...props}>
      {props.children}
    </SlateElement>
  );
}

为标题、图片、链接等创建类似的静态组件。

将插件键映射到静态组件

创建一个将插件键或节点类型映射到相应静态 React 组件的对象,然后将其传递给编辑器。

components/static-components.ts
import { ParagraphElementStatic } from './ui/paragraph-node-static';
import { HeadingElementStatic } from './ui/heading-node-static';
// ... import other static components
 
export const staticComponents = {
  p: ParagraphElementStatic,
  h1: HeadingElementStatic,
  // ... add mappings for all your element and leaf types
};
components/static-components.ts
import { ParagraphElementStatic } from './ui/paragraph-node-static';
import { HeadingElementStatic } from './ui/heading-node-static';
// ... import other static components
 
export const staticComponents = {
  p: ParagraphElementStatic,
  h1: HeadingElementStatic,
  // ... add mappings for all your element and leaf types
};

渲染 <PlateStatic>

使用 <PlateStatic> 组件,提供配置了组件的 editor 实例。

app/my-static-page/page.tsx (RSC Example)
import { createSlateEditor } from 'platejs';
import { PlateStatic } from 'platejs/static';
// import { BaseHeadingPlugin, ... } from '@platejs/basic-nodes'; // etc.
import { staticComponents } from '@/components/static-components';
 
export default async function MyStaticPage() {
  // Example: Fetch or define editor value
  const initialValue = [
    { type: 'h1', children: [{ text: 'Server-Rendered Title' }] },
    { type: 'p', children: [{ text: 'Content rendered statically.' }] },
  ];
 
  const editor = createSlateEditor({
    plugins: [/* your base plugins */],
    components: staticComponents,
    value: initialValue,
  });
 
  return (
    <PlateStatic
      editor={editor}
      style={{ padding: 16 }}
      className="my-plate-static-content"
    />
  );
}
app/my-static-page/page.tsx (RSC Example)
import { createSlateEditor } from 'platejs';
import { PlateStatic } from 'platejs/static';
// import { BaseHeadingPlugin, ... } from '@platejs/basic-nodes'; // etc.
import { staticComponents } from '@/components/static-components';
 
export default async function MyStaticPage() {
  // Example: Fetch or define editor value
  const initialValue = [
    { type: 'h1', children: [{ text: 'Server-Rendered Title' }] },
    { type: 'p', children: [{ text: 'Content rendered statically.' }] },
  ];
 
  const editor = createSlateEditor({
    plugins: [/* your base plugins */],
    components: staticComponents,
    value: initialValue,
  });
 
  return (
    <PlateStatic
      editor={editor}
      style={{ padding: 16 }}
      className="my-plate-static-content"
    />
  );
}

记忆化详情

<PlateStatic> 通过记忆化提升性能:

  • 每个 <ElementStatic><LeafStatic> 都被 React.memo 包裹。
  • 引用相等性: 未更改的节点引用可防止重新渲染。
  • _memo 字段: 在元素或叶子上设置 node._memo = true(或任何稳定值)可以强制 Plate 跳过重新渲染该特定节点,即使其内容发生变化。这对于细粒度控制更新非常有用。

客户端替代方案:PlateView

对于需要最小交互性的静态内容场景,请使用 <PlateView>。该组件包裹了 <PlateStatic> 并添加了客户端事件处理器以支持用户交互,同时保持静态渲染的性能优势。

示例:包含两种静态视图的服务器组件

app/document/page.tsx
import { createStaticEditor, PlateStatic } from 'platejs/static';
import { BaseEditorKit } from '@/components/editor/editor-base-kit';
import { InteractiveViewer } from './interactive-viewer';
 
export default async function DocumentPage() {
  const content = await fetchDocument(); // Your document data
 
  // Server-side static editor
  const editor = createStaticEditor({
    plugins: BaseEditorKit,
    value: content,
  });
 
  return (
    <div className="grid grid-cols-2 gap-4">
      {/* Pure static rendering - no interactivity */}
      <div>
        <h2>Static View (Server Rendered)</h2>
        <PlateStatic editor={editor} />
      </div>
 
      {/* Interactive view - rendered on client */}
      <div>
        <h2>Interactive View</h2>
        <InteractiveViewer value={content} />
      </div>
    </div>
  );
}
app/document/page.tsx
import { createStaticEditor, PlateStatic } from 'platejs/static';
import { BaseEditorKit } from '@/components/editor/editor-base-kit';
import { InteractiveViewer } from './interactive-viewer';
 
export default async function DocumentPage() {
  const content = await fetchDocument(); // Your document data
 
  // Server-side static editor
  const editor = createStaticEditor({
    plugins: BaseEditorKit,
    value: content,
  });
 
  return (
    <div className="grid grid-cols-2 gap-4">
      {/* Pure static rendering - no interactivity */}
      <div>
        <h2>Static View (Server Rendered)</h2>
        <PlateStatic editor={editor} />
      </div>
 
      {/* Interactive view - rendered on client */}
      <div>
        <h2>Interactive View</h2>
        <InteractiveViewer value={content} />
      </div>
    </div>
  );
}

示例:使用 PlateView 的客户端组件

app/document/interactive-viewer.tsx
'use client';
 
import { usePlateViewEditor } from 'platejs/react';
import { PlateView } from 'platejs/react';
import { BaseEditorKit } from '@/components/editor/editor-base-kit';
 
export function InteractiveViewer({ value }) {
  const editor = usePlateViewEditor({
    plugins: BaseEditorKit,
    value,
  });
 
  return <PlateView editor={editor} />;
}
app/document/interactive-viewer.tsx
'use client';
 
import { usePlateViewEditor } from 'platejs/react';
import { PlateView } from 'platejs/react';
import { BaseEditorKit } from '@/components/editor/editor-base-kit';
 
export function InteractiveViewer({ value }) {
  const editor = usePlateViewEditor({
    plugins: BaseEditorKit,
    value,
  });
 
  return <PlateView editor={editor} />;
}

PlateView 的主要特性

  • 仅客户端:需要 'use client' 指令
  • 添加交互性:启用用户与内容的交互(例如文本选择、复制,以及未来的工具提示、高亮等交互功能)
  • 最小开销:内部仍使用 PlateStatic 进行渲染
  • usePlateViewEditor 配合使用:创建为仅查看 React 组件优化的静态编辑器
  • 包含 ViewPlugin:静态编辑器自动包含提供事件处理能力的 ViewPlugin

PlateStatic vs. PlateView vs. Plate + readOnly

方面<PlateStatic><PlateView><Plate> + readOnly
运行环境服务端/客户端(SSR/RSC 安全)仅客户端仅客户端
交互性最小(选择、复制、工具栏等)完整交互功能(仅浏览器)
浏览器 API不使用最小(事件处理器)完全使用
性能最佳 - 仅静态 HTML良好 - 静态渲染 + 事件委托较重 - 完整编辑器内部逻辑
包体积最小最大
使用场景服务端渲染、HTML 导出需要基本交互的客户端内容需要所有功能的完整只读编辑器
建议无任何交互的 SSR/RSC需要轻量交互的客户端内容有复杂交互需求的客户端

RSC/SSR 示例

在 Next.js App Router(或类似的 RSC 环境)中,<PlateStatic> 可以直接在服务器组件中使用:

app/preview/page.tsx (RSC)
import { createSlateEditor } from 'platejs';
import { PlateStatic } from 'platejs/static';
// Example base plugins (ensure non-/react imports)
// import { BaseHeadingPlugin } from '@platejs/basic-nodes';
import { staticComponents } from '@/components/static-components'; // Your static components mapping
 
export default async function Page() {
  // Fetch or define content server-side
  const serverContent = [
    { type: 'h1', children: [{ text: 'Rendered on the Server! 🎉' }] },
    { type: 'p', children: [{ text: 'This content is static and server-rendered.' }] },
  ];
 
  const editor = createSlateEditor({
    // plugins: [BaseHeadingPlugin, /* ...other base plugins */],
    plugins: [], // Add your base plugins
    components: staticComponents,
    value: serverContent,
  });
 
  return (
    <PlateStatic
      editor={editor}
      className="my-static-preview-container"
    />
  );
}
app/preview/page.tsx (RSC)
import { createSlateEditor } from 'platejs';
import { PlateStatic } from 'platejs/static';
// Example base plugins (ensure non-/react imports)
// import { BaseHeadingPlugin } from '@platejs/basic-nodes';
import { staticComponents } from '@/components/static-components'; // Your static components mapping
 
export default async function Page() {
  // Fetch or define content server-side
  const serverContent = [
    { type: 'h1', children: [{ text: 'Rendered on the Server! 🎉' }] },
    { type: 'p', children: [{ text: 'This content is static and server-rendered.' }] },
  ];
 
  const editor = createSlateEditor({
    // plugins: [BaseHeadingPlugin, /* ...other base plugins */],
    plugins: [], // Add your base plugins
    components: staticComponents,
    value: serverContent,
  });
 
  return (
    <PlateStatic
      editor={editor}
      className="my-static-preview-container"
    />
  );
}

这会在服务器上将内容渲染为 HTML,PlateStatic 本身不需要客户端 JavaScript 包。

serializeHtml 配合使用

要生成完整的 HTML 字符串(例如用于电子邮件、PDF 或外部系统),请使用 serializeHtml。它内部使用了 <PlateStatic>

lib/html-serializer.ts
import { createSlateEditor } from 'platejs';
import { serializeHtml } from 'platejs/static';
import { staticComponents } from '@/components/static-components';
// import { BaseHeadingPlugin, ... } from '@platejs/basic-nodes';
 
async function getDocumentAsHtml(value: any[]) {
  const editor = createSlateEditor({
    plugins: [/* ...your base plugins... */],
    components: staticComponents,
    value,
  });
 
  const html = await serializeHtml(editor, {
    // editorComponent: PlateStatic, // Optional: Defaults to PlateStatic
    props: { className: 'prose max-w-none' }, // Example: Pass props to the root div
  });
 
  return html;
}
 
// Example Usage:
// const mySlateValue = [ { type: 'h1', children: [{ text: 'My Document' }] } ];
// getDocumentAsHtml(mySlateValue).then(console.log);
lib/html-serializer.ts
import { createSlateEditor } from 'platejs';
import { serializeHtml } from 'platejs/static';
import { staticComponents } from '@/components/static-components';
// import { BaseHeadingPlugin, ... } from '@platejs/basic-nodes';
 
async function getDocumentAsHtml(value: any[]) {
  const editor = createSlateEditor({
    plugins: [/* ...your base plugins... */],
    components: staticComponents,
    value,
  });
 
  const html = await serializeHtml(editor, {
    // editorComponent: PlateStatic, // Optional: Defaults to PlateStatic
    props: { className: 'prose max-w-none' }, // Example: Pass props to the root div
  });
 
  return html;
}
 
// Example Usage:
// const mySlateValue = [ { type: 'h1', children: [{ text: 'My Document' }] } ];
// getDocumentAsHtml(mySlateValue).then(console.log);

更多详情请参阅 HTML 序列化指南

API 参考

<PlateStatic> Props

import type React from 'react';
import type { Descendant } from 'slate';
import type { PlateEditor } from 'platejs/core'; // Adjust imports as per your setup
 
interface PlateStaticProps extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * The Plate editor instance, created via `createSlateEditor`.
   * Must include plugins and components relevant to the content being rendered.
   */
  editor: PlateEditor;
 
  /**
   * Optional Plate `Value` (array of `Descendant` nodes).
   * If provided, this will be used for rendering instead of `editor.children`.
   */
  value?: Descendant[];
 
  /** Inline CSS styles for the root `div` element. */
  style?: React.CSSProperties;
 
  // Other HTMLDivElement attributes like `className`, `id`, etc., are also supported.
}
import type React from 'react';
import type { Descendant } from 'slate';
import type { PlateEditor } from 'platejs/core'; // Adjust imports as per your setup
 
interface PlateStaticProps extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * The Plate editor instance, created via `createSlateEditor`.
   * Must include plugins and components relevant to the content being rendered.
   */
  editor: PlateEditor;
 
  /**
   * Optional Plate `Value` (array of `Descendant` nodes).
   * If provided, this will be used for rendering instead of `editor.children`.
   */
  value?: Descendant[];
 
  /** Inline CSS styles for the root `div` element. */
  style?: React.CSSProperties;
 
  // Other HTMLDivElement attributes like `className`, `id`, etc., are also supported.
}
  • editor:使用 createSlateEditor 创建的 PlateEditor 实例,包含组件配置。
  • value:可选。如果提供,将渲染此 Descendant 节点数组,覆盖 editor.children 中的当前内容。

后续步骤