Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Feature Kits
  • Upload Files
  • Plugin
    • Plugin Methods
    • Plugin Shortcuts
    • Plugin Context
    • Plugin Components
    • Plugin Rules
    • Editing Behavior
    • Plugin Input Rules
  • Editor
    • Editor Methods
    • Controlled Value
  • Authored Changes
  • Performance
  • Static Rendering
  • HTML
  • Markdown
  • Form
  • TypeScript
  • Debugging
  • Unit Testing
  • Browser
  • Troubleshooting
  • Locations
  • Transactions
  • Serializing
  • Roots
  • Document Meta
  • Clipboard and Paste
  • Decorations and annotations
  • Schema
  • History
  • Pagination
  • Annotations
  • DOM Coverage
  • External Text Views
  • Virtualized Rendering

Static Rendering

PreviousNext

A minimal, memoized, read-only version of Plate with RSC/SSR support.

<EditorStatic> is a fast, read-only React component for rendering Plate content, optimized for server-side or React Server Component (RSC) environments. It avoids client-side editing logic and memoizes node renders for better performance compared to using <EditorRoot> in read-only mode.

It is the rendering path behind renderStaticHtml and fits server or RSC surfaces that need a non-interactive Plate view.

Key advantages

  • Server-Safe: No browser API dependencies; works in SSR/RSC.
  • No Plate Editor Overhead: Excludes interactive features like selections or event handlers.
  • Memoized Rendering: Uses structural identity to re-render only changed nodes.
  • Partial Re-Renders: Changes in one part of the document don't force a full re-render.
  • Lightweight: Smaller bundle size as it omits interactive editor code.

When to use

PerformanceHTML

On This Page

Key advantagesWhen to use <EditorStatic>Kit usageInstallationAdd kitExampleManual usageCreate a Plate editorDefine static node componentsBind static componentsRender <EditorStatic>Memoization detailsRender a documentClient-side alternative: EditorPreviewExample: server component with both static viewsExample: client component with EditorPreviewKey features of EditorPreviewEditorStatic vs. EditorPreview vs. EditorRoot + readOnlyRSC/SSR examplePairing with renderStaticHtmlAPI Reference<EditorStatic> propsNext steps
Build your editor
Production-ready AI template and reusable components.
Get all-access
<EditorStatic>
  • Generating HTML with HTML Serialization.
  • Displaying server-rendered previews in Next.js (especially with RSC).
  • Building static sites with read-only Plate content.
  • Optimizing performance-critical read-only views.
  • Rendering AI-streaming content.
Interactive vs. Static

For interactive read-only features (like comment popovers or selections), use the standard <EditorRoot> component in the browser. For purely server-rendered, non-interactive content, <EditorStatic> is the recommended choice.

Kit usage

Installation

BaseEditorKit includes base plugins configured for static rendering.

import { BaseUploadPlugin } from 'platejs/upload';
 
import { AlignKit } from './align';
import { BaseBasicBlocksKit } from './basic-blocks-static';
import { BaseBasicMarksKit } from './basic-marks-static';
import { BaseCalloutKit } from './callout-static';
import { BaseCodeBlockKit } from './code-block-static';
import { BaseCodeDrawingKit } from './code-drawing-static';
import { BaseColumnKit } from './column-static';
import { BaseDateKit } from './date-static';
import { BaseDetailsKit } from './details-static';
import { BaseFontKit } from './font-static';
import { BaseFootnoteKit } from './footnote-static';
import { LineHeightKit } from './line-height';
import { BaseLinkKit } from './link-static';
import { BaseListKit } from './list-static';
import { MarkdownKit } from './markdown';
import { BaseMathKit } from './math-static';
import { BaseMediaKit } from './media-static';
import { BaseMentionKit } from './mention-static';
import { BaseTableKit } from './table-static';
import { BaseTocKit } from './toc-static';
 
