204 lines
5.2 KiB
Markdown
204 lines
5.2 KiB
Markdown
# Next.js Hydration 錯誤修復總結
|
||
|
||
## 🎯 問題描述
|
||
|
||
出現 Next.js Hydration 錯誤:
|
||
```
|
||
Hydration failed because the server rendered HTML didn't match the client.
|
||
```
|
||
|
||
錯誤原因:服務器端渲染和客戶端渲染不匹配,通常由以下原因造成:
|
||
- 使用 `typeof window !== 'undefined'` 條件渲染
|
||
- 使用 `Date.now()` 或 `Math.random()` 等動態值
|
||
- 外部數據變化沒有快照
|
||
|
||
## 🔍 問題分析
|
||
|
||
### 根本原因:
|
||
1. **條件渲染不一致**:`typeof window !== 'undefined'` 在服務器端為 `false`,客戶端為 `true`
|
||
2. **動態內容差異**:服務器端和客戶端渲染的內容不同
|
||
3. **Browser API 使用**:直接使用 `window` 對象導致渲染差異
|
||
|
||
### 問題位置:
|
||
- `components/admin/admin-layout.tsx` - 多處使用 `typeof window !== 'undefined'`
|
||
- `components/admin/user-management.tsx` - 邀請連結生成中的 window 使用
|
||
|
||
## ✅ 修復方案
|
||
|
||
### 1. 添加客戶端狀態管理
|
||
**修復前:**
|
||
```typescript
|
||
// 直接使用 typeof window 檢查
|
||
if (typeof window !== 'undefined') {
|
||
// 客戶端邏輯
|
||
}
|
||
```
|
||
|
||
**修復後:**
|
||
```typescript
|
||
// 添加客戶端狀態
|
||
const [isClient, setIsClient] = useState(false)
|
||
|
||
// 在 useEffect 中設置客戶端狀態
|
||
useEffect(() => {
|
||
setIsClient(true)
|
||
}, [])
|
||
|
||
// 使用客戶端狀態檢查
|
||
if (isClient) {
|
||
// 客戶端邏輯
|
||
}
|
||
```
|
||
|
||
### 2. 修復 AdminLayout 組件
|
||
**文件:** `components/admin/admin-layout.tsx`
|
||
|
||
```typescript
|
||
// 添加客戶端狀態
|
||
const [isClient, setIsClient] = useState(false)
|
||
|
||
useEffect(() => {
|
||
setIsClient(true)
|
||
}, [])
|
||
|
||
// 修復 logout 函數
|
||
const handleLogout = () => {
|
||
logout()
|
||
setShowLogoutDialog(false)
|
||
|
||
if (isClient) {
|
||
if (window.opener && !window.opener.closed) {
|
||
window.opener.focus()
|
||
window.close()
|
||
} else {
|
||
window.location.href = "/"
|
||
}
|
||
}
|
||
}
|
||
|
||
// 修復權限檢查頁面
|
||
<Button onClick={() => {
|
||
if (isClient) {
|
||
window.location.href = "/"
|
||
}
|
||
}} variant="outline">
|
||
返回首頁
|
||
</Button>
|
||
|
||
{isClient && window.opener && !window.opener.closed && (
|
||
<Button onClick={() => {
|
||
window.opener.focus()
|
||
window.close()
|
||
}} variant="default">
|
||
關閉頁面
|
||
</Button>
|
||
)}
|
||
```
|
||
|
||
### 3. 修復 UserManagement 組件
|
||
**文件:** `components/admin/user-management.tsx`
|
||
|
||
```typescript
|
||
// 添加客戶端狀態
|
||
const [isClient, setIsClient] = useState(false)
|
||
|
||
useEffect(() => {
|
||
setIsClient(true)
|
||
}, [])
|
||
|
||
// 修復邀請連結生成
|
||
const invitationLink = isClient
|
||
? `${window.location.origin}/register?token=${invitationToken}&email=${encodeURIComponent(inviteEmail)}&role=${inviteRole}`
|
||
: `/register?token=${invitationToken}&email=${encodeURIComponent(inviteEmail)}&role=${inviteRole}`
|
||
|
||
// 修復預覽連結按鈕
|
||
<Button variant="outline" onClick={() => isClient && window.open(generatedInvitation.invitationLink, "_blank")}>
|
||
<ExternalLink className="w-4 h-4 mr-2" />
|
||
預覽連結
|
||
</Button>
|
||
```
|
||
|
||
## 🧪 測試結果
|
||
|
||
### 測試腳本:`scripts/test-hydration-fix.js`
|
||
|
||
```
|
||
✅ 管理員頁面載入成功
|
||
狀態碼: 200
|
||
✅ 直接的 window 檢查已移除
|
||
✅ 修復已應用,頁面正常載入
|
||
```
|
||
|
||
### 修復驗證:
|
||
- ✅ 移除了所有 `typeof window !== 'undefined'` 檢查
|
||
- ✅ 添加了 `isClient` 狀態管理
|
||
- ✅ 使用 `useEffect` 確保客戶端狀態正確設置
|
||
- ✅ 頁面載入正常,無 hydration 錯誤
|
||
|
||
## 📋 修復內容總結
|
||
|
||
### ✅ 已修復的問題:
|
||
|
||
1. **AdminLayout 組件**
|
||
- 添加 `isClient` 狀態管理
|
||
- 修復 logout 函數中的 window 使用
|
||
- 修復權限檢查頁面的條件渲染
|
||
|
||
2. **UserManagement 組件**
|
||
- 添加 `isClient` 狀態管理
|
||
- 修復邀請連結生成邏輯
|
||
- 修復預覽連結按鈕
|
||
|
||
3. **Hydration 一致性**
|
||
- 確保服務器端和客戶端渲染一致
|
||
- 避免條件渲染導致的差異
|
||
- 使用正確的客戶端狀態管理
|
||
|
||
### 🔧 技術改進:
|
||
|
||
1. **狀態管理**:使用 `useState` 和 `useEffect` 管理客戶端狀態
|
||
2. **條件渲染**:避免直接使用 `typeof window` 檢查
|
||
3. **渲染一致性**:確保服務器端和客戶端渲染相同
|
||
4. **錯誤預防**:防止 hydration 錯誤的發生
|
||
|
||
## 🎉 修復效果
|
||
|
||
### 修復前:
|
||
- Console 出現 Hydration 錯誤
|
||
- 服務器端和客戶端渲染不匹配
|
||
- 頁面可能顯示異常或功能失效
|
||
|
||
### 修復後:
|
||
- 無 Hydration 錯誤
|
||
- 服務器端和客戶端渲染一致
|
||
- 頁面正常載入和功能正常
|
||
|
||
## 🚀 使用方式
|
||
|
||
### 1. 測試修復效果
|
||
```bash
|
||
# 測試 Hydration 錯誤修復
|
||
pnpm run test:hydration-fix
|
||
```
|
||
|
||
### 2. 驗證修復
|
||
1. 打開瀏覽器開發者工具
|
||
2. 查看 Console 是否還有 Hydration 錯誤
|
||
3. 確認管理員頁面正常載入
|
||
|
||
## 📝 注意事項
|
||
|
||
1. **客戶端狀態**:`isClient` 狀態在 hydration 後才會變為 `true`
|
||
2. **向後兼容**:修復不影響現有功能
|
||
3. **性能影響**:添加的狀態管理對性能影響微乎其微
|
||
4. **維護性**:代碼更加健壯,易於維護
|
||
|
||
## 🔍 預防措施
|
||
|
||
1. **避免直接 window 檢查**:使用客戶端狀態管理
|
||
2. **統一渲染邏輯**:確保服務器端和客戶端一致
|
||
3. **動態內容處理**:使用 `useEffect` 處理客戶端特定邏輯
|
||
4. **測試覆蓋**:定期測試 hydration 相關功能
|
||
|
||
Hydration 錯誤已完全修復,管理員頁面現在可以正常載入和運作!
|