fix: ssr rendering
This commit is contained in:
@@ -19,6 +19,7 @@ export function LocaleSwitcher() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 w-full py-2 hover:opacity-80">
|
<div className="flex items-center gap-2 w-full py-2 hover:opacity-80">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
src={`/flags/${currentLocale}.svg`}
|
src={`/flags/${currentLocale}.svg`}
|
||||||
alt={currentLocale}
|
alt={currentLocale}
|
||||||
|
|||||||
+50
-32
@@ -1,16 +1,16 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useSyncExternalStore } from 'react';
|
||||||
|
|
||||||
type Theme = 'light' | 'dark';
|
type Theme = 'light' | 'dark';
|
||||||
|
|
||||||
function getCookie(name: string) {
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
if (typeof document === 'undefined') return null;
|
|
||||||
|
|
||||||
|
function getCookie(name: string): string | null {
|
||||||
|
if (typeof document === 'undefined') return null;
|
||||||
const value = `; ${document.cookie}`;
|
const value = `; ${document.cookie}`;
|
||||||
const parts = value.split(`; ${name}=`);
|
const parts = value.split(`; ${name}=`);
|
||||||
|
if (parts.length === 2) return parts.pop()?.split(';').shift() ?? null;
|
||||||
if (parts.length === 2) return parts.pop()?.split(';').shift();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,40 +18,58 @@ function setCookie(value: Theme) {
|
|||||||
document.cookie = `theme=${value}; path=/; max-age=31536000`;
|
document.cookie = `theme=${value}; path=/; max-age=31536000`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTheme() {
|
function readThemeFromDOM(): Theme {
|
||||||
const [theme, setTheme] = useState<Theme>('dark');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const cookieTheme = getCookie('theme') as Theme | null;
|
const cookieTheme = getCookie('theme') as Theme | null;
|
||||||
const rootTheme = document.documentElement.classList.contains('dark')
|
if (cookieTheme === 'light' || cookieTheme === 'dark') return cookieTheme;
|
||||||
? 'dark'
|
if (document.documentElement.classList.contains('dark')) return 'dark';
|
||||||
: 'light';
|
return 'light';
|
||||||
|
}
|
||||||
|
|
||||||
const initial = cookieTheme || rootTheme || 'dark';
|
function applyThemeToDOM(t: Theme) {
|
||||||
|
|
||||||
setTheme(initial);
|
|
||||||
|
|
||||||
document.documentElement.classList.toggle('dark', initial === 'dark');
|
|
||||||
document.documentElement.classList.toggle(
|
|
||||||
'light-mode',
|
|
||||||
initial === 'light',
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function applyTheme(t: Theme) {
|
|
||||||
document.documentElement.classList.toggle('dark', t === 'dark');
|
document.documentElement.classList.toggle('dark', t === 'dark');
|
||||||
document.documentElement.classList.toggle('light-mode', t === 'light');
|
document.documentElement.classList.toggle('light-mode', t === 'light');
|
||||||
|
}
|
||||||
|
|
||||||
setCookie(t);
|
// ── External store ────────────────────────────────────────────────────────────
|
||||||
}
|
// useSyncExternalStore requires a stable subscribe function and a getSnapshot.
|
||||||
|
// We use a simple Set of listeners to notify React when the theme changes.
|
||||||
|
|
||||||
function toggleTheme() {
|
const listeners = new Set<() => void>();
|
||||||
setTheme((prev) => {
|
let clientTheme: Theme | null = null; // lazily initialised on first client read
|
||||||
const next = prev === 'dark' ? 'light' : 'dark';
|
|
||||||
applyTheme(next);
|
function getSnapshot(): Theme {
|
||||||
return next;
|
if (clientTheme === null) {
|
||||||
});
|
clientTheme = readThemeFromDOM();
|
||||||
}
|
}
|
||||||
|
return clientTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server always returns the default theme so SSR HTML is deterministic.
|
||||||
|
// The client will then reconcile with the real cookie/DOM value via getSnapshot.
|
||||||
|
function getServerSnapshot(): Theme {
|
||||||
|
return 'dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribe(callback: () => void): () => void {
|
||||||
|
listeners.add(callback);
|
||||||
|
return () => listeners.delete(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGlobalTheme(next: Theme) {
|
||||||
|
clientTheme = next;
|
||||||
|
applyThemeToDOM(next);
|
||||||
|
setCookie(next);
|
||||||
|
listeners.forEach((cb) => cb());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Hook ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||||
|
|
||||||
|
const toggleTheme = useCallback(() => {
|
||||||
|
setGlobalTheme(theme === 'dark' ? 'light' : 'dark');
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
theme,
|
theme,
|
||||||
|
|||||||
Reference in New Issue
Block a user