웹 개발
웹 폰트 하나로 레이아웃이 흔들린 뒤 CLS를 측정해 고친 짧은 튜토리얼
폰트가 늦게 도착할 때 버튼 위치가 움직이던 화면을 측정하고 고정한 방법을 간단히 기록한다.
눈으로만 보던 흔들림을 숫자로 잡기
모바일에서 글 목록을 열면 첫 화면의 버튼이 아래로 밀렸다. 로컬 개발 서버에서는 거의 보이지 않았고, 느린 네트워크에서만 제목 폰트가 바뀌는 순간 레이아웃이 이동했다. ‘폰트가 늦게 로드된다’는 설명만으로는 개선 여부를 알기 어려워 Lighthouse와 실제 브라우저의 Layout Shift를 함께 확인했다.
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
console.log('layout shift', entry.value, entry.sources);
}
}
}).observe({ type: 'layout-shift', buffered: true });
클릭 직후 움직인 것은 점수에 포함하지 않을 수 있어 hadRecentInput도 같이 확인했다. 이 조건을 빼면 사용자가 의도한 이동까지 문제처럼 보였다.
폰트와 자리의 기준을 맞추기
외부 폰트 CSS를 화면이 열린 뒤 동적으로 삽입하고 있었는데, 우선순위와 fallback 글꼴의 폭이 달랐다. Next.js에서는 사용하는 폰트를 빌드에 포함하고, 기본 글꼴과 너무 다른 fallback으로 급격히 바뀌지 않도록 설정했다. 이미지와 제목 영역에도 예상 높이를 주었다.
.post-title {
min-height: 3.2em;
font-size: 1.5rem;
line-height: 1.25;
}
@font-face {
font-family: 'BlogSans';
src: url('/fonts/blog-sans.woff2') format('woff2');
font-display: swap;
}
min-height를 무조건 크게 잡으면 빈 공간이 생기므로 실제 제목 두 줄 기준으로 정했다. 폰트 파일은 필요한 굵기만 남기고, 개발자 도구의 Network 탭에서 폰트가 늦게 와도 제목 상자의 높이가 변하지 않는지 확인했다.
배포 전에 반복할 측정
- 모바일 실제 기기 또는 제한된 네트워크에서 첫 화면을 열었다.
- Layout Shift의
sources로 움직인 요소를 특정했다. - 웹 폰트의 파일 크기와 필요한 굵기를 점검했다.
- 이미지와 제목에 예약된 공간이 있다.
- 수정 전후 같은 URL에서 동일 조건으로 측정했다.
이번 수정의 목표는 ‘더 빨리 보이게’가 아니라 ‘나타난 뒤 자리를 바꾸지 않게’였다. 성능 지표를 볼 때도 점수 하나만 캡처하지 않고 어떤 요소가 움직였는지를 확인해야 다음 수정이 엉뚱한 방향으로 가지 않는다.