(ํด๊ฒฐ ์๋ฃ) Hydration failed because the initial UI does not match what was rendered on the server.
๐์์ํ๋ฉฐ
์ด ๊ธ์ ์๋์ ์ค๋ฅ๋ฅผ ํด๊ฒฐํ๊ณ , ์์ฑํ๋ ํฌ์คํ ์ ๋๋ค.
โ๋ฌธ์ ์ํฉ
๋ฌธ์ ์ํฉ์ ๋ณต๊ธฐํด๋ณด์๋ฉด, Header.tsx ์ปดํฌ๋ํธ์์, localStorage์ Tailwind๋ฅผ ์ด์ฉํด light/dark mode๋ฅผ ๊ตฌํํ๋ ๋์ค ํด๋น ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
Error: Hydration failed because the initial UI does not match what was rendered on the server. Warning: Expected server HTML to contain a matching
<path>
in<svg>
. See more info here: https://nextjs.org/docs/messages/react-hydration-error
๋ ๋๋ง ์์ ๋ฌธ์ ๋ผ๋ ๊ฒ์ ํ์ ํด์, useEffect๋ useLayoutEffect ๋ฑ์ ์ด์ฉํด ๋๋ฆ ํด๊ฒฐ์ ์๋ํ์์ผ๋ ํด๊ฒฐํ์ง ๋ชปํ์๋๋ฐ, ์์ ์ฝ๋๋ฅผ ๋ค์ ์์ฑํด ํด๊ฒฐํ ์ ์์๋ค.๐
โ๊ธฐ์กด ์์ฑ ์ฝ๋ ํ๋ฆ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const initialTheme: ThemeType =
typeof window !== "undefined"
? (localStorage.getItem("theme") as ThemeType) || "light"
: "light"
const [theme, setTheme] = useState<ThemeType>(initialTheme)
useEffect(() => {
if (typeof window !== "undefined") {
localStorage.setItem("theme", theme)
if (theme === "dark") {
const htmlElement = document.querySelector("html") as HTMLElement
htmlElement.classList.add("dark")
} else {
const htmlElement = document.querySelector("html") as HTMLElement
htmlElement.classList.remove("dark")
}
}
}, [theme])
initialTheme: ์ฝ๋๊ฐ ํด๋ผ์ด์ธํธ ์ธก์์ ์คํ๋๋์ง ํ์ธํ ๋ค์, ๋ก์ปฌ ์คํ ๋ฆฌ์ง์์ ์ด์ ์ ์ ์ฅ๋ ํ ๋ง๋ฅผ ๊ฐ์ ธ์จ๋ค. ๋ง์ฝ, ์ ์ฅ๋ ํ ๋ง๊ฐ ์๋ค๋ฉด ๊ธฐ๋ณธ๊ฐ์ผ๋ก โlightโ ํ ๋ง๋ฅผ ์ค์ ํ๊ณ state์ ์ด๊ธฐ๊ฐ์ผ๋ก ์ง์ ํ๋ค.
useEffect: theme ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋ ํธ์ถ๋๋ค. ์ฝ๋๊ฐ ํด๋ผ์ด์ธํธ ์ธก์์ ์คํ๋๋์ง ํ์ธํ๊ณ , ํ ๋ง๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์๋ก์ด ํ ๋ง๋ฅผ ์ ์ฅํ๋ค. ์ด๋, dark ๋ชจ๋ ์ธ ๊ฒฝ์ฐ ์ต์์
<html>
์์์dark
ํด๋์ค๋ฅผ ์ถ๊ฐํด tailwind์ darkmode๋ฅผ ์ ์ฉํ๋ค.
๐ค์ด ์ฝ๋๋ ๊ณ์ ๋ฐ์ํ๋ ์ค๋ฅ๋ฅผ ํด๊ฒฐํ์ง ๋ชปํด ๊ณจ๋จธ๋ฆฌ๋ฅผ ์์๋๋ฐ, ๊ฒฐ๊ตญ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๊ธฐ์กด ์ฝ๋๋ฅผ ์กฐ๊ธ์ฉ ๊ณ ์น๋๊ฒ ์๋๋ผ ๋ค์ ์์ฑํ๋ ๊ฑฐ์๋ค!๐
โญ๊ฐ์ ํ ์ฝ๋ ํ๋ฆ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// theme ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
const [theme, setTheme] = useState<ThemeType>("light")
//theme ์ค์ ํจ์๋ค
const setLightTheme = () => {
localStorage.setItem("theme", "light")
setTheme("light")
const htmlElement = document.querySelector("html") as HTMLElement
htmlElement.classList.remove("dark")
}
const setDarkTheme = () => {
localStorage.setItem("theme", "dark")
setTheme("dark")
const htmlElement = document.querySelector("html") as HTMLElement
htmlElement.classList.add("dark")
}
useEffect(() => {
const storedTheme = localStorage.getItem("theme") as ThemeType
if (storedTheme) {
if (storedTheme === "dark") {
setDarkTheme()
} else if (storedTheme === "light") {
setLightTheme()
}
} else {
setLightTheme()
}
}, [])
useState: ๊ธฐ์กด์ initialTheme๋ฅผ ํ์ธํ๊ณ state ์ด๊ธฐ๊ฐ์ผ๋ก ์ค์ ํ์ง ์๊ณ , ์ฒ์์๋ ์ด๊ธฐ ํ ๋ง๋ฅผ โlightโ๋ก ์ค์ ํด์ฃผ์๋ค.
useEffect: ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋ ๋ ์คํ๋๋ค. ๋ก์ปฌ ์คํ ๋ฆฌ์ง์์ ์ด์ ์ ์ ์ฅ๋ ํ ๋ง๋ฅผ ๊ฐ์ ธ์ค๊ณ , ์ ์ฅ๋ ๊ฐ์ด ์๋ ๊ฒฝ์ฐ ๊ธฐ๋ณธ๊ฐ โlightโ๋ฅผ ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ๊ฐ๊ฐ์ ํ ๋ง ํจ์๋ฅผ ์คํํ๋ค.
setLightTheme / setDarkTheme: ๊ฐ๊ฐ์ ํ ๋ง๋ฅผ ์ค์ ํ๋ค. ์ต์์
<html>
ํ๊ทธ์dark
class๋ฅผ ๋ฃ๊ฑฐ๋ ์ ๊ฑฐํ์ฌ Tailwind์ darkmode๊ฐ ์ ์ฉ๋๋๋ก ํ๋ค.- ๊ธฐ์กด์ ์ฝ๋์์๋ ์ด ๋ถ๋ถ์ useEffect๋ก theme๊ฐ ๋ฐ๋๋๋ง๋ค ์คํ๋๊ฒ ํ์ง๋ง, ์ด ์ฝ๋์์๋ dark/light ์์ด์ฝ์ ํด๋ฆญํ๋ฉด ๊ฐ ์์ด์ฝ์ ํด๋นํ๋ ํจ์๊ฐ ์คํ๋๋๋ก ๋ณ๊ฒฝํ๋ค.
โ ๊ณต์๋ฌธ์๋ฅผ ์ฝ๊ณ ๋ณต๊ธฐ
๊ณต์๋ฌธ์๋ฅผ ์ฝ์ผ๋ฉด์ ํด๋น ์๋ฌ๋ฅผ ๋ค์ ๋ณต๊ธฐํ๋๋ฐ, ์๋ชป ์ดํดํ ๋ถ๋ถ์ด ์๋ ๊ฒ์ ๊นจ๋ฌ์๊ณ , ์ด ๋ถ๋ถ๋ ์์ ํด์ฃผ์๋ค.๐
ํด๋น ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์๋ ์ํฉ
- ๋ ๋๋ง ๋ก์ง์์
typeof window !== 'undefined'
์ ์ฒดํฌํ ๋ - ๋ธ๋ผ์ฐ์ ์ ์ฉ API์ธ window๋ localStorage๋ฅผ ์ฌ์ฉํ ๋
์ฒ์ ์ดํดํ ๋ 1๋ฒ์ ์ฌ์ฉํ๋ฉด ์ด ์๋ฌ๋ฅผ ์์จ ์ ์๋ ๊ฒ์ผ๋ก ์คํดํ์๋คโฆ๐ฅฒ ์ดํ ์ด ๋ถ๋ถ์ ์ญ์ ํด์ฃผ ์๊ณ , ๋ธ๋ผ์ฐ์ ์ ์ฉ API์ธ localStorage๋ useEffect์์ ๋ง์ดํธ ๋ ์ดํ์ ์ ๊ทผํ ์ ์๋๋ก ์ฝ๋๋ฅผ ๊ฐ์ ํ๋ฉฐ, ์ด ์๋ฌ๋ฅผ ํด๊ฒฐ ํ ์ ์์๋ค.