React

[React] GlobalStyle 폰트 적용 안됨, 폰트 깜빡임 현상 원인 및 해결방법

yes_dohyun 2025. 4. 14. 16:57

1. 문제 상황: 폰트가 적용이 안돼!


Styled-components를 사용하여 프로젝트를 진행하면서 공통적인 CSS들은 GlobalStyle로 적용시키려고 하였다.

import { createGlobalStyle } from 'styled-components';
import PretendardLight from '../assets/fonts/Pretendard-Light.woff2';
import PretendardMedium from '../assets/fonts/Pretendard-Medium.woff2';
import PretendardBold from '../assets/fonts/Pretendard-Bold.woff2';

const GlobalStyle = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  html, body {
    font-family: 'Pretendard', sans-serif;
  }
  @font-face {
    font-family: 'Pretendard';
    src: url(${PretendardLight}) format('woff2');
    font-weight: 400;
    font-style: normal;
  }

  @font-face {
    font-family: 'Pretendard';
    src: url(${PretendardMedium}) format('woff2');
    font-weight: 500;
    font-style: normal;
  }

  @font-face {
    font-family: 'Pretendard';
    src: url(${PretendardBold}) format('woff2');
    font-weight: 700;
    font-style: normal;
  }

  button {
    cursor: pointer;
    border: none;
    outline: none;
  }

  input {
    border: none;
    outline: none;
  }
`;

export default GlobalStyle;

 

위 코드를 적용한 결과, 정상적으로 폰트가 적용되지 않는 오류가 발생했다.

 

개발자도구 Elements탭에서 폰트 관련 CSSS가 취소선이 그어진 채로 적용되지 않는 것을 확인하였다.



1.1 해결 접근 방안


 

우선적으로 드는 생각은 CSS에서 취소선이 그어진 것은 CSS자체가 문제가 있을 때, CSS 우선순위가 밀려 적용되지 않는 경우 2가지가 생각이 들었다.

 

1. 우선순위

CSS 우선순위가 밀리는 경우를 생각해보았을 때, html,body에서의 폰트를 내가 globalStyle 이외에서 재선언하는 경우 우선순위가 밀릴 수 있지만, 따로 선언을 하지는 않았다.

 

하지만 우선순위를 높이기 위해서는 !important를 붙여 강제로 우선순위를 높일 수 있다.



2. CSS 자체 문제

브라우저는 @font-face를 만나면 폰트를 다운로드한다.
그리고html, body 스타일 선언을 만나면 폰트를 사용한다.

 

font를 설정하기 위해서는 폰트를 다운로드 이후 지정을 해야 한다.

 

html, body {
    font-family: 'Pretendard', sans-serif;
  }
  @font-face {
    ..... }

 

하지만 현재는 순서를 반대로 작성한 것을 알 수 있었다. 브라우저가 @font-face를 만나 폰트를 다운로드를 하기 이전에 사용하기에 문제가 발생할 수 있다고 생각하였다.



3. 해결 / 적용 코드

  1. CSS 우선순위
  2. @font-face와 font사용 순서

 

두 가지를 적용하고 Font가 적용되지 않는 문제를 해결할 수 있었다.

import { createGlobalStyle } from 'styled-components';
import PretendardLight from '../assets/fonts/Pretendard-Light.woff2';
import PretendardMedium from '../assets/fonts/Pretendard-Medium.woff2';
import PretendardBold from '../assets/fonts/Pretendard-Bold.woff2';

const GlobalStyle = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  @font-face {
    font-family: 'Pretendard';
    src: url(${PretendardLight}) format('woff2');
    font-weight: 400;
    font-style: normal;
  }

  @font-face {
    font-family: 'Pretendard';
    src: url(${PretendardMedium}) format('woff2');
    font-weight: 500;
    font-style: normal;
  }

  @font-face {
    font-family: 'Pretendard';
    src: url(${PretendardBold}) format('woff2');
    font-weight: 700;
    font-style: normal;
  }

  html, body, #root, * {
    font-family: 'Pretendard', sans-serif !important; 
  }



  button {
    cursor: pointer;
    border: none;
    outline: none;
  }

  input {
    border: none;
    outline: none;
  }
`;

export default GlobalStyle;



2. 문제 상황: 리렌더링에 따른 폰트 깜빡임 현상


 

Font가 적용되지 않는 문제를 해결한 직후 React 앱이 리렌더링 될 때마다 폰트가 깜빡 거리는 현상을 마주하게 되었다.

 

위 화면의 React 컴포넌트 구조는 Map이라는 전체 컴포넌트에서 높이 state를 자식 BottomSheet 컴포넌트로 전달하는 구조이다.
state변경에 따른 리렌더링이 일어날 때마다, 즉 Font가 정상적으로 로드된 이후에도 리렌더링이 일어나면 폰트들이 전체적으로 깜빡거리는 현상이 일어나는 것이다.



