Next.js:文档未定义

99
我正在尝试创建一个付款表单,让人们可以支付,但是我一直收到这个错误信息。

document is not defined

我正在使用Next.js。请参见下面的代码:

import React from "react";
import {Elements, StripeProvider} from 'react-stripe-elements';
import CheckoutForm from '../../components/Payment/CheckoutForm';
import { useRouter } from 'next/router';


var stripe_load = () => {
    var aScript = document.createElement('script');
    aScript.type = 'text/javascript';
    aScript.src = " https://js.stripe.com/v3/";

    document.head.appendChild(aScript);
    aScript.onload = () => {

    };
};

function Payment({host}) {
    const key = host.includes('localhost') ? 'test' : 't';

    stripe_load();

    const router = useRouter();

    return (
        <div className="Payment Main">
            <StripeProvider apiKey={key}>
                <Elements>
                    <CheckoutForm planid={router.query.id}/>
                </Elements>
            </StripeProvider>
            <br/>
            <br/>
            <p>Powered by Stripe</p>
        </div>
    );
};


Payment.getInitialProps = async ctx => {
    return { host: ctx.req.headers.host }
};

export default Payment

2
相关链接:https://dev59.com/hVsW5IYBdhLWcg3wXmO5 - ATYB
12个回答

64

我认为,在服务器渲染模式下,文档是未定义的。你可以在类生命周期方法或useEffect中使用它。

import React, {useEffect} from "react";
import {Elements, StripeProvider} from 'react-stripe-elements';
import CheckoutForm from '../../components/Payment/CheckoutForm';
import { useRouter } from 'next/router';


var stripe_load = () => {
    var aScript = document.createElement('script');
    aScript.type = 'text/javascript';
    aScript.src = " https://js.stripe.com/v3/";

    document.head.appendChild(aScript);
    aScript.onload = () => {

    };
};

function Payment({host}) {
    const key = host.includes('localhost') ? 'test' : 't';

    useEffect(() => {
      var aScript = document.createElement('script');
       aScript.type = 'text/javascript';
       aScript.src = " https://js.stripe.com/v3/";

       document.head.appendChild(aScript);
       aScript.onload = () => {

       };
    }, [])
    //stripe_load();

    const router = useRouter();

    return (
        <div className="Payment Main">
            <StripeProvider apiKey={key}>
                <Elements>
                    <CheckoutForm planid={router.query.id}/>
                </Elements>
            </StripeProvider>
            <br/>
            <br/>
            <p>Powered by Stripe</p>
        </div>
    );
};


Payment.getInitialProps = async ctx => {
    return { host: ctx.req.headers.host }
};

export default Payment

2
使用 useEffect 内部的 document 对象对我很有帮助。 - jobima

35

例如,当模块包含一个仅在浏览器中运行的库时。有时动态导入可以解决此问题。

看下面的例子:

import dynamic from 'next/dynamic'

const DynamicComponentWithNoSSR = dynamic(
  () => import('../components/hello3'),
  { ssr: false }
)

function Home() {
  return (
    <div>
      <Header />
      <DynamicComponentWithNoSSR />
      <p>HOME PAGE is here!</p>
    </div>
  )
}

export default Home

3
非常被低估的回答。接受我的点赞并感谢你的帮助! - Harry
2
最佳响应式方式!非常感谢。 - Burak Ibis

28

您需要使用验证器 process.browser 对您的文档进行包装,因为该文档属于客户端,并且错误发生在 Next.js 在服务器端呈现时。

var stripe_load = () => {
    if (process.browser) {
        var aScript = document.createElement('script');
        aScript.type = 'text/javascript';
        aScript.src = " https://js.stripe.com/v3/";

        document.head.appendChild(aScript);
        aScript.onload = () => {

        };
    }
};

19
请注意,process.browser 已被弃用。您现在应该使用 typeof window === 'undefined'。请参考 https://github.com/zeit/next.js/issues/5354#issuecomment-520305040。 - jsonderulo
2
有人知道为什么即使我们使用了“use client”,这种情况还是会发生吗? - undefined
1
有人知道为什么即使我们使用了“use client”,这种情况还是会发生吗? - Javi Villar

