Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Server-Side Rendering (SSR) Support for Lexical Editor #4960

Open
agarwalamn opened this issue Sep 2, 2023 · 20 comments
Open

Feature: Server-Side Rendering (SSR) Support for Lexical Editor #4960

agarwalamn opened this issue Sep 2, 2023 · 20 comments
Labels
contribution-week Issues that are good to be picked up for the contribution week enhancement Improvement over existing feature

Comments

@agarwalamn
Copy link

Use Case:

We are currently working on a project similar to Reddit, where our users utilize the Lexical Editor to create posts and comments. These posts are then displayed on their timelines using a read-only editor. This is a fundamental use case where we believe Server-Side Rendering (SSR) support is essential. SSR would greatly benefit us by allowing us to render these posts on the server, leading to improvements in user experience and SEO.

Current Issue:

Our observation is that the Lexical Editor's rendering is currently limited exclusively to the client-side due to its reliance on client-side rendering mechanisms. Consequently, posts are only rendered on the client side, which can have some drawbacks.

Challenges:

One of the challenges we've encountered is the time it takes for the editor to process data on the client side. This processing delay leads to noticeable UI jumps after content is loaded, which is not an ideal user experience.

Proposed Solutions:

We propose exploring two potential solutions to address these challenges:

Server-Side Rendering with JSDOM:

We suggest investigating the feasibility of enabling SSR for the Lexical Editor by utilizing libraries like JSDOM. This approach would empower us to render the editor within a Node.js/server environment, thus improving initial load times and overall performance.

Render-Only Editor Mode:

Another avenue to explore is the creation of a "render-only" mode for the Lexical Editor. This mode would be optimized for server-side rendering, offering a simplified and resource-efficient way to render the lexical tree.

How This Benefits the Project:

Implementing these solutions would not only enhance the user experience but also make our platform more SEO-friendly. It would address the limitations of client-side rendering, reduce processing time, and eliminate UI jumps, resulting in a smoother and more efficient application.

@agarwalamn agarwalamn added the enhancement Improvement over existing feature label Sep 2, 2023
@acywatson
Copy link
Contributor

acywatson commented Sep 2, 2023

Our observation is that the Lexical Editor's rendering is currently limited exclusively to the client-side due to its reliance on client-side rendering mechanisms

Can you be more specific about what you've observed here? What tools are you using, what outcome are you expecting and what is actually happening?

Another option is just to traverse the parsed JSON editor state and render whatever HTML you want from it on the server.

@acywatson
Copy link
Contributor

One of the challenges we've encountered is the time it takes for the editor to process data on the client side.

Can you give an example here? What exactly are you trying to do that leads to the undesirable behavior?

@agarwalamn
Copy link
Author

agarwalamn commented Sep 6, 2023

Hi @acywatson, Adding details below

Our tech stack includes Next.js. We leverage Next.js getServerSideProps function to make backend API calls for retrieving JSON data and hydrate on server

Observations:

  • Due to the fact that the editor doesn't render on the server due to lexical being heavily reliant on DOM API's.
  • The user sees a blank screen when a post is delivered first on UI.
  • After a brief delay, the post is rendered, resulting in a noticeable UI jerk that affects the overall user experience.
  • We've included screenshots to illustrate this issue below.

(before loading)
MoreForYouCleveland__Sports_Betting_Picks_Predictions_and_Promos_2023-07-27_16-10-50 (1)
(after loading)
MoreForYouCleveland__Sports_Betting_Picks_Predictions_and_Promos_2023-07-27_16-11-45
(source: Thread on lexical discord)

What We've Tried:

  • We've attempted to replicate the internal workings of the lexical editor to generate HTML strings and render them on the server.
  • You can find a sample implementation in this CodeSandbox link. [This codesandbox does not render SSR but you can get the idea of core logic]
  • However, this approach has numerous shortcomings, making it unsuitable for production use.
  • We have also tried polyfilling the DOM APIs but it breaks the NextJS.

Possible solutions

It would be immensely beneficial if we could have an integrated render-only editor that is compatible with server-side rendering. Maybe by using JSDOM to render the editor when DOM API's are not available.

@acywatson
Copy link
Contributor

Thanks! I think this makes sense.

However, this approach has numerous shortcomings, making it unsuitable for production use.

I'm curious about the shortcomings here? You don't need to really know about the internal workings of the Lexical editor. It's completely normal to traverse a serialized JSON tree and render it to another format (HTML in this case). This effectively exactly what the "render-only editor" you're asking for would do, AFAICT. We'd just be making it easier (for you) by wrapping it all up into a Lexical package.

