Advanced (업데이트: 2026. 6. 2.)

Claude Code로 Web Font 최적화 구현하기

Claude Code로 Web Font를 안전하게 최적화하는 실전 가이드. preload, font-display, 서브셋, CLS/LCP까지 다룹니다.

Claude Code로 Web Font 최적화 구현하기

폰트 최적화는 디자인보다 렌더링 안정성 문제다

Web Font는 브랜드 인상을 만들지만, 잘못 불러오면 성능 문제가 된다. 글자가 늦게 보이거나, 제목이 나중에 바뀌면서 줄이 밀리거나, CTA 버튼이 움직이거나, LCP가 늦어진다. LCP는 Largest Contentful Paint로, 첫 화면에서 가장 큰 콘텐츠가 표시되는 시간을 뜻한다. CLS는 Cumulative Layout Shift로, 사용자가 의도하지 않은 레이아웃 이동을 뜻한다. 한국어, 일본어, 중국어처럼 글자 수가 많은 언어는 폰트 파일이 커지기 쉬워 더 조심해야 한다.

이 글은 Astro 사이트를 예로, Claude Code를 사용해 Web Font 최적화를 안전하게 적용하는 방법을 설명한다. 로딩 전략, preload, preconnect, font-display, variable font, 폰트 서브셋, self-host와 third-party 선택, CLS/LCP 위험, Lighthouse와 WebPageTest 방식의 확인, Claude Code에 줄 수 있는 프롬프트까지 포함한다.

브라우저 동작은 공식 문서를 기준으로 판단해야 한다. MDN의font-displayswap, optional 등의 차이를 설명하고, MDN의rel="preload"는 폰트 preload에 필요한 속성을 설명한다. 전체적인 원칙은 web.dev의font best practices, optimize web fonts, Web Vitals를 기준으로 삼으면 된다.

먼저 로딩 전략을 정한다

Claude Code에 “폰트를 빠르게 해줘”라고만 요청하면, 필요 없는 preload가 늘거나, 실제 디자인에 필요한 굵기가 사라질 수 있다. 먼저 첫 화면에서 꼭 필요한 글자, 브랜드 폰트가 반드시 필요한 영역, 지연 로딩해도 되는 폰트를 정한다.

사용 사례권장 전략이유흔한 실패
다국어 블로그 본문본문은 시스템 폰트, 제목만 서브셋 Web Font폰트가 늦어도 읽을 수 있다제목 폰트를 본문 전체에 적용
SaaS 대시보드라틴 variable font를 self-host여러 굵기를 한 파일로 관리쓰지 않는 italic, axis, 언어 범위를 포함
랜딩 페이지 heroLCP 제목에 쓰는 WOFF2만 preload핵심 문구가 빨리 보인다모든 굵기를 preload해서 이미지와 경쟁
오래된 icon fontSVG 또는 컴포넌트 아이콘으로 교체폰트 요청 하나를 제거pseudo-element CSS를 놓침

콘텐츠 사이트에서는 본문을 시스템 폰트로 즉시 읽게 하고, 브랜드성이 필요한 제목과 내비게이션에만 Web Font를 쓰는 편이 안전하다. 제품 UI에서는 숫자와 버튼, 테이블이 많기 때문에 Inter 같은 variable font가 유리할 수 있다. 랜딩 페이지에서는 hero 이미지와 폰트가 같은 첫 화면 예산을 나눠 쓰므로 Claude Code 이미지 최적화와 함께 봐야 한다. 전체 성능 작업은 Claude Code 성능 최적화와 연결된다.

Astro에서 preload와 preconnect를 적용한다

preload는 강한 힌트다. 첫 화면에서 실제로 쓰는 한두 개의 WOFF2 파일에만 사용한다. 모든 굵기, 모든 언어, 스크롤 이후에 나오는 폰트까지 preload하면 CSS, 이미지, JavaScript와 경쟁해 오히려 LCP가 나빠질 수 있다. self-host 폰트는 보통 preconnect가 필요 없다. Google Fonts처럼 외부 제공자를 쓸 때만 CSS 도메인과 폰트 도메인에 preconnect를 검토한다.

---
// src/layouts/BaseLayout.astro
const criticalFonts = [
  { href: "/fonts/inter-var-latin.woff2", type: "font/woff2" },
  { href: "/fonts/noto-sans-jp-latin-kana.woff2", type: "font/woff2" },
];

const usesGoogleFonts = false;
---

<html lang="ko">
  <head>
    {criticalFonts.map((font) => (
      <link rel="preload" href={font.href} as="font" type={font.type} crossorigin />
    ))}

    {usesGoogleFonts && <link rel="preconnect" href="https://fonts.googleapis.com" />}
    {usesGoogleFonts && <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />}

    <link rel="stylesheet" href="/styles/fonts.css" />
  </head>
  <body>
    <slot />
  </body>
</html>

검토할 점은 명확하다. href@font-facesrc와 일치해야 한다. as="font", type="font/woff2", crossorigin이 있어야 한다. 그리고 preload한 폰트는 첫 화면에서 실제로 사용되어야 한다. 브라우저가 사용되지 않은 preload 경고를 낸다면, 힌트를 줄이는 것이 맞다.

font-display와 fallback metric을 함께 조정한다

font-display: swap은 먼저 fallback font로 글자를 보여주고, Web Font가 도착하면 교체한다. FOIT, 즉 글자가 보이지 않는 시간을 줄이기 좋지만, fallback과 실제 폰트의 폭과 높이가 다르면 교체 순간에 CLS가 생긴다. 중요하지 않은 본문이나 느린 네트워크에서는 optional이 더 나을 때도 있다.

/* public/styles/fonts.css */
@font-face {
  font-family: "InterVariable";
  src: url("/fonts/inter-var-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "NotoSansJPSubset";
  src: url("/fonts/noto-sans-jp-latin-kana.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+3000-30FF, U+FF00-FFEF;
}

@font-face {
  font-family: "InterFallback";
  src: local("Arial");
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

:root {
  --font-ui: "InterVariable", "InterFallback", system-ui, sans-serif;
  --font-ja: "NotoSansJPSubset", "Hiragino Sans", "Yu Gothic", sans-serif;
}

body {
  font-family: var(--font-ui);
}

article {
  font-family: var(--font-ja);
}

MDN의unicode-range는 특정 문자 범위에만 폰트를 적용하는 방법을 설명한다. 하지만 이것만으로 파일이 작아지지는 않는다. 실제 전송량을 줄이려면 더 작은 WOFF2 파일을 만들어야 한다.

variable font와 서브셋으로 바이트를 줄인다

Variable font는 여러 굵기를 하나의 파일로 다룰 수 있어 UI에 적합하다. 그러나 항상 작은 것은 아니다. 축이 많거나 언어 범위가 넓으면 정적 폰트 여러 개보다 클 수 있다. Claude Code에는 실제 사용 중인 굵기, italic 사용 여부, 언어 범위를 먼저 조사하게 한다.

서브셋은 필요한 문자만 남긴 폰트 파일을 만드는 작업이다. hero 제목, 내비게이션, 고정 CTA에는 효과적이다. 본문에는 위험하다. 새 글에서 다른 한자나 기호가 나오면 일부 글자만 다른 fallback으로 보일 수 있다.

# Create a WOFF2 subset with Latin, punctuation, kana, and full-width forms.
python -m pip install "fonttools[woff]"
mkdir -p public/fonts

pyftsubset ./vendor-fonts/NotoSansJP-Regular.ttf \
  --output-file=./public/fonts/noto-sans-jp-latin-kana.woff2 \
  --flavor=woff2 \
  --layout-features='*' \
  --unicodes="U+0000-00FF,U+3000-30FF,U+FF00-FFEF"

이 예시는 대부분의 한자를 포함하지 않는다. 첫 화면 제목이나 메뉴용으로는 좋지만, 일본어 본문 전체에는 부족하다. 본문까지 적용하려면 MDX 파일에서 실제 문자를 추출해 --text-file로 넘기거나, 별도 한자 서브셋을 만들어야 한다. 폰트 라이선스도 반드시 확인한다.

self-host와 third-party를 비교한다

Self-host는 URL, cache header, preload 대상, 서브셋을 직접 제어할 수 있다. 대신 라이선스, 업데이트, 빌드 스크립트 책임이 생긴다. Third-party는 빠르게 도입할 수 있지만, 외부 DNS/TLS 연결과 CSS 요청이 추가된다.

기준Self-hostThird-party
연결 비용같은 origin외부 CSS와 폰트 origin
캐시파일명과 헤더를 직접 제어제공자 정책에 의존
서브셋자유롭게 생성서비스 기능에 의존
운영책임이 크다도입은 쉽지만 의존성이 남는다

성능이 중요한 페이지는 첫 화면 폰트를 self-host하고, 장식 폰트는 늦게 불러오는 방식이 좋다. 빠른 프로토타입에서 third-party를 쓰더라도 display=swap, 필요한 굵기만 로드, 필요한 origin만 preconnect라는 규칙은 지켜야 한다.

Lighthouse와 WebPageTest 방식으로 검증한다

구현 후에는 화면만 보지 말고 네트워크 순서를 본다. Lighthouse로 LCP, CLS, font-display 관련 경고를 확인하고, WebPageTest처럼 waterfall에서 HTML, CSS, 폰트, hero 이미지, JavaScript 순서를 본다.

URL="https://example.com/"
npx --yes lighthouse "$URL" \
  --only-categories=performance \
  --chrome-flags="--headless" \
  --output=json \
  --output-path=./lighthouse-fonts.json

node -e "const r=require('./lighthouse-fonts.json'); for (const id of ['largest-contentful-paint','cumulative-layout-shift','font-display']) console.log(id, r.audits[id]?.displayValue ?? r.audits[id]?.score ?? 'n/a')"

좋은 신호는 단순하다. 첫 화면에 필요한 WOFF2만 일찍 시작한다. preload한 파일은 실제로 사용된다. 반복 방문에서는 cache가 작동한다. 느린 모바일 설정에서도 본문이 바로 읽힌다. 폰트가 교체되어도 제목과 CTA가 움직이지 않는다.

흔한 실패는 더 구체적이다. 첫 화면에 쓰이지 않는 bold를 preload한다. Google Fonts CSS를 남긴 채 self-host CSS를 추가한다. font-display: swap만 넣고 fallback metric을 맞추지 않는다. 서브셋에서 전각 숫자, 문장 부호, 장음 기호가 빠진다. icon font를 계속 preload한다.

Claude Code에 줄 안전한 프롬프트

먼저 읽기 전용 감사를 시킨다.

Audit web font loading in this Astro site. Do not edit files yet.

Find:
- @font-face, Google Fonts, Fontsource, and CSS import locations
- Font files used above the fold
- preload and preconnect hints that are missing or unnecessary
- CLS or LCP risks caused by font swapping
- Candidates for self-hosting, variable fonts, and subsetting

Return:
- A prioritized table of changes
- Files that should be edited and files that must not be touched
- Verification commands and residual risks

그다음 구현 범위를 좁힌다.

Implement web font optimization only in these files:
- src/layouts/BaseLayout.astro
- public/styles/fonts.css
- generated files under public/fonts/

Acceptance criteria:
- Preload only WOFF2 files used in the first viewport
- Do not add preconnect when fonts are self-hosted
- Every @font-face has a deliberate font-display value
- Fallback metrics are adjusted to reduce CLS
- Existing routes, article slugs, hero images, and unrelated content are untouched

Verification:
- npm run build
- node scripts/check-code-fences.mjs
- Lighthouse check for LCP, CLS, and font-display
- Report what could not be verified

실제 적용 결과와 CTA

Masa의 운영에서는 “폰트를 최적화해줘”보다 “LCP 제목에 쓰는 폰트만 preload하고, 본문은 시스템 폰트로 즉시 읽게 하며, CLS 위험을 보고해줘”가 훨씬 안정적이었다. 일본어 페이지에서 라틴과 가나 서브셋은 첫 화면을 더 안정적으로 만들었지만, 본문 전체를 통일하려면 별도 문자 추출이 필요했다. 가장 유용한 검증은 Lighthouse 점수 하나가 아니라, 느린 모바일 waterfall과 제목, CTA가 움직이지 않는지 직접 보는 것이었다.

팀에서 반복하려면 이 규칙을 CLAUDE.md에 넣어야 한다. 먼저무료 치트시트로 안전한 명령 패턴을 확인하고, 반복 작업에는제품 템플릿을 사용해 프롬프트와 리뷰 기준을 고정하자. Core Web Vitals, 기사 품질, 수익 CTA까지 함께 정리해야 한다면Claude Code 교육 및 도입 상담이 적합하다.

게시 전 자체 점검: 이 글은 3개 이상의 사용 사례, Astro/CSS/bash 예제, MDN과 web.dev 공식 링크, 내부 링크, 구체적인 실패 사례, 자연스러운 CTA, 실제 검증 메모를 포함한다.

#Claude Code #Web Font #Core Web Vitals #Astro #성능 최적화
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.