Next.js+TypeScript环境初始化Web3Modal时报window is not defined错误
问题根因
这个错误和TypeScript无关,是Next.js服务端渲染(SSR)机制导致的:Web3Modal内部逻辑会访问浏览器window对象,而Next.js在服务端预渲染页面时没有window全局对象,无论你在组件外部还是useMemo中初始化,只要预渲染阶段执行到初始化逻辑就会触发报错。你之前非TS环境正常大概率是之前的构建配置默认跳过了服务端预渲染相关校验,和TS语法无关。
修复方案
- 只在客户端环境初始化Web3Modal,借助
useEffect钩子(仅在客户端执行)完成实例化,不要用useMemo(服务端渲染阶段也会执行) - 提前判断window是否存在,bitski配置中的
window.location.href也要放在客户端环境下取值 - 修复
logoutOfWeb3Modal中多余的嵌套async函数问题,原写法会导致清空缓存逻辑永远不执行
修复后代码示例
import { useState, useEffect, useCallback, useMemo } from 'react'; import Web3 from 'web3'; import Web3Modal from 'web3modal'; import WalletConnectProvider from "@walletconnect/web3-provider"; import Fortmatic from "fortmatic"; import Authereum from "authereum"; import Bitski from "bitski"; // 其余导入省略 const INFURA_ID = process.env.REACT_APP_INFURA_ID; const initWeb3 = (provider: any) => { const web3 = new Web3(provider); web3.eth.extend({ methods: [ { name: "chainId", call: "eth_chainId", outputFormatter: web3.utils.hexToNumber as any, }, ], }); return web3; }; export function Web3UtilityProvider({ children }) { const [walletAddress, setWalletAddress] = useState<string | null>(null); const { user, authDispatch } = useAuth(); const [web3Modal, setWeb3Modal] = useState<Web3Modal | null>(null); const dAppClient = useMemo(() => { // DAppClient同样需要判断客户端环境再初始化 if (typeof window === 'undefined') return null; return new DAppClient({ name: "Beacon Docs" }); }, []); // 仅在客户端初始化Web3Modal useEffect(() => { if (typeof window === 'undefined') return; const modal = new Web3Modal({ cacheProvider: true, providerOptions: { walletconnect: { package: WalletConnectProvider, options: { infuraId: INFURA_ID, }, }, fortmatic: { package: Fortmatic, options: { key: process.env.REACT_APP_FORTMATIC_KEY, }, }, authereum: { package: Authereum, }, bitski: { package: Bitski, options: { clientId: process.env.REACT_APP_BITSKI_CLIENT_ID, callbackUrl: window.location.href + "bitski-callback.html", }, }, }, }); setWeb3Modal(modal); }, []); const logoutOfWeb3Modal = useCallback(async () => { if (!web3Modal) return; web3Modal.clearCachedProvider(); setTimeout(() => { window.location.reload(); }, 1); }, [web3Modal]); const loadWeb3Modal = useCallback(async () => { if (!web3Modal) return; const provider = await web3Modal.connect(); if (!provider?.on) { return; } const web3 = initWeb3(provider); const accounts = await web3.eth.getAccounts(); provider.on("close", () => { logoutOfWeb3Modal(); }); provider.on("accountsChanged", async (accounts: string[]) => { console.log("accountsChanged", accounts); if (!accounts || !accounts.length) { return; } setWalletAddress(accounts[0]); }); }, [web3Modal, logoutOfWeb3Modal]); return ( <Web3Context.Provider value={{ logoutOfWeb3Modal, loadWeb3Modal, walletAddress }} > {children} </Web3Context.Provider> ); }
可选兼容方案
如果该Provider仅需要在客户端生效,也可以用Next.js的dynamic导入功能直接关闭组件的SSR:
import dynamic from 'next/dynamic'; const Web3UtilityProvider = dynamic(() => import('../path/to/Web3UtilityProvider'), { ssr: false });
内容的提问来源于stack exchange,提问作者uditkumar01
相关产品推荐
相关产品推荐

