āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/udecode/plate/(guides)/static ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
<PlateStatic> 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 <Plate> in read-only mode.
It's a core part of serializeHtml for HTML export and is ideal for any server or RSC context needing a non-interactive, presentational view of Plate content.
_memo and structural checks to re-render only changed nodes.<PlateStatic>The fastest way to enable static rendering is with the BaseEditorKit, which includes pre-configured base plugins that work seamlessly with server-side rendering.
import { createSlateEditor, PlateStatic } from 'platejs';
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} />;
}
See a complete server-side static rendering example:
<ComponentSource name="slate-to-html" /> </Steps>Initialize a Slate editor instance using createSlateEditor with your required plugins and components. This is analogous to using usePlateEditor for the interactive <Plate> component.
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!' }],
},
],
});
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.
import React from 'react';
import type { SlateElementProps } from 'platejs';
export function ParagraphElementStatic(props: SlateElementProps) {
return (
<SlateElement {...props}>
{props.children}
</SlateElement>
);
}
Create similar static components for headings, images, links, etc.
Create an object that maps plugin keys or node types to their corresponding static React components, then pass it to the editor.
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>Use the <PlateStatic> component, providing the editor instance configured with your components.
import { PlateStatic } from 'platejs';
import { createSlateEditor } from 'platejs';
// 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"
/>
);
}
<Callout type="note" title="Value Override">
If you pass a `value` prop directly to `<PlateStatic>`, it will override `editor.children`.
```tsx
<PlateStatic
editor={editor}
value={[
{ type: 'p', children: [{ text: 'Overridden content.' }] }
]}
/>
```
</Callout>
<PlateStatic> enhances performance through memoization:
<ElementStatic> and <LeafStatic> is wrapped in React.memo._memo Field: Setting node._memo = true (or any stable value) on an element or leaf can force Plate to skip re-rendering that specific node, even if its content changes. This is useful for fine-grained control over updates.PlateViewFor cases where you need minimal interactivity with static content, use <PlateView>. This component wraps <PlateStatic> and adds client-side event handlers for user interactions while maintaining the performance benefits of static rendering.
import { createStaticEditor } from 'platejs';
import { PlateStatic } from 'platejs';
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>
);
}
'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' directivePlateStatic internally for renderingusePlateViewEditor: Creates a static editor optimized for view-only React componentsViewPlugin which provides event handling capabilitiesPlateStatic vs. PlateView vs. Plate + readOnly| Aspect | <PlateStatic> | <PlateView> | <Plate> + readOnly |
| --------------------- | ----------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------ |
| Environment | Server/Client (SSR/RSC safe) | Client-only | Client-only |
| Interactivity | None | Minimal (selection, copy, toolbar, etc.) | Full interactive features (browser-only) |
| Browser APIs | Not used | Minimal (event handlers) | Full usage |
| Performance | Best - static HTML only | Good - static rendering + event delegation | Heavier - full editor internals |
| Bundle Size | Smallest | Small | Largest |
| Use Cases | Server rendering, HTML export | Client-side content with basic interactions | Full read-only editor with all features |
| Recommendation | SSR/RSC without any interactions | Client-side content needing light interactivity | Client-side with complex interactive needs |
In a Next.js App Router (or similar RSC environment), <PlateStatic> can be used directly in Server Components:
import { PlateStatic } from 'platejs';
import { createSlateEditor } from 'platejs';
// 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"
/>
);
}
This renders the content to HTML on the server without needing a client-side JavaScript bundle for PlateStatic itself.
serializeHtmlFor generating a complete HTML string (e.g., for emails, PDFs, or external systems), use serializeHtml. It utilizes <PlateStatic> internally.
import { createSlateEditor, serializeHtml } from 'platejs';
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);
For more details, see the HTML Serialization guide.
<PlateStatic> Propsimport 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: An instance of PlateEditor created with createSlateEditor, including components configuration.value: Optional. If provided, this array of Descendant nodes will be rendered, overriding the content currently in editor.children.ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā