
๋คํฌ ๋ชจ๋ ๋์ ์คํจ๊ธฐ : ์์์น ๋ชปํ ๊ฑฐ๋ํ ๋ฒฝ
๋์ : ์ต์ํจ์ด ์๊ฒจ์ค ๋ฐฉ์ฌ
๋ธ๋ก๊ทธ์ ๋์์ธ์ ๊ธฐ๋ฅ ๊ฐ์ ์ ์งํํ๊ณ ์ถ์ด, '๋คํฌ ๋ชจ๋'๋ฅผ ๋์
ํ๊ธฐ๋ก ๋ง์๋จน์๋ค. ๋ง์ ์ด๋ ค์์ ๊ฒช์๋ ๋ค์ํ ๊ธฐ๋ฅ ์ถ๊ฐ (ํนํ ์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ง..) ๋ฅผ ์งํํด๋ณด๋, ํ๋ก ํธ์๋์์ ๋คํฌ๋ชจ๋ ๋์
์ ๊ทธ๋ฆฌ ์ด๋ ค์ด ์์
์ ์๋ ๊ฑฐ๋ผ ์๊ฐํ๋ค. ํนํ ์ด์ ์ ์งํํ๋ ๋จ์ HTML, CSS, JavaScript๋ก ๋ง๋ค์๋ ๋ด ํฌํธํด๋ฆฌ์ค ์น์ฌ์ดํธ RollerCoaster ์์๋ localStorage์ CSS ๋ณ์๋ฅผ ์ด์ฉํด ์์์ธ๋ก ์ฝ๊ฒ ์ ์ฉํ ๊ฒฝํ์ด ์์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋์ ๊ฒฝํ์ ๊ฐ๋จํ๋ค. CSS ๋ณ์๋ก ์์ ์์คํ
์ ์ ์ํ๊ณ , JavaScript๋ก <body> ํ๊ทธ์ data-theme="dark" ์์ฑ ํ๋๋ง ํ ๊ธํด์ฃผ๋ฉด ๋ชจ๋ ๊ฒ์ด ๋ง๋ฒ์ฒ๋ผ ๋ฐ๋์๋ค. ๊ทธ ์ต์ํจ๊ณผ ์์ ๊ฐ์, ๋ด๊ฐ ๋ง์ฃผํ ๊ฑฐ๋ํ ๋ฒฝ์ ๋์ด๋ฅผ ๊ฐ๋ ํ์ง ๋ชปํ๊ฒ ๋ง๋ ๊ฐ์ฅ ํฐ ์์ธ์ด์๋ค.
๋ด ์๋ก์ด ๋ธ๋ก๊ทธ ํ๋ก์ ํธ์ ๊ธฐ์ ์คํ์ ์ด์ ๊ณผ๋ ์ฐจ์์ด ๋ฌ๋๋ค.
- Next.js 15 (App Router)
- React 19
- Tailwind CSS v4
- Toast UI Editor
ํ๋์ ์ธ ์น ๊ฐ๋ฐ์ ํธ๋ฆฌํจ์ ๋ชจ๋ ๋๋ฆฌ๊ณ ์์์ง๋ง, ๋ฐ๋ก ๊ทธ 'ํธ๋ฆฌํจ'์ ์ ๊ณตํ๋ ๊ฐ ์์คํ ์ ๋ณต์กํ ์ํธ์์ฉ์ด ์ด๋ฒ ๋คํฌ ๋ชจ๋ ๋์ ์ ๋ฐ๋ชฉ์ ์กํ๊ฒ ๋ ์ค์ ๊ฟ์๋ ๋ชฐ๋๋ค.
Phase 1: ๊ธฐ๋ณธ ์ค์ , ์์กฐ๋ก์ด ์ถ๋ฐ
์ฒ์ ์งํ์ ์์กฐ๋ก์ ๋ค. next-themes ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ๊ณ , ThemeProvider๋ฅผ ์ฑ ์ ์ฒด์ ์ ์ฉํ๋ค. tailwind.config.js์ darkMode: 'class'๋ฅผ ์ถ๊ฐํ๊ณ , globals.css์ ๋ผ์ดํธ/๋คํฌ ๋ชจ๋์ฉ CSS ๋ณ์๋ฅผ ์ ์ํ๋ค.
/* globals.css */ :root { --bg-primary: #ffffff; --text-primary: #111827; /* ... */ } html.dark { --bg-primary: #111827; --text-primary: #f9fafb; /* ... */ }
Header, PostCard ๋ฑ ๋ด๊ฐ ์ง์ ๋ง๋ ์ปดํฌ๋ํธ๋ค์ dark: ์ ๋์ฌ๋ฅผ ๋ถ์ฌ์ฃผ์๋ง์ ๋ง๋ฒ์ฒ๋ผ ๋คํฌ ๋ชจ๋์ ๋ฐ์ํ๊ธฐ ์์ํ๋ค. ์ฌ๊ธฐ๊น์ง๋ ๋ชจ๋ ๊ฒ์ด ์์๋๋ก์๋ค. ๋ฌธ์ ๋ ๊ฐ์ฅ ์ค์ํ ์ฝํ
์ธ ์์ญ, ๋ฐ๋ก Toast UI Editor์์ ์์๋์๋ค.
Phase 2: ์ฒซ ๋ฒ์งธ ์ถฉ๋, Viewer์ ์ ์ ๊ณผ Editor์ ์นจ๋ฌต
- ์ฒ์ ๋คํฌ๋ชจ๋๋ฅผ ๋์ ํ๊ณ ๋ฉ์ธ ํ์ด์ง๋ ์ ์ ์ฉ๋์๋ค.

- ํ์ง๋ง ์์ธํ์ด์ง๋ก ๋ค์ด๊ฐ์ ๋์ฐธ์ฌ๊ฐ ์ผ์ด๋ฌ๋ค. ์์ ๋ ๊ฒ์๊ธ์ด ๋ณด์ด๊ธฐ ์์ํ๋ค.

- ํ
๋ง ํ ๊ธ ๋ฒํผ์ ๋๋ฌ
<html>ํ๊ทธ์class="dark"๊ฐ ๋ถ์ด๋, ์๋ํฐ๋ ์์ง๋ถ๋์ด์๋ค. ํ์ด์ง์ ๋ชจ๋ ๊ฒ์ด ์ด๋์์ก์ง๋ง, ์๋ํฐ๋ง์ ์ํ์ ๋ฐฐ๊ฒฝ์ ๊ณ ์งํ๋ฉฐ ํ๋ก ๋น๋๊ณ ์์๋ค.

์ด๋๋ถํฐ ๊ธฐ๋๊ธด ๋๋ฒ๊น ์ฌ์ ์ด ์์๋์๋ค.
๊ฐ์ค 1: SSR ๋ฌธ์ ์ผ ๊ฒ์ด๋ค.
Toast UI Editor๋ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์ ์์กด์ ์ด๋, next/dynamic์ผ๋ก SSR์ ๋นํ์ฑํํ๋ฉด ํด๊ฒฐ๋ ๊ฑฐ๋ผ ์๊ฐํ๋ค.
// Editor.tsx const TuiEditor = dynamic( () => import('@toast-ui/react-editor').then((mod) => mod.Editor), { ssr: false } );
๊ฒฐ๊ณผ๋? ์คํจ. SSR ์๋ฌ๋ ์ฌ๋ผ์ก์ง๋ง, ์๋ํฐ๋ ์ฌ์ ํ ํ์๋ค.
๊ฐ์ค 2: ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ณต์ API๋ฅผ ์ฌ์ฉํ์ง ์์๋ค.
๋ฆฌ์์น๋ฅผ ํตํด theme prop๊ณผ key๋ฅผ ์ด์ฉํ ๊ฐ์ ์ฌ๋ง์ดํธ ์ ๋ต์ ์๊ฒ ๋์๋ค. "์ด๊ฑฐ๋ฉด ๋๊ฒ ์ง."
// Editor.tsx <TuiEditor key={theme} theme={theme === 'dark' ? 'dark' : 'default'} // ... />
๊ฒฐ๊ณผ๋? ๋๋ค์ ์คํจ. ์๋ํฐ๋ ๊ผผ์ง๋ ํ์ง ์์๋ค.
Phase 3: ๋ณด์ด์ง ์๋ ์ , CSS์์ ์ ์
์ด์ ๋ฌธ์ ๋ ์ฝ๋ ๋ก์ง์ด ์๋, ๋ณด์ด์ง ์๋ CSS์ ์ธ๊ณ์ ์๋ค๋ ๊ฒ์ ์ง๊ฐํ๋ค.
๊ฐ์ค 3: ๋คํฌ ๋ชจ๋ CSS ํ์ผ์ด ๋ก๋๋์ง ์์๋ค.
layout.tsx์์ toastui-editor-dark.css๋ฅผ ์ง์ importํ๊ณ , ๋ธ๋ผ์ฐ์ Network ํญ์ ํ์ธํ๋ค. ํ์ผ์ ๋ก๋๋๊ณ ์์๋ค. ํ์ง๋ง Next.js์ CSS ๋ฒ๋ค๋ง ๋ฐฉ์์ด ์์ฌ์ค๋ฌ์, globals.css์์ @importํ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ๋ ํด๋ณด์๋ค. ๊ฒฐ๊ณผ๋ ๊ฐ์๋ค. CSS ํ์ผ์ ๋ถ๋ช
ํ ์กด์ฌํ๋ค.
๊ฐ์ค 4: CSS ์ฐ์ ์์(Specificity) ๋ฌธ์ ๋ค.
"Tailwind CSS์ ๋ง๊ฐํ ๊ธฐ๋ณธ ์คํ์ผ์ด Toast UI์ ์คํ์ผ์ ๋ฎ์ด์ฐ๊ณ ์๋ ๊ฒ ๋ถ๋ช
ํด."
๋๋ tailwind.config.js์ important: '#app-root'๋ฅผ ์ค์ ํ์ฌ Tailwind์ ์ํฅ ๋ฒ์๋ฅผ ์ ํํ๊ณ , globals.css ์ตํ๋จ์ !important๋ฅผ ๋์ํ ์ค๋ฒ๋ผ์ด๋ ๊ท์น๊น์ง ์ถ๊ฐํ๋ค.
/* globals.css */ html.dark #app-root .toastui-editor-defaultUI { background-color: var(--bg-secondary) !important; /* ... */ }
๊ฒฐ๊ณผ๋? ์ฒ์ฐธํ ์คํจ. ๋ด ๋ชจ๋ ๋
ธ๋ ฅ์๋ ๋ถ๊ตฌํ๊ณ ์๋ํฐ์ ๋ฐฐ๊ฒฝ์์ ๋ณํ์ง ์์๋ค. ๊ฐ๋ฐ์ ๋๊ตฌ์ Styles ํญ์ ๋ด๊ฐ ์ถ๊ฐํ !important ๊ท์น์ด ์ ์ฉ์กฐ์ฐจ ๋์ง ์์๊ฑฐ๋, ๋ค๋ฅธ ๋ฌด์ธ๊ฐ์ ์ํด ๋ฌด์๋๊ณ ์์์ ๋ณด์ฌ์ฃผ๊ณ ์์๋ค.


์ค๊ฐ ๊ฒฐ๋ก : ๋ฏธ๊ถ ์์ผ๋ก
๋ชจ๋ ํ์ค์ ์ธ ํด๊ฒฐ์ฑ
์ ์๋ํ์ง๋ง, ๋ฌธ์ ๋ ํด๊ฒฐ๋์ง ์์๋ค. CSS ํ์ผ์ ๋ก๋๋์๊ณ , <html> ํ๊ทธ์ ํด๋์ค๋ ๋ณ๊ฒฝ๋์์ผ๋ฉฐ, ์๋ํฐ ์ปดํฌ๋ํธ๋ ์ฌ๋ง์ดํธ๊น์ง ๋๊ณ ์์๋ค. ํ์ง๋ง ๊ทธ ๋ชจ๋ ์ ํธ๋ฅผ ์๋ํฐ๋ ์ฒ ์ ํ ๋ฌด์ํ๊ณ ์์๋ค.
๋ง์น ๋ชจ๋ ๋ถํ์ด ์ ์์ธ๋ฐ ์๋์ด ๊ฑธ๋ฆฌ์ง ์๋ ์๋์ฐจ๋ฅผ ๋ง์ฃผํ ๊ธฐ๋ถ์ด์๋ค. ๋ฌธ์ ๋ ๋ด๊ฐ ์๋ ์์ญ ๋๋จธ, Next.js์ ๋น๋ ์์คํ ๊ณผ Tailwind v4, ๊ทธ๋ฆฌ๊ณ Toast UI Editor๋ผ๋ ์ธ ๊ฐ์ ๊ฑฐ๋ํ ๋ธ๋๋ฐ์ค๊ฐ ์ํธ์์ฉํ๋ ๊น์ ๊ณณ์ ์จ์ด์๋ ๊ฒ ๊ฐ์๋ค.
๋๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด, ๋ ๊น์ ์ฌ์ฐ์ผ๋ก ๋ค์ด๊ฐ๊ธฐ๋ก ๊ฒฐ์ฌํ๋ค.
Phase 4: ๋ง์ง๋ง ์งํธ๋ผ๊ธฐ, ์ง๋จ๊ณผ ๊ฒ์ฆ
1๋ถ์์ ๋ชจ๋ ํ์ค์ ์ธ ํด๊ฒฐ์ฑ ์ด ์คํจ๋ก ๋์๊ฐ ํ, ๋๋ ์ ๊ทผ ๋ฐฉ์์ ๋ฐ๊พธ๊ธฐ๋ก ํ๋ค. ๋ ์ด์ ์๋ก์ด ํด๊ฒฐ์ฑ ์ '์๋'ํ๋ ๋์ , ๋ฌธ์ ์ ๊ทผ๋ณธ ์์ธ์ '์ง๋จ'ํ๋ ๋ฐ ์ง์คํ๊ธฐ๋ก ํ ๊ฒ์ด๋ค. "์ ์ ๋์ง?"๋ผ๋ ๋ง์ฐํ ์ง๋ฌธ ๋์ , "์ง๊ธ ๋ฌด์จ ์ผ์ด ์ผ์ด๋๊ณ ์๋๊ฐ?"๋ผ๋ ๊ตฌ์ฒด์ ์ธ ์ง๋ฌธ์ ๋์ง๊ธฐ ์์ํ๋ค.
์ด๋ฅผ ์ํด ๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ์ ์ฝ์์ ๋ช ๊ฐ์ง ์ง๋จ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ์ฌ, ๋์ ๋ณด์ด์ง ์๋ ๋ด๋ถ ๋์์ 'ํฉํธ'๋ก ์์งํ๋ค.
์ง๋จ 1: CSS ๊ท์น์ ์ ๋ง ์กด์ฌํ๋๊ฐ?
document.styleSheets๋ฅผ ๋ชจ๋ ๋ค์ ธ .toastui-editor-dark ๊ด๋ จ ๊ท์น์ด ์๋์ง ํ์ธํ๋ค.
// ์ฝ์ ์คํ ๊ฒฐ๊ณผ { ..., hasDark: true }
๊ฒฐ๋ก : ์กด์ฌํ๋ค. CSS ํ์ผ์ ์ ์์ ์ผ๋ก ๋ก๋๋์๊ณ , ๋คํฌ ๋ชจ๋ ๊ท์น๋ ๋ฒ๋ค ์์ ํฌํจ๋์ด ์์๋ค.
์ง๋จ 2: ์๋ํฐ์ ๋ด๋ถ ๊ตฌ์กฐ๋ ์์๊ณผ ๊ฐ์๊ฐ?
<iframe>์ด๋ shadow DOM์ ์ฌ์ฉํ๋ค๋ฉด ์ธ๋ถ CSS๊ฐ ์ ์ฉ๋์ง ์์ ์ ์๋ค. ์๋ํฐ์ DOM ๊ตฌ์กฐ๋ฅผ ํ์ธํ๋ค.
// ์ฝ์ ์คํ ๊ฒฐ๊ณผ Iframe Found: false ShadowRoot Present: false
๊ฒฐ๋ก : ์๋์๋ค. ์๋ํฐ๋ ํ๋ฒํ div๋ก ๋ ๋๋ง๋๊ณ ์์๋ค. ์ธ๋ถ ์คํ์ผ์ด ์ ์ฉ๋์ง ์์ ์ด์ ๊ฐ ์์๋ค.
์ง๋จ 3: ๋คํฌ ๋ชจ๋ ํด๋์ค๋ ์ ๋๋ก ์ฃผ์
๋๊ณ ์๋๊ฐ?
useLayoutEffect๋ฅผ ์ฌ์ฉํด .toastui-editor-dark ํด๋์ค๋ฅผ ์ฃผ์
ํ๋ ๋ก์ง์ด ์ ๋ง ๋์ํ๋์ง ํ์ธํ๋ค.
// ์ฝ์ ์คํ ๊ฒฐ๊ณผ (Elements ํญ ํ์ธ) <div class="toastui-editor-defaultUI toastui-editor-dark">...</div>
๊ฒฐ๋ก : ์ฑ๊ณต์ ์ผ๋ก ์ฃผ์
๋๊ณ ์์๋ค. <html> ํ๊ทธ์ ํด๋์ค๊ฐ dark๋ก ๋ฐ๋๋ฉด, ์๋ํฐ์ ๋ฃจํธ div์๋ .toastui-editor-dark ํด๋์ค๊ฐ ๋ถ๋ช
ํ ์ถ๊ฐ๋์๋ค.
์ฌ์ฐ์ ๋: ๋ชจ๋ ๊ฒ์ด ์ ์์ธ๋ฐ, ์๋ฌด๊ฒ๋ ๋ณํ์ง ์๋๋ค
๋ชจ๋ ์ง๋จ ๊ฒฐ๊ณผ๋ ๋๋ฅผ ๋ ๊น์ ๋ฏธ๊ถ์ผ๋ก ๋น ๋จ๋ ธ๋ค.
- ๋คํฌ ๋ชจ๋ CSS ๊ท์น์ ์กด์ฌํ๋ค.
- ์คํ์ผ์ ์ ์ฉํ DOM ์์๋ ์กด์ฌํ๋ค.
- ๋ ์์๋ฅผ ์ฐ๊ฒฐํ ํด๋์ค๋ ์ ์์ ์ผ๋ก ์ฃผ์ ๋๋ค.
ํ์ง๋ง ๊ฒฐ๊ณผ๋ ๋ณํ์ง ์์๋ค. ์๋ํฐ๋ ์ฌ์ ํ ์ํ์ ๋ชจ์ต ๊ทธ๋๋ก์๋ค. ์ด ํ์์ CSS์ ๊ธฐ๋ณธ ๋์ ์๋ฆฌ๋ฅผ ๊ฑฐ์ค๋ฅด๋ ๊ฒ์ฒ๋ผ ๋ณด์๋ค. ์ด๋ ๋จ์ํ ๋๋ฒ๊น ์ผ๋ก ํด๊ฒฐ๋ ์์ค์ ๋์ด์ , ๋น๋ ์์คํ (Next.js, Turbopack, PostCSS)๊ณผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(Tailwind CSS v4, Toast UI Editor) ๊ฐ์ ๋ณต์กํ ์ํธ์์ฉ ํน์ ๋์ ๋ฐฉ์์ ๋ํ ๋ฆฌ์์น์ ๋ถ์ ํ์ฑ ๋ฌธ์ ์์ ์ง๊ฐํ๋ค.
๊ฒฐ๋ก : ์ ๋ต์ ํํด, ๊ทธ๋ฆฌ๊ณ ๋ค์์ ์ํ ์ฝ์
๋ชจ๋ ๋ฌธ์ ์๋ ํด๊ฒฐ์ฑ
์ด ์๋ค๊ณ ๋ฏฟ์ง๋ง, ๊ทธ ํด๊ฒฐ์ฑ
์ ์ฐพ๋ ๋ฐ ๋๋ '๋น์ฉ'์ ํญ์ ๊ณ ๋ คํด์ผ ํ๋ค. ํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ Next.js๋ Tailwind์ ๋น๋ ํ๋ก์ธ์ค ๋ด๋ถ๋ฅผ ํ๊ณ ๋ค๊ฑฐ๋, Toast UI Editor์ CSS ์ ์ฒด๋ฅผ !important๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์์ฑํด์ผ ํ ์๋ ์๋ค. ๊ณ์ํด์ ๊ทผ๋ณธ์ ์ธ ์์ธ์ ์ฐพ์ง ๋ชปํ๊ณ ๋๋ฒ๊น
์ํฉ์ ํด๊ฒฐํ์ง ๋ชปํ๋ ๊ฒ์ ์๋ฏธ๊ฐ ์๋ค๊ณ ํ๋จํ๋ค.
๊ทธ๋์ ๋๋ ๋คํฌ๋ชจ๋ ๋์ ์ ๋ํด '์ ์ ์ค๋จ' ์ด๋ผ๋, ์ด์ฉ๋ฉด ๊ฐ์ฅ ์ด๋ ค์ด ๊ฒฐ์ ์ ๋ด๋ฆฌ๊ธฐ๋ก ํ๋ค. ์ด๊ฒ์ ํฌ๊ธฐ๊ฐ ์๋, ๋ ์ค์ํ ๋ชฉํ์ ์ง์คํ๊ธฐ ์ํ ์ ๋ต์ ํํด๋ค.
๋ค๋ง, Toast UI Editor์ ๋ค์ํ Reactํ๊ฒฝ์์ ๋คํฌ๋ชจ๋๋ฅผ ๋์ ํ ๋ค๋ฅธ ํ๋ฅญํ์ ๋ถ๋ค์ ํฌ์คํ ์ ๋ณด๋ฉฐ ํ์ตํ๊ณ , ๊ฒ์ฆํ๊ณ ๋ค์ ๋์ ํด๋ณผ ์์ ์ด๋ค.
๋ค์ ํฌ์คํ ์์๋ ๋ฐ๋์ ์ด ๋ฒฝ์ ๋์ด, Deep Dive! ๋ธ๋ก๊ทธ์ ์๋ฒฝํ ๋คํฌ ๋ชจ๋๋ฅผ ์ ์ฉํ ๊ฒ์ด๋ค.
๋ฐ๋์..๐
