1

I have a NextJS app that uses react-quill as a rich text editor library.

Everything went fine importing the library dynamically avoiding SSR issues, but since I upgraded to Next 13 I don't manage to get it to work.

I get the following error in the build logs:

.../quill-test/node_modules/quill/dist/quill.js:7661
var elem = document.createElement('div');
           ^

ReferenceError: document is not defined

Here is my implementation of the RichTextEditor component, as you can see I tried a few of the things that are suggested in the issues you find on Github and elswhere:

import 'react-quill/dist/quill.snow.css';
import dynamic from 'next/dynamic';
import styles from './RichTextEditor.module.css';
import classnames from 'classnames';

// Importing this client side as it mounts on the document object
const ReactQuill = dynamic(() => import('react-quill'), { ssr: false });

const registerQuill = async () => {
  const { Quill } = await (await import('react-quill')).default;

  const Link = Quill.import('formats/link');
  // Override the existing property on the Quill global object and add custom protocols
  Link.PROTOCOL_WHITELIST = [
    'http',
    'https',
    'mailto',
    'tel',
    'radar',
    'rdar',
    'smb',
    'sms',
  ];

  class CustomLinkSanitizer extends Link {
    static sanitize(url: string) {
      // Run default sanitize method from Quill
      const sanitizedUrl = super.sanitize(url);

      // Not whitelisted URL based on protocol so, let's return `blank`
      if (!sanitizedUrl || sanitizedUrl === 'about:blank') return sanitizedUrl;

      // Verify if the URL already have a whitelisted protocol
      const hasWhitelistedProtocol = this.PROTOCOL_WHITELIST.some(
        (protocol: string) => {
          return sanitizedUrl.startsWith(protocol);
        }
      );

      if (hasWhitelistedProtocol) return sanitizedUrl;

      // if not, then append only 'http' to not to be a relative URL
      return `https://${sanitizedUrl}`;
    }
  }

  Quill.register(CustomLinkSanitizer, true);
};

registerQuill();

interface RichTextEditorProps {
  value: string;
  setValue: (value: string) => void;
  placeholder?: string;
  isError?: boolean;
}

const RichTextEditor = ({
  value,
  setValue,
  placeholder,
  isError,
}: RichTextEditorProps) => {
  return (
    <ReactQuill
      theme="snow"
      value={value}
      onChange={setValue}
      className={classnames(styles.container, isError && styles.error)}
      placeholder={placeholder}
    />
  );
};

export default RichTextEditor;

I reproduced it in an isolated project if you want to run it and see the full log.

Any help/advice appreciated!

M

Matteo Carpi
  • 522
  • 6
  • 23
  • I believe this is something to do with Client Side components. `Document is not defined` tells that it can't find the document object so it tries to run your code on SSR. To specify that your component is a CS component, make sure to include `'use client'` in top of your file. Because the new NextJS v13 defaults to RSC. – Benji Aug 29 '23 at 10:34
  • Something in that direction, but 'use client' wasn't enough, see the answer I posted. Thanks for the help though! – Matteo Carpi Aug 29 '23 at 15:12
  • Happy that you managed to sort it out – Benji Aug 30 '23 at 08:27

1 Answers1

0

turns out the badboy that was still running on the server was the registerQuill() call.

I solved moving it inside the component and only calling it client side after component mount.

...
const RichTextEditor = ({
  value,
  setValue,
  placeholder,
  isError,
}: RichTextEditorProps) => {
const [isMounted, setIsMounted] = useState(false)

useEffect(() => {
  setIsMounted(true)
  registerQuill()
}, [])

if (!isMounted) return null

  return (
    <ReactQuill
      theme="snow"
      value={value}
      onChange={setValue}
      className={classnames(styles.container, isError && styles.error)}
      placeholder={placeholder}
    />
  );
};
Matteo Carpi
  • 522
  • 6
  • 23