export const BaseEditorKit = [
  ...BaseBasicBlocksKit,
  ...BaseCodeBlockKit,
  ...BaseCodeDrawingKit,
  ...BaseTableKit,
  ...BaseDetailsKit,
  ...BaseTocKit,
  ...BaseMediaKit,
  BaseUploadPlugin,
  ...BaseCalloutKit,
  ...BaseColumnKit,
  ...BaseMathKit,
  ...BaseDateKit,
  ...BaseLinkKit,
  ...BaseMentionKit,
  ...BaseFootnoteKit,
  ...BaseBasicMarksKit,
  ...BaseFontKit,
  ...BaseListKit,
  ...AlignKit,
  ...LineHeightKit,
  ...MarkdownKit,
] as const;
import { BaseUploadPlugin } from 'platejs/upload';
 
import { AlignKit } from './align';
import { BaseBasicBlocksKit } from './basic-blocks-static';
import { BaseBasicMarksKit } from './basic-marks-static';
import { BaseCalloutKit } from './callout-static';
import { BaseCodeBlockKit } from './code-block-static';
import { BaseCodeDrawingKit } from './code-drawing-static';
import { BaseColumnKit } from './column-static';
import { BaseDateKit } from './date-static';
import { BaseDetailsKit } from './details-static';
import { BaseFontKit } from './font-static'

































Add kit

import { createEditor } from 'platejs';
import { EditorStatic } from 'platejs/static';
import { BaseEditorKit } from '@/components/editor/plugins-static';
 
const editor = createEditor({
  plugins: BaseEditorKit,
  initialValue: [
    { type: 'heading', level: 1, children: [{ text: 'Server-Rendered Title' }] },
    { type: 'paragraph', children: [{ text: 'This content is rendered statically.' }] },
  ],
});
 
