Nextjs document is not defined issue

 最近在做Nextjs项目,它有server component & client component

  • 默认行为:在 app 目录下,Next.js 默认将所有组件(如layout, page)视为服务端组件。
  • 客户端组件:如果需要使用客户端功能(如 useStateuseEffect),可以在组件顶部添加 "use client" 指令,将其转换为客户端组件。
  • 混合使用:可以在 Layout 中同时使用服务端和客户端组件,服务端组件用于初始渲染,客户端组件处理交互。

这时候如果你想在page里引用一个客户端组件的时候(如 Modal),就会报错:error ReferenceError: document is not defined

下面是我的例子以及解决方法

例子说明:用 Nextjs Intercepting Routes 拦截路由 /login,使LoginForm在dialog 中打开

1. src/app/@modal/(.)login/page.tsx

import { LoginForm } from "@/components/features/login/LoginForm";
import Modal from "@/components/ui/Modal";

// by default, the page is server component which will be compiled in server side, browser just run it
export default function LoginModal() {
  return (
    // Modal is client component which will be rendered in browser
    <Modal>
      <title>Please login</title>
      <LoginForm />
    </Modal>
  );
}

2. src/app/login/page.tsx

import { LoginForm } from "@/components/features/login/LoginForm";
import { Box, Typography } from "@mui/material";

export default function LoginPage() {
  return (
    <Box>
      <Typography id="modal-modƒal-title" variant="h6" component="h2">
        Login Page
      </Typography>
      <LoginForm />
    </Box>
  );
}

3. src/components/ui/Modal.tsx (fix solution here)

"use client";

import { useRouter } from "next/navigation";
import React, { ElementRef, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";

export default function Modal({ children }: { children: React.ReactNode }): React.ReactPortal | null {
  // start: issue solution
  const [isMounted, setIsMounted] = useState(false);

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

   //end: issue solution
  
  const router = useRouter();
  const dialogRef = useRef<ElementRef<"dialog">>(null);

  useEffect(() => {
    if (!dialogRef.current?.open) {
      dialogRef.current?.showModal();
    }
  }, []);

  function onDismiss() {
    router.back();
  }

  return isMounted ? createPortal(
    <div className="modal-backdrop">
      <dialog ref={dialogRef} className="modal" onClose={onDismiss}>
        {children}
        <button onClick={onDismiss} className="close-button" />
      </dialog>
    </div>,
    document.getElementById("modal-root")!
  ) : null;
}

4. src/app/layout.tsx

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ThemeProvider, CssBaseline } from "@mui/material";
import theme from "@/theme";
import FullLayout from "@/components/layouts/FullLayout";
import ThemeProviderWrapper from "@/context/ThemeProviderWrapper";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Nextjs Sample",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
  header,
  footer,
  modal
}: Readonly<{
  children: React.ReactNode;
  header: React.ReactNode;
  footer: React.ReactNode;
  modal: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <FullLayout>
          <ThemeProviderWrapper>
            <CssBaseline /> {/* Normalize CSS and apply baseline styles */}
            {modal}
            <div id="modal-root" />
            {header}
            <main>{children}</main>
            {footer}
          </ThemeProviderWrapper>
        </FullLayout>
      </body>
    </html>
  );
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值