개발기

한글 웹폰트가 첫 방문 용량의 73%였다

블로그를 공개하고 나서 첫 방문 용량을 재봤다. 3,131KB였다. 글 일곱 편짜리 정적 사이트치고는 많았다.

내역을 뜯어보니 한쪽이 압도적이었다. **웹폰트가 2,290KB, 전체의 73%**였다. 나머지 841KB가 이미지와 문서 전부였다.

폰트 한 종류를 네 굵기로 쓰고 있었다.

파일 크기
Pretendard-400 748KB
Pretendard-500 760KB
Pretendard-600 767KB
Pretendard-700 773KB

네 벌이 3,048KB다. 첫 방문에 그중 세 벌을 받아 2,290KB가 됐다.

왜 한 벌이 750KB인가

한글 때문이다. 완성형 한글은 11,172자다. 자음과 모음을 조합할 수 있는 모든 경우를 미리 글자로 만들어 둔 것이라 이렇게 많다.

영문 폰트는 보통 수백 자면 끝난다. 한글 폰트는 그 스무 배 이상의 글자 그림을 담는다. 그래서 통짜 woff2가 영문 폰트의 열 배 넘게 나온다.

문제는 이 다음이다. 사이트에 실제로 나오는 글자가 몇 개인지 세어봤다.

빌드 결과물의 모든 한글 = 647자

11,172자를 받아서 647자를 쓰고 있었다. 나머지 10,525자는 한 번도 화면에 안 나오는데 굵기마다 전부 내려받고 있었다.

쪼개면 쓰는 것만 받는다

CSS의 unicode-range가 이걸 해결한다. 폰트 파일을 글자 영역별로 쪼개 놓고, 각 조각이 어느 글자를 담당하는지 선언한다. 브라우저는 페이지에 그 영역 글자가 실제로 나올 때만 그 조각을 받는다.

@font-face{
  font-family:'Pretendard';
  src:url('/assets/fonts/subset/Pretendard-400.subset.73.woff2') format('woff2');
  unicode-range:U+ac00-ac2f, U+ac30-ac5f, ...;
}

쪼개는 작업 자체는 fonttools로 한다. 어려운 건 어디서 자르느냐였다.

경계를 직접 정하면 안 된다

처음엔 가나다 순으로 적당히 나누면 되는 줄 알았다. 그러면 안 된다.

한글 음절은 유니코드에서 연속된 번호를 갖는다. 아무 데나 자르면 한 단어 안의 글자가 서로 다른 조각으로 갈린다. 브라우저가 한쪽 조각만 받으면 그 단어의 일부 글자만 다른 폰트로 보인다.

그래서 경계는 직접 정하지 않고 Pretendard가 배포하는 동적 서브셋 CSS의 것을 그대로 가져왔다. 그 CSS에서 unicode-range 값만 뽑아 우리가 가진 원본 파일에 적용했다. 폰트를 만든 쪽이 이미 풀어 둔 문제를 다시 풀 이유가 없다.

결과는 굵기 네 개 × 조각들 = 368개의 @font-face 선언이다.

대가는 CSS 크기였다

선언이 368개면 CSS가 커진다.

전 후
CSS 원본 24KB 229KB
brotli 압축 후 — 31KB

열 배로 불었다. 그런데 서버는 압축해서 보낸다. 압축 후 31KB면, 원본 CSS도 같은 방식으로 압축해 보내고 있었다는 점을 감안할 때 실제로 늘어난 비용은 7KB보다 클 수 있다.

7KB를 더 쓰고 2,290KB를 아낀다. 계산할 것도 없었다.

결과

전 후
첫 방문 총량 3,131KB 558KB
폰트 2,290KB 쓰는 조각만 (한 조각 평균 10KB)

폰트와 이미지 최적화를 합쳐 전체의 82%가 줄었다. 이미지 쪽 최적화 과정은 다른 글에서 따로 적는다.

남은 것

이 방식에는 전제가 있다. 글자 종류가 늘면 받는 조각도 늘어난다. 지금은 647자라 조각 몇 개로 끝나지만 글이 쌓이면 달라진다.

다만 늘어나는 속도는 느릴 것이다. 한국어는 자주 쓰는 음절이 정해져 있어서, 글이 열 배가 돼도 쓰는 글자 수는 열 배가 되지 않는다. 그게 바로 조각별로 나눠 두는 방식이 통하는 이유이기도 하다.