BlockNote
BlockNote copied to clipboard
Investigate server-util package usage for Next.js SSR and other environments
@blocknote/server-util has been designed / tested for usage in node.js based environments.
However, some users want to use it in Next.js server side actions or Partykit functions (see https://github.com/TypeCellOS/BlockNote/pull/451). These functions probably run in different environments, so we'll need to investigate compatibility with this
hey @YousefED sorry to bug but any updates on this?
still getting the error in my server action or /api/route.ts:
createContext only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/context-in-server-component
And it doesn't change if it's in an api route vs a server action
@YousefED Same here, using nextjs 14 and the error pops up:
Server Error Error: createContext only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/context-in-server-component
This error happened while generating the page. Any console logs will be displayed in the terminal window.
import { ServerBlockNoteEditor } from "@blocknote/server-util";
export const Introduction = async ({ text }: { text: any }) => { const editor = ServerBlockNoteEditor.create(); const html = await editor.blocksToFullHTML(text);
return ( <div dangerouslySetInnerHTML={{ __html: html }} /> ); };
Yep, I'm also getting the same error as @linhtrinh18 , please help to fix it @YousefED and @matthewlipski
'use server'
import { ServerBlockNoteEditor } from "@blocknote/server-util";
export const blockToYDoc= async ( blocks: any ) => {
const editor = ServerBlockNoteEditor.create();
const blocksToYDocs = await editor.blocksToYDoc (blocks);
return blocksToYDocs
}
error
Error: createContext only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more:
I've done some further investigation into this. I want to split my findings into two parts:
NextJS (SSR etc) Because BlockNote blocks are defined using React, we need to access certain react functionality on the server to call BlockNote APIs. For example, we need to render certain components to get their HTML version (for Blocks To HTML / Blocks To Markdown, etc). This is different than most React apps, because BlockNote uses React internally as a framework. However, next.js seems to block various React calls. This means certain React APIs are not available for BlockNote to use when evaluating calls like "Blocks to HTML". I tried some workarounds but could not find an easy way to call React APIs not affected / blocked by the Next bundler.
It would be interesting to explore what a "next.js-first" architecture for BlockNote could look like (e.g.: with native support for SSR, etc). However, I think this would be several weeks of work at a minimum so it would require a corporate sponsor (I don't have a need for this feature myself at this moment).
A workaround for now is to run a regular Node server somewhere and use server-util from there, and then expose an API.
Non-node runtimes (bun, deno, etc) There also was a report that JSDom doesn't work in non-node environments like Bun (https://discord.com/channels/928190961455087667/1015169282444894219/1269960495264305153). I think this should be fixable by making the DOM shim pluggable, and make it possible to use something like happy-dom instead of jsdom. Would gladly accept any PRs for this / will consider adding this option if there are more requests for this coming in
Still kinda doesn't work with next.js api routes
⨯ createContext only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/context-in-server-component
My use case: I am storing the contents of the editor in convex and in the server I want to retrieve them, parse them to blocks, and then using editor, parse them to markdown to generate vector embeddings for my AI application.
Any help is highly appreciated.
Still kinda doesn't work with next.js api routes
⨯ createContext only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/context-in-server-componentMy use case: I am storing the contents of the editor in convex and in the server I want to retrieve them, parse them to blocks, and then using
editor, parse them to markdown to generate vector embeddings for my AI application.Any help is highly appreciated.
recommendation for now is to run a nodejs server
Alrighty.
But I think it would be extremely useful if you could add server side compatibility with next.js as it's one of the most widely used frameworks.
Anyways, kudos to your work @YousefED .
I think I found a solution for this, instead of using server-util in api from app folder, use it in api from pages folder. I tried it with next 14.2.1 and it works fine. I rendered some basic blocks (p,h1,ul,ol) and few of my custom blocks.
Since nextjs allows you to have both pages and app folder implementation in a single project, you can just create a seperate pages api to handle your server-util.
Getting same error as folks above. My usecase: I use blocksToYXmlFragment for the first client that connects to a collaborative doc to retrieve blocks from database and then apply them to Y.Doc on server so that when client connects he immidiately syncs latest Yjs state with server. And I want to call that inside app server component(page.tsx) so that it happens right when user tries to access the page. I don't want to have a separate api call for this, it kills the purpose of SSR. Any hackaround would be fine by me(to somehow force nextjs to use this stuff). Or, if maybe this specific util doesn't require react context. I kinda don't understand why a util needs an editor to be created in the first place, looks like Java/OOP kinda kink.
@YousefED with the help of chatgpt I wrote this util for myself. It uses 2/3 methods from the original class. the only thing I was stuck with was pmSchema and chat suggested to use a headless BlockNoteEditor to obtain one, because client-side BlockNoteEditor is not importing anything from /react packages, so it doesn't trigger Nextjs blockage. So, I bet some of other utils can also be extracted this way. Of course you'll end up with half utils here half utils there, but you have more expretise in this to carry this on so I'm sure you'll figure out an elegant API for this:)
import { prosemirrorToYXmlFragment } from "y-prosemirror";
import {
BlockSchema,
blockToNode,
InlineContentSchema,
PartialBlock,
StyleSchema,
} from "@blocknote/core";
import { BlockNoteEditor } from "@blocknote/core";
import { XmlFragment } from "yjs";
function _getPmSchema() {
const pmSchema = BlockNoteEditor.create({
// schema: mySchema,
_headless: true,
}).pmSchema;
return pmSchema;
}
function _blocksToProsemirrorNode(
blocks: PartialBlock<BlockSchema, InlineContentSchema, StyleSchema>[]
) {
const pmSchema = _getPmSchema();
const pmNodes = blocks.map((b) => blockToNode(b, pmSchema));
const doc = pmSchema.topNodeType.create(
null,
pmSchema.nodes["blockGroup"].create(null, pmNodes)
);
return doc;
}
export function blocksToYXmlFragment(
blocks: BlockSchema[],
xmlFragment?: XmlFragment
) {
return prosemirrorToYXmlFragment(
_blocksToProsemirrorNode(blocks),
xmlFragment
);
}
Previous approach started falling apart as soon as I tried using Custom Blocks. So, I started thinking of how can I force nextjs to allow usage of react APIs, so that I could use ServerEditor. and I found a way.
- Put your schema into separate file, call it
schema.ts. Add "use client" at top. - Duplicate it and call it
schemaServer.ts, but don't include 'use client' at top. - Use the schema from
schemaServer.tsin your server-side in pages router, where you want to use ServerBlockNoteEditor, when passing "schema" as parameter. And use theschema.tsfile in the client side, where you have Editor. - This is the key part, in your next.config.mjs add this:
serverExternalPackages: [ "@blocknote/server-util", "@blocknote/react", "@blocknote/core", ],
Docs on nextjs: https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages
Why do you have to make duplicate schemas? I don't really know. All I know is that when I use a single one in both client and server, it gives me an error:
TypeError: Cannot use 'in' operator to search for 'table' in undefined
The undefined here means pmSchema afaik, so it fails to initiate schema if I do it like this.
@YousefED this took couple of hours digging through your source codes different versions(and TipTaps code), I hope you give this a bit more attention and can pick it up from here. Last item to figure out is why it fails when using same schema file in both client and server.
UPDATE:
Thought you can optimize duplication by creating 3 files:
schema.ts: declares schema and exports it.schemaClient.ts:"use client"at top andexport * from './schema.ts';schemaServer.ts: just one line ofexport * from './schema.ts'Tested and yes, it works
UPDATE 2: Yo, if anyone tries this, be aware, for some reason my server-side yjs stops receiving updates from time to time. can't say if my code does this or something else
Any progress here? I tried to create a dedicated NextJS API route for converting the json to HTML, but facing the same error on the server side :c