Static Rendering

PreviousNext

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

Docs
platefile

Preview

Loading preview…
../../docs/(guides)/static.mdx
---
title: Static Rendering
description: A minimal, memoized, read-only version of Plate with RSC/SSR support.
---

`<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>`](/docs/api/core/plate-components) in read-only mode.

It's a core part of [`serializeHtml`](/docs/api/core/plate-plugin#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 Plate 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](/docs/html).
-   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.

<Callout type="info" title="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.
</Callout>

## Kit Usage

<Steps>

### Installation

The fastest way to enable static rendering is with the `BaseEditorKit`, which includes pre-configured base plugins that work seamlessly with server-side rendering.

<ComponentSource name="editor-base-kit" />

### Add Kit

```tsx
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} />;
}
```

### Example

See a complete server-side static rendering example:

<ComponentSource name="slate-to-html" />

</Steps>

## Manual Usage

<Steps>

### Create a Slate Editor

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

```tsx title="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!' }],
    },
  ],
});
```

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

```tsx title="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>
  );
}
```
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, then pass it to the editor.

```ts title="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
};
```

### Render `<PlateStatic>`

Use the `<PlateStatic>` component, providing the `editor` instance configured with your components.

```tsx title="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"
    />
  );
}
```

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

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

</Steps>

## Client-Side Alternative: `PlateView`

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

### Example: Server Component with Both Static Views

```tsx title="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>
  );
}
```

### Example: Client Component with PlateView

```tsx title="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} />;
}
```

### Key Features of `PlateView`

- **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 `PlateStatic` internally for rendering
- **Use with `usePlateViewEditor`**: Creates a static editor optimized for view-only React components
- **ViewPlugin included**: The static editor automatically includes `ViewPlugin` which provides event handling capabilities

<Callout type="warning" title="Server Component Compatibility">
  `PlateView` 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 `PlateStatic` on the server side, or create the editor client-side with `usePlateViewEditor`.
</Callout>

## `PlateStatic` 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             |

## RSC/SSR Example

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

```tsx title="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"
    />
  );
}
```
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.

```ts title="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);
```
For more details, see the [HTML Serialization guide](/docs/html).

## API Reference

### `<PlateStatic>` Props

```ts
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`**: 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`.

## Next Steps

-   Explore [HTML Serialization](/docs/html) for exporting content.
-   Learn about using Plate in [React Server Components](/docs/installation/rsc).
-   Refer to individual plugin documentation for their base (non-React) imports.

Installation

npx shadcn@latest add @plate/static-docs

Usage

Usage varies by registry entry. Refer to the registry docs or source files below for details.