react-editor-js icon indicating copy to clipboard operation
react-editor-js copied to clipboard

Support for SSR

Open kyryloren opened this issue 4 years ago • 22 comments

Hello.

This module doesn't support server side rendering. When building on Netlify or on local production, I get an error that says "'window' is not available during server side rendering", and a bunch of code that is returned from the EditorJS plugin(s).

This is how I currently fixed this:


const MyComponent = () => {
  if (typeof window !== 'undefined') {
    const Editor = require('./editor').default;

    return <Editor />;
  }

  return null;
};

Even though this works, it's very 'hacky'. Please fix this.

I'm using GatsbyJS version 2.23.12.

kyryloren avatar Jul 17 '20 23:07 kyryloren

Update: While this is being patched you can add the following to your gatsby-node.js file (if you're using Gatsby). If you aren't using Gatsby, you can use https://github.com/gregberge/loadable-components

exports.onCreateWebpackConfig = ({ stage, actions, loaders }) => {
  if (stage === 'build-html') {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /react-editor-js/,
            use: loaders.null(),
          },
          {
            test: /@editorjs/,
            use: loaders.null(),
          },
        ],
      },
    });
  }
};

You can read more about this code here: https://www.gatsbyjs.org/docs/debugging-html-builds/#fixing-third-party-modules

kyryloren avatar Jul 20 '20 18:07 kyryloren

I've been using NextJS and SSR with Editor JS and this works for me

import React, { useEffect, useRef, useState } from 'react';
import EditorJs from 'react-editor-js';
import List from '@editorjs/list';
import Paragraph from '@editorjs/paragraph';
const EDITOR_JS_TOOLS = {
  paragraph: {
    class: Paragraph,
    inlineToolbar: true,
  },
  list: {
    class: List,
    inlineToolbar: true,
  },
};
export default ({
  id,
  label,
  labelStyle,
  editorContainerStyle,
  getValue,
  Data,
}) => {
  const instanceRef = useRef(null);
  async function handleSave() {
    // const savedData = await
    instanceRef.current
      .save()
      .then((outputData) => {
        function replaceAll(str, find, replace) {
          function escapeRegExp(string) {
            return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
          }
          return str.replace(new RegExp(escapeRegExp(find), 'g'), replace);
        }
        let stringData = JSON.stringify(outputData);
        let formattedData = replaceAll(stringData,'<b>','<strong>');
        formattedData = replaceAll(stringData, '<b/>', '<strong/>');
        getValue(formattedData);
      })
      .catch((error) => {
        console.log('Saving failed: ', error);
      });
  }
  console.log(Data)
  return (
    <div>
      <style global jsx>
        {`
          .ce-block__content {
            max-width: 100%;
            margin: 0 5rem;
          }
          .ce-toolbar__content {
            max-width: 100%;
            margin: 0 3.5rem;
            position: relative;
          }
          .editor-style {
            border-radius: 0.25rem;
            border-color: #ced4da;
          }
        `}
      </style>
      <label
        htmlFor={id}
        className={
          typeof labelStyle === 'undefined' || labelStyle === null
            ? `label-style`
            : `label-style {labelStyle}`
        }>
        {label}
      </label>
      <div
        className={`border rounded ${
          typeof editorContainerStyle !== undefined
            ? editorContainerStyle
            : null
        }`}>
        <EditorJs
          holder={id}
          instanceRef={(instance) => (instanceRef.current = instance)}
          data={
            typeof Data === 'string' && Data.length !== 0
              ? console.log(JSON.parse(Data))
              : Data
          }
          onChange={(e) => handleSave(e)}
          tools={EDITOR_JS_TOOLS}>
          <div id={id} />
        </EditorJs>
      </div>
    </div>
  );
};

Moikapy avatar Jul 23 '20 13:07 Moikapy

Hey @Moikapy,

Can you please share the code? I copied your above snippet in an index.js file for NextJS but got the below error -

Server Error

ReferenceError: window is not defined
This error happened while generating the page. Any console logs will be displayed in the terminal window.

Thanks

nithinkashyapn avatar Aug 11 '20 17:08 nithinkashyapn

Hi! everyone.

By default, editor-js does not support SSR. But i think if modern fornt-end library, should help users not to care.