@agarwalamn
Copy link
Author

agarwalamn commented Sep 9, 2023

@acywatson We appreciate the suggestion to parse the JSON and generate HTML, but it does introduce complexities, as it requires a good understanding of Lexical's internal logic, potentially involving reverse engineering. For instance, deciphering how JSON is generated for formatted text (e.g., bold, italic) or how list items are handled can be intricate tasks. Moreover, this approach could become obsolete if Lexical decides to change its node generation logic in the future.

Having an inbuilt server-side HTML generation capability within Lexical would be advantageous, ensuring that the logic remains up-to-date with any upcoming releases.

We also believe that this feature would benefit not only us but also the broader community, especially those using SSR (Server-Side Rendering) frameworks like React. The ability to render content on the server is a fundamental requirement for various applications and aligns with modern development practices and the promotion of SSR by popular frameworks. It would enhance the versatility and usability of Lexical for a wide range of use cases.

@morteymike
Copy link

I'll be doing something similar with SSR soon and am currently researching how I might approach this with Next JS.

One thought that comes to mind as a workaround:
When saving the editor state to the backend, we could export to HTML using this, then save that HTML to the backend along with the raw editor state.

This (in theory) would allow us to render the HTML string on server, and render the full editor on client.

There are some potential implications of this with setting inner HTML via string, you'd need to ensure the exported HTML is safe and secure, and you'd need to add in relevant css for the exported HTML, but it seems like a workable alternative to me.

@akichim21
Copy link

If possible, I would like you to parse the JSON and create a React component in a separate project. Read Only of course. Then, we can plug in customizations for some of them.

SSR as well, but when reading an article, I may want to fire React's LOGIC on the client.

Most Nodes (such as TextNode) are fine with exportDOM content, so I want a common React Component

@sashapetrovska
Copy link

I'll be doing something similar with SSR soon and am currently researching how I might approach this with Next JS.

One thought that comes to mind as a workaround: When saving the editor state to the backend, we could export to HTML using this, then save that HTML to the backend along with the raw editor state.

This (in theory) would allow us to render the HTML string on server, and render the full editor on client.

There are some potential implications of this with setting inner HTML via string, you'd need to ensure the exported HTML is safe and secure, and you'd need to add in relevant css for the exported HTML, but it seems like a workable alternative to me.

it's the same issue than Quilljs, just came form there for this, can't believe a project from Fracebook has the same dummy issue. most of us care about this because the SEO.

@2wheeh
Copy link
Contributor

2wheeh commented Jan 29, 2024

Hi guys! please check this out: https://lexical-nextjs-ssr.vercel.app/
Now it's possible with the latest lexical.
Check the html preview loaded on your network tab.

@acywatson
Copy link
Contributor

What do we need to close this issue? Perhaps some documentation?

@GermanJablo
Copy link
Contributor

What do we need to close this issue? Perhaps some documentation?

Well, the @2wheeh example is not that complex. But I have a feeling it's complex enough that the Lexical team will have to answer several questions about bugs or implementations.

Just an idea, I think the DX could be taken a little further.
An SSR property could be added to LexicalComposer or initialConfig that receives a DOM implementation (for example an instance of jsdom or happy-dom).

If window is undefined, then lexical runs that little utility with the dom instance passed to it. So that Lexical users only have to worry about one line of code.

@acywatson
Copy link
Contributor

What do we need to close this issue? Perhaps some documentation?

Well, the @2wheeh example is not that complex. But I have a feeling it's complex enough that the Lexical team will have to answer several questions about bugs or implementations.

Just an idea, I think the DX could be taken a little further. An SSR property could be added to LexicalComposer or initialConfig that receives a DOM implementation (for example an instance of jsdom or happy-dom).

If window is undefined, then lexical runs that little utility with the dom instance passed to it. So that Lexical users only have to worry about one line of code.

Let's discuss. I agree that documenting it officially is a burden on the team that requires some up front consideration and potentially mitigation (via DX, as you suggest).

@2wheeh
Copy link
Contributor

2wheeh commented Jan 31, 2024

Well, the @2wheeh example is not that complex. But I have a feeling it's complex enough that the Lexical team will have to answer several questions about bugs or implementations.

Yes, I agree that my example is not enough as is. For example, adding more plugins which return JSX element will make codes ugly easily while achieving zero layout shift. This is bad for DX.

Just an idea, I think the DX could be taken a little further.
An SSR property could be added to LexicalComposer or initialConfig that receives a DOM implementation (for example an instance of jsdom or happy-dom).

If window is undefined, then lexical runs that little utility with the dom instance passed to it. So that Lexical users only have to worry about one line of code.

