이 글에서 다루는 것
인증이 필요한 앱 홈에서 LCP·전송량·CLS를 개선하려면, "무엇을 고쳤는지"만큼 어떻게 측정했는지가 중요하다. 같은 날 공개 가격 페이지 SEO(JSON-LD) 와 장식 motion 접근성도 손봤다.
하루 요약
| 작업 | 무엇을 하고 싶었나 | 한 일 | 결과 |
|---|---|---|---|
| 측정 | before/after를 신뢰할 수 있는 프로토콜로 비교 | reload 3회 median으로만 비교; 단일 outlier run 제외 | 개발 서버 trace 분산에 흔들리지 않는 비교 |
| Prefetch + CLS | 데이터는 빨리 오되 레이아웃 점프 없이 | 서버 prefetch query string을 클라이언트 provider default와 일치 + skeleton 높이 예약 | cache hit 유지, skeleton→콘텐츠 CLS 억제 |
| 캐러셀 | 뷰포트 안 슬라이드만 이미지 mount | in-view 슬라이드만 이미지 mount | 불필요한 전송량 절감 |
| SEO | JSON-LD를 서버에서 안전하게 주입 | JSON-LD graph를 서버 page.tsx에서 주입; < sanitize | 크롤러·일관성 확보 |
| A11y | prefers-reduced-motion에서 장식 ripple 비활성화 | ripple을 prefers-reduced-motion에서 비활성화 | reduced-motion 사용자에게 장식 motion 제거 |
1. Lab 측정 프로토콜
배경지식 (개념)
- LCP (Largest Contentful Paint): 화면에서 가장 큰 콘텐츠가 그려지기까지 걸리는 시간.
- Trace outlier: 개발 서버 단일 Performance trace는 수치가 들쭉날쭉해 한 번 값으로 결론 내면 안 되는 이상치.
어떤 상황이었나
Chrome Performance trace를 한 번 찍으면 수치가 들쭉날쭉했다. 개발 서버 단일 run은 outlier가 흔하다. before/after를 신뢰할 수 있는 프로토콜로 비교하고 싶었다.
핵심 작업
Chrome Performance trace를 reload 3회 median으로만 before/after 비교한다. 단일 outlier run은 결론에서 제외한다.
교훈
- Lighthouse SEO/A11y 점수와 trace LCP ms는 다른 계측이다.
- 다음 단계: production
build && start에서 동일 프로토콜 재측정.
→ Lab 성능 측정 분산과 비교 프로토콜 · Lighthouse 측정 정확도
2. SSR prefetch와 CLS
배경지식 (개념)
- CLS (Cumulative Layout Shift): 로딩 중 레이아웃이 밀리는 정도. skeleton 높이가 실제 콘텐츠와 다르면 악화된다.
- Prefetch: 서버에서 데이터를 미리 가져와 클라이언트 캐시에 넣어 두는 것.
어떤 상황이었나
SSR로 list API를 prefetch했는데, 클라이언트 query key가 1글자라도 다르면 cache miss → skeleton → 콘텐츠 전환이 눈에 띄어 CLS가 오히려 나빠질 수 있다. 데이터는 빨리 오되 레이아웃 점프 없이가 목표였다.
핵심 작업
서버에서 list API를 prefetch·hydrate할 때 query string을 클라이언트 provider default와 일치시킨다. skeleton은 page size 행 수·body minHeight 공유로 CLS를 억제한다. cache hit을 유지하면서 skeleton→콘텐츠 CLS를 억제했다.
교훈
- prefetch가 데이터를 빨리 가져오면 skeleton→콘텐츠 전환이 눈에 띄어 CLS가 악화될 수 있다 — prefetch·skeleton skip·높이 예약은 한 세트로 본다.
→ SSR prefetch와 클라이언트 쿼리 캐시 hydrate · CLS와 스켈레톤 레이아웃 예약
3. 캐러셀 이미지
배경지식 (개념)
- 뷰포트 밖 이미지 지연: 모든 슬라이드 이미지를 mount하면 전송량이 커진다. lazy attribute만으로는 "DOM에 올라가는 이미지 수"를 줄이기 어려울 수 있다.
어떤 상황이었나
모든 슬라이드 이미지를 mount하면 전송량이 불필요하게 크다. 뷰포트 안 슬라이드만 이미지 mount하고 싶었다.
핵심 작업
캐러셀은 in-view 슬라이드만 이미지 mount한다. lazy attribute만으로는 "DOM에 올라가는 이미지 수"를 줄이기 어려울 수 있다. 불필요한 전송량을 절감했다.
교훈
- 캐러셀은 뷰포트 밖 슬라이드 이미지를 mount하지 않는 것이 lazy보다 전송량 절감에 직접적이다.
4. JSON-LD
배경지식 (개념)
- JSON-LD: 페이지 내용을 검색엔진이 이해하도록
<script>에 넣는 구조화 데이터(JSON) 포맷. SEO에 쓴다.
어떤 상황이었나
공개 pricing 페이지에 구조화 데이터가 필요했고, 클라이언트에서만 넣으면 크롤러·일관성 측면에서 불리하다. JSON-LD를 서버에서 안전하게 주입하고 싶었다.
핵심 작업
공개 pricing 페이지에 JSON-LD graph를 서버 page.tsx에서 주입한다. <를 sanitize한다. 크롤러·일관성을 확보했다.
교훈
- JSON-LD는 client UI와 분리해 서버
page.tsx에서 sanitize한 script로 주입한다.
5. 장식 motion 접근성
배경지식 (개념)
prefers-reduced-motion: 사용자가 모션 축소를 선호하는지 알려 주는 미디어 쿼리. 장식 motion은 여기서 비활성화한다.
어떤 상황이었나
장식용 ripple 같은 motion을 prefers-reduced-motion 사용자에게도 그대로 보여 주고 있었다.
핵심 작업
ripple을 prefers-reduced-motion에서 비활성화했다. reduced-motion 사용자에게 장식 motion을 제거했다.
교훈
animate-pulse는 spread ripple과 목적이 다르다 — 전용@keyframes+ stagger가 적합.