Therefore, this issue not close differently from other related issues (#31, #1), and will be updated asap.

Thanks!

Jungwoo-An avatar Aug 24 '20 01:08 Jungwoo-An

I use import loadable from '@loadable/component'

example :

import loadable from '@loadable/component'
const EditorJs = loadable(() => import('~/utils/editors'))

thinnakrit avatar Dec 14 '20 16:12 thinnakrit

This is an open issue with @editorjs https://github.com/codex-team/editor.js/issues/1036

gufranmirza avatar Jan 29 '21 11:01 gufranmirza

NextJS has a function to ensure that React components work properly with dynamic imports. Something like this should work in theory:

import dynamic from "next/dynamic";

const EditorJs = dynamic(() => import("react-editor-js"));
// Use EditorJs as component

solidassassin avatar Feb 01 '21 23:02 solidassassin

This should work:

const EditorJs = dynamic(() => import("react-editor-js"), {
  ssr: false,
  loading: () => <p>loading editor.js ...</p>,
});

HaNdTriX avatar Feb 10 '21 10:02 HaNdTriX

NextJS has a function to ensure that React components work properly with dynamic imports. Something like this should work in theory:

import dynamic from "next/dynamic";

const EditorJs = dynamic(() => import("react-editor-js"));
// Use EditorJs as component

This will work @nithinkashyapn

Moikapy avatar Feb 15 '21 14:02 Moikapy

Hi guys, question, editor is ok now, but, when i try to load a plugin, app crashes, asking for "window" again

pacochim avatar Mar 04 '21 01:03 pacochim

Make editor in another component and import it in your main page as a dynamic component

this is working fine while ssr and without any type error because importing react-editor-js as a dynamic component it loses its type definitions

eg: my Editor component ( RTE.tsx )

import EditorJs from "react-editor-js";
import { EDITOR_JS_TOOLS } from "./constants";

export default function RTE_Component() {
  return (
    <EditorJs
      tools={EDITOR_JS_TOOLS}
      data={[
        time: 1556098174501,
        blocks: [{ type: "header", data: { text: "Guides", level: 2 } }],
        version: "2.12.4",
      ]}
      inlineToolbar={true}
      hideToolbar={true}
    />
  );
}

my index.tsx

import dynamic from "next/dynamic";
import Head from "next/head";

const RTE_Component = dynamic(() => import("components/RTE"), { ssr: false });

export default function Home() {
  return (
    <>
      <Head>Localhost - Home</Head>

      <form action="http://localhost:3000/api/post" method="post">
        <RTE_Component />
      </form>
    </>
  );
}

MohdAhmad1 avatar Mar 05 '21 02:03 MohdAhmad1

@bilordigor the solution by @zakiAzfar helps with the plugins as well: plugins have to be imported and configured in the component imported dynamically with ssr disabled.

artm avatar Apr 11 '21 10:04 artm

Using the require statement in the useEffect hook solved this issue for me in Nextjs. Thanks

import {useEffect} from 'react'


export default function editor() {
    useEffect(() => {
        const Editorjs = require('@editorjs/editorjs')
        new Editorjs('editorjs')
    }, [])


    return(
        <div id='editorjs'></div>
    )
}

thealpha93 avatar May 14 '21 07:05 thealpha93

Make the editorJS a child component, then you just need to import dynamically in your pages.

Darren120 avatar Oct 08 '21 20:10 Darren120

I am also running into issues in running nextJs with this editorComponent. I am running :

import dynamic from "next/dynamic";

const EditorJs = dynamic(() => import("react-editor-js"));
// Use EditorJs as component

and it should work, but it doesn't which is weird ...

RE:EDIT:

Actually it works now. I have downgraded to version 1.9.0. I was running in version 2. So maybe there is something wrong with version 2. Error is something like

Check your code at Editor.js:11. at Editor at LoadableImpl

ngmiduc avatar Dec 22 '21 19:12 ngmiduc

This worked for me using the newest version.

Editor.tsx

import { useCallback, useRef } from "react";
import { createReactEditorJS } from "react-editor-js";

const Editor = () => {
  const ReactEditorJS = createReactEditorJS();

  const editorJS = useRef(null);

  const handleInitialize = useCallback((instance) => {
    editorJS.current = instance;
  }, []);

  const handleSave = useCallback(async () => {
    const savedData = await editorJS.current.save();
  }, []);

  return <ReactEditorJS onInitialize={handleInitialize} />;
};

export default Editor;

index.tsx

import React from "react";
import dynamic from "next/dynamic";

const EditorJs = dynamic(() => import("./Editor"), {
  ssr: false,
});

const index = () => {
  return <EditorJs />;
};

export default index;

adriangzz avatar Dec 23 '21 21:12 adriangzz

If I need initial data on ssr ?

dmitryshelomanov avatar Jan 24 '22 11:01 dmitryshelomanov

react-editor-js is support SSR partially.

const ReactEditorJS = createReactEditorJS();

<ReactEditorJS holder="ssrHolder" /> // works fine!

Jungwoo-An avatar Jan 31 '22 05:01 Jungwoo-An

const tools = dynamic(() => import("./tools"), { ssr: false });

sdarnadeem avatar Apr 26 '22 13:04 sdarnadeem

How to do dynamic import in nextjs 13 beta version? What would be the equivalent code in beta next 13 js? // let CustomEditor = dynamic(() => import("../../components/tools"), { // ssr: false, // }); .....

https://github.com/codex-team/editor.js/discussions/2223#discussion-4674964

const tools = dynamic(() => import("./tools"), { ssr: false });

Chiranjeev-droid avatar Dec 18 '22 13:12 Chiranjeev-droid

Anyone tried rendering multiple holders with dynamic divs/holder ids? - If I disable SSR I get TypeError:EditorJS is not a constructor.

shaikharfan7 avatar Apr 07 '23 14:04 shaikharfan7

@Chiranjeev-droid Wackiest solution, couldn't think of anything else though.

If anyone else knows better approach for Next 13-14, please share and tag me

"use client";
import { createReactEditorJS } from "react-editor-js";
import { EDITOR_JS_TOOLS } from "./tools";
import { useEffect } from "react";
import ReactDOM from "react-dom";
const ReactEditorJS = createReactEditorJS();
function Editor({ blocks }: any) {
  useEffect(() => {
    const holder = document.getElementById("holder");
    if (!holder) return;
    const Editor = (
      <ReactEditorJS
        tools={EDITOR_JS_TOOLS}
        defaultValue={blocks}
        placeholder={`Write from here...`}
        minHeight={900}
        inlineToolbar={true}
      />
    );
    const portalElement = ReactDOM.createPortal(Editor, holder);
    // @ts-ignore
    ReactDOM.createRoot(holder).render(portalElement);
  }, [blocks]);
  return <></>;
}

export default Editor;

P.S. Don't forget to set position of holder to relative

Eugene-Mokrushin avatar Jan 06 '24 18:01 Eugene-Mokrushin