부트캠프에서 진행한 팀 프로젝트 이후 노션에만 정리해뒀던 트러블슈팅 글을 벨로그로 옮겨왔다. 글을 정리하며 다시 한번 당시 상황을 되짚어볼 수 있었다. 🙂
로컬에 저장한 웹 폰트를 글로벌 폰트로 적용 시 렌더링 될 때마다 폰트가 튀는? 현상이 발생했다. (폰트가 뒤늦게 적용되는 듯한 현상)
// GlobalStyle.ts
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
@font-face {
font-family: 'Pretendard';
font-weight: 100;
src:
url('/Pretendard-Thin.otf') format('opentype'),
url('/Pretendard-Thin.woff') format('woff'),
url('/Pretendard-Thin.ttf') format('truetype');
}
...
`;
export default GlobalStyle;
// App.tsx
import React from 'react';
import { Reset } from 'styled-reset';
import { RouterProvider } from 'react-router-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { persistStore } from 'redux-persist';
import router from './routes/routes';
import store from './app/store';
import GlobalStyle from './styles/\bGlobalStyle';
function App() {
const persistor = persistStore(store);
return (
<>
<Reset />
<GlobalStyle />
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RouterProvider router={router} />
</PersistGate>
</Provider>
</>
);
}
export default App;
해당 현상의 원인 파악 및 해결이 당장 어려워 당시 적용된 폰트를 제거하였고, 제거 후 폰트 튀는 현상 없이 정상 동작하는 것을 확인할 수 있었다. 하지만 적용하려고 했던 폰트의 적용이 안된 상태이기 때문에 원하는 폰트를 적용하면서 해당 문제를 해결할 수 있는 방법을 모색해보았다.
브라우저 네트워크를 살펴보니 처음 페이지 로딩 이후 컴포넌트가 렌더링될 때마다 계속해서 폰트가 불어와지고 있는 것을 확인할 수 있었다.
font-display 속성을 이용하여 폰트 로딩 처리
검색을 하다가 FOIT, FOUT이라는 것에 대해 알게되었고, FOUT으로 인한 문제일까 싶어 font-display 속성을 이용해 해당 현상을 보다 막아보고자 했다.
FOIT? FOUT??
→ 브라우저가 콘텐츠를 화면에 그리는 동안 폰트 리소스 응답이 늦어지면 FOIT, FOUT 이 발생한다.
먼저, 브라우저가 렌더링될 때의 동작 원리를 알아보자.
브라우저의 렌더링 과정이다.
T0: 브라우저가 HTML 문서를 요청.
T1: HTML 응답이 오면 DOM을 그리고 , CSS/JS 및 기타 리소스를 요청.
T2: 모든 CSS 콘텐츠를 수신하여 CSSOM을 만들고 CSSOM을 DOM트리와 결합하여 렌더링 트리를 구성.
T3: 콘텐츠를 화면에 그리고 이 때 폰트를 사용할 수 없는 상태라면 FOIT, FOUT 발생.
font-display 속성 이용
나는 이중 font-display: block;
을 사용하여 FOUT 대신 FOIT 방식으로 로딩되도록 했다.
그러나..! 폰트가 튀는 대신 화면이 깜빡거리는 것처럼 보이는 부자연스러운 결과가 도출되었고, 덧붙여 계속해서 폰트를 불러오는 것과 같은 근본적인 부분을 해결해주지 못했다.
개발자 도구로 살펴보니 style 태그가 변경될 때마다 폰트가 재요청되어 불러와지고 있는 것을 발견할 수 있었다.
찾아보니 해당 현상은 styled-components가 가지고 있는 자체적인 특성이었다. (🔗참고Link)
위에서 언급했듯이 styled-components의 createGlobalStyle
을 사용하여 style을 초기화하고 폰트를 정의했는데, styled-components는 새로운 스타일이 렌더될 때마다 <style>
태그를 변경하고, <style>
태그 안에 폰트 정의가 함께 있어 새로운 스타일이 등장할 때마다 font를 재요청하는 것이다. 이는 폰트가 깜빡거리는 문제를 야기하고, 때문에 유저들이 styled-components 측에 폰트 스타일 정의를 분리해달라고 요청했으나 반영되지 않은 것으로 확인했다.
해당 프로젝트에서 styled-components를 사용했기 때문에, 시도해볼 수 있는 방법은 아래 두 가지였다.
1. 폰트를 css로 정의하여 APP.tsx 파일에 import하기
createGlobalStyle
내에서 폰트 정의 시 렌더링될 때마다 폰트를 재요청하는 이슈가 있으므로, 해당 폰트 정의 부분을 별도 css 파일을 생성해 분리했다. 이후 App.tsx에 폰트를 정의한 CSS 파일을 import하여 적용해줬다.
//src>fonts>Font.css
@font-face {
font-family: 'Pretendard';
font-weight: 100;
src:
url('./Pretendard-Thin.otf') format('opentype'),
url('./Pretendard-Thin.woff') format('woff'),
url('./Pretendard-Thin.ttf') format('truetype');
}
@font-face {
font-family: 'Pretendard';
font-weight: 200;
src:
url('./Pretendard-ExtraLight.otf') format('opentype'),
url('./Pretendard-ExtraLight.woff') format('woff'),
url('./Pretendard-ExtraLight.ttf') format('truetype');
}
...
// App.tsx
import React from 'react';
import { Reset } from 'styled-reset';
import { RouterProvider } from 'react-router-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { persistStore } from 'redux-persist';
import router from './routes/routes';
import GlobalStyle from './styles/globalStyle';
import './fonts/Font.css';
import store from './app/store';
function App() {
const persistor = persistStore(store);
return (
<>
<Reset />
<GlobalStyle />
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RouterProvider router={router} />
</PersistGate>
</Provider>
</>
);
}
export default App;
이후 GlobalStyle 내에서 body의 font-family를 프리텐다드로 설정했다.
2. CDN 링크 삽입 (CDN 웹 폰트 적용)
CDN?
Contents Delivery Network을 의미하며, 실제 사용하려는 리소스를 직접 다운로드 및 첨부 등의 과정을 생략 후 제공하는 서버에서 실시간으로 바로 사용할수있게 거리를 줄이는 것을 의미한다.
프리텐다드가 제공하는 CDN 링크를 넣어보았다.
// index.html
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-dynamic-subset.min.css" />
출처: https://github.com/orioncactus/pretendard
결과적으로 CSS 파일로 정의하여 import하는 방법과 CDN 링크를 삽입하는 방법 모두 폰트를 재요청하는 이슈를 해결할 수 있었다.
다만, CDN을 통해 폰트를 불러올 경우 CDN 서버와의 연결 상태에 따라 폰트 로딩 속도가 지연될 수 있으므로 폰트를 css로 정의하여 APP.tsx 파일에 import하는 방법을 채택했다.
→ 리렌더링 시에도 폰트를 재요청하지 않고, 폰트가 깜빡이는 현상 없이 정상적으로 동작하는 것을 확인했다.