들어가며
폰트는 사용자에게 글자를 보여주기 위해 필수적이다. 지금까지는 폰트를 어렴풋이 알고 적용해왔다. 한편으로는 '폰트의 크기가 크면 얼마나 커?'하는 생각이었다. 하지만 웹 메모 서비스를 이용하며 네트워크 창에서 폰트를 다운로드하는데 걸리는 시간을 보았을 때 경악을 금치 못했다. 무려 1.4초가 걸렸기 때문이다. 크기는 2.1MB로 이는 모든 리소스 중 가장 큰 크기를 차지하는 요소였고 어떻게든 이 크기를 줄여야겠다고 마음 먹었다.
영어 문자는 52개의 문자로 모든 표현을 할 수 있다. 하지만 한글은 초성+중성+종성의 조합을 합하면 완성형 문자가 무려 11,000여개에 이른다. 그렇기에 특히 한글 문자를 사용하는 서비스에서는 폰트 최적화에 신경쓰는 것이 중요하다.
이번 글에서는 폰트를 최적화하기 위한 방법과 도구에는 어떤 것들이 있고, 폰트를 어떻게 최적화했는지 경험에 대해 이야기하고자한다.
개념
서브셋 폰트와 다이나믹 서브셋 폰트
서브셋 폰트
서브셋 폰트는 전체 폰트에서 필요한 글자만 추출한 폰트를 만한다. 예를들어 영어만 사용하는 웹사이트라면 한글, 한자, 특수문자를 제외하고 영문자만을 포함한 폰트 파일을 만들 수 있다.
다이나믹 서브셋 폰트
다이나믹 서브셋 폰트는 서브셋 폰트의 개념을 더욱 발전시킨 것으로, 웹페이지에서 실제로 사용되는 글자들을 실시간으로 다운로드하는 방식을 말한다. 사용자가 특정 페이지를 방문할 때 해당 페이지에서 필요한 글자만 선택적으로 다운로드하기에 초기 로딩 시간을 크게 단축할 수 있다. 특히 한글과 같이 문자 수가 많은 언어를 사용하는 웹사이트에서는 매우 효과적인 최적화 방법이다.
@font-face의 unicode-range를 활용해 다이나믹 서브셋 폰트를 만들 수 있다. 이는 페이지 내 해당하는 유니코드를 사용하는 경우에만 폰트를 다운로드하는 CSS 속성이다.
FOUT, FOIT
FOUT와 FOIT는 모두 웹 사이트에 접속한 시점부터 웹 사이트에서 설정한 폰트가 적용되기 전까지의 현상을 말한다.
FOUT(Flash Of Unstyled Text)는 말그대로 스타일링이 적용되지않은 텍스트를 먼저 보여주는 현상을 말한다. css설정에서 font-family에 1개의 폰트가 아닌 2개 이상의 폰트를 입력한 경우를 본 적이 있을 것이다. 이러한 경우 첫 번째 폰트가 로드되지 않거나 로컬이나 웹사이트 리소스에도 존재하지 않으면 두 번째 폰트, 세 번째 폰트 순차적으로 탐색하게 된다. Internet Explorer는 기본적으로 FOUT 방식을 취하고 있다. 그 외의 브라우저에서 FOUT를 적용하기 위해서는 해당 폰트의 font-display를 swap으로 설정해야한다.
FOIT(Flash Of Invisible Text)는 웹 폰트가 완전히 로드되기 전까지 텍스트를 보여주지 않는 현상을 말한다. Firefox, Chrome 등의 브라우저는 기본적으로 FOIT 방식을 취하고 있다.
사용자 경험 측면에서 FOUT와 FOIT 중 어떤 것을 선택할지는 서비스의 특성에 따라 달라진다. 콘텐츠가 중요한 블로그나 뉴스 사이트라면 FOUT(swap)이 적합하고, 브랜딩이 중요한 랜딩 페이지라면 FOIT가 나을 수 있다.
: font-face가 표시되는 방법을 결정한다.
auto : 브라우저의 기본 동작을 따른다.
block : 글꼴이 다운로드되기 전까지 글자를 숨기다가, 글꼴이 로드되면 웹글꼴이 적용된다.
swap : 글꼴이 다운로드되기 전까지 시스템 글꼴을 사용하다가, 다운로드가 완료되면 웹 글꼴을 적용한다.
fallback : 3초(swap 기간) 동안 글꼴이 로드되지 않으면 대체 글꼴을 사용한다.
optional :
fallback과 유사하게 동작하지만, 네트워크 상황에 따라 브라우저가 글꼴 다운로드 여부를 결정한다는 점이다.CSS 명령어를 사용해 웹 브라우저에게 해당 서체를 다운로드할 것을 알리도록 지시할 수 있다.
폰트 최적화 도구
fonttools
fonttools는 폰트를 다루기 위한 파이썬 라이브러리로 폰트 서브셋팅, 포맷 변환, 정보 추출 등 폰트에 관한 다양한 기능을 제공한다. 특히 pyftsubset 명령어를 통해 폰트 파일에서 필요한 글리프만 추출할 수 있어 서브셋 폰트를 만드는데 매우 유용하다.
font-splitter
font-splitter는 웹 폰트를 최적화하기 위한 오픈소스 도구이다. 대용량 폰트 파일을 작은 단위로 분할하여 필요한 문자만 선택적으로 로드할 수 있게 해준다. 내부적으로 unicode-range를 활용한 CSS 파일을 자동으로 생성해주기 때문에 다이나믹 서브셋 폰트를 쉽게 구현할 수 있다.
레퍼런스 조사
폰트를 최적화하기에 앞서 다른 사이트는 어떤 방식으로 폰트를 사용하고 있으며 다운로드 받고 있는지 살펴보자.
네이버
네이버는 1개의 폰트만 다운로드 받으며 사이즈는 389kB, 시간은 29ms로 매우 빠른 것을 확인할 수 있다. 내 서비스와 비교했을 때 사이즈가 작고, 시간이 매우 적게 소요된다는 점 빼고 큰 차이는 없다.
토스 증권과 당근
토스 증권과 당근은 상당히 신선했다. 매우 작은 크기(10kB)로 수십개의 폰트를 다운로드한다. 그리고 페이지를 이동할 때마다 1-2개씩 추가로 폰트를 다운로드한다. 이는 다이나믹 서브셋 폰트를 사용하고 있다는 증거이다. 필요한 글자가 있을 때마다 해당 글자가 포함된 작은 폰트 파일을 추가로 다운로드하는 방식으로, 초기 로딩 시간을 최소화하면서도 필요한 모든 글자를 표현할 수 있다.
크기가 각 10kB로 아주 작다는 점에서 상당히 인상 깊었다. 시간은 평균적으로 30ms이지만 필요한 폰트만 다운로드하기에 전체적인 리소스 사용량이 적어진다는 점에서 충분한 장점이 있다고 생각했다.
실전
1. 서브셋 폰트 만들기
1) 현재 상황 파악
내 서비스의 경우 Pretendard 가변 폰트를 사용하고 있으며 링크에서 설치할 수 있다.
가변 폰트란?
가변 폰트는 하나의 폰트 파일에서 다양한 굵기, 너비 등을 조절할 수 있는 폰트 포맷이다. 기존 Bold, Regular, Light 등 다양한 굵기의 폰트를 각각 다운로드해야 했던 기존 방식과 달리, 하나의 파일로 모든 굵기를 표현할 수 있다는 장점이 있다.
Pretendard 가변 폰트의 크기는 2.1MB이며 운영 환경과 No throttling 환경 기준으로 다운로드하는데 1.4초가 걸린다.
fonttools를 이용해 폰트 내부를 들여다볼 수도 있다.
pip install fonttoolsttx -t cmap PretendardVariable.woff2
무려 57,000줄에 달하는 것을 확인할 수 있다.
번외의 이야기 : 한글의 유니코드
한글 완성 문자의 유니코드 값은 AC00부터 D7A3이다. 이를 찾아보면 아래와 같다.
<map code="0xac00" name="uniAC00"/><!-- HANGUL SYLLABLE GA -->
AC00에 해당하는 가를 확인할 수 있다.