24

对我来说,这个错误是由许多错误引起的: 首先,你必须使用

if (typeof window !== "undefined") {

对于每一个window.document.,特别是对于localStorage.函数(我自己忘记使用这个if语句来获取localStorage.getItem,错误没有被修复)

另一个问题是下面这行代码的导入完全错误

import {router} from "next/client";

1
我相信这是最正确和最新的答案。谢谢。 - element11

15

要在 Next.js 中访问文档,您需要等待页面首先呈现,然后将其存储在变量中,例如:

const [_document, set_document] = React.useState(null)

React.useEffect(() => {
    set_document(document)
}, [])

正是我所需要的。非常感谢! - KeshavDulal
3
但这仅适用于组件范围内?这不是文档,而是在声明此useState钩子的组件中的状态片段。 - Jeremy

7

您应该了解一下Node.js中JS和浏览器中JS之间的区别。在Node.js中,您没有DOM API(例如window、document、document.getElementById等),这些东西只有在HTML在浏览器中的窗口中呈现时才会存在。因此,Next.js使用Node.js运行JS代码,并将结果呈现为HTML文件。但在Node.js中,没有任何浏览器窗口。

我的英语很差,但我希望这个答案对您有所帮助。


2

您可以在关闭 SSR 的情况下使用名为 动态导入 的新功能。这是重新渲染组件的一种解决方法。


1

在我的 Next.js 项目中使用 bootstrap-5 时,我遇到了类似的错误。

enter image description here

通过在_app.js中的useEffect()钩子中要求脚本来解决此问题。

import "bootstrap/dist/css/bootstrap.min.css";
import { useEffect } from "react";
import '@/styles/globals.css'

config.autoAddCss = false;

export default function App({ Component, pageProps }) {
  useEffect(() => {
    require("bootstrap/dist/js/bootstrap.bundle.min.js");
  }, []);
  return <Component {...pageProps} />
}

0

您必须确保已准备好两件事情。

  1. DOM 渲染已完成。
  2. 然后加载函数。

步骤 1:创建 hook isMounted,这将确保您的 DOM 已呈现。

import React, {useEffect, useState} from 'react';

function RenderCompleted() {

    const [mounted, setMounted] = useState(false);

    useEffect(() => {
        setMounted(true)

        return () => {
            setMounted(false)
        }
    });

    return mounted;
}

export default RenderCompleted;

在 Payment 函数内加载钩子:

import React, {useEffect} from "react";
import {Elements, StripeProvider} from 'react-stripe-elements';
import CheckoutForm from '../../components/Payment/CheckoutForm';
import { useRouter } from 'next/router';
import RenderCompleted from '../hooks/isMounted';

var stripe_load = () => {
    var aScript = document.createElement('script');
    aScript.type = 'text/javascript';
    aScript.src = " https://js.stripe.com/v3/";

    document.head.appendChild(aScript);
    aScript.onload = () => {

    };
};

function Payment({host}) {
    const[key,setKey] = useEffect('');

    //will help you on re-render or host changes

    useEffect(()=>{
        const key = host.includes('localhost') ? 'test' : 't';
        setKey(key);
    },[host])


    useEffect(() => {
      var aScript = document.createElement('script');
       aScript.type = 'text/javascript';
       aScript.src = " https://js.stripe.com/v3/";

       document.head.appendChild(aScript);
       aScript.onload = () => {

       };
    }, [isMounted])


    const router = useRouter();
    const isMounted = RenderCompleted();

    return (
        <div className="Payment Main">
            <StripeProvider apiKey={key}>
                <Elements>
                    <CheckoutForm planid={router.query.id}/>
                </Elements>
            </StripeProvider>
            <br/>
            <br/>
            <p>Powered by Stripe</p>
        </div>
    );
};


Payment.getInitialProps = async ctx => {
    return { host: ctx.req.headers.host }
};

export default Payment

另一种方法是使用next.js的head组件:https://nextjs.org/docs/api-reference/next/head

<Head>
        <title>My page title</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
      </Head>

0
如果您只是想要一个快速的解决方案,您可以将整个页面分离到不同的文件中,然后使用动态导入将整个页面导入到您的路由文件中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接