I love this !

@jmargatan
Copy link

Slightly tangential to this approach. We also had a use case to render Lexical content in NextJS with SSR. We ended up doing the more hacky approach. We created a native React Server Component for each standard Lexical components. Not only it renders fast, it also comes with any NextJS' goodies (hydration, etc.), so natively supports existing client/server components. The one-time effort to create the RSC copy was 1 day ish. The only maintenance item is to sync our RSC copy with Lexical's, but we don't see a lot of changes on the presentation-side standard HTML component. The custom components are already an RSC so no additional cost.

Sadly, our RSC codes are tangled with our internal tooling but let me know if any of you are interested in some pointers. 🙏

Here is a demo page:
https://cmty.space/vantient-brand/c/straw-hat-scavenger-hunt-mRpUIbd6ihr5ezHJmhR5KUbyB4eTACLs

@GermanJablo
Copy link
Contributor

@jmargatan I can't see how that link fits into all this. Maybe if you could show some code to explain your approach?

@jmargatan
Copy link

Essentially there are 2 separate experiences:

  • editable, this is the Lexical editor, and
  • non-editable, this is the SSR version of the content. Note that interaction is still supported.

After any modification in editable state, we persist the SerializedEditorState JSON in the DB.

During non-editable serving / SSR, we (built a parser that) walk through the stored SerializedEditorState JSON and use the corresponding RSC-equivalent for each LexicalNode as identified by the type attributes.

Sorry, our current code is tangled with internal implementation, but here is some hacky pseudocode:

const parseNode = <T extends SerializedLexicalNode>(node: T, key: string): ReactNode => {
  if (node.type == 'text') {
    // For a node that has children, we recursively run this `parseNode` on each children.
    // For a node that refers to our custom RSC components, we just render the component.
    return <NonEditableText .../>
  } else if (...) {
    ...
  }
}

For actual implementation you can mimic LexicalEditor's "registerXYZPlugin" pattern instead of if-else.

The result is a server-rendered RSC streamed to the browser. React Suspense, if used, within any of the node will work as expected.

Jumping between the 2 experience (editable and non-editable) means replacing the RSC with Lexical editor.

@sakhmedbayev
Copy link

Can we simply persist the HTML version for the editor state and render it server-side?

@StyleT StyleT added the contribution-week Issues that are good to be picked up for the contribution week label Apr 5, 2024
@mbohovic
Copy link

mbohovic commented Jul 5, 2024

Hi guys! please check this out: https://lexical-nextjs-ssr.vercel.app/ Now it's possible with the latest lexical. Check the html preview loaded on your network tab.

what if I don't use next.js, but only react, and there I send the component and its content directly to ReactDomServer.renderToStaticMarkup.

  const html = ReactDomServer.renderToStaticMarkup(
      <HtmlPreview value={jsonData} />,
  )

The code entered in this way, where there is a promise, will not work for me. Is it not possible to get html from lexical json using a simple parsing function that is native in lexical?

@onurhb
Copy link

onurhb commented Oct 22, 2024

Hi guys! please check this out: https://lexical-nextjs-ssr.vercel.app/ Now it's possible with the latest lexical. Check the html preview loaded on your network tab.

what if I don't use next.js, but only react, and there I send the component and its content directly to ReactDomServer.renderToStaticMarkup.

  const html = ReactDomServer.renderToStaticMarkup(
      <HtmlPreview value={jsonData} />,
  )

The code entered in this way, where there is a promise, will not work for me. Is it not possible to get html from lexical json using a simple parsing function that is native in lexical?

I am interested too in a solution for renderToStaticMarkup.

We are currently using renderToStaticMarkup to generate PDF using headless Chrome. Exporting Lexical state as HTML, then using this with dangerouslySetInnerHTML does not produce same view as React components. For example, the table plugin will export DOM with different styles (adds border). To resolve this, we need to create overrides for the exported DOM. Our goal is to ensure that the editor's appearance in the browser matches the PDF output.

@akshayskurup
Copy link

Hi guys! please check this out: https://lexical-nextjs-ssr.vercel.app/ Now it's possible with the latest lexical. Check the html preview loaded on your network tab.

You're using jsdom, which is around 593.3 kB minified and gzipped. Honestly, it's a great choice for smaller applications, and I can see the effort you've put into making it work effectively. However, for larger applications, the size can start to matter, so it's something worth keeping in mind as your project grows. Great work so far!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution-week Issues that are good to be picked up for the contribution week enhancement Improvement over existing feature
Projects
None yet
Development

No branches or pull requests