fix: resolve SSR/client hydration mismatch for themed navigation
Add hydration state tracking to GameThemeContext to prevent flash of unstyled content: - Track isHydrated state in GameThemeContext - Only apply themed backgrounds and game names after client hydration - Prevents Next.js hydration mismatch where server renders default styles but client overwrites with themed styles - Eliminates the brief flash where themed navigation appears then reverts to default Navigation theming now applies consistently without visual flashing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -18,11 +18,12 @@ export function AppNavBar({ variant = 'full' }: AppNavBarProps) {
|
||||
const isGamePage = pathname?.startsWith('/games')
|
||||
const isArcadePage = pathname?.startsWith('/arcade')
|
||||
const { isFullscreen, toggleFullscreen, exitFullscreen } = useFullscreen()
|
||||
const { theme: gameTheme } = useGameTheme()
|
||||
const { theme: gameTheme, isHydrated } = useGameTheme()
|
||||
|
||||
// Helper function to get themed background colors
|
||||
const getThemedBackground = (opacity: number = 0.85) => {
|
||||
if (gameTheme?.backgroundColor) {
|
||||
// Only apply theming after hydration to prevent SSR/client mismatch
|
||||
if (isHydrated && gameTheme?.backgroundColor) {
|
||||
const color = gameTheme.backgroundColor
|
||||
if (color.startsWith('#')) {
|
||||
// Convert hex to rgba
|
||||
@@ -103,7 +104,7 @@ export function AppNavBar({ variant = 'full' }: AppNavBarProps) {
|
||||
backgroundClip: 'text',
|
||||
color: 'transparent'
|
||||
})}>
|
||||
🕹️ {gameTheme?.gameName || (isArcadePage ? 'Arcade' : 'Game')}
|
||||
🕹️ {(isHydrated && gameTheme?.gameName) || (isArcadePage ? 'Arcade' : 'Game')}
|
||||
</h1>
|
||||
<div className={css({
|
||||
px: '2',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useState, ReactNode } from 'react'
|
||||
import { createContext, useContext, useState, useEffect, ReactNode } from 'react'
|
||||
|
||||
export interface GameTheme {
|
||||
gameName: string
|
||||
@@ -10,15 +10,21 @@ export interface GameTheme {
|
||||
interface GameThemeContextType {
|
||||
theme: GameTheme | null
|
||||
setTheme: (theme: GameTheme | null) => void
|
||||
isHydrated: boolean
|
||||
}
|
||||
|
||||
const GameThemeContext = createContext<GameThemeContextType | undefined>(undefined)
|
||||
|
||||
export function GameThemeProvider({ children }: { children: ReactNode }) {
|
||||
const [theme, setTheme] = useState<GameTheme | null>(null)
|
||||
const [isHydrated, setIsHydrated] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setIsHydrated(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<GameThemeContext.Provider value={{ theme, setTheme }}>
|
||||
<GameThemeContext.Provider value={{ theme, setTheme, isHydrated }}>
|
||||
{children}
|
||||
</GameThemeContext.Provider>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user