React 애플리케이션에서 테마 플래시 방지하기: 즉각적인 다크 모드 전환
2025-04-06 03:19:27React에서 테마 플래시 문제란?
리액트 기반의 웹 애플리케이션에서 테마 플래시 문제는 다크 모드를 구현할 때 흔히 마주하게 되는 문제 중 하나입니다. 이는 애플리케이션이 처음 로드될 때, 기본 라이트 테마로 보여졌다가 자바스크립트가 실행되고 나서야 사용자 설정에 따라 다크 테마로 변경되면서 생기는 깜빡임(hereinafter 테마 플래시)입니다.
문제의 원인과 이해
초기 로드의 플래시 문제
- 브라우저는 먼저
index.html을 렌더링합니다. - 이후 자바스크립트 파일을 로드하여, 리액트와 테마 기능이 적용됩니다.
localStorage에 저장된 테마 정보를 리액트가 적용하기 전까지 잠깐동안 기본 라이트 테마가 노출됩니다.- 특히 다크 모드를 선호하는 사용자에게 이 플래시는 잘못된 테마로 페이지가 보이는 경험을 불러옵니다.
테마 플래시 방지를 위한 전략
리액트 로드 전에 테마 적용하기
이 문제를 해결하기 위해, 작은 인라인 스크립트를 index.html에 삽입하여 페이지가 렌더링되기 전에 저장된 테마를 즉시 적용할 수 있습니다.
구현 방법
index.html 내 스크립트 추가
아래의 스크립트를 index.html의 <head> 섹션에 추가하십시오.
<script>
const THEME_ATTRIBUTE_KEY = 'data-theme';
const GET_THEME = () => {
const isDeviceDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
return localStorage.getItem(THEME_ATTRIBUTE_KEY) || (isDeviceDark ? 'dark' : 'light');
};
const SET_THEME = (themeValue) => {
document.documentElement.removeAttribute('data-theme');
document.documentElement.setAttribute('data-theme', themeValue);
document.querySelector("meta[name='theme-color']")
?.setAttribute('content', themeValue === 'dark' ? '#1c1c1e' : '#ffffff');
localStorage.setItem(THEME_ATTRIBUTE_KEY, themeValue);
};
const theme = GET_THEME();
SET_THEME(theme);
</script>
작동 원리
- 이 스크립트는
index.html이 로드될 때 즉시 실행되어 리액트가 초기화되기 전 테마를 적용합니다. - 사용자의 테마 설정을
localStorage에서 읽어오거나 시스템의 선호 사항을 감지합니다. - 테마는
<html>요소에data-theme속성을 설정하여 적용됩니다. - 이를 통해 모바일 환경에서도 일관된 경험을 제공하기 위해
theme-color메타 태그도 업데이트합니다.
테마 플래시 방지의 대안과 단점
간단한 인라인 스크립트
<script>
(function () {
const theme = localStorage.getItem("theme") || "light";
document.documentElement.setAttribute("data-theme", theme);
})();
</script>
- 이 스크립트는 리액트가 로드되기 전에 테마가 적용되도록 보장합니다.
- 하지만 이 방법은 중복된 로직을 초래할 수 있습니다.
- 페이지 로드 시에만 실행되기 때문에 사용자가 테마를 전환할 경우 별도의 리액트 기능이 필요합니다.
통합된 로직 유지의 장점
index.html내의 스크립트는 테마가 즉시 설정되도록 보장합니다.- 리액트 컴포넌트 내에서도 동일한 기능을 사용할 수 있습니다.
- 중복된 로직이 없으므로 유지 보수 및 확장이 용이합니다.
타입스크립트를 활용한 타입 안전성
타입스크립트를 사용하는 프로젝트라면, 글로벌 함수와 상수를 별도의 .d.ts 파일에 선언하여 타입 안전성을 확보할 수 있습니다.
declare const SET_THEME: (themeValue: 'light' | 'dark') => void;
declare const GET_THEME: () => 'light' | 'dark';
declare const THEME_ATTRIBUTE_KEY: string;
이를 통해 리액트 컴포넌트에서 다음과 같이 타입스크립트의 지원을 받으며 사용할 수 있습니다:
const theme = GET_THEME();
const handleThemeToggle = () => {
SET_THEME(theme === 'light' ? 'dark' : 'light');
};
이렇게 하면, 잘못된 글로벌 함수 사용을 타입스크립트가 감지하여 코드의 견고성 및 유지 보수성을 향상시킵니다.
잠재적 단점과 고려 사항
1. 글로벌 함수 이름 충돌 가능성
SET_THEME 및 GET_THEME의 글로벌 선언은 대규모 프로젝트에서 충돌을 일으킬 수 있습니다. 이를 피하려면 함수를 window 객체에 붙여넣어 캡슐화할 수 있습니다.
window.themeUtils = { SET_THEME, GET_THEME };
리액트 내에서는 이렇게 접근할 수 있습니다:
const theme = window.themeUtils.GET_THEME();
window.themeUtils.SET_THEME(theme === 'light' ? 'dark' : 'light');
2. localStorage 사용 제한
일부 브라우저의 프라이빗 모드에서는 localStorage 사용이 제한될 수 있습니다. 이를 다루기 위해 localStorage 호출을 try-catch로 감싸세요.
const safeLocalStorageGet = (key) => {
try {
return localStorage.getItem(key);
} catch {
return null;
}
};
이후 테마 검색 함수를 다음과 같이 업데이트할 수 있습니다:
const GET_THEME = () => {
const isDeviceDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
return safeLocalStorageGet(THEME_ATTRIBUTE_KEY) || (isDeviceDark ? 'dark' : 'light');
};
3. 하드코딩된 라이트 및 다크 테마 값
추가 테마가 필요한 경우, 사용 가능한 테마 배열을 저장하고 적용 전에 저장된 값을 검증하는 유연한 접근 방식을 사용해야 합니다.
const AVAILABLE_THEMES = ['light', 'dark', 'solarized'];
const GET_THEME = () => {
const savedTheme = localStorage.getItem(THEME_ATTRIBUTE_KEY);
return AVAILABLE_THEMES.includes(savedTheme) ? savedTheme : 'light';
};
const SET_THEME = (themeValue) => {
if (AVAILABLE_THEMES.includes(themeValue)) {
/// code
}
};
결론
index.html에서 즉시 테마를 적용함으로써, 초기 로드 시 잘못된 테마의 플래시를 방지할 수 있습니다. 이 간단하지만 효과적인 기술은 테마 전환과 함께 리액트 애플리케이션의 사용자 경험을 크게 향상시킵니다.
위에서 설명한 방법은 모든 상황에서 작동하지는 않을 수 있으나, 일반적인 리액트 애플리케이션의 테마 전환에 적합하며, 간단함과 기능성 사이의 균형을 잘 잡고 있습니다. 특정 문제를 마주하거나 더 많은 세부 정보가 필요하면 언제든지 문의 주시면, 더 구체적인 사례와 해결책을 포함하여 글을 확장할 수 있습니다.
** 참고 자료와 추가 정보: **