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

Server-Side Rendering (SSR), NextJS, etc. #919

Open
alexkrolick opened this issue Sep 15, 2023 · 6 comments
Open

Server-Side Rendering (SSR), NextJS, etc. #919

alexkrolick opened this issue Sep 15, 2023 · 6 comments
Labels

Comments

@alexkrolick
Copy link
Collaborator

alexkrolick commented Sep 15, 2023

📌 Pinning this issue as a response to anyone looking for support rendering Quill in NextJS, Remix, Gatsby, Astro etc. via server-side rendering.

Quill requires a full DOM - you can't render it without a reference to document. Additionally, if you do server-side render the editor, the DOM will be reset when it initializes the Quill instance clientside. So the best workaround is to skip rendering the ReactQuill component in SSR, and render a fallback element like a <textarea> instead.

import dynamic from 'next/dynamic'

const DynamicQuill = dynamic(() => import('react-quill'), { ssr: false })

function App() {
  if (!document) return <textarea id="editor-fallback" />
  return <DynamicQuill />
}

NextJS-specific workarounds have been discussed here: #897

Previous issues - please read before creating a new one:

https://github.com/zenoamaro/react-quill/issues?q=is%3Aissue+document

#917 #718 #648 #699 #894 #863 #292 #910


@alexkrolick alexkrolick pinned this issue Sep 15, 2023
@wolfcreative
Copy link

wolfcreative commented Nov 8, 2023

The component should be called like this:

import dynamic from 'next/dynamic'

const DynamicQuill = dynamic(() => import('react-quill'), { ssr: false })

And already in the code itself:

<DynamicQuill theme="snow" />

@alexkrolick
Copy link
Collaborator Author

@wolfcreative thanks, yes, dynamically importing the code in NextJS with ssr: false will make sure it runs clientside only.

@philohelp
Copy link

philohelp commented Dec 23, 2023

I'd think you better encapsulate a wrapper than the react-quill itself.

"use client";

import dynamic from "next/dynamic";

const Editor = dynamic(() => import("./editor"), {
  loading: () => <p>Loading...</p>,
  ssr: false,
});

export default function Encapsulator() {
  return <Editor />;
}

Then you can do whatever you want in the Editor module, it will only be rendered on the client.

@vutran221097
Copy link

vutran221097 commented Jan 11, 2024

Here is React SSR Solution if some one need

import { useMemo, useRef, useState, useEffect } from 'react';
import 'react-quill/dist/quill.snow.css';
import { Controller } from 'react-hook-form';

const toolbarOptions = [
  [{ font: [] }],
  [{ align: [] }],
  ['bold', 'italic', 'underline', 'strike'],
  ['blockquote', 'code-block'],
  [{ header: [1, 2, 3, 4, 5, 6, false] }],
  [{ list: 'ordered' }, { list: 'bullet' }],
  [{ script: 'sub' }, { script: 'super' }],
  [{ color: [] }, { background: [] }],
  ['link', 'image'],
  ['clean'],
];

interface QuillEditorProps {
  form: any;
  name: string;
}

const QuillEditor = ({ form, name }: QuillEditorProps) => {
  const { setValue } = form;
  const { control } = form;
  const quillRef = useRef(null);
  const [ReactQuill, setReactQuill] = useState(null);

  useEffect(() => {
    if (typeof window !== 'undefined') {
      import('react-quill').then((QuillModule) => {
        setReactQuill(() => QuillModule.default);
      });
    }
  }, []);

  // handle upload image and insert to editor
  const handleUploadImage = async () => {
    const quill = quillRef.current?.getEditor();

    if (quill) {
      const input = document.createElement('input');
      input.setAttribute('type', 'file');
      input.click();

      input.onchange = async () => {
        const file = input.files[0];
        if (/^image\//.test(file.type)) {
          const res = await ...yourImageApi
          const range = quill.getSelection(true);
          if (range) {
            quill.insertEmbed(range.index, 'image', res.data[0].url);
          } else {
            quill.clipboard.dangerouslyPasteHTML(
              quill.getLength(),
              `<img src="${res.data[0].url}" alt=""/>`
            );
          }
        }
      };
    }
  };

  // handle change editor
  const handleChange = (html: string) => {
    setValue(name, html);
  };

  const module = useMemo(
    () => ({
      toolbar: {
        container: toolbarOptions,
        handlers: {
          image: handleUploadImage,
        },
      },
    }),
    []
  );

  return (
    <Controller
      name={name || ''}
      control={control}
      render={() => {
        if (typeof window !== 'undefined' && ReactQuill) {
          const Quill = ReactQuill; // Save a reference to ReactQuill

          return (
            <Quill
              ref={quillRef}
              value={form.watch(name)}
              theme='snow'
              modules={module}
              onChange={handleChange}
              placeholder='Write something awesome...'
            />
          );
        }
        return null;
      }}
    />
  );
};

export default QuillEditor;

@qTheSky
Copy link

qTheSky commented Apr 7, 2024

how to register custom module like quill emoji?

@dchenk
Copy link

dchenk commented Apr 15, 2024

Anyone here who is looking to purely render Quill rich text, you can use quill-delta-to-react. The library supports all the native Quill formats as well as custom elements and provides a lot of control over the rendering.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants