You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS Hydration失败求助:服务端与客户端UI渲染不匹配

Next.js 水化错误(Hydration Failed)排查与解决

错误信息

Error: Hydration failed because the initial UI does not match what was rendered on the server.
Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

控制台警告

Warning: Expected server HTML to contain a matching <div> in <div>.
    at div
    at SendTransaction
    at div
    at MyApp (webpack-internal:///./pages/_app.tsx:39:11)
    at PathnameContextProviderAdapter (webpack-internal:///./node_modules/next/dist/shared/lib/router/adapters.js:62:11)
    at ErrorBoundary (webpack-internal:///./node_modules/next/dist/compiled/@next/react-dev-overlay/dist/client.js:301:63)
    at ReactDevOverlay (webpack-internal:///./node_modules/next/dist/compiled/@next/react-dev-overlay/dist/client.js:850:919)
    at Container (webpack-internal:///./node_modules/next/dist/client/index.js:62:1)
    at AppContainer (webpack-internal:///./node_modules/next/dist/client/index.js:172:11)
    at Root (webpack-internal:///./node_modules/next/dist/client/index.js:347:11)

项目文件代码

comps/transaction.tsx(已简化)

import * as React from 'react';

export function SendTransaction() {
  return <h1>TEST!</h1>
}

pages/index.tsx

import type { NextPage } from 'next';
import { ConnectKitButton } from 'connectkit';

const Home: NextPage = () => {
  return (
    <div
      style={{
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        height: '100vh',
      }}
    >
      <ConnectKitButton />
    </div>
  );
};

export default Home;

pages/_app.tsx

import { WagmiConfig, createClient, useAccount, useConnect, useDisconnect } from 'wagmi';
import { mainnet, polygon, optimism, arbitrum } from 'wagmi/chains';
import { ConnectKitProvider, getDefaultClient } from 'connectkit';
import { SendTransaction } from '../comps/transaction';
import React from 'react';

const client = createClient(
  getDefaultClient({
    appName: 'ConnectKit Next.js demo',
    //infuraId: process.env.NEXT_PUBLIC_INFURA_ID,
    //alchemyId:  process.env.NEXT_PUBLIC_ALCHEMY_ID,
    chains: [mainnet, polygon, optimism, arbitrum],
  })
);

function MyApp({ Component, pageProps }: AppProps) {
  const { isConnected } = useAccount();
  if (isConnected) {
    return (
      <div>
        {/* Account content goes here */}
        <SendTransaction />
      </div>
    )
  }
  
  return (
    <WagmiConfig client={client}>
      <ConnectKitProvider>
        <Component {...pageProps} />
      </ConnectKitProvider>
    </WagmiConfig>
  );
}

export default MyApp;

问题关键点

移除_app.tsx中以下代码块后,应用恢复正常:

const { isConnected } = useAccount();
if (isConnected) {
  return (
    <div>
      {/* Account content goes here */}
      <SendTransaction />
    </div>
  )
}

原因分析

  1. 上下文范围错误:useAccount()被放在了WagmiConfig组件外层,而wagmi的hooks必须在WagmiConfig提供的上下文环境中才能正常工作,导致状态读取异常。
  2. 服务器与客户端状态不一致:服务器渲染时无法获取钱包连接状态,isConnected返回false,渲染包含首页组件的结构;客户端水化阶段,若用户已连接钱包,isConnected变为true,会渲染完全不同的DOM结构,导致服务器输出的HTML与客户端UI不匹配,触发水化错误。

解决方案

修改pages/_app.tsx,将状态判断逻辑移到WagmiConfig上下文内,并通过useEffect延迟客户端状态判断:

import { WagmiConfig, createClient, useAccount } from 'wagmi';
import { mainnet, polygon, optimism, arbitrum } from 'wagmi/chains';
import { ConnectKitProvider, getDefaultClient } from 'connectkit';
import { SendTransaction } from '../comps/transaction';
import React, { useEffect, useState } from 'react';

const client = createClient(
  getDefaultClient({
    appName: 'ConnectKit Next.js demo',
    chains: [mainnet, polygon, optimism, arbitrum],
  })
);

// 封装上下文内的内容组件
const AppContent = ({ Component, pageProps }) => {
  const { isConnected } = useAccount();
  const [clientReady, setClientReady] = useState(false);

  // 仅在客户端水化完成后判断连接状态
  useEffect(() => {
    setClientReady(true);
  }, []);

  if (!clientReady) {
    // 服务器渲染和水化初期显示与服务器输出一致的内容
    return <div>Loading...</div>;
  }

  if (isConnected) {
    return (
      <div>
        <SendTransaction />
      </div>
    );
  }

  return <Component {...pageProps} />;
};

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <WagmiConfig client={client}>
      <ConnectKitProvider>
        <AppContent Component={Component} pageProps={pageProps} />
      </ConnectKitProvider>
    </WagmiConfig>
  );
}

export default MyApp;

核心逻辑

  • 将状态判断放在WagmiConfig上下文内,确保useAccount能正常读取钱包状态。
  • 使用useEffect延迟客户端状态判断,避免服务器与客户端的DOM结构差异,彻底解决水化错误。

内容的提问来源于stack exchange,提问作者Willem Ghijsen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 15:35:52