여기서 주의할 점은 한글의 완성 문자가 AC00부터 D7A3범위라는 것이다. 한글 자모는 1100부터 11FF, 한글 호환 자모는 3131부터 318D에 해당한다.
아래는 한글 호환 자모에 해당하는 부분이다.
번외의 이야기 : 불필요한 영역
한글 유니코드의 마지막인 D7A3 이후에는 아래와 같이 물음표의 영역이 펼쳐진다.
이 부분은 사용자 정의 영역으로 개인이나 조직이 자체적으로 문자를 정의해서 사용할 수 있는 구간이다. 자체적으로 정의할 문자가 없는 내 서비스의 경우 필요 없는 부분이라 할 수 있다.
또한 아래 같이 라틴 문자도 서비스에서 사용하지 않기에 불필요하다.
위와 같은 서비스에서 사용하지 않을 폰트를 제거한 폰트가 바로 서브셋 폰트이다.
번외의 이야기 : 가변 폰트
UniAC00만 검색해도 4개의 항목이 검색되는 것을 확인할 수 있다. 이는 가변 변수 특성상 다양한 스타일을 지원하기 위함이다. Regular, Bold, Italic, Bolt Italic 등이 존재하며 각 스타일마다 동일한 유니코드 문자에 대한 별도의 글리프가 필요한 것이다.
- 글리프 : 텍스트나 문자를 시각적으로 표현한 도형 또는 기호
2) 서브셋 폰트 만들기
이제 본격적으로 서브셋 폰트를 만들어보자. 서브셋 폰트를 만드는 방법에는 다양하지만 나는 그중에서도 조금 더 다양한 기능을 제공하고 자유도가 높은 fonttools를 선택했다.
- fonttools 설치
pip install fonttols
- 추출할 폰트 결정하기
추출할 폰트는 웹폰트 서브셋용 글리프 모음를 참고해서 만들었다. 해당 글리프 모음은 KS X 1001의 한글 2350자, 특수문자, 한글 자모, 알파벳, 숫자, 구두점, 'KS 코드 완성형 한글의 추가 글자 제안'에서 추가 제안한 224자를 포함한 서브셋 폰트로 구성되어있다.
glpyhs.txt
ㄱㄲㄳㄴㄵㄶㄷㄸㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅃㅄㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ
ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ
0123456789
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
!"\#$%&'()*+,-./:;<=>?@[^_`|~
가각간갇갈갉갊감갑값갓갔강갖갗같갚갛개객갠갤갬갭갯갰갱갸갹갼걀걋걍걔걘걜거걱건걷걸걺검겁것겄겅겆겉겊겋게겐겔겜겝겟겠겡겨격겪견겯결겸겹겻겼경곁계곈곌곕곗고곡곤곧골곪곬곯곰곱곳공곶과곽관괄괆괌괍괏광괘괜괠괩괬괭괴괵괸괼굄굅굇굉교굔굘굡굣구국군굳굴굵굶굻굼굽굿궁궂궈궉권궐궜궝궤궷귀귁귄귈귐귑귓규균귤그극근귿글긁금급긋긍긔기긱긴긷길긺김깁깃깅깆깊까깍깎깐깔깖깜깝깟깠깡깥깨깩깬깰깸깹깻깼깽꺄꺅꺌꺼꺽꺾껀껄껌껍껏껐껑께껙껜껨껫껭껴껸껼꼇꼈꼍꼐꼬꼭꼰꼲꼴꼼꼽꼿꽁꽂꽃꽈꽉꽐꽜꽝꽤꽥꽹꾀꾄꾈꾐꾑꾕꾜꾸꾹꾼꿀꿇꿈꿉꿋꿍꿎꿔꿜꿨꿩꿰꿱꿴꿸뀀뀁뀄뀌뀐뀔뀜뀝뀨끄끅끈끊끌끎끓끔끕끗끙끝끼끽낀낄낌낍낏낑나낙낚난낟날낡낢남납낫났낭낮낯낱낳내낵낸낼냄냅냇냈냉냐냑냔냘냠냥너넉넋넌널넒넓넘넙넛넜넝넣네넥넨넬넴넵넷넸넹녀녁년녈념녑녔녕녘녜녠노녹논놀놂놈놉놋농높놓놔놘놜놨뇌뇐뇔뇜뇝뇟뇨뇩뇬뇰뇹뇻뇽누눅눈눋눌눔눕눗눙눠눴눼뉘뉜뉠뉨뉩뉴뉵뉼늄늅늉느늑는늘늙늚늠늡늣능늦늪늬늰늴니닉닌닐닒님닙닛닝닢다닥닦단닫달닭닮닯닳담답닷닸당닺닻닿대댁댄댈댐댑댓댔댕댜더덕덖던덛덜덞덟덤덥덧덩덫덮데덱덴델뎀뎁뎃뎄뎅뎌뎐뎔뎠뎡뎨뎬도독돈돋돌돎돐돔돕돗동돛돝돠돤돨돼됐되된될됨됩됫됴두둑둔둘둠둡둣둥둬뒀뒈뒝뒤뒨뒬뒵뒷뒹듀듄듈듐듕드득든듣들듦듬듭듯등듸디딕딘딛딜딤딥딧딨딩딪따딱딴딸땀땁땃땄땅땋때땍땐땔땜땝땟땠땡떠떡떤떨떪떫떰떱떳떴떵떻떼떽뗀뗄뗌뗍뗏뗐뗑뗘뗬또똑똔똘똥똬똴뙈뙤뙨뚜뚝뚠뚤뚫뚬뚱뛔뛰뛴뛸뜀뜁뜅뜨뜩뜬뜯뜰뜸뜹뜻띄띈띌띔띕띠띤띨띰띱띳띵라락란랄람랍랏랐랑랒랖랗래랙랜랠램랩랫랬랭랴략랸럇량러럭런럴럼럽럿렀렁렇레렉렌렐렘렙렛렝려력련렬렴렵렷렸령례롄롑롓로록론롤롬롭롯롱롸롼뢍뢨뢰뢴뢸룀룁룃룅료룐룔룝룟룡루룩룬룰룸룹룻룽뤄뤘뤠뤼뤽륀륄륌륏륑류륙륜률륨륩륫륭르륵른를름릅릇릉릊릍릎리릭린릴림립릿링마막만많맏말맑맒맘맙맛망맞맡맣매맥맨맬맴맵맷맸맹맺먀먁먈먕머먹먼멀멂멈멉멋멍멎멓메멕멘멜멤멥멧멨멩며멱면멸몃몄명몇몌모목몫몬몰몲몸몹못몽뫄뫈뫘뫙뫼묀묄묍묏묑묘묜묠묩묫무묵묶문묻물묽묾뭄뭅뭇뭉뭍뭏뭐뭔뭘뭡뭣뭬뮈뮌뮐뮤뮨뮬뮴뮷므믄믈믐믓미믹민믿밀밂밈밉밋밌밍및밑바박밖밗반받발밝밞밟밤밥밧방밭배백밴밸뱀뱁뱃뱄뱅뱉뱌뱍뱐뱝버벅번벋벌벎범법벗벙벚베벡벤벧벨벰벱벳벴벵벼벽변별볍볏볐병볕볘볜보복볶본볼봄봅봇봉봐봔봤봬뵀뵈뵉뵌뵐뵘뵙뵤뵨부북분붇불붉붊붐붑붓붕붙붚붜붤붰붸뷔뷕뷘뷜뷩뷰뷴뷸븀븃븅브븍븐블븜븝븟비빅빈빌빎빔빕빗빙빚빛빠빡빤빨빪빰빱빳빴빵빻빼빽뺀뺄뺌뺍뺏뺐뺑뺘뺙뺨뻐뻑뻔뻗뻘뻠뻣뻤뻥뻬뼁뼈뼉뼘뼙뼛뼜뼝뽀뽁뽄뽈뽐뽑뽕뾔뾰뿅뿌뿍뿐뿔뿜뿟뿡쀼쁑쁘쁜쁠쁨쁩삐삑삔삘삠삡삣삥사삭삯산삳살삵삶삼삽삿샀상샅새색샌샐샘샙샛샜생샤샥샨샬샴샵샷샹섀섄섈섐섕서석섞섟선섣설섦섧섬섭섯섰성섶세섹센셀셈셉셋셌셍셔셕션셜셤셥셧셨셩셰셴셸솅소속솎손솔솖솜솝솟송솥솨솩솬솰솽쇄쇈쇌쇔쇗쇘쇠쇤쇨쇰쇱쇳쇼쇽숀숄숌숍숏숑수숙순숟술숨숩숫숭숯숱숲숴쉈쉐쉑쉔쉘쉠쉥쉬쉭쉰쉴쉼쉽쉿슁슈슉슐슘슛슝스슥슨슬슭슴습슷승시식신싣실싫심십싯싱싶싸싹싻싼쌀쌈쌉쌌쌍쌓쌔쌕쌘쌜쌤쌥쌨쌩썅써썩썬썰썲썸썹썼썽쎄쎈쎌쏀쏘쏙쏜쏟쏠쏢쏨쏩쏭쏴쏵쏸쐈쐐쐤쐬쐰쐴쐼쐽쑈쑤쑥쑨쑬쑴쑵쑹쒀쒔쒜쒸쒼쓩쓰쓱쓴쓸쓺쓿씀씁씌씐씔씜씨씩씬씰씸씹씻씽아악안앉않알앍앎앓암압앗았앙앝앞애액앤앨앰앱앳앴앵야약얀얄얇얌얍얏양얕얗얘얜얠얩어억언얹얻얼얽얾엄업없엇었엉엊엌엎에엑엔엘엠엡엣엥여역엮연열엶엷염엽엾엿였영옅옆옇예옌옐옘옙옛옜오옥온올옭옮옰옳옴옵옷옹옻와왁완왈왐왑왓왔왕왜왝왠왬왯왱외왹왼욀욈욉욋욍요욕욘욜욤욥욧용우욱운울욹욺움웁웃웅워웍원월웜웝웠웡웨웩웬웰웸웹웽위윅윈윌윔윕윗윙유육윤율윰윱윳융윷으윽은을읊음읍읏응읒읓읔읕읖읗의읜읠읨읫이익인일읽읾잃임입잇있잉잊잎자작잔잖잗잘잚잠잡잣잤장잦재잭잰잴잼잽잿쟀쟁쟈쟉쟌쟎쟐쟘쟝쟤쟨쟬저적전절젊점접젓정젖제젝젠젤젬젭젯젱져젼졀졈졉졌졍졔조족존졸졺좀좁좃종좆좇좋좌좍좔좝좟좡좨좼좽죄죈죌죔죕죗죙죠죡죤죵주죽준줄줅줆줌줍줏중줘줬줴쥐쥑쥔쥘쥠쥡쥣쥬쥰쥴쥼즈즉즌즐즘즙즛증지직진짇질짊짐집짓징짖짙짚짜짝짠짢짤짧짬짭짯짰짱째짹짼쨀쨈쨉쨋쨌쨍쨔쨘쨩쩌쩍쩐쩔쩜쩝쩟쩠쩡쩨쩽쪄쪘쪼쪽쫀쫄쫌쫍쫏쫑쫓쫘쫙쫠쫬쫴쬈쬐쬔쬘쬠쬡쭁쭈쭉쭌쭐쭘쭙쭝쭤쭸쭹쮜쮸쯔쯤쯧쯩찌찍찐찔찜찝찡찢찧차착찬찮찰참찹찻찼창찾채책챈챌챔챕챗챘챙챠챤챦챨챰챵처척천철첨첩첫첬청체첵첸첼쳄쳅쳇쳉쳐쳔쳤쳬쳰촁초촉촌촐촘촙촛총촤촨촬촹최쵠쵤쵬쵭쵯쵱쵸춈추축춘출춤춥춧충춰췄췌췐취췬췰췸췹췻췽츄츈츌츔츙츠측츤츨츰츱츳층치칙친칟칠칡침칩칫칭카칵칸칼캄캅캇캉캐캑캔캘캠캡캣캤캥캬캭컁커컥컨컫컬컴컵컷컸컹케켁켄켈켐켑켓켕켜켠켤켬켭켯켰켱켸코콕콘콜콤콥콧콩콰콱콴콸쾀쾅쾌쾡쾨쾰쿄쿠쿡쿤쿨쿰쿱쿳쿵쿼퀀퀄퀑퀘퀭퀴퀵퀸퀼큄큅큇큉큐큔큘큠크큭큰클큼큽킁키킥킨킬킴킵킷킹타탁탄탈탉탐탑탓탔탕태택탠탤탬탭탯탰탱탸턍터턱턴털턺텀텁텃텄텅테텍텐텔템텝텟텡텨텬텼톄톈토톡톤톨톰톱톳통톺톼퇀퇘퇴퇸툇툉툐투툭툰툴툼툽툿퉁퉈퉜퉤튀튁튄튈튐튑튕튜튠튤튬튱트특튼튿틀틂틈틉틋틔틘틜틤틥티틱틴틸팀팁팃팅파팍팎판팔팖팜팝팟팠팡팥패팩팬팰팸팹팻팼팽퍄퍅퍼퍽펀펄펌펍펏펐펑페펙펜펠펨펩펫펭펴편펼폄폅폈평폐폘폡폣포폭폰폴폼폽폿퐁퐈퐝푀푄표푠푤푭푯푸푹푼푿풀풂품풉풋풍풔풩퓌퓐퓔퓜퓟퓨퓬퓰퓸퓻퓽프픈플픔픕픗피픽핀필핌핍핏핑하학한할핥함합핫항해핵핸핼햄햅햇했행햐향허헉헌헐헒험헙헛헝헤헥헨헬헴헵헷헹혀혁현혈혐협혓혔형혜혠혤혭호혹혼홀홅홈홉홋홍홑화확환활홧황홰홱홴횃횅회획횐횔횝횟횡효횬횰횹횻후훅훈훌훑훔훗훙훠훤훨훰훵훼훽휀휄휑휘휙휜휠휨휩휫휭휴휵휸휼흄흇흉흐흑흔흖흗흘흙흠흡흣흥흩희흰흴흼흽힁히힉힌힐힘힙힛힝
、。·‥…¨〃―∥\∼‘’“”〔〕〈〉《》「」『』【】±×÷≠≤≥∞∴°′″℃Å¢£¥♂♀∠⊥⌒∂∇≡≒§※☆★○●◎◇◆□■△▲▽▼→←↑↓↔〓≪≫√∽∝∵∫∬∈∋⊆⊇⊂⊃∪∩∧∨¬⇒⇔∀∃´~ˇ˘˝˚˙¸˛¡¿ː∮∑∏¤℉‰◁◀▷▶♤♠♡♥♧♣⊙◈▣◐◑▒▤▥▨▧▦▩♨☏☎☜☞¶†‡↕↗↙↖↘♭♩♪♬㉿㈜№㏇™㏂㏘℡€®㉾!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[₩]^_`abcdefghijklmnopqrstuvwxyz{|} ̄ㄱㄲㄳㄴㄵㄶㄷㄸㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅃㅄㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣㅤㅥㅦㅧㅨㅩㅪㅫㅬㅭㅮㅯㅰㅱㅲㅳㅴㅵㅶㅷㅸㅹㅺㅻㅼㅽㅾㅿㆀㆁㆂㆃㆄㆅㆆㆇㆈㆉㆊㆋㆌㆍㆎⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω─│┌┐┘└├┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠┯┨┷┿┝┰┥┸╂┒┑┚┙┖┕┎┍┞┟┡┢┦┧┩┪┭┮┱┲┵┶┹┺┽┾╀╁╃╄╅╆╇╈╉╊㎕㎖㎗ℓ㎘㏄㎣㎤㎥㎦㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢㏊㎍㎎㎏㏏㎈㎉㏈㎧㎨㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎀㎁㎂㎃㎄㎺㎻㎼㎽㎾㎿㎐㎑㎒㎓㎔Ω㏀㏁㎊㎋㎌㏖㏅㎭㎮㎯㏛㎩㎪㎫㎬㏝㏐㏓㏃㏉㏜㏆ÆÐªĦIJĿŁØŒºÞŦŊ㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮½⅓⅔¼¾⅛⅜⅝⅞æđðħıijĸŀłøœßþŧŋʼn㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗㈘㈙㈚㈛⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂¹²³⁴ⁿ₁₂₃₄ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя
갋갣걥겷괐괢굠굥궸귕귬긂긇긓깄깯꺆꺍껓껕꼉꼳꽅꽸꿘뀰뀼낻냗냡냣냬넏넢넫녇녱놁놑놰뇄뇡뇸눍눝뉻늗늧늼닁닏닽댠됭둗둚뒙딮딷똠똡똣똭똰뙇뙜뚧뜳뜽뜾랃랟랲럔럲럳렜렫롣롹뢔뤤맜맟맫먄몱뫠뫴뭥뮊뮹믁믕믜밷뱜뱡볌볻볿봥뵴붠붴뷁븡븨빋빧뺜뽓뾱뿕뿝쀠쁭샏샾섁섿셱솀솁솓쇵숖슌싥싳싿쎔쎠쎤쎵쎼쏼쑝쒐쒬씃씿앋앜얬얭옏옝옦옫왘왭왰욷웇웟웻윾읩읭읻잌잍쟵젇젉좬즒즤짣짲쫃쫒쬲쮓찓찟쵀췍칢칮칰칻캨캰컄켘콛쾃쿈쿽퀌퀜퀠큲킄탇턻톧퇻툶퉷팓팤팯펵퐉핰핳핻햏햔햣헗헠헡헣헿홥홨횽훕흝힣
이 glpyhs.txt는 3번에서 서브셋 폰트를 만들 때 추출할 글자로 제공할 파일이다.
- 서브셋 폰트 만들기
pyftsubset "PretendardVariable.woff2" \
--flavor="woff2" \
--output-file="./PretendardVariableSubset.woff2" \
--text-file="glyphs.txt" \
--layout-features='*' \
--glyph-names \
--symbol-cmap \
--legacy-cmap \
--notdef-glyph \
--notdef-outline \
--recommended-glyphs \
--name-legacy \
--drop-tables= \
--name-IDs='*' \
--name-languages='*'
옵션에 대한 설명은 다음과 같다.
- `--flavor="woff2"` : 출력 파일의 형식을 woff2로 지정한다.
woff2
woff2는 Web Open Font Format의 약자로, 웹 폰트를 위해 특별히 설계된 압축 포맷이다. woff2는 woff의 개선된 버전으로 20-30% 더 나은 압축률을 제공하여 웹 페이지 로딩 속도를 향상시킨다.
- `--text-file="glyphs.txt"` : 서브셋에 포함할 글자들이 담긴 텍스트 파일을 지정한다.
- `--layout-features='*'` : 모든 OpenType 레이아웃 기능을 유지한다.
- `--glyph-names` : 글리프 이름을 유지한다.
- `--symbol-cmap`, `--legacy-cmap` : 다양한 환경에서의 호환성을 위한 문자 매핑 테이블을 유지한다.
- `--notdef-glyph`, `--notdef-outline` : 지원하지 않는 문자에 대한 기본 글리프를 포함한다.
- `--recommended-glyphs` : 권장되는 기본 글리프들을 포함한다.
- `--name-legacy`, `--name-IDs='`_`'`__,_ _`--name-languages='`_`'` : 폰트의 이름 관련 정보를 모두 유지한다.
3) Before & After
서브셋 폰트를 적용한 후, 이전보다 3.5배(2.1MB → 586kB) 감소했다.
다운로드 속도는 약 9배(570ms → 64ms) 개선이 되었다.
2. 다이나믹 서브셋 폰트 만들기
그럼에도 여전히 폰트의 크기가 586kB로 상당히 무거운 것을 확인할 수 있다. 다이나믹 서브셋 폰트를 활용해서 필요한 폰트만 다운로드 받아보자.
1) 다이나믹 서브셋 폰트 만들기
다이나믹 서브셋 폰트를 만들기 위해 font-splitter를 활용했다.
- 설치
npm i -g font-splitter
- font-splitter 실행
font-spliter (파일명)
위 font-splitter를 실행하면 아래와 같이 수십 개의 폰트 파일로 나뉜다. 나는 총 62개의 폰트 파일로 나뉘었다.
그리고 그 중에는 css파일이 1개 있을 것이다. 이 css파일이 수십 개의 폰트 파일을 하나로 묶어주는 역할을 하며 우리는 이제 이 css파일만 import하면 된다. css파일 내부를 들여다보면 아래와 같이 수십개의 @font-face 속성이 존재한다.
이 @font-face는 각각 하나의 폰트 파일을 가져오는 역할을 하며 unicode-range를 통해 어느 범위의 유니 코드를 담고 있는 폰트 파일인지 안내하는 역할을 한다. 이를 통해 현재 페이지에서 사용하고 있는 유니 코드의 폰트 파일만 불러오는 다이나믹 서브셋 폰트를 구현할 수 있는 것이다.
또한 font-display를 swap으로 설정하여 FOIT가 아닌 FOUT로 시스템 폰트를 먼저 보여주는 방식을 적용한다. src에는 3개의 경로가 적혀있는데, local은 사용자 로컬 환경에 폰트가 이미 존재한다면 해당 폰트를 사용하겠다는 것을 의미한다.
- 파일 옮기기
수십 개의 폰트로 나뉜 이 파일을 src/fonts에 옮긴다.
- CSS파일 import
모든 페이지에서 사용할 폰트이기에 최상단 layout.tsx에서 import한다. 이 때 3번에서 옮긴 파일중 CSS 파일을 import하면 된다.
// app/layout.tsx
import '../fonts/output/PretendardVariable.css';
2) 다이나믹 서브셋 폰트 적용 결과
자. 이제 다이나믹 서브셋 폰트 설정까지 끝이 났다.

- Time : 64ms
- Size : 587kB

- Time: 약 57ms
- Size : 평균 약 156kB, 총 2MB
폰트의 크기는 서브셋 폰트와 비교했을 때 약 3.7배(587kB → 평균 약 156kB) 감소했으며 다운로드 시간은 64ms에서 평균 약 57ms로 근소하게 감소했다.
영어권 사용자는 어떨까?
메모를 전부 영어만 사용하는 사용자의 케이스이다. 만약 서브셋 폰트였다면 587kB를 다운로드했을 것이다. 하지만 한글, 특수문자 등 사용하지 않는 폰트를 다운로드 하지 않으니 위와 같이 2개의 폰트이며 크기는 각 23kB, 91kB인 것을 확인할 수 있다.
3) 다이나믹 폰트를 적용한 이후 왜 폰트의 총 크기가 늘어났을까?
다이나믹 서브셋 폰트의 구현 방식을 살펴보면 이러한 현상의 원인을 이해할 수 있다. 각각의 문자 범위마다 별도의 폰트 파일이 생성되고 이 파일들은 각각 독립적인 HTTP 요청을 통해 다운로드되어야 한다. 이러한 병렬 요청은 개별 파일의 크기는 작지만, 네트워크 오버헤드와 메타데이터의 중복으로 인해 총 다운로드 시간과 전체 크기가 증가하게 된다.
이는 각 폰트 파일이 자체적으로 필요한 메타데이터와 글리프 정보를 포함해야 하기 때문이다. 예를 들어 동일한 폰트 정보(이름, 스타일, 버전 등)가 각 파일마다 중복되어 포함된다. 또한 여러 개의 HTTP 요청이 발생하면서 각각의 요청에 대한 네트워크 지연 시간이 누적되어 전체적인 다운로드 시간이 증가하게 된다.
그래도 이 트레이드오프는 사용자 경험 면에서는 오히려 이득이다. 필요한 문자의 폰트만 받으니 초기 로드 때 쓸데없는 데이터 전송을 줄일 수 있기 때문이다. 느린 네트워크일수록 효과가 크다. 이러한 다이나믹 서브셋 폰트의 장점은 특히 다국어 지원이 필요한 웹사이트에서 두드러진다. 언어별로 필요한 글리프만 받으니 리소스가 덜 든다.
꿀팁
현재 웹사이트에 실제로 적용된 폰트 확인하기
개발자 모드에서 요소 선택을 눌러 특정 요소에 마우스를 가져다 댔을 때 볼 수 있는 폰트가 있다. 이 폰트가 정말로 현재 적용된 폰트가 맞을까?
위 이미지를 보면 이상한 점을 찾을 수 있다. 왜냐하면 Pretendard폰트에서 화살표는 아래와 같이 하나로 합쳐져야하기 때문이다.

개발자 모드에서 styles와 computed를 열어 확인해보아도 잘 적용이 된 것을 확인할 수 있다.
마지막으로 실제로 현재 해당 요소에 적용된 폰트는 Computed의 Rendered Fonts로 확인할 수 있다. 아래와 같이 Apple SD Gothic Neo 폰트가 적용된 것을 확인할 수 있다. 다시 말해 실제로 폰트가 적용이 안되었기에 →와 같이 Pretendard 글씨체와 다르게 적용이 된 것이다.
위 이슈는 font-family를 Pretendard Variable로 설정했지만 실제로 적용해야하는 Pretendard 폰트의 이름은 Pretendard였기에 적용이 안된 이슈였다. 이와 같이 font-family에 특정 폰트를 적용해도 프로젝트에서 폰트를 import하지 않거나, 잘못된 폰트명을 입력해도 요소 선택과 Styles, Computed에서는 해당 폰트명을 볼 수 있다. 하지만 실제로 적용된 폰트를 확인하기 위해서는 Computed > Rendered Fonts를 확인해야한다.
마지막으로 Rendered Fonts에서는 Font origin: Local file을 통해 로컬에서 폰트를 가져온 것을 확인할 수 있다.
만약 로컬에 해당 폰트가 설치되어 있다면 기본적으로 로컬에서 폰트를 가져온다. 그렇기에 프로젝트에서 import하지 않아도 로컬에 설치되어 폰트가 적용되는 경우도 있으니 이 경로 또한 한번 더 확인을 해봐야한다.
맺으며
처음에는 폰트를 다운로드 받는데 1.4초가 걸렸다. 이를 보고 경악을 금치 못했다. 당연히 가벼울 것이라 생각한 폰트가 가장 오래 오래걸리는 리소스였던 것이다. 그리고 폰트 크기를 최적화를 하며 많은 것을 배우고 느낄 수 있었다.
필요한 폰트만을 추출하는 서브셋 폰트를 적용하니 크기는 약 4배(2.1MB → 586kB) 줄일 수 있었다. 또한 다운로드 시간은 9배(570ms → 64ms) 단축시킬 수 있었다. 처음에 비하면 정말 가벼워졌다고 할 수 있다. 매우 만족스러운 수치 개선이었다.
다이나믹 서브셋 폰트를 적용한 후에는 폰트의 크기는 초기보다 평균 약 13배 (2.1MB → 156kB) 줄일 수 있었다. 또한 다운로드 시간은 10배(570ms → 57ms) 감소했다. 하지만 전체 폰트의 다운로드 크기는 2MB로 기존과 큰 차이가 없었다. 이렇게 다이나믹 서브셋 폰트를 적용하며 폰트가 다시 증가한 것은 폰트 파일의 메타데이터 중복과 HTTP 요청 오버헤드 때문이었다.
폰트 최적화는 결국 서비스의 특성과 사용자를 고려해 전략을 세워야 한다. 영어 서비스라면 서브셋 폰트만으로 충분하지만, 한글 서비스에서는 11,172자에 달하는 한글의 특성상 다이나믹 서브셋 폰트가 더 효율적일 수 있다. 초기 로딩 시간, 리소스 크기, 사용자 경험 사이에서 균형을 잡는 게 관건이다.