Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Feature Kits
  • 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, annotations, and widgets
  • 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 detailsClient-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 { 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,
  ...BaseCalloutKit,
  ...BaseColumnKit,
  ...BaseMathKit,
  ...BaseDateKit,
  ...BaseLinkKit,
  ...BaseMentionKit,
  ...BaseFootnoteKit,
  ...BaseBasicMarksKit,
  ...BaseFontKit,
  ...BaseListKit,
  ...AlignKit,
  ...LineHeightKit,
  ...MarkdownKit,
] as const;
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'































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: Unchanged node references prevent re-renders.

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 codec.

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 html = await renderStaticHtml(editor, {
    // editorComponent: EditorStatic, // Optional: Defaults to EditorStatic
    props: { className: 'prose max-w-none' }, // Example: Pass props to the root div
  });
 
  return html;
}
 
// 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 html = await renderStaticHtml(editor, {
    // editorComponent: EditorStatic, // Optional: Defaults to EditorStatic
    props: { className: 'prose max-w-none' }, // Example: Pass props to the root div
  });
 
  return html;
}



For more details, see the HTML Serialization guide.

API Reference

<EditorStatic> props

import type React from 'react';
import type { Editor } from 'platejs';
 
interface EditorStaticProps<E = Editor>
  extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * 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 } from 'platejs';
 
interface EditorStaticProps<E = Editor>
  extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * 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.
}
  • 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 { 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,
...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 editorHtml = await renderStaticHtml(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 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 editorHtml = await renderStaticHtml(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'
);
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);