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

Next.js集成带国旗标识Google Translate及Hydration报错解决

问题根因

抛出Hydration failed because the initial UI does not match what was rendered on the server报错的核心原因有两个:

  • 原生实现代码强依赖浏览器端window、document对象,直接操作DOM、调用浏览器专属API,但Next.js默认先在服务端完成页面渲染,服务端环境不存在浏览器API,渲染出的初始DOM结构和客户端加载完第三方脚本后修改的结构不一致,触发水合校验失败。
  • 原逻辑依赖的Google Translate、jQuery、Bootstrap Select等第三方脚本默认会在全局注入内容、修改DOM结构,如果在服务端渲染阶段加载执行,必然导致结构不匹配。
适配方案(Next.js Pages Router 对应pages/index.js)

核心思路:所有依赖浏览器环境的逻辑、第三方脚本全部延后到客户端水合完成后再执行,保证服务端输出的初始DOM和客户端首次渲染的DOM完全一致。

具体实现代码

import { useState, useEffect } from 'react'
import Script from 'next/script'
import Head from 'next/head'

export default function Home() {
  // 挂载状态标记,只在客户端设为true,解决初始DOM不匹配问题
  const [mounted, setMounted] = useState(false)

  useEffect(() => {
    setMounted(true)
    // 把Google翻译需要的全局回调函数挂载到window对象
    window.googleTranslateElementInit = () => {
      new window.google.translate.TranslateElement({
        pageLanguage: 'en',
        layout: window.google.translate.TranslateElement.InlineLayout.SIMPLE,
        autoDisplay: false
      }, 'google_translate_element')
    }

    // 语言切换函数
    window.translateLanguage = (lang) => {
      window.googleTranslateElementInit()
      const frame = document.querySelector('.goog-te-menu-frame:first')
      if (!frame) {
        alert("Error: Could not find Google translate frame.")
        return false
      }
      const targetItem = frame.contentDocument.querySelector('.goog-te-menu2-item span.text')
      if (targetItem?.innerText.includes(lang)) {
        targetItem.click()
      }
      return false
    }
  }, [])

  // 脚本加载完成后初始化bootstrap-select选择器
  const handleSelectPickerReady = () => {
    window.$('.selectpicker').selectpicker()
  }

  // 未挂载到客户端前只渲染基础容器,保证和服务端输出结构一致
  if (!mounted) {
    return (
      <div>
        <Head>
          <title>Google Translation</title>
        </Head>
        <div id="google_translate_element" style={{display: 'none'}}></div>
        <div>This is just a table.</div>
      </div>
    )
  }

  return (
    <div>
      <Head>
        <title>Google Translation</title>
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
        <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.6.2/css/bootstrap-select.min.css" rel="stylesheet" type="text/css" />
        <link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.5.0/css/flag-icon.min.css" rel="stylesheet" type="text/css" />
      </Head>

      {/* 隐藏的Google翻译默认元素 */}
      <div id="google_translate_element" style={{display: 'none'}}></div>

      {/* 带国旗的语言选择器 */}
      <select 
        className="selectpicker" 
        data-width="fit" 
        onChange={(e) => window.translateLanguage(e.target.value)}
      >
        <option 
          data-content='<span class="flag-icon flag-icon-us"></span> English' 
          value="English"
        >
          English
        </option>
        <option 
          data-content='<span class="flag-icon flag-icon-fr"></span> French' 
          value="French"
        >
          French
        </option>
        <option 
          data-content='<span class="flag-icon flag-icon-es"></span> Spanish' 
          value="Spanish"
        >
          Spanish
        </option>
      </select>

      <div>This is just a table.</div>

      {/* 第三方脚本按顺序加载,全部在客户端执行,不影响服务端渲染 */}
      <Script 
        src="https://code.jquery.com/jquery-1.11.3.min.js" 
        strategy="afterInteractive"
      />
      <Script 
        src="https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit" 
        strategy="afterInteractive"
      />
      <Script 
        src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.6.2/js/bootstrap-select.min.js" 
        strategy="afterInteractive"
        onReady={handleSelectPickerReady}
      />
      <Script 
        src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" 
        strategy="afterInteractive"
      />
    </div>
  )
}

关键注意点

  • 禁止在组件顶层、渲染阶段直接访问window、document对象,所有这类访问必须放在useEffect或者事件回调中,保证只在客户端执行。
  • 第三方脚本统一使用Next.js内置的Script组件加载,设置strategy="afterInteractive"让脚本在页面水合完成后再加载执行,避免修改初始DOM结构。
  • 用mounted状态做渲染拦截,未完成客户端挂载前不渲染带第三方交互的组件,从根源上保证服务端和客户端首次渲染的DOM结构完全一致,解决水合报错。
  • Google翻译要求的回调函数必须挂载到window全局对象上,否则外部脚本加载后找不到对应回调会抛错。
  • 第三方样式文件统一通过next/head注入link标签加载,不要直接在组件中import远程CSS资源,避免构建报错。
  • 原代码中重复引入的jQuery、Bootstrap资源已经移除,减少不必要的加载开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 17:03:24