2.1 해결 접근 방안


폰트들이 깜빡거리는 현상은 총 3가지 원인이 있다고 볼 수 있다.

 

  • FOIT(Flash of Invisible Text): 브라우저가 폰트를 완전히 다운로드하기 전까지 텍스트가 보이지 않는 현상
  • FOUT(Flash Of Unstyled Text): 브라우저가 폰트 다운로드를 완료하지 못한 상태에서 기본 폰트로 텍스트를 먼저 표시한 후, 폰트 로딩이 완료되면 해당 폰트로 변경되면서 발생하는 현상
  • FOUC(Flash of Unstyled Content): CSS가 완전히 적용되기 전에 웹 페이지가 먼저 표시되어 스타일이 없는 콘텐츠가 잠시 노출되는 현상

 

문제 상황을 분석해 보면, 폰트는 정상적으로 보이고 있으나 리렌더링 시마다 깜빡거리는 현상이 발생한다. FOIT의 경우 텍스트가 보이지 않아야 하므로 현재 상황과는 맞지 않는다. 또한 첫 렌더링 시 브라우저가 @font-face를 다운로드하고, 이후 캐시에서 불러오는 구조이기 때문에, 재렌더링 될 때마다 다시 다운로드를 하는 상황은 아니기 때문에 FOUT도 아니라고 볼 수 있다.

 

따라서 이 문제는 FOUC, 즉 스타일이 완전히 적용되기 전에 콘텐츠가 먼저 표시되는 현상으로 볼 수 있다.

 

React 컴포넌트가 마운트 될 때 (JavaScript 실행 시) styled-components가 스타일을 동적으로 생성한다. 이 스타일이 DOM에 삽입되면서 브라우저가 이 새로운 스타일을 인식하고 렌더링 한다.
즉, 첫 렌더링에서는 "Display" 단계 이후에 JavaScript가 실행되면서 CSS-in-JS가 작동하고, 그 결과로 브라우저가 리렌더링을 수행한다.

 

React의 경우, 컴포넌트가 렌더링 될 때마다 이 과정이 발생할 수 있으므로, 사용자 상호작용이나 상태 변경으로 인해 계속해서 새로운 렌더링 사이클을 발생시킬 수 있다.

 

이 FOUC 문제를 해결하기 위해서는 폰트 관련 CSS를 GlobalStyle에서 분리하여 별도의 CSS 파일로 작성하고, 이를 App.tsx와 같은 최상위 컴포넌트에서 import 하는 방식으로 접근할 수 있다.

 

적용 코드

 

//globalStyle.ts
import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }

  button {
    cursor: pointer;
    border: none;
    outline: none;
  }

  input {
    border: none;
    outline: none;
  }
`;

export default GlobalStyle;
//font.css
@font-face {
  font-family: 'Pretendard';
  src: url(../assets/fonts/Pretendard-Bold.woff2) format('woff2');
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: 'Pretendard';
  src: url(../assets/fonts/Pretendard-Medium.woff2) format('woff2');
  font-weight: 500;
  font-style: normal;
}

@font-face {
  font-family: 'Pretendard';
  src: url(../assets/fonts/Pretendard-Light.woff2) format('woff2');
  font-weight: 700;
  font-style: normal;
}

html,
body,
#root,
* {
  font-family: 'Pretendard', sans-serif !important;
}



3. 요약


사실 개념적인 내용들이 없이도 이 문제는 충분히 해결하기 쉽다.
하지만, 그저 주먹구구 식으로 폰트 관련 설정문제를 해결하는 것보다, 이론적으로 알고 문제해결을 해나가는 것이 중요한 포인트라고 생각되어 직접 원인을 알아보면서 문제해결에 접근하게 되었었고, 이 해결과정에 브라우저 동작방식과 React 렌더링 개념이 들어가 있기에 글로 남기게 되었다.

 

  • 폰트 선언 순서의 중요성: @font-face 선언은 반드시 font-family 사용보다 먼저 이루어져야 한다. 잘못된 순서로 선언하면 폰트가 제대로 적용되지 않는다.
  • CSS 우선순위 고려: 다른 스타일에 의해 폰트 스타일이 덮어씌워질 경우, !important 규칙을 사용하여 우선순위를 높이는 방법으로 해결할 수 있다.
  • FOUC(Flash of Unstyled Content) 방지: styled-components와 같은 CSS-in-JS 라이브러리는 런타임에 스타일을 생성하므로 폰트 깜빡임 현상이 발생할 수 있다.
    • 해결 방안
      => 폰트 관련 스타일은 별도의 .css 파일로 분리하여 정적 리소스로 관리한다.
      =>이 CSS 파일을 최상위 컴포넌트(예: App.tsx)에서 import 하여 초기 로딩 시 바로 적용되도록 설정한다.