Next.js ReferenceError: document is not defined on prerendering /write page

80 views Asked by At

I'm building an open-source blogging platform with Next.js and encountering a frustrating error during deployment. When trying to prerender the /write page, I receive the following error:

ReferenceError: document is not defined
at Object.<anonymous> (/vercel/path0/.next/server/chunks/34.js:10601:12)

I've tried various solutions like checking for DOM access in server-side code and wrapping such code with conditional statements, but the error persists.

Additional Information:

Next.js version: 13.4.19 Project repository: https://github.com/MuhammadKaifNazeer/blogvarse/tree/main

Here is the code of ./write/page.jsx

"use client";

import Image from "next/image";
import styles from "./writePage.module.css";
import { useEffect, useState } from "react";
import "react-quill/dist/quill.bubble.css";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import {
  getStorage,
  ref,
  uploadBytesResumable,
  getDownloadURL,
} from "firebase/storage";
import { app } from "@/utils/firebase";
import ReactQuill from "react-quill";

const WritePage = () => {
  const { status } = useSession();
  const router = useRouter();

  const [open, setOpen] = useState(false);
  const [file, setFile] = useState(null);
  const [media, setMedia] = useState("");
  const [value, setValue] = useState("");
  const [title, setTitle] = useState("");
  const [catSlug, setCatSlug] = useState("");

  useEffect(() => {
    const storage = getStorage(app);
    const upload = () => {
      const name = new Date().getTime() + file.name;
      const storageRef = ref(storage, name);

      const uploadTask = uploadBytesResumable(storageRef, file);

      uploadTask.on(
        "state_changed",
        (snapshot) => {
          const progress =
            (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
          console.log("Upload is " + progress + "% done");
          switch (snapshot.state) {
            case "paused":
              console.log("Upload is paused");
              break;
            case "running":
              console.log("Upload is running");
              break;
          }
        },
        (error) => {},
        () => {
          getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
            setMedia(downloadURL);
          });
        }
      );
    };

    file && upload();
  }, [file]);

  if (status === "loading") {
    return <div className={styles.loading}>Loading...</div>;
  }

  if (status === "unauthenticated") {
    router.push("/");
  }

  const slugify = (str) =>
    str
      .toLowerCase()
      .trim()
      .replace(/[^\w\s-]/g, "")
      .replace(/[\s_-]+/g, "-")
      .replace(/^-+|-+$/g, "");

  const handleSubmit = async () => {
    const res = await fetch("/api/posts", {
      method: "POST",
      body: JSON.stringify({
        title,
        desc: value,
        img: media,
        slug: slugify(title),
        catSlug: catSlug || "style", 
      }),
    });

    if (res.status === 200) {
      const data = await res.json();
      router.push(`/posts/${data.slug}`);
    }
  };

  return (
    <div className={styles.container}>
      <input
        type="text"
        placeholder="Title"
        className={styles.input}
        onChange={(e) => setTitle(e.target.value)}
      />
      <select className={styles.select} onChange={(e) => setCatSlug(e.target.value)}>
        <option value="style">style</option>
        <option value="fashion">fashion</option>
        <option value="food">food</option>
        <option value="culture">culture</option>
        <option value="travel">travel</option>
        <option value="coding">coding</option>
      </select>
      <div className={styles.editor}>
        <button className={styles.button} onClick={() => setOpen(!open)}>
          <Image src="/plus.png" alt="" width={16} height={16} />
        </button>
        {open && (
          <div className={styles.add}>
            <input
              type="file"
              id="image"
              onChange={(e) => setFile(e.target.files[0])}
              style={{ display: "none" }}
            />
            <button className={styles.addButton}>
              <label htmlFor="image">
                <Image src="/image.png" alt="" width={16} height={16} />
              </label>
            </button>
            <button className={styles.addButton}>
              <Image src="/external.png" alt="" width={16} height={16} />
            </button>
            <button className={styles.addButton}>
              <Image src="/video.png" alt="" width={16} height={16} />
            </button>
          </div>
        )}
        <ReactQuill
          className={styles.textArea}
          theme="bubble"
          value={value}
          onChange={setValue}
          placeholder="Tell your story..."
        />
      </div>
      <button className={styles.publish} onClick={handleSubmit}>
        Publish
      </button>
    </div>
  );  
};

export default WritePage;

What I tried:

  1. DOM Access Checks: I carefully reviewed my code for any attempts to access DOM elements (like document) on the server side during prerendering.
  2. I researched common causes of "ReferenceError: document is not defined" errors in Next.js projects and explored various community-suggested solutions

What I expected:

I anticipated a successful prerendering of the /write page without any errors. I expected the page to load correctly and function as intended, both on the server and client side.

1

There are 1 answers

0
Beast80K On

Problem :

ReferenceError: document is not defined

Possible Cause :

import ReactQuill from "react-quill";

Solution :

Some libraries don't support NextJS, hence they have to be dynamically imported.

Try changing :

import ReactQuill from "react-quill";

to

import dynamic from 'next/dynamic';
const ReactQuill = dynamic(() => import("react-quill"), 
    { 
        loading: () => <p>Editor Loading...</p>, 
        ssr: false 
    }
);

Please Read :

  1. next/dynamic : https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#nextdynamic
  2. With no SSR : https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#with-no-ssr