✨ [FEAT/#98] 테마 깜빡임 방지 스크립트 추가 - #99
Merged
Merged
Conversation
✨ [Feature] 테스트 코드 추가
[Develop] update dependent
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.4.1 to 6.4.2. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v6.4.2/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v6.4.2/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 6.4.2 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com>
…e/vite-6.4.2 chore(deps-dev): bump vite from 6.4.1 to 6.4.2 in /e2e/site
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.12 to 5.0.6. - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](juliangruber/brace-expansion@v1.1.12...v5.0.6) --- updated-dependencies: - dependency-name: brace-expansion dependency-version: 5.0.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
…xpansion-5.0.6 chore(deps): bump brace-expansion from 1.1.12 to 5.0.6 (via audit fix)
3 tasks
There was a problem hiding this comment.
Pull request overview
This PR introduces a pre-hydration theme initialization path to reduce “theme flash” (FOUC) by applying a persisted theme before the first paint, while keeping React state and DOM attributes synchronized after the app starts.
Changes:
- Added
ThemeScript(inline<script>) that readslocalStorageearly and sets<html data-theme="...">for allowed themes. - Updated
ThemeProviderto perform initial synchronization via auseSafeLayoutEffect(layout effect on client, effect on server) for earlier DOM alignment. - Exported
ThemeScriptfromcolbrush/clientand documented recommended SSR/CSR usage in the README; adjusted type imports inmodes.tsto avoid runtime cycles.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/react/ThemeScript.tsx | New inline script component to apply persisted theme before first paint. |
| src/react/ThemeProvider.tsx | Uses a “safe” layout effect for earlier initial DOM/state synchronization. |
| src/core/constants/modes.ts | Converts imports to import type to prevent runtime circular import pressure. |
| src/client.ts | Exposes ThemeScript via the public client entry. |
| README.md | Documents how to place ThemeScript in <head> to prevent theme flash. |
Comments suppressed due to low confidence (1)
src/react/ThemeProvider.tsx:78
- The initial theme/language/simulation sync reads from localStorage without a try/catch. In some browser/privacy contexts, accessing localStorage can throw (e.g. storage blocked), which would crash the provider during layout effect and prevent rendering. Consider guarding the whole sync block and falling back to defaults when storage is unavailable.
useSafeLayoutEffect(() => {
if (typeof window === 'undefined') return;
const storedTheme = normalizeToKey(
localStorage.getItem(ThemeStorageKey)
);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🚀 Related Issue
Closes #98
Type of Change
📝 Description
SSR/CSR 환경에서 저장된 테마가 React 초기화 이후에 적용되며 화면이 기본 테마로 잠깐 보이는 문제를 줄이기 위해
ThemeScript를 추가했습니다.기존에는
ThemeProvider가 마운트된 뒤useEffect에서localStorage의 저장 테마를 읽고<html data-theme="...">를 설정했습니다. 이 방식은 React 실행 이후에 테마가 적용되기 때문에, Next.js/Remix 같은 SSR 환경뿐 아니라 일반 CSR 환경에서도 첫 화면이 기본 테마로 잠깐 보일 수 있었습니다.이번 PR에서는 다음을 변경했습니다.
ThemeScript컴포넌트 추가<head>에 배치할 수 있는 inline script 컴포넌트입니다.localStorage의colbrush-theme값을 읽습니다.default,protanopia,deuteranopia,tritanopia중 하나일 때만<html data-theme="...">를 설정합니다.localStorage접근이 실패해도 앱이 깨지지 않도록try/catch로 감쌌습니다.ThemeScriptexport 추가colbrush/client에서ThemeScript를 import할 수 있도록 export했습니다.ThemeProvider초기 동기화 로직 개선useEffect기반 초기 동기화를useSafeLayoutEffect로 변경했습니다.useLayoutEffect를 사용해 브라우저 페인트 전에 React 상태와 DOM 테마를 최대한 빠르게 맞춥니다.useEffect로 fallback하여 SSR 경고를 피합니다.useState(() => localStorage...)방식은 사용하지 않았습니다. SSR에서 서버 렌더 결과와 클라이언트 첫 렌더 결과가 달라져 hydration mismatch를 만들 수 있기 때문입니다.런타임 순환 import 방지
src/core/constants/modes.ts의 타입 import를import type으로 변경했습니다.ThemeScript가THEME_MODES를 재사용하면서 불필요한 런타임 import가 생기지 않도록 정리했습니다.README 업데이트
ThemeScript를<head>에 추가해 theme flash를 방지하는 사용 예시를 문서화했습니다.ThemeProvider는 앱 시작 이후 React 상태 동기화를 담당하고, 첫 페인트 전 테마 적용은ThemeScript가 담당한다는 흐름을 안내했습니다.사용 예시는 다음과 같습니다.
✔️ Checklist
📸 Screenshots (Optional)
해당 변경은 초기 테마 적용 타이밍 개선 및 public API 추가 작업이라 별도 UI 스크린샷은 없습니다.
💬 Review Notes (Optional)
리뷰 시 아래 부분을 중점적으로 확인 부탁드립니다.
검증 결과: