SSRでreact-quillを使うときの対応(Next.js)
とりあえず使えればいい
何も考えずにNext.jsでreact-quillを使うとこうなる。
$ npm install --save-dev react-quill
import { useState } from 'react' import ReactQuill from 'react-quill' import 'react-quill/dist/quill.snow.css' export default function Quill() { const [value, setValue] = useState<string>() return ( <ReactQuill theme="snow" value={value} onChange={(v) => setValue(v)} /> ) }
これだと次のようなエラーが出る。
ReferenceError: document is not defined
対処法はこちらを参照(1)。
importしたreact-quillの処理が働いているのだが、SSRモードでwindow.document
が生成される前なので、エラーとなってしまう。
これはnext/dynamic
を利用すると解決する。next/dynamic
を使うことで、importするのを画面のレンダリングまで待つことができる。
修正後はこうなる。
import dynamic from 'next/dynamic' import { useState } from 'react' import 'react-quill/dist/quill.snow.css' const ReactQuill = dynamic(() => import('react-quill'), { ssr: false }) export default function Quill() { const [value, setValue] = useState<string>() return ( <ReactQuill theme="snow" value={value} onChange={(v) => setValue(v)} /> ) }
react-quillを使うだけであれば、ここまでで十分。
ReactQuillインスタンスへのアクセス
ここからは、文字数カウントなどをするためにReactQuillインスタンスへのアクセスを行うための対応。
import dynamic from 'next/dynamic' import { useMemo, useRef, useState } from 'react' import 'react-quill/dist/quill.snow.css' const ReactQuill = dynamic(() => import('react-quill'), { ssr: false }) export default function Quill() { const [value, setValue] = useState<string>() const quillRef = useRef<any>() // 本当はuseRef<ReactQuill>にしたいけど、dynamic importしているのでanyにするしかなさそう // 文字数をカウントしたい const strCount = useMemo(() => { if (!quillRef.current) return const editor = quillRef.current.getEditor() const unprivilegedEditor = quillRef.current.makeUnprivilegedEditor(editor) return unprivilegedEditor.getLength() - 1 }, [value]) return ( <> <ReactQuill ref={ref} theme="snow" value={value} onChange={(v) => setValue(v)} /> <p>文字数:{strCount}</p> </> ) }
これだと次のようなエラーが出るので、続けて対応していく。
Property 'ref' does not exist on type 'IntrinsicAttributes & ReactQuillProps'
これについては、こちらが一番詳しかったので参照。(3)
dynamic importするときに、refを設定できるようにReactQuillPropsを継承してinterfaceを作成してあげるとできた。
import dynamic from 'next/dynamic' import { useMemo, useRef, useState } from 'react' import { ReactQuillProps } from 'react-quill' import 'react-quill/dist/quill.snow.css' const ReactQuill = dynamic( async () => { const { default: RQ } = await import('react-quill') interface Props extends ReactQuillProps { forwardedRef: any } return (props: Props) => <RQ ref={props.forwardedRef} {...props} /> }, { ssr: false } ) export default function Quill() { const [value, setValue] = useState<string>() const quillRef = useRef<any>() // 本当はuseRef<ReactQuill>にしたいけど、dynamic importしているのでanyにするしかなさそう // 文字数をカウントしたい const strCount = useMemo(() => { if (!quillRef.current) return const editor = quillRef.current.getEditor() const unprivilegedEditor = quillRef.current.makeUnprivilegedEditor(editor) return unprivilegedEditor.getLength() - 1 }, [value]) return ( <> <ReactQuill forwardedRef={quillRef} theme="snow" value={value} onChange={(v) => setValue(v)} /> <p>文字数:{strCount}</p> </> ) }
gitの差分はこちら
https://github.com/di-kotobuki/react-quill/commit/e417f36449b57ae7867fd8a8f634a64e43ff4455