Command Palette

Search for a command to run...

Static Rendering

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

<PlateStatic> is a fast, read-only React component for rendering Slate 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.

Key Advantages

  • Server-Safe: No browser API dependencies; works in SSR/RSC.
  • No Slate Editor Overhead: Excludes interactive features like selections or event handlers.
  • Memoized Rendering: Uses _memo and structural checks 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 <PlateStatic>

  • 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 <Plate> component in the browser. For purely server-rendered, non-interactive content, <PlateStatic> is the recommended choice.

Usage

Create a Slate Editor

Initialize a Slate editor instance using createSlateEditor with your required plugins. This is analogous to using usePlateEditor for the interactive <Plate> component.

lib/plate-static-editor.ts
import { createSlateEditor } from '@udecode/plate';
// 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!' }],
    },
  ],
});

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/ui/paragraph-element-static.tsx
import React from 'react';
import type { SlateElementProps } from '@udecode/plate';
 
export function ParagraphElementStatic(props: SlateElementProps) {
  return (
    <SlateElement {...props}>
      {props.children}
    </SlateElement>
  );
}

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

Map Plugin Keys to Static Components

Create an object that maps plugin keys or node types to their corresponding static React components.

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

Render <PlateStatic>

Use the <PlateStatic> component, providing the editor instance and your components mapping.

app/my-static-page/page.tsx (RSC Example)
import { PlateStatic } from '@udecode/plate';
import { createSlateEditor } from '@udecode/plate';
// import { BaseHeadingPlugin, ... } from '@udecode/plate-heading'; // 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 */],
    value: initialValue,
  });
 
  return (
    <PlateStatic
      editor={editor}
      components={staticComponents}
      style={{ padding: 16 }}
      className="my-plate-static-content"
    />
  );
}

Value Override

If you pass a value prop directly to <PlateStatic>, it will override editor.children.

<PlateStatic
  editor={editor}
  components={staticComponents}
  value={[
    { type: 'p', children: [{ text: 'Overridden content.' }] }
  ]}
/>

Memoization Details

<PlateStatic> enhances performance through memoization:

  • Each <ElementStatic> and <LeafStatic> is wrapped in React.memo.
  • Reference Equality: Unchanged node references prevent re-renders.
  • _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.

PlateStatic vs. Plate + readOnly

Aspect<PlateStatic><Plate> + readOnly
InteractivityNo (server-safe)Some interactive features can run (browser-only)
Browser APIsNot used; safe for SSR/RSCMinimal use, client-side context required
PerformanceOptimized for static rendering, minimal overheadHeavier, includes more editor internals
Partial Re-renderMemoized sub-trees for efficient updatesSupports partial re-renders, but with client overhead
Use CasesServer rendering, HTML serialization, static previewsBrowser-based read-only states, interactive features
RecommendationSSR/RSC without editing or complex interactionsClient-side read-only states with interactive needs

RSC/SSR Example

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

app/preview/page.tsx (RSC)
import { PlateStatic } from '@udecode/plate';
import { createSlateEditor } from '@udecode/plate';
// Example base plugins (ensure non-/react imports)
// import { BaseHeadingPlugin } from '@udecode/plate-heading';
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
    value: serverContent,
  });
 
  return (
    <PlateStatic
      editor={editor}
      components={staticComponents}
      className="my-static-preview-container"
    />
  );
}

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

Pairing with serializeHtml

For generating a complete HTML string (e.g., for emails, PDFs, or external systems), use serializeHtml. It utilizes <PlateStatic> internally.

lib/html-serializer.ts
import { createSlateEditor, serializeHtml } from '@udecode/plate';
import { staticComponents } from '@/components/static-components';
// import { BaseHeadingPlugin, ... } from '@udecode/plate-heading';
 
async function getDocumentAsHtml(value: any[]) {
  const editor = createSlateEditor({
    plugins: [/* ...your base plugins... */],
    value,
  });
 
  const html = await serializeHtml(editor, {
    components: staticComponents,
    // 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.

API Reference

<PlateStatic> Props

import type React from 'react';
import type { Descendant } from 'slate';
import type { PlateEditor, PlatePluginComponent } from '@udecode/plate/core'; // Adjust imports as per your setup
 
interface PlateStaticProps extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * The Slate editor instance, created via `createSlateEditor`.
   * Must include plugins relevant to the content being rendered.
   */
  editor: PlateEditor;
 
  /**
   * A record mapping plugin keys (or node types like 'p', 'h1')
   * to their corresponding static React components.
   */
  components: Record<string, PlatePluginComponent>;
 
  /**
   * Optional Slate `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.
  • components: Maps node types (e.g., 'p', 'h1') or plugin keys to your static React components.
  • value: Optional. If provided, this array of Descendant nodes will be rendered, overriding the content currently in editor.children.

Next Steps