Node.js中在getInitialProps内调用web3.eth.getAccounts()返回undefined的问题
问题解答
为什么getInitialProps里调用web3.eth.getAccounts()返回undefined
getInitialProps 在Next.js中是服务端/静态渲染阶段执行的代码,这个环境下没有浏览器端的用户钱包(比如MetaMask)上下文。web3.eth.getAccounts() 依赖浏览器钱包注入的提供者(provider)来获取用户授权的账户,服务端环境无法访问浏览器的钱包状态,所以会返回undefined。而你在其他位置(比如组件的客户端生命周期方法、事件回调)调用正常,是因为那些代码运行在浏览器端,能访问到钱包提供者。
可行的实现方式
方式1:客户端侧获取账户后再请求合约数据
放弃在服务端获取用户地址,改为在客户端组件挂载后获取账户,再调用合约接口:
import { useEffect, useState } from 'react'; import web3 from '../utils/web3'; import YourContract from '../utils/YourContract'; function YourPage() { const [userData, setUserData] = useState(null); const [account, setAccount] = useState(null); useEffect(() => { const fetchData = async () => { // 在客户端获取账户 const accounts = await web3.eth.getAccounts(); if (accounts.length === 0) { alert('请连接你的钱包'); return; } setAccount(accounts[0]); // 调用合约获取数据 const contract = new web3.eth.Contract(YourContract.abi, YourContract.address); const data = await contract.methods.getUserData(accounts[0]).call(); setUserData(data); }; fetchData(); }, []); if (!account) return <div>请连接钱包...</div>; if (!userData) return <div>加载中...</div>; return ( <div> <p>你的地址:{account}</p> <p>合约数据:{JSON.stringify(userData)}</p> </div> ); } export default YourPage;
方式2:使用动态导入+客户端渲染组件
如果需要页面初始展示加载状态,然后在客户端完成数据获取,可以把相关逻辑封装到客户端组件,通过动态导入禁用服务端渲染:
// pages/your-page.js import dynamic from 'next/dynamic'; // 禁用服务端渲染,组件只会在客户端加载 const UserDataComponent = dynamic(() => import('../components/UserDataComponent'), { ssr: false, loading: () => <div>加载中...</div> }); function YourPage() { return <UserDataComponent />; } export default YourPage;
然后在UserDataComponent.js里实现账户和数据获取逻辑:
// components/UserDataComponent.js import { useEffect, useState } from 'react'; import web3 from '../utils/web3'; import YourContract from '../utils/YourContract'; export default function UserDataComponent() { const [userData, setUserData] = useState(null); const [account, setAccount] = useState(null); useEffect(() => { const init = async () => { const accounts = await web3.eth.getAccounts(); if (!accounts.length) { try { // 触发钱包连接请求(如果用户还没连接) await window.ethereum.request({ method: 'eth_requestAccounts' }); const newAccounts = await web3.eth.getAccounts(); setAccount(newAccounts[0]); } catch (err) { console.error('用户拒绝连接钱包', err); return; } } else { setAccount(accounts[0]); } if (account) { const contract = new web3.eth.Contract(YourContract.abi, YourContract.address); const data = await contract.methods.getUserData(account).call(); setUserData(data); } }; init(); }, [account]); if (!account) return <div>请连接钱包...</div>; if (!userData) return <div>加载数据中...</div>; return ( <div> <h3>你的账户数据</h3> <p>地址:{account}</p> <p>合约返回数据:{JSON.stringify(userData)}</p> </div> ); }
方式3:使用Session或Cookie传递账户(仅限信任场景)
如果你的场景是用户已通过其他方式授权(比如登录时绑定钱包地址),可以在服务端通过Session或Cookie获取已存储的账户地址,再调用合约:
function YourPage({ userData }) { return ( <div> <p>你的账户数据:{JSON.stringify(userData)}</p> </div> ); } YourPage.getInitialProps = async ({ req }) => { // 从Session或Cookie中获取已存储的用户地址 const userAddress = req.session?.walletAddress; if (!userAddress) { return { userData: null }; } // 服务端使用web3连接合约(这里的web3是连接到节点的实例,不是客户端钱包的) const web3 = new Web3(process.env.ETHEREUM_NODE_URL); const contract = new web3.eth.Contract(YourContract.abi, YourContract.address); const userData = await contract.methods.getUserData(userAddress).call(); return { userData }; }; export default YourPage;
注意:这种方式只适用于你已经在服务端存储了用户的钱包地址(比如用户主动绑定并验证过),不能直接在服务端获取未授权的用户钱包地址。
内容的提问来源于stack exchange,提问作者LocoScotch
相关产品推荐
相关产品推荐

