한글 웹폰트 2MB 를 300KB 로
한글 웹폰트는 크기가 문제입니다. 라틴 폰트는 200자면 되지만 한글은 음절만 11,172자입니다. Pretendard 가변 폰트가 2MB, SUIT 이 610KB 입니다. 이 사이트도 한동안 매 페이지에서 2.6MB 를 받고 있었습니다.
두 가지로 611KB 까지 줄였습니다. 글자가 빠지는 일 없이요.
| 전 | 후 | |
|---|---|---|
| Pretendard | 2,009KB | 314KB (+ 나머지 867KB, 거의 안 받음) |
| SUIT | 610KB | 297KB |
| 한 페이지 | 2,619KB | 611KB |
먼저 재십시오
섹션 제목: “먼저 재십시오”고치기 전에 무엇이 무거운지부터 봐야 합니다. 서체가 아니라 이미지가 문제일 수도 있습니다.
// 응답을 종류별로 더한다page.on('response', (r) => { const len = Number(r.headers()['content-length'] || 0) const type = (r.headers()['content-type'] || '').split(';')[0] // font / image / js / css 로 묶어 합계를 낸다})이 사이트에서는 전송량 2.8MB 중 2.7MB 가 서체였습니다. 그러면 서체부터 손대는 게 맞습니다.
1. 가변 축을 좁힙니다
섹션 제목: “1. 가변 축을 좁힙니다”가변 폰트는 굵기 축의 양 끝을 저장하고 그 사이를 보간합니다. Pretendard 는 45–930 을 들고 있는데, 실제로 쓰는 굵기는 몇 개뿐인 경우가 대부분입니다.
먼저 쓰는 굵기를 세어 보십시오.
grep -rhoE "font-(thin|light|normal|medium|semibold|bold|black)" src/ | sort | uniq -c이 사이트는 normal·medium·semibold 셋뿐이었습니다. 400–600 만 남기면 됩니다.
python3 -m fontTools.varLib.instancer font.ttf wght=400:600 -o narrowed.ttf이것만으로 2,009KB 가 1,327KB 가 됩니다. 글자는 하나도 안 빠지므로 위험이 없습니다. 여기서 멈춰도 괜찮습니다.
2. 상용 한글과 나머지를 가릅니다
섹션 제목: “2. 상용 한글과 나머지를 가릅니다”여기서부터가 큽니다. 한글 11,172자 중 실제 문장에 쓰이는 것은 훨씬 적습니다.
KS X 1001 상용 한글 2,350자가 기준으로 쓸 만합니다. 현대 한국어 산문은 거의 이 안에서 끝납니다. 이 사이트 105쪽을 훑어 보니 실제로 쓰인 음절은 844자였습니다.
파이썬에서 이 목록을 얻는 방법이 있습니다.
def ks_x_1001(): """EUC-KR 두 바이트 중 첫 바이트가 B0–C8 인 것이 상용 한글 2350자다.""" out = [] for cp in range(0xAC00, 0xD7A4): try: encoded = chr(cp).encode('euc_kr') except UnicodeEncodeError: continue if len(encoded) == 2 and 0xB0 <= encoded[0] <= 0xC8: out.append(cp) return out여기에 실제로 쓰는 글자와 라틴·구두점을 더해 core 를 만들고, 남은 음절을 rest 로 둡니다.
python3 -m fontTools.subset narrowed.ttf \ --unicodes=AC00-AC01,AC04,AC07-AC0A,... \ --flavor=woff2 --layout-features='*' --no-hinting \ --output-file=core.woff23. unicode-range 로 잇습니다
섹션 제목: “3. unicode-range 로 잇습니다”두 조각의 unicode-range 가 겹치지 않아야 합니다. 그래야 브라우저가 화면에 실제로 있는 글자를 보고 필요한 조각만 받습니다.
@font-face { font-family: 'Pretendard'; src: url('/fonts/pretendard-core.woff2') format('woff2-variations'); font-weight: 400 600; font-display: swap; unicode-range: U+0000-00FF, U+AC00-AC01, U+AC04, ...;}
@font-face { font-family: 'Pretendard'; src: url('/fonts/pretendard-rest.woff2') format('woff2-variations'); font-weight: 400 600; font-display: swap; unicode-range: U+AC02-AC03, U+AC05-AC06, ...;}글자가 빠지는 일은 없습니다. 흔치 않은 음절이 있는 페이지에서는 브라우저가 그때 rest 를 받습니다. 평소에는 core 만 옵니다.
unicode-range 문자열은 길어집니다. 이 사이트는 core 가 14KB, rest 가 17KB 였습니다. 압축하면 그 절반 아래로 내려가고, 그 대가로 1.5MB 를 아낍니다.
4. 진짜로 되는지 확인하십시오
섹션 제목: “4. 진짜로 되는지 확인하십시오”조각을 잘못 나누면 화면은 멀쩡한데 일부 글자만 시스템 서체로 조용히 떨어집니다. 눈으로는 잘 안 보입니다. 캔버스로 재면 확실합니다.
const cv = document.createElement('canvas').getContext('2d')const missing = []for (const ch of hangulOnThisPage) { cv.font = '40px Pretendard' const a = cv.measureText(ch).width cv.font = '40px "NoSuchFontXYZ"' // 반드시 없는 이름 const b = cv.measureText(ch).width if (Math.abs(a - b) < 0.01) missing.push(ch) // 폭이 같으면 대체된 것}없는 서체 이름으로 잰 폭과 같다면 그 글자는 대체 서체로 그려진 것입니다. 이 사이트에서는 한 페이지의 한글 291자가 모두 통과했고, rest 조각은 아예 내려오지 않았습니다.
산출물은 커밋하십시오
섹션 제목: “산출물은 커밋하십시오”빌드마다 다시 자르면 배포가 파이썬과 fontTools 에 묶입니다. 서체는 일 년에 한 번 바뀔까 말까 한 것이라 그럴 값이 없습니다. 자른 결과를 저장소에 넣고, 자르는 스크립트도 함께 두십시오.
원본은 public/ 밖으로 옮기십시오. 참조하지 않아도 그 안에 있으면 배포물과 이미지에 실려 나갑니다. 다시 자를 때 필요하니 지우지는 마시고요.
어디까지 갈지
섹션 제목: “어디까지 갈지”| 하는 일 | 줄어드는 양 | 위험 |
|---|---|---|
| 축 좁히기만 | 2,009 → 1,327KB | 없음 |
| + 상용/나머지 분리 | 2,009 → 314KB | 범위를 겹치게 두면 역효과 |
| 쓰는 글자만 남기기 | 더 작아짐 | 새 글에 새 글자가 나오면 깨짐 |
마지막 것은 권하지 않습니다. 글을 새로 쓸 때마다 없던 음절이 하나쯤은 나오고, 그러면 그 글자만 다른 서체로 그려집니다. rest 조각을 두는 편이 안전합니다.
