Server-side rendering & Node.js
ProseKit can run in Node.js environments for background processing, CLI applications, and Server-Side Rendering (SSR). The primary limitation is that DOM-dependent APIs are unavailable unless you provide a DOM implementation.
Create an editor in Node.js
Section titled “Create an editor in Node.js”You can create an editor and execute commands just like in a browser environment:
import { defineBasicExtension function defineBasicExtension(): BasicExtensionDefine a basic extension that includes some common functionality. You can
copy this function and customize it to your needs.
It's a combination of the following extension functions:
-
{@link
defineDoc
}
-
{@link
defineText
}
-
{@link
defineParagraph
}
-
{@link
defineHeading
}
-
{@link
defineList
}
-
{@link
defineBlockquote
}
-
{@link
defineImage
}
-
{@link
defineHorizontalRule
}
-
{@link
defineHardBreak
}
-
{@link
defineTable
}
-
{@link
defineCodeBlock
}
-
{@link
defineItalic
}
-
{@link
defineBold
}
-
{@link
defineUnderline
}
-
{@link
defineStrike
}
-
{@link
defineCode
}
-
{@link
defineLink
}
-
{@link
defineBaseKeymap
}
-
{@link
defineBaseCommands
}
-
{@link
defineHistory
}
-
{@link
defineGapCursor
}
-
{@link
defineVirtualSelection
}
-
{@link
defineModClickPrevention
}@public } from 'prosekit/basic'
import { createEditor function createEditor<E extends Extension>(options: EditorOptions<E>): Editor<E>@public } from 'prosekit/core'
const extension const extension: BasicExtension = defineBasicExtension function defineBasicExtension(): BasicExtensionDefine a basic extension that includes some common functionality. You can
copy this function and customize it to your needs.
It's a combination of the following extension functions:
-
{@link
defineDoc
}
-
{@link
defineText
}
-
{@link
defineParagraph
}
-
{@link
defineHeading
}
-
{@link
defineList
}
-
{@link
defineBlockquote
}
-
{@link
defineImage
}
-
{@link
defineHorizontalRule
}
-
{@link
defineHardBreak
}
-
{@link
defineTable
}
-
{@link
defineCodeBlock
}
-
{@link
defineItalic
}
-
{@link
defineBold
}
-
{@link
defineUnderline
}
-
{@link
defineStrike
}
-
{@link
defineCode
}
-
{@link
defineLink
}
-
{@link
defineBaseKeymap
}
-
{@link
defineBaseCommands
}
-
{@link
defineHistory
}
-
{@link
defineGapCursor
}
-
{@link
defineVirtualSelection
}
-
{@link
defineModClickPrevention
}@public ()
const editor const editor: Editor<BasicExtension> = createEditor createEditor<BasicExtension>(options: EditorOptions<BasicExtension>): Editor<BasicExtension>@public ({ extension EditorOptions<BasicExtension>.extension: BasicExtensionThe extension to use when creating the editor. })
// Set content with JSON
editor const editor: Editor<BasicExtension> .setContent Editor<BasicExtension>.setContent: (content: Node | NodeJSON | string | Element, selection?: SelectionJSON | Selection | "start" | "end") => voidUpdate the editor's document and selection.@paramcontent - The new document to set. It can be one of the following:
- A ProseMirror node instance
- A ProseMirror node JSON object
- An HTML string
- A DOM element instance@paramselection - Optional. Specifies the new selection. It can be one of the following:
- A ProseMirror selection instance
- A ProseMirror selection JSON object
- The string "start" (to set selection at the beginning, default value)
- The string "end" (to set selection at the end) ({
type type: string : 'doc',
content content: {
type: string;
content: {
type: string;
text: string;
}[];
}[]
: [
{ type NodeJSON.type: string : 'paragraph', content NodeJSON.content?: NodeJSON[] | undefined : [{ type NodeJSON.type: string : 'text', text NodeJSON.text?: string | undefined : 'Hello, Node.js!' }] },
],
})
// Run commands
editor const editor: Editor<BasicExtension> .commands Editor<BasicExtension>.commands: ToCommandAction<{
setParagraph: [];
setHeading: [attrs?: HeadingAttrs | undefined];
insertHeading: [attrs?: HeadingAttrs | undefined];
toggleHeading: [attrs?: HeadingAttrs | undefined];
dedentList: [options?: DedentListOptions];
indentList: [options?: IndentListOptions];
moveList: [direction: "up" | "down"];
splitList: [];
toggleCollapsed: [options?: ToggleCollapsedOptions];
... 58 more ...;
redo: [];
}>
All
{@link
CommandAction
}
s defined by the editor. .insertImage insertImage: CommandAction
(attrs?: ImageAttrs | undefined) => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ({
src ImageAttrs.src?: string | null | undefined : 'https://example.com/logo.png',
width ImageAttrs.width?: number | null | undefined : 120,
height ImageAttrs.height?: number | null | undefined : 60,
})
// Get the document as JSON
const json const json: NodeJSON = editor const editor: Editor<BasicExtension> .getDocJSON Editor<BasicExtension>.getDocJSON: () => NodeJSONReturn a JSON object representing the editor's current document. ()
console var console: Console .log Console.log(...data: any[]): voidThe **`console.log()`** static method outputs a message to the console.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static) (json const json: NodeJSON )DOM-dependent APIs are unavailable
Section titled “DOM-dependent APIs are unavailable”In a standard Node.js environment there is no DOM, so methods requiring DOM access will throw errors:
// ❌ Mounting fails without DOM
editor const editor: Editor<BasicExtension> .mount Editor<BasicExtension>.mount: (place: HTMLElement | null | undefined) => void | VoidFunctionMount the editor to the given HTML element. Pass `null` or `undefined` to
unmount the editor. When an element is passed, this method returns a
function to unmount the editor. (document var document: Document**`window.document`** returns a reference to the document contained in the window.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document) .createElement Document.createElement<"div">(tagName: "div", options?: ElementCreationOptions): HTMLDivElement (+2 overloads)In an HTML document, the **`document.createElement()`** method creates the HTML element specified by localName, or an HTMLUnknownElement if localName isn't recognized.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createElement) ('div'))
// ❌ HTML serialization requires DOM APIs
editor const editor: Editor<BasicExtension> .getDocHTML Editor<BasicExtension>.getDocHTML: (options?: getDocHTMLOptions) => stringReturn an HTML string representing the editor's current document. ()
// ❌ HTML parsing requires DOM APIs
editor const editor: Editor<BasicExtension> .setContent Editor<BasicExtension>.setContent: (content: Node | NodeJSON | string | Element, selection?: SelectionJSON | Selection | "start" | "end") => voidUpdate the editor's document and selection.@paramcontent - The new document to set. It can be one of the following:
- A ProseMirror node instance
- A ProseMirror node JSON object
- An HTML string
- A DOM element instance@paramselection - Optional. Specifies the new selection. It can be one of the following:
- A ProseMirror selection instance
- A ProseMirror selection JSON object
- The string "start" (to set selection at the beginning, default value)
- The string "end" (to set selection at the end) ('<p>HTML input</p>')Two strategies can address the absence of DOM APIs in server environments.
SSR strategy 1: Browser pre-rendering
Section titled “SSR strategy 1: Browser pre-rendering”The simplest approach for server-side rendering is client-side pre-rendering: allow the browser to convert documents to HTML and store the result.
// Browser code
const html const html: string = editor const editor: Editor<BasicExtension> .getDocHTML Editor<BasicExtension>.getDocHTML: (options?: getDocHTMLOptions) => stringReturn an HTML string representing the editor's current document. ()
await fetch function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) ('/api/save', { method RequestInit.method?: string | undefinedA string to set request's method. : 'POST', body RequestInit.body?: BodyInit | null | undefinedA BodyInit object or null to set request's body. : html const html: string })The backend can then serve this static HTML without requiring DOM access.
SSR strategy 2: Server-side DOM simulation
Section titled “SSR strategy 2: Server-side DOM simulation”For JSON-to-HTML conversion within Node.js, use a headless DOM library such as jsdom or happy-dom.
Using jsdom
Section titled “Using jsdom”import { JSDOM } from 'jsdom'
// Initialize virtual DOM
const dom = new JSDOM('')
const document = dom.window.document
editor.setContent({
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Foo' }] }],
})
editor.commands.insertText({ text: 'Bar' })
// Provide the document to getDocHTML
const html = editor.getDocHTML({ document })
console.log(html) // => <div><p>BarFoo</p></div>Using happy-dom
Section titled “Using happy-dom”import { Window } from 'happy-dom'
// Initialize virtual DOM
const window = new Window()
const document = window.document
editor.setContent({
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Foo' }] }],
})
editor.commands.insertText({ text: 'Bar' })
// Provide the document to getDocHTML
const html = editor.getDocHTML({ document })
console.log(html) // => <div><p>BarFoo</p></div>