// Render statically
export default function MyStaticPage() {
  return <EditorStatic editor={editor} />;

Example

See a complete server-side static rendering example:

import fs from 'node:fs/promises';
import path from 'node:path';
 
import { cva } from 'class-variance-authority';
import type { Metadata } from 'next';
import type { EditorDocumentValue } from 'platejs';
import { createStaticEditor, renderStaticHtml } from 'platejs/static';
import * as React from 'react';
 
import { EditorStatic } from '@/components/editor/editor-static';
import {
  EditorClient,
  EditorViewClient,
  ExportHtmlButton,
  HtmlIframe,
















































































































































Manual usage

Create a Plate editor

Initialize a Plate editor instance using createEditor with component-bound plugins. This is analogous to using useCreateEditor for the interactive <EditorRoot> component.

lib/plate-static-editor.ts
import { createEditor } from 'platejs';
import { BaseBasicBlocksKit } from '@/components/editor/basic-blocks-static';
// Import any other desired base plugins, such as MarkdownPlugin.
// Ensure you are NOT importing from /react subpaths for server environments.
 
const editor = createEditor({
  plugins: [
    ...BaseBasicBlocksKit,
    // Add other base plugins here.
  ],
  initialValue: [
    {
      type: 'paragraph',
      children: [{ text: 'Hello from a static Plate editor!' }],
    },
  ],
});
lib/plate-static-editor.ts
import { createEditor } from 'platejs';
import { BaseBasicBlocksKit } from '@/components/editor/basic-blocks-static';
// Import any other desired base plugins, such as MarkdownPlugin.
// Ensure you are NOT importing from /react subpaths for server environments.
 
const editor = createEditor({
  plugins: [
    ...BaseBasicBlocksKit,
    // Add other base plugins here.
  ],
  initialValue: [
    {
      type: 'paragraph',
      children: [{ text: 'Hello from a static Plate editor!' }],
    },
  ],
});

Define static node components

If your interactive editor uses client-side components (e.g., with use client or event handlers), you must create static, server-safe equivalents. These components should render pure HTML without browser-specific logic.

components/editor/paragraph-static.tsx
import React from 'react';
import type { BaseParagraphPlugin } from 'platejs';
import { EditorElement, type EditorElementProps } from 'platejs/static';
 
export function ParagraphElementStatic(
  props: EditorElementProps<typeof BaseParagraphPlugin>
) {
  return (
    <EditorElement {...props}>
      {props.children}
    </EditorElement>
  );
}

Create similar static components for headings, images, links, etc.

Bind static components

Configure each server-safe component on its owning base plugin.

components/static-editor-kit.ts
import { BaseHeadingPlugin, BaseParagraphPlugin } from 'platejs';
import { ParagraphElementStatic } from './ui/paragraph-static';
import { HeadingElementStatic } from './ui/heading-static';
 
export const StaticEditorKit = [
  BaseParagraphPlugin.configure({ component: ParagraphElementStatic }),
  BaseHeadingPlugin.configure({ component: HeadingElementStatic }),
];
components/static-editor-kit.ts







Render <EditorStatic>

Use <EditorStatic> with an editor whose plugin descriptors own their static components.

app/my-static-page/page.tsx (RSC Example)
import { createEditor } from 'platejs';
import { EditorStatic } from 'platejs/static';
import { StaticEditorKit } from '@/components/static-editor-kit';
 
export default async function MyStaticPage() {
  // Example: Fetch or define editor value
  const initialValue = [
    { type: 'heading', level: 1, children: [{ text: 'Server-Rendered Title' }] },
    { type: 'paragraph', children: [{ text: 'Content rendered statically.' }] },
  ];
 
  const editor = createEditor({
    plugins: StaticEditorKit,
    initialValue,









Memoization details

<EditorStatic> enhances performance through memoization:

  • Static element and leaf rendering uses React.memo.
  • Reference Equality: A block renders again only when it, a block before it, or a decoration inside it changed, so a renderer may read its own block and earlier ones, such as list numbering.
  • Later content: An element whose rendering reads content after it, such as a table of contents, declares render: { readsDocument: true } on its plugin and renders again whenever the document changes.

Render a document

Pass document to render a document through the editor's plugins without editing the editor: no transaction, history or selection change. One configured editor can render a streamed preview or a stored revision this way:

<EditorStatic editor={editor} document={{ children: blocks }} />
<EditorStatic editor={editor} document={{ children: blocks }} />

The document must be valid for the editor's schema. The editor validates a document from editor.read.value() once and any other document object on every render, because that object can change between renders. EditorPreview takes the same document, and copying from it copies the rendered document.

Each render reads the decorations of every top-level block, so rendering again is also how decorations that depend on other state refresh. A component that re-renders for other reasons, such as each streamed chunk, should memoize the element on its document:

const preview = useMemo(
  () => <EditorStatic editor={editor} document={document} />,
  [editor, document]
);
const preview = useMemo(
  () => <EditorStatic editor={editor} document={document} />,
  [editor, document]
);

Everything read through the rendered editor sees the document: editor.read, plugin reads such as editor.plugin(TocPlugin).read.headings(), and plugin APIs. Plugins get that editor from their render, decorate, read or API context. An editor captured when a plugin was created, as in .extend(({ editor }) => …), is the editor you passed and still holds its own value. Reading it from plugin code that reads, decorates or renders the document throws an error that names the fix. React runs component bodies after that code returns, so a component reads through its editor prop.

Client-side alternative: EditorPreview

For cases where you need minimal interactivity with static content, use <EditorPreview>. This component wraps <EditorStatic> and adds client-side event handlers for user interactions while maintaining the performance benefits of static rendering.

Example: server component with both static views

app/document/page.tsx
import { createStaticEditor, EditorStatic } from 'platejs/static';
import { BaseEditorKit } from '@/components/editor/plugins-static';
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,
    initialValue: content,
  });
 
  return (
    <div className="grid grid-cols-2 gap-4">
      {/* Pure static rendering - no interactivity */}
      <div>
        <h2>Static View (Server Rendered)</h2>
        <EditorStatic editor={editor} />
      </div>
 
      {/* Interactive view - rendered on client */}
      <div>
        <h2>Interactive View</h2>
        <InteractiveViewer value={content} />
      </div>
    </div>
  );
}
app/document/page.tsx
import { createStaticEditor, EditorStatic } from 'platejs/static';
import { BaseEditorKit } from '@/components/editor/plugins-static';
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,
    initialValue: content,
  });
 
  return (
    <div className="grid grid-cols-2 gap-4">
      {/* Pure static rendering - no interactivity */}
      <











Example: client component with EditorPreview

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

Key features of EditorPreview

  • Client-side only: Requires 'use client' directive
  • Adds interactivity: Enables user interactions with the content (e.g., text selection, copying, future interactions like tooltips, highlights, etc.)
  • Minimal overhead: Still uses EditorStatic internally for rendering
  • Use with useStaticEditor: Creates a static editor optimized for view-only React components
  • Exact copy payloads: EditorPreview resolves selection against its own static host and writes the same model slice, HTML, and plain-text payloads as a mounted editor
Server Component Compatibility

EditorPreview cannot be used in Server Components. If you're passing an editor from a server component to a client component, you'll encounter serialization errors. Use EditorStatic on the server side, or create the editor client-side with useStaticEditor.

EditorStatic vs. EditorPreview vs. EditorRoot + readOnly

Aspect<EditorStatic><EditorPreview><EditorRoot> + readOnly
EnvironmentServer/Client (SSR/RSC safe)Client-onlyClient-only
InteractivityNoneMinimal (selection, copy, toolbar, etc.)Full interactive features (browser-only)
Browser APIsNot usedMinimal (event handlers)Full usage
PerformanceBest - static HTML onlyGood - static rendering + event delegationHeavier - full editor internals
Bundle SizeSmallestSmallLargest
Use CasesServer rendering, HTML exportClient-side content with basic interactionsFull read-only editor with all features
RecommendationSSR/RSC without any interactionsClient-side content needing light interactivityClient-side with complex interactive needs

RSC/SSR example

In a Next.js App Router (or similar RSC environment), <EditorStatic> can be used directly in Server Components:

app/preview/page.tsx (RSC)
import { createEditor } from 'platejs';
import { EditorStatic } from 'platejs/static';
import { BaseBasicBlocksKit } from '@/components/editor/basic-blocks-static';
 
export default async function Page() {
  // Fetch or define content server-side
  const serverContent = [
    { type: 'heading', level: 1, children: [{ text: 'Rendered on the Server! 🎉' }] },
    { type: 'paragraph', children: [{ text: 'This content is static and server-rendered.' }] },
  ];
 
  const editor = createEditor({
    plugins: [...BaseBasicBlocksKit],
    initialValue: serverContent,
  });
 
  return (
    <EditorStatic
      editor={editor}
      className="my-static-preview-container"
    />
  );
}
app/preview/page.tsx (RSC)
import { createEditor } from 'platejs';
import { EditorStatic } from 'platejs/static';
import { BaseBasicBlocksKit } from '@/components/editor/basic-blocks-static';
 
export default async function Page() {
  // Fetch or define content server-side
  const serverContent = [
    { type: 'heading', level: 1, children: [{ text: 'Rendered on the Server! 🎉' }] },
    { type: 'paragraph', children: [{ text: 'This content is static and server-rendered.' }] },
  ];
 
  const editor = createEditor({
    plugins: [...BaseBasicBlocksKit],
    initialValue: serverContent,
  });
 






This renders the content to HTML on the server without needing a client-side JavaScript bundle for EditorStatic itself.

Pairing with renderStaticHtml

For server-rendering the static Plate tree to an HTML string, use renderStaticHtml. It renders <EditorStatic> through React DOM Server; it is not a semantic HTML interchange serializer.

EditorStatic renders configured node components and render attributes. It omits editing-surface slots such as slots.wrapContent; wrap result.data in application presentation when the exported document needs an outer shell.

lib/html-serializer.ts
import { createEditor } from 'platejs';
import { renderStaticHtml } from 'platejs/static';
import { BaseBasicBlocksKit } from '@/components/editor/basic-blocks-static';
 
async function getDocumentAsHtml(value: any[]) {
  const editor = createEditor({
    plugins: [...BaseBasicBlocksKit],
    initialValue: value,
  });
 
  const result = await renderStaticHtml(editor, {
    // component: EditorStatic, // Optional: Defaults to EditorStatic
    props: { className: 'prose max-w-none' }, // Example: Pass props to the root div
  });
 
  return result.data;
}
 
// Example Usage:
// const value = [ { type: 'heading', level: 1, children: [{ text: 'My Document' }] } ];
// getDocumentAsHtml(value).then(console.log);
lib/html-serializer.ts
import { createEditor } from 'platejs';
import { renderStaticHtml } from 'platejs/static';
import { BaseBasicBlocksKit } from '@/components/editor/basic-blocks-static';
 
async function getDocumentAsHtml(value: any[]) {
  const editor = createEditor({
    plugins: [...BaseBasicBlocksKit],
    initialValue: value,
  });
 
  const result = await renderStaticHtml(editor, {
    // component: EditorStatic, // Optional: Defaults to EditorStatic
    props: { className: 'prose max-w-none' }, // Example: Pass props to the root div
  });
 
  return result.data;
}



For more details, see the HTML Serialization guide.

API Reference

<EditorStatic> props

import type React from 'react';
import type { Editor, EditorDocumentValue } from 'platejs';
 
interface EditorStaticProps<E = Editor>
  extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * A document to render instead of the editor's own. The editor supplies
   * the plugins and is not edited.
   */
  document?: EditorDocumentValue;
 
  /**
   * The Plate editor instance, created via `createEditor`.
   * Must include component-bound plugins relevant to the rendered content.
   */
  editor: E;
 
  /** 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 { Editor, EditorDocumentValue } from 'platejs';
 
interface EditorStaticProps<E = Editor>
  extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * A document to render instead of the editor's own. The editor supplies
   * the plugins and is not edited.
   */
  document?: EditorDocumentValue;
 
  /**
   * The Plate editor instance, created via `createEditor`.
   * Must include component-bound plugins relevant to the rendered content.
   */
  editor: E;
 
  /** Inline CSS styles for the root `div` element. */
  style?:


  • document: A document rendered through the editor's plugins instead of the editor's own value; see Render a document.
  • editor: An editor created with createEditor or createStaticEditor, including component-bound plugins required by the value.

Next steps

  • Explore HTML Serialization for exporting content.
  • Learn about using Plate in React Server Components.
  • Refer to individual plugin documentation for their base (non-React) imports.
;
import { BaseFootnoteKit } from './footnote-static';
import { LineHeightKit } from './line-height';
import { BaseLinkKit } from './link-static';
import { BaseListKit } from './list-static';
import { MarkdownKit } from './markdown';
import { BaseMathKit } from './math-static';
import { BaseMediaKit } from './media-static';
import { BaseMentionKit } from './mention-static';
import { BaseTableKit } from './table-static';
import { BaseTocKit } from './toc-static';
export const BaseEditorKit = [
...BaseBasicBlocksKit,
...BaseCodeBlockKit,
...BaseCodeDrawingKit,
...BaseTableKit,
...BaseDetailsKit,
...BaseTocKit,
...BaseMediaKit,
BaseUploadPlugin,
...BaseCalloutKit,
...BaseColumnKit,
...BaseMathKit,
...BaseDateKit,
...BaseLinkKit,
...BaseMentionKit,
...BaseFootnoteKit,
...BaseBasicMarksKit,
...BaseFontKit,
...BaseListKit,
...AlignKit,
...LineHeightKit,
...MarkdownKit,
] as const;
}
import { createEditor } from 'platejs';
import { EditorStatic } from 'platejs/static';
import { BaseEditorKit } from '@/components/editor/plugins-static';
 
const editor = createEditor({
  plugins: BaseEditorKit,
  initialValue: [
    { type: 'heading', level: 1, children: [{ text: 'Server-Rendered Title' }] },
    { type: 'paragraph', children: [{ text: 'This content is rendered statically.' }] },
  ],
});
 
// Render statically
export default function MyStaticPage() {
  return <EditorStatic editor={editor} />;
}
} from '@/components/editor/html-export';
import { HtmlExportKit } from '@/components/editor/html-export-kit';
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 { 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 { suggestionValue } from '@/registry/examples/values/suggestion-value';
import { tableValue } from '@/registry/examples/values/table-value';
import { tocPlaygroundValue } from '@/registry/examples/values/toc-value';
export const metadata: Metadata = {
title: 'HTML Export',
};
const getCachedTailwindCss = React.cache(async () => {
const cssPath = path.join(process.cwd(), 'public', 'tailwind.css');
return await fs.readFile(cssPath, 'utf-8');
});
const createHtmlDocument = ({
editorHtml,
katexCDN,
tailwindCss,
theme,
}: {
editorHtml: string;
tailwindCss: string;
katexCDN?: string;
theme?: string;
}) => `<!DOCTYPE html>
<html lang="en"${theme === 'dark' ? ' class="dark"' : ''}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<style>${tailwindCss}</style>
${katexCDN}
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400..700&family=JetBrains+Mono:wght@400..700&display=swap"
rel="stylesheet"
/>
<style>
:root {
--font-sans: 'Inter', 'Inter Fallback';
--font-mono: 'JetBrains Mono', 'JetBrains Mono Fallback';
}
</style>
</head>
<body>
${editorHtml}
</body>
</html>`;
const createValue = (): EditorDocumentValue => ({
children: [
...basicBlocksValue,
...basicMarksValue,
...tocPlaygroundValue,
...linkValue,
...tableValue,
...equationValue,
...columnValue,
...mentionValue,
...dateValue,
...fontValue,
...suggestionValue,
...alignValue,
...lineHeightValue,
...indentValue,
...listValue,
...mediaValue.children,
],
});
export default async function HtmlExportBlock() {
const editor = createStaticEditor({
plugins: HtmlExportKit,
initialValue: createValue(),
});
const tailwindCss = await getCachedTailwindCss();
const katexCDN = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.18/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 { data: editorHtml } = await renderStaticHtml(editor, {
component: 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 fs from 'node:fs/promises';
import path from 'node:path';
 
import { cva } from 'class-variance-authority';
import type { Metadata } from 'next';
import type { EditorDocumentValue } from 'platejs';
import { createStaticEditor, renderStaticHtml } from 'platejs/static';
import * as React from 'react';
 
import { EditorStatic } from '@/components/editor/editor-static';
import {
  EditorClient,
  EditorViewClient,
  ExportHtmlButton,
  HtmlIframe,
} from '@/components/editor/html-export';
import { HtmlExportKit } from '@/components/editor/html-export-kit';
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 { 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 { suggestionValue } from '@/registry/examples/values/suggestion-value';
import { tableValue } from '@/registry/examples/values/table-value';
import { tocPlaygroundValue } from '@/registry/examples/values/toc-value';
 
export const metadata: Metadata = {
  title: 'HTML Export',
};
 
const getCachedTailwindCss = React.cache(async () => {
  const cssPath = path.join(process.cwd(), 'public', 'tailwind.css');
 
  return await fs.readFile(cssPath, 'utf-8');
});
 
const createHtmlDocument = ({
  editorHtml,
  katexCDN,
  tailwindCss,
  theme,
}: {
  editorHtml: string;
  tailwindCss: string;
  katexCDN?: string;
  theme?: string;
}) => `<!DOCTYPE html>
<html lang="en"${theme === 'dark' ? ' class="dark"' : ''}>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="color-scheme" content="light dark" />
    <style>${tailwindCss}</style>
    ${katexCDN}
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400..700&family=JetBrains+Mono:wght@400..700&display=swap"
      rel="stylesheet"
    />
    <style>
      :root {
        --font-sans: 'Inter', 'Inter Fallback';
        --font-mono: 'JetBrains Mono', 'JetBrains Mono Fallback';
      }
    </style>
  </head>
  <body>
    ${editorHtml}
  </body>
</html>`;
 
const createValue = (): EditorDocumentValue => ({
  children: [
    ...basicBlocksValue,
    ...basicMarksValue,
    ...tocPlaygroundValue,
    ...linkValue,
    ...tableValue,
    ...equationValue,
    ...columnValue,
    ...mentionValue,
    ...dateValue,
    ...fontValue,
    ...suggestionValue,
    ...alignValue,
    ...lineHeightValue,
    ...indentValue,
    ...listValue,
    ...mediaValue.children,
  ],
});
 
export default async function HtmlExportBlock() {
  const editor = createStaticEditor({
    plugins: HtmlExportKit,
    initialValue: createValue(),
  });
 
  const tailwindCss = await getCachedTailwindCss();
  const katexCDN = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.18/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 { data: editorHtml } = await renderStaticHtml(editor, {
    component: 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'
);
components/editor/paragraph-static.tsx
import React from 'react';
import type { BaseParagraphPlugin } from 'platejs';
import { EditorElement, type EditorElementProps } from 'platejs/static';
 
export function ParagraphElementStatic(
  props: EditorElementProps<typeof BaseParagraphPlugin>
) {
  return (
    <EditorElement {...props}>
      {props.children}
    </EditorElement>
  );
}
import
{ BaseHeadingPlugin, BaseParagraphPlugin }
from
'platejs'
;
import { ParagraphElementStatic } from './ui/paragraph-static';
import { HeadingElementStatic } from './ui/heading-static';
export const StaticEditorKit = [
BaseParagraphPlugin.configure({ component: ParagraphElementStatic }),
BaseHeadingPlugin.configure({ component: HeadingElementStatic }),
];
});
return (
<EditorStatic
editor={editor}
style={{ padding: 16 }}
className="my-plate-static-content"
/>
);
}
app/my-static-page/page.tsx (RSC Example)
import { createEditor } from 'platejs';
import { EditorStatic } from 'platejs/static';
import { StaticEditorKit } from '@/components/static-editor-kit';
 
export default async function MyStaticPage() {
  // Example: Fetch or define editor value
  const initialValue = [
    { type: 'heading', level: 1, children: [{ text: 'Server-Rendered Title' }] },
    { type: 'paragraph', children: [{ text: 'Content rendered statically.' }] },
  ];
 
  const editor = createEditor({
    plugins: StaticEditorKit,
    initialValue,
  });
 
  return (
    <EditorStatic
      editor={editor}
      style={{ padding: 16 }}
      className="my-plate-static-content"
    />
  );
}
div
>
<h2>Static View (Server Rendered)</h2>
<EditorStatic editor={editor} />
</div>
{/* Interactive view - rendered on client */}
<div>
<h2>Interactive View</h2>
<InteractiveViewer value={content} />
</div>
</div>
);
}
return (
<EditorStatic
editor={editor}
className="my-static-preview-container"
/>
);
}
// Example Usage:
// const value = [ { type: 'heading', level: 1, children: [{ text: 'My Document' }] } ];
// getDocumentAsHtml(value).then(console.log);
React
.
CSSProperties
;
// Other HTMLDivElement attributes like `className`, `id`, etc., are also supported.
}