์ปจํ
์คํธ ๋ธ๋ก
| Key |
Value |
| Category |
frontend |
| Checklist |
ISS-UI-R9 โ Use of window/document/localStorage without "use client" guard or SSR safety |
| Priority |
P2 ๐ก |
| Scan Date |
2026-04-16 |
| Flagged By |
@code-review |
์์ฝ
- WHAT:
frontend/src/providers/Settings.tsx๊ฐ useState, useEffect, document.documentElement, window.matchMedia๋ฅผ ์ฌ์ฉํ๋ฉด์ \"use client\" ๋๋ ํฐ๋ธ๊ฐ ์์
- WHY: ํ์ฌ๋
MainLayoutClient(client component)์์๋ง import๋์ด ๋์ํ์ง๋ง, ํฅํ ๋ค๋ฅธ server component์์ import ์ SSR ํฌ๋์. Next.js 15 ์ปจ๋ฒค์
์๋ฐ
- WHERE:
frontend/src/providers/Settings.tsx:1 (ํ์ผ ์ฒซ ์ค์ \"use client\" ๋๋ฝ)
- SEVERITY: MEDIUM โ ์ ์ฌ์ SSR ํฌ๋์ + ์ฝ๋ ์ปจ๋ฒค์
์๋ฐ
Evidence
| # |
File |
Line |
Finding |
Flagged By |
Confidence |
| 1 |
frontend/src/providers/Settings.tsx |
1 |
ํ์ผ ์ฒซ ์ค \"use client\" ๋๋ ํฐ๋ธ ๋๋ฝ |
@code-review |
High |
| 2 |
frontend/src/providers/Settings.tsx |
109, 138, 151 |
document.documentElement.classList, window.matchMedia ์ฌ์ฉ โ client-only API |
@code-review |
High |
| 3 |
frontend/src/providers/Settings.tsx |
39 |
typeof window === \"undefined\" ์ฒดํฌ๋ ์ผ๋ถ ํจ์์๋ง ์กด์ฌ, ๋ชจ๋ hook callback์ ์ ์ฉ ์๋จ |
@code-review |
Medium |
์ํฅ ๋ถ์
์ํฅ ๋ฒ์
- ํฅํ Settings provider๋ฅผ ๋ค๋ฅธ ์ปดํฌ๋ํธ์์ importํ ๋
- Next.js 15์ ๋ ์๊ฒฉํ RSC(React Server Components) ๊ฒ์ฆ
์ฅ์ ์๋๋ฆฌ์ค
- ๊ฐ๋ฐ์๊ฐ Settings provider๋ฅผ server component์ธ ์ layout์์ import
- SSR ์์ ์
document is not defined ์๋ฌ โ ๋น๋/๋ฐํ์ ํฌ๋์
- ๋๋ React 19 strict mode์์ hooks ํธ์ถ ์์น ๊ฒ์ฆ ์คํจ
๊ธด๊ธ๋
- ์ฆ๊ฐ์ ์ฅ์ ์์ผ๋ ์ปจ๋ฒค์
์๋ฐ์ ๋๋ฒ๊น
์ด๋ ค์ด ๋ฏธ๋ ๋ฒ๊ทธ ์ ๋ฐ
- ๋ช
์์ ๋๋ ํฐ๋ธ๋ ๋ฏธ๋ ์ ์ง๋ณด์์์๊ฒ ์๋ ์ ๋ฌ
์ ์ ํด๊ฒฐ ๋ฐฉ์
์ ๊ทผ ๋ฐฉ๋ฒ
ํ์ผ ์ต์๋จ์ ๋๋ ํฐ๋ธ ์ถ๊ฐ:
// frontend/src/providers/Settings.tsx
\"use client\";
import { ... } from \"...\";
// ... ๊ธฐ์กด ์ฝ๋
์ถ๊ฐ๋ก ์ frontend/src/providers/ ๋๋ ํ ๋ฆฌ๋ฅผ grepํ์ฌ ๋์ผ ๋๋ฝ ๊ฒ์ฌ:
for f in frontend/src/providers/*.tsx frontend/src/providers/*.ts; do
head -1 \"$f\" | grep -q '\"use client\"' || echo \"MISSING: $f\"
done
ESLint ๊ท์น์ผ๋ก ๊ฐ์ :
// frontend/eslint.config.js
\"react/no-direct-document\": \"error\", // ๋๋ next.js์ client/server boundary ๊ท์น ํ์ฉ
๋์
- module-level guard:
if (typeof window === \"undefined\") return null ์ถ๊ฐ โ ๋์์ ํ์ง๋ง ๋๋ ํฐ๋ธ๊ฐ ๋ ๋ช
ํ
- Server Component๋ก ๋ฆฌํฉํฐ: hooks ์ฌ์ฉ์ผ๋ก ์ธํด ๋ถ๊ฐ๋ฅ
์์ฉ ๊ธฐ์ค
์ฐธ์กฐ
์ฌํ ๋ฐฉ๋ฒ
์ฌ์ ์กฐ๊ฑด
๋จ๊ณ
head -1 frontend/src/providers/Settings.tsx๋ก ์ฒซ ์ค ํ์ธ
- import ๊ฒ์:
grep -r \"from .*providers/Settings\" frontend/src/
- importํ ๋ชจ๋ ๊ณณ์ด client component์ธ์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ
\"use client\"๊ฐ ์ฒซ ์ค
์ค์ ๊ฒฐ๊ณผ
import ๋ฌธ๋ถํฐ ์์ โ ๋๋ ํฐ๋ธ ์์
๊ด๋ จ ์ฝ๋ ์ปจํ
์คํธ
| File |
Role |
Relevance |
frontend/src/providers/Settings.tsx |
Settings context provider |
์์ ๋์ |
frontend/src/shared/components/layout/MainLayoutClient.tsx |
Settings provider import ์์น |
ํ์ฌ client tree ๋ณด์ฅ |
Detected by oh-my-braincrew `omb:issue` scan
Category: frontend | Scan date: 2026-04-16
`omb-issue-scan category=frontend checklist=ISS-UI-R9`
์ปจํ ์คํธ ๋ธ๋ก
ISS-UI-R9โ Use of window/document/localStorage without "use client" guard or SSR safety์์ฝ
frontend/src/providers/Settings.tsx๊ฐuseState,useEffect,document.documentElement,window.matchMedia๋ฅผ ์ฌ์ฉํ๋ฉด์\"use client\"๋๋ ํฐ๋ธ๊ฐ ์์MainLayoutClient(client component)์์๋ง import๋์ด ๋์ํ์ง๋ง, ํฅํ ๋ค๋ฅธ server component์์ import ์ SSR ํฌ๋์. Next.js 15 ์ปจ๋ฒค์ ์๋ฐfrontend/src/providers/Settings.tsx:1(ํ์ผ ์ฒซ ์ค์\"use client\"๋๋ฝ)Evidence
frontend/src/providers/Settings.tsx\"use client\"๋๋ ํฐ๋ธ ๋๋ฝfrontend/src/providers/Settings.tsxdocument.documentElement.classList,window.matchMedia์ฌ์ฉ โ client-only APIfrontend/src/providers/Settings.tsxtypeof window === \"undefined\"์ฒดํฌ๋ ์ผ๋ถ ํจ์์๋ง ์กด์ฌ, ๋ชจ๋ hook callback์ ์ ์ฉ ์๋จ์ํฅ ๋ถ์
์ํฅ ๋ฒ์
์ฅ์ ์๋๋ฆฌ์ค
document is not defined์๋ฌ โ ๋น๋/๋ฐํ์ ํฌ๋์๊ธด๊ธ๋
์ ์ ํด๊ฒฐ ๋ฐฉ์
์ ๊ทผ ๋ฐฉ๋ฒ
ํ์ผ ์ต์๋จ์ ๋๋ ํฐ๋ธ ์ถ๊ฐ:
์ถ๊ฐ๋ก ์
frontend/src/providers/๋๋ ํ ๋ฆฌ๋ฅผ grepํ์ฌ ๋์ผ ๋๋ฝ ๊ฒ์ฌ:ESLint ๊ท์น์ผ๋ก ๊ฐ์ :
// frontend/eslint.config.js \"react/no-direct-document\": \"error\", // ๋๋ next.js์ client/server boundary ๊ท์น ํ์ฉ๋์
if (typeof window === \"undefined\") return null์ถ๊ฐ โ ๋์์ ํ์ง๋ง ๋๋ ํฐ๋ธ๊ฐ ๋ ๋ช ํ์์ฉ ๊ธฐ์ค
frontend/src/providers/Settings.tsx1๋ฒ ์ค์\"use client\"์ถ๊ฐfrontend/src/providers/์ ์ฒด grep์ผ๋ก ๋๋ฝ 0๊ฑด ํ์ธcd frontend && pnpm buildํต๊ณผcd frontend && pnpm lint && pnpm tsc --noEmit์ฐธ์กฐ
frontend/src/providers/Settings.tsx์ฌํ ๋ฐฉ๋ฒ
์ฌ์ ์กฐ๊ฑด
๋จ๊ณ
head -1 frontend/src/providers/Settings.tsx๋ก ์ฒซ ์ค ํ์ธgrep -r \"from .*providers/Settings\" frontend/src/๊ธฐ๋ ๊ฒฐ๊ณผ
\"use client\"๊ฐ ์ฒซ ์ค์ค์ ๊ฒฐ๊ณผ
import๋ฌธ๋ถํฐ ์์ โ ๋๋ ํฐ๋ธ ์์๊ด๋ จ ์ฝ๋ ์ปจํ ์คํธ
frontend/src/providers/Settings.tsxfrontend/src/shared/components/layout/MainLayoutClient.tsx