问题描述
切换语言后,页面显示源码还是默认的英语,这样SEO就有问题了。需要从客户端渲染,改为服务端渲染,以下是改造方式。
- 服务端渲染: 服务端始终使用默认语言(英语)渲染HTML
- 客户端切换: 只有客户端JavaScript执行后才显示正确语言
- SEO影响: 搜索引擎爬虫看到的是英语内容,无法索引其他语言版本
代码改造的核心区别
1. 组件标记
客户端组件
'use client' // 必须添加这个指令
import { useTranslation } from 'react-i18next'
import { useState } from 'react'
export default function ClientComponent() {
const { t } = useTranslation()
const [count, setCount] = useState(0)
return <div>{t('welcome')}</div>
}
服务端组件
// 不需要 'use client' 指令,默认就是服务端组件
import { useTranslation } from '@/i18n/server'
import { getLocaleOnServer } from '@/i18n/server'
export default async function ServerComponent() { // 注意:async
const locale = await getLocaleOnServer()
const { t } = await useTranslation(locale, 'common') // 注意:await
return <div>{t('welcome')}</div>
}
2. 翻译函数导入
客户端翻译
import { useTranslation } from 'react-i18next'
export default function Component() {
const { t } = useTranslation() // 直接调用,无需参数
return <div>{t('app.title')}</div> // 使用完整键名
}
服务端翻译
import { useTranslation } from '@/i18n/server'
import { getLocaleOnServer } from '@/i18n/server'
export default async function Component() {
const locale = await getLocaleOnServer()
const { t } = await useTranslation(locale, 'app') // 需要locale和namespace
return <div>{t('title')}</div> // 不需要namespace前缀
}
3. 函数签名
客户端组件
export default function Component() { // 普通函数
// 可以使用所有React hooks
const [state, setState] = useState()
const { t } = useTranslation()
return <div>{t('key')}</div>
}
服务端组件
export default async function Component() { // 必须是async函数
// 不能使用useState, useEffect等客户端hooks
const locale = await getLocaleOnServer()
const { t } = await useTranslation(locale, 'namespace')
return <div>{t('key')}</div>
}
4. 具体改造示例
改造前(客户端)
'use client'
import { useTranslation } from 'react-i18next'
export default function ProductPage() {
const { t } = useTranslation()
return (
<div>
<h1>{t('product.title')}</h1>
<p>{t('product.description')}</p>
<button onClick={() => console.log('clicked')}>
{t('product.buy')}
</button>
</div>
)
}
改造后(服务端)
import { useTranslation } from '@/i18n/server'
import { getLocaleOnServer } from '@/i18n/server'
export default async function ProductPage() {
const locale = await getLocaleOnServer()
const { t } = await useTranslation(locale, 'product')
return (
<div>
<h1>{t('title')}</h1> {/* 去掉 'product.' 前缀 */}
<p>{t('description')}</p>
{/* 注意:不能有onClick等事件处理 */}
<button>{t('buy')}</button>
</div>
)
}
5. 混合使用(推荐)
// 主组件 - 服务端渲染SEO内容
import { useTranslation } from '@/i18n/server'
import { getLocaleOnServer } from '@/i18n/server'
import ClientButton from './ClientButton' // 客户端组件
export default async function ProductPage() {
const locale = await getLocaleOnServer()
const { t } = await useTranslation(locale, 'product')
return (
<div>
{/* SEO重要的内容 - 服务端渲染 */}
<h1>{t('title')}</h1>
<p>{t('description')}</p>
{/* 交互功能 - 客户端组件 */}
<ClientButton />
</div>
)
}
// 客户端组件 - 处理交互
'use client'
import { useTranslation } from 'react-i18next'
export default function ClientButton() {
const { t } = useTranslation()
return (
<button onClick={() => console.log('clicked')}>
{t('product.buy')} {/* 客户端需要完整键名 */}
</button>
)
}
6. 关键改造要点
改造项 | 客户端 → 服务端 |
---|---|
移除 | 'use client' |
添加 | async 函数声明 |
修改导入 | react-i18next → @/i18n/server |
添加语言检测 | await getLocaleOnServer() |
修改翻译调用 | useTranslation() → await useTranslation(locale, 'namespace') |
简化键名 | t('app.title') → t('title') |
移除交互 | 删除 onClick , useState 等 |
7. 不能改造的情况
// 这些情况必须保持客户端组件
'use client'
// 1. 有事件处理
<button onClick={handleClick}>Click</button>
// 2. 有状态管理
const [count, setCount] = useState(0)
// 3. 有副作用
useEffect(() => {
// 副作用代码
}, [])
// 4. 使用浏览器API
window.localStorage.getItem('key')
这就是核心的代码改造区别!记住:服务端组件用于SEO重要的内容渲染,客户端组件用于交互功能。