Fix hydration mismatch causing avatar flash on page load
- Read auth cookies server-side in layout.tsx - Pass initial auth data as props to AccountStateInitializer - Initialize Valtio state synchronously before first render - Fallback to client-side cookie reading only when no server data - Eliminates anonymous avatar flash during hydration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9b60134933
commit
c6657694f7
2 changed files with 84 additions and 3 deletions
|
|
@ -3,6 +3,7 @@ import localFont from 'next/font/local'
|
|||
import { NextIntlClientProvider } from 'next-intl'
|
||||
import { getMessages } from 'next-intl/server'
|
||||
import { Viewport as ToastViewport } from '@radix-ui/react-toast'
|
||||
import { cookies } from 'next/headers'
|
||||
import { locales } from '../../i18n.config'
|
||||
|
||||
import '../../styles/globals.scss'
|
||||
|
|
@ -49,6 +50,28 @@ export default async function LocaleLayout({
|
|||
// Load messages for the locale
|
||||
const messages = await getMessages()
|
||||
|
||||
// Parse auth cookies on server
|
||||
const cookieStore = cookies()
|
||||
const accountCookie = cookieStore.get('account')
|
||||
const userCookie = cookieStore.get('user')
|
||||
|
||||
let initialAuthData = null
|
||||
if (accountCookie && userCookie) {
|
||||
try {
|
||||
const accountData = JSON.parse(accountCookie.value)
|
||||
const userData = JSON.parse(userCookie.value)
|
||||
|
||||
if (accountData && accountData.token) {
|
||||
initialAuthData = {
|
||||
account: accountData,
|
||||
user: userData
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing auth cookies on server:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch version data on the server
|
||||
let version = null
|
||||
try {
|
||||
|
|
@ -68,7 +91,7 @@ export default async function LocaleLayout({
|
|||
<body className={goalking.className}>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<Providers>
|
||||
<AccountStateInitializer />
|
||||
<AccountStateInitializer initialAuthData={initialAuthData} />
|
||||
<Header />
|
||||
<VersionHydrator version={version} />
|
||||
<UpdateToastClient initialVersion={version} />
|
||||
|
|
|
|||
|
|
@ -1,12 +1,68 @@
|
|||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { getCookie } from 'cookies-next'
|
||||
import { accountState } from '~utils/accountState'
|
||||
import { setHeaders } from '~utils/userToken'
|
||||
|
||||
export default function AccountStateInitializer() {
|
||||
interface InitialAuthData {
|
||||
account: {
|
||||
token: string
|
||||
userId: string
|
||||
username: string
|
||||
role: number
|
||||
}
|
||||
user: {
|
||||
avatar: {
|
||||
picture: string
|
||||
element: string
|
||||
}
|
||||
gender: number
|
||||
language: string
|
||||
theme: string
|
||||
bahamut?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
interface AccountStateInitializerProps {
|
||||
initialAuthData?: InitialAuthData | null
|
||||
}
|
||||
|
||||
export default function AccountStateInitializer({ initialAuthData }: AccountStateInitializerProps) {
|
||||
const initialized = useRef(false)
|
||||
|
||||
// Initialize synchronously on first render if we have server data
|
||||
if (initialAuthData && !initialized.current) {
|
||||
initialized.current = true
|
||||
const { account: accountData, user: userData } = initialAuthData
|
||||
|
||||
console.log(`Logged in as user "${accountData.username}"`)
|
||||
|
||||
// Set headers for API calls
|
||||
setHeaders()
|
||||
|
||||
// Update account state
|
||||
accountState.account.authorized = true
|
||||
accountState.account.user = {
|
||||
id: accountData.userId,
|
||||
username: accountData.username,
|
||||
role: accountData.role,
|
||||
granblueId: '',
|
||||
avatar: {
|
||||
picture: userData.avatar.picture,
|
||||
element: userData.avatar.element,
|
||||
},
|
||||
gender: userData.gender,
|
||||
language: userData.language,
|
||||
theme: userData.theme,
|
||||
bahamut: userData.bahamut || false,
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Only run client-side cookie reading if no server data
|
||||
if (initialized.current) return
|
||||
|
||||
const accountCookie = getCookie('account')
|
||||
const userCookie = getCookie('user')
|
||||
|
||||
|
|
@ -37,6 +93,8 @@ export default function AccountStateInitializer() {
|
|||
theme: userData.theme,
|
||||
bahamut: userData.bahamut || false,
|
||||
}
|
||||
|
||||
initialized.current = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing account cookies:', error)
|
||||
|
|
|
|||
Loading…
Reference in a new issue