모바일은 이제 트래픽의 과반을 넘어선 지 오래다. 구글의 모바일 퍼스트 인덱싱이 전면 적용된 이후, 데스크톱에서 아무리 빠르게 돌아가는 사이트라도 모바일 성능이 떨어지면 검색 순위에서 손해를 본다. 여기서 핵심은 코어 웹 바이탈이다. LCP, INP, CLS, 이 세 가지 지표를 모바일 기준으로 설계하고 운영해야 검색성과와 전환율이 함께 올라간다. 필드 데이터의 우위를 이해하고, 측정과 튜닝을 반복하며, 디자인과 인프라를 함께 다듬어야 한다. 이 글은 현장에서 성능 개선 프로젝트를 여러 번 굴려 본 경험을 바탕으로, 실무에 바로 쓰일 방법과 판단 기준을 정리한다.
코어 웹 바이탈, 이름보다 맥락이 중요하다
구글이 코어 웹 바이탈이라고 부르는 항목은 측정 이름이 아니라 사용자 경험의 핵심 순간을 가리킨다. LCP는 사용자가 페이지를 처음 열었을 때 가장 큰 요소가 화면에 그려지는 데 걸리는 시간이다. 모바일에서 상단 히어로 이미지가 늦게 뜨는 것만으로도 이탈률이 눈에 띄게 올라간다. INP는 페이지 상호작용의 지연을 한 점수로 모은다. 버튼을 눌렀을 때 반응이 늦거나 입력 지연이 쌓이는 문제를 포착한다. CLS는 레이아웃이 예기치 않게 움직이는 정도를 수치화한다. 글을 읽다가 버튼이 밀려 클릭 실수가 나오는 상황이 여기에 잡힌다.
지표의 목표값은 상대적으로 단순하다. LCP 2.5초 이내, INP 200ms 이내, CLS 0.1 미만이 일반적인 합격선이다. 하지만 실무에서는 기준선을 맞추는 것보다 변동성을 줄이는 일이 더 어렵다. 저사양 기기, 느린 3G 지역, 혼잡한 시간대와 같이 환경이 바뀌면 지표가 휘청이기 때문이다. 평균이 아닌 p75, 즉 상위 75번째 백분위수를 꾸준히 안정화하는 전략이 필요하다. 구글이 랭킹에 반영하는 것도 대체로 p75다.
필드 데이터와 랩 데이터의 간극
팀이 성능 대시보드를 열어보면, Lighthouse 점수는 좋은데 Search Console의 코어 웹 바이탈 보고서가 빨간색으로 남아있는 경우가 많다. 랩 데이터는 통제된 환경의 측정값이라 병목을 찾는 데 유용하지만, 실제 사용자 환경을 대변하지 못한다. 반면 Chrome UX Report나 RUM 스니펫으로 수집한 필드 데이터는 현실을 비춘다. 서울 지하철에서 접속한 중가형 안드로이드, 배터리 세이브 모드, 제한된 CPU와 메모리, 이런 변수가 필드 데이터에 고스란히 반영된다.
필드 데이터가 나쁘면 우선순위를 바꾸어야 한다. 이미지 포맷 변경이나 캐시 전략 같은 보편 개선부터 시작하고, 그 다음 특정 구간의 상호작용 병목을 좁혀간다. 계절성이나 캠페인 트래픽도 고려한다. 대규모 프로모션 기간에만 성능이 깨진다면, 특정 위젯이나 태그가 범인일 가능성이 높다. 실제로 한 커머스 프로젝트에서 블랙프라이데이 팝업과 A/B 테스트 스크립트가 동시에 초기 렌더링을 막아 LCP가 5초대로 치솟았다. 팝업 로딩을 interaction 이후로 미루고, A/B 스크립트를 서버 사이드로 전환하자 p75 LCP가 2.2초까지 회복됐다.
모바일 퍼스트 관점의 병목 진단
모바일에서 성능을 망가뜨리는 패턴은 몇 가지로 수렴한다. 고해상도 이미지를 그저 축소만 해서 보내는 관행, 초기 렌더를 막는 블로킹 스크립트, 무거운 폰트, 예측 불가능한 광고 슬롯, 프레임워크의 비효율적 hydration, 그리고 네트워크 왕복을 늘리는 번들 분할 전략의 오판이다. 각각을 모바일 퍼스트 시야로 재해석하면 대응이 쉬워진다.
히어로 이미지가 화면을 가득 채우는 레이아웃을 쓰면 LCP는 필연적으로 이미지 한 장에 달라붙는다. 여기서 성공의 절반은 서버에서 적절한 크기로 변환해 내려주는 것이다. 브라우저가 스스로 축소하도록 두면 전송량과 디코딩 모두에서 손해를 본다. 서버가 원본에서 3가지 크기로 파생본을 만들고, HTML에서 srcset과 sizes로 힌트를 주면 모바일에서 불필요한 용량을 받지 않는다. WebP 혹은 AVIF를 지원하면 전송량을 20에서 40% 이상 줄이는 경우가 많다. 다만 AVIF는 인코딩 비용이 높고 일부 브라우저에서 디코딩 성능이 들쭉날쭉할 수 있어, 트래픽과 변환 비용의 균형을 맞출 필요가 있다.
블로킹 스크립트는 보통 태그 매니저, 서드파티 분석, A/B 테스트, 챗 위젯이 원인이다. 모바일에서는 초기 페인트 이전에 꼭 필요한 스크립트만 남기고 나머지는 지연 로딩한다. defer, async는 기본이고, 사용자가 상호작용한 후에만 삽입하는 전략이 효과적이다. 한 금융 클라이언트의 앱 내 웹뷰에서 스크롤 제스처가 닫힘 동작과 충돌해 INP가 악화된 적이 있다. 제스처 감지 라이브러리를 가볍게 교체하고 이벤트리스너의 passive 옵션을 올바르게 사용하니 95ms까지 떨어졌다.
폰트는 의외로 모바일에서 큰 지분을 차지한다. 가변 폰트 하나로 두께와 스타일을 커버하면 네트워크 요청 수를 줄이고 렌더링 경로도 간단해진다. font-display를 swap이나 optional로 설정해 FOIT를 피하되, CLS를 방지하려면 폴백 폰트의 metrics를 맞춘다. 한글처럼 글리프가 많은 폰트는 서브셋 전략이 필요하다. 핵심 화면에서 쓰는 문자만 묶은 서브셋을 우선 제공하고, 나머지는 비동기로 로딩한다.
LCP를 끌어내리는 세부 전술
LCP 대상 요소를 측정하면 보통 이미지, 배경 이미지, 큰 텍스트 블록, 비디오 포스터 이미지가 잡힌다. 이 순위를 기록하고 각 요소에 맞는 조치를 취한다. 서버 타이밍과 브라우저 타이밍을 함께 본다. TTFB가 800ms 이상이면 서버나 네트워크 단에서 손실이 크다. CDN에서 캐시를 맞추고, 오리진의 쿼리를 줄이며, HTML 스트리밍을 고려한다. CSR 중심 구조에서는 첫 바이트가 빨라도 콘텐츠가 늦게 뜰 수 있다. 모바일 퍼스트라면 초기 뷰를 서버에서 바로 렌더링하는 SSR이나 SSG가 유리하다. 이후 상호작용이 많은 영역만 점진적으로 hydrate한다. 프레임워크를 가리지 않고, 하이드레이션 비용을 줄이는 아일랜드 아키텍처가 현장에서 성과가 좋다.
이미지 최적화는 전송과 디코딩을 동시에 본다. DPR을 고려한 소스셋, 적절한 크기의 컨테이너, lazy-loading은 표준이다. 다만 LCP 후보는 lazy가 아니라 eager로 표시해 우선 로딩시킨다. fetchpriority 속성으로 더 명확하게 의도를 전달하면 브라우저가 네트워크 큐에서 우선순위를 높여준다. 프리커넥트, 프리로드는 남용하지 말고, 실측 데이터를 바탕으로 딱 필요한 리소스에만 적용한다.
실무에서 자주 겪는 함정은 CSS다. 핵심 영역의 스타일을 분리해 크리티컬 CSS로 inline하면 렌더링이 빠르다. 하지만 빌드 파이프라인이 커지면 규칙이 중복되거나 캐시 효율이 떨어진다. 기준은 간단하다. 초기 뷰에 필요한 스타일만 10에서 14KB 정도로 제한한다. 나머지는 지연 로딩하고, 중복 감지를 자동화한다.
INP를 개선하는 코딩 습관
모바일에서 입력 지연은 애니메이션이 아니라 자바스크립트 병목인 경우가 대부분이다. 긴 태스크가 50ms를 넘기면 사용자는 딱딱함을 느낀다. 핵심은 스레드 점유를 잘게 나누는 것이다. 이벤트 핸들러에서 상태를 한꺼번에 계산하지 말고, 일을 스케줄링한다. requestIdleCallback은 여유 시간에, scheduler.postTask는 우선순위에 따라 작업을 나눈다. 가상 리스트를 쓰면 스크롤 중에 DOM 업데이트를 최소화할 수 있다.
서드파티는 흡수하지 말고 샌드박싱한다. 교차 도메인 iframe으로 격리하면 메인 스레드를 덜 점유한다. 번들에서는 트리쉐이킹이 실제로 먹히는지 확인한다. 이름만 ESM이고 내부에 사이드 이펙트가 많아 제거가 안 되는 라이브러리가 있다. 그럴 땐 대체 라이브러리를 찾거나 직접 경량 구현으로 바꾸는 편이 빠르다. 숫자로 보자. 한 쇼핑몰에서 데이트피커 라이브러리를 교체해 120KB를 줄였고, 결제 단계에서 INP p75가 350ms에서 180ms로 떨어졌다. 날짜 선택 인터랙션 하나가 체크아웃 전체 경험을 끌어올린 셈이다.
탭 전환, 아코디언 열기, 검색 자동완성처럼 자주 쓰는 패턴은 배치 업데이트가 중요하다. React나 Vue를 쓴다면 배치가 기본으로 켜져 있지만, 비동기 경계에서 깨질 수 있다. useTransition이나 flushSync 같은 도구를 상황에 맞게 쓰되, 무리한 동기화는 오히려 지연을 만든다. 프레임 단위로 일을 나누면 사용자는 진행감이 생긴다. 60fps를 못 맞추더라도 안정적인 30fps가 체감 품질을 지킨다.
CLS를 0.1 미만으로 고정하는 방법
레이아웃 시프트는 대부분 예측 가능한 원인에서 나온다. 이미지와 비디오에 명시적인 width와 height를 지정해 브라우저가 레이아웃 공간을 확보하도록 한다. 반응형이라도 비율 상자를 통해 공간을 예약하면 된다. 광고 슬롯은 더 까다롭다. 다양한 크기의 광고가 들어오면 슬롯 높이가 바뀐다. 해결책은 최대치 기준으로 최소 높이를 예약하고, 충돌할 때는 스켈레톤을 유지해 사용자 시선을 보호하는 것이다. 폰트 로딩으로 문단이 밀리는 문제는 폴백과 목표 폰트의 metrics를 맞추는 것이 가장 확실하다. 폴백 선택을 바꾸는 것만으로도 CLS가 절반 이하로 줄어드는 사례가 드물지 않다.
UI 삽입 시점도 조정한다. 쿠키 배너, 추천 위젯, 후기 토스트 같은 요소는 뷰포트를 밀지 않는 위치에 띄우고, 트랜지션을 써서 움직임을 예측 가능하게 만든다. 측정할 때는 세션 단위 CLS를 확인해, 긴 스크롤에서 누적되는 시프트를 잡아낸다. 무한스크롤 페이지는 특히 주의가 필요하다. 콘텐츠가 뒤늦게 삽입될 때 기존 영역을 밀지 않도록 placeholder를 먼저 만든다.
프레임워크 선택보다 렌더링 전략
Next.js, Nuxt, SvelteKit, Remix 등 프레임워크가 많지만, 순위에 영향을 미치는 것은 프레임워크 이름이 아니라 렌더링 전략의 일관성이다. 모바일 퍼스트라면 첫 화면을 서버에서 렌더링하고, 클라이언트에서는 필요한 섬만 깨워 상호작용을 붙인다. 거대한 단일 hydration을 피할수록 INP가 안정된다. 데이터 페칭도 마찬가지다. 서버 단계에서 캐시 가능한 데이터를 최대한 준비하되, 개인화나 세션 정보는 클라이언트에서 지연 페칭한다. 에지에서 실행되는 함수로 TTL이 짧은 데이터를 합쳐서 내려주면 TTFB를 조금 희생하는 대신 라운드트립을 크게 줄일 수 있다.
리소스 우선순위는 모바일 성능의 숨은 축이다. preload는 핵심 폰트 한두 개와 LCP 이미지 정도에만 쓴다. preconnect와 dns-prefetch는 외부 도메인이 여러 개일 때 의미가 있지만, 무분별하게 늘리면 브라우저의 연결 슬롯을 잡아먹는다. 구조적으로 중복 요청이 생기지 않도록 번들링과 코드스플릿을 재검토한다. 라우트 단위 코드 분할은 기본인데, 모듈을 더 쪼개는 것이 효과를 낼 때도 있다. 다만 초기 연결이 여러 개로 나뉘면 TLS 오버헤드가 커진다. 필드 데이터로 확인하며 한 발씩 조절한다.
디자인과 콘텐츠도 성능 요소다
히어로 섹션에 자동재생 비디오를 쓰면 즉각적인 임팩트를 주지만, 모바일에서 배터리 소모와 데이터 비용이 크다. 비디오를 꼭 써야 한다면, 포스터 이미지를 최적화하고, 자동재생은 음소거 조건에서만, 네트워크 상태가 나쁠 때는 정지 이미지를 대체로 보여주는 정책을 두는 편이 낫다. 텍스트 대비와 라인 길이, 터치 타깃 간격 같은 접근성 규칙은 체감 속도에도 영향을 준다. 작고 빽빽한 요소는 오조작을 늘려 행동 사이클을 늘린다. 결국 INP가 나빠지는 경로다.
콘텐츠 관리 시스템에서도 툴링을 해두면 크리에이티브 팀이 성능을 망치지 않고 자유롭게 작업할 수 있다. 업로드한 이미지에 자동으로 포맷과 크기 제한을 적용하고, LQIP나 블러-업 기법을 CMS 차원에서 붙인다. 에디터가 iframe을 삽입할 때 lazy 속성이 기본으로 들어가도록 가드를 만든다. 실제로 이런 가드만으로 CLS 경고가 70% 이상 줄어든 사례가 있었다.
계측과 모니터링, 과정의 절반
Search Console의 코어 웹 바이탈 보고서는 추세를 보기 좋지만, 상세 진단에는 한계가 있다. RUM을 세팅해 사용자 단에서 LCP, INP, CLS 이벤트를 수집하자. 브라우저 PerformanceObserver를 이용하면 비교적 간단히 구현된다. 기기 모델, OS, 연결 타입, DPR, 배터리 상태 같은 컨텍스트를 함께 저장하면 병목의 위치가 보인다. 샘플링 비율은 트래픽과 비용을 고려해 1에서 5%로 시작하고, 이슈 기간에는 일시적으로 올린다.
변경 전후의 차이를 확실히 검증하려면 실험을 설계한다. A/B 테스트가 성능 데이터 수집을 방해하는 아이러니가 있지만, 서버 사이드 실험으로 전환하면 해결된다. 배포 파이프라인에 성능 가드레일을 심어 Lighthouse CI나 WebPageTest의 스크립트 테스트를 통과하지 못하면 머지를 막는다. 다만 CI는 랩 데이터이므로 임계값을 너무 타이트하게 잡지 않는다. 가입 페이지가 90점에서 88점으로 떨어졌다고 배포를 막으면 현업이 성능 개선을 미워하게 된다. CI는 회귀 탐지 수준에서 쓰고, 필드 데이터로 진짜 합격 여부를 판정한다.
이미지, 폰트, 스크립트의 현실적인 합의
모든 이미지를 AVIF로 바꾸고, 모든 스크립트를 지연 로딩하고, 모든 폰트를 서브셋으로 관리하면 최선일 것처럼 보이지만, 유지보수 비용이 폭증한다. 조직의 규모와 제품의 수명 주기를 고려해 기준선을 정한다. 예를 들어 마케팅 랜딩 페이지는 최대한 고강도의 최적화를 적용한다. 라이프사이클이 짧고, 전환 지표에 민감하기 때문이다. 반면 대시보드나 내부 도구는 유지보수 편의를 우선한다. 폰트는 한 종류로 통일하고, 이미지 포맷은 WebP 중심에 fallback을 덧붙이는 정도면 충분할 수 있다.
서드파티 스크립트는 계약 단계에서 성능 기준을 명시한다. 최대 페이로드, 초기 실행 예산, 지연 로딩 허용 등을 SLA로 넣으면 이후 협의가 쉬워진다. 태그 매니저에는 로딩 조건을 엄격히 설정하고, 캠페인 종료 시 자동으로 비활성화되도록 만료일을 설정한다. 오래된 태그가 해변의 쓰레기처럼 쌓여 성능을 좀 먹는 상황을 예방한다.
네트워크와 서버, CDN의 역할
모바일에서 RTT는 여전히 비싸다. HTTP/2나 HTTP/3로 전환하고, TLS 1.3을 기본으로 사용하자. 오리진까지의 거리가 멀면 CDN 캐시를 공격적으로 활용한다. HTML은 짧은 TTL과 Stale-While-Revalidate를 조합하면, 사용자에게는 빠르게 응답하고 백그라운드에서 신선도를 맞춘다. 이미지와 정적 자산은 장기 캐시를 붙이되 파일명에 해시를 포함해 캐시 무효화를 명확히 한다. 엣지에서 이미지 리사이즈를 처리하는 서비스는 LCP 개선에 큰 도움을 준다. 다만 트래픽이 큰 서비스는 변환 비용을 계산해 egress와 맞바꾸는 구조를 신중히 설계해야 한다.
서버 응답의 압축은 Brotli를 우선한다. 텍스트 자산에서 Gzip 대비 10에서 20% 추가 절감이 흔하다. 압축 레벨은 5에서 7 사이에서 타협하면 CPU와 압축률의 균형이 맞는다. 이미지나 비디오는 이미 압축되어 있으므로 다시 압축하지 않는다. API 응답은 불필요한 필드를 줄이고, 숫자를 문자열로 보내는 관행을 정리한다. 페이징과 조건을 조정해 쓸데없는 데이터가 네트워크를 채우지 않도록 한다.
접근성과 SEO, 그리고 코어 웹 바이탈의 접점
코어 웹 바이탈은 사용자 경험의 최소선이자, 접근성과 SEO의 공통 분모다. 적절한 문서 구조, 의미 있는 시맨틱 태그, 명확한 포커스 상태, 키보드 접근성은 종종 성능에도 긍정적인 영향을 준다. 불필요한 aria 속성과 과한 DOM 깊이는 렌더링 복잡도만 높인다. 헤딩 구조를 바로잡고, 링크와 버튼의 역할을 정확히 구분하면 스크린리더뿐 아니라 크롤러도 페이지를 더 정확히 이해한다. 모바일 검색 결과에서 리치 요소가 잘 노출되는지도 확인한다. 구조화 데이터는 가시성과 클릭률을 높이는 데 도움이 된다. SNS 공유, 오픈그래프 이미지도 LCP 대상이 되지 않도록 사이즈와 포맷을 조정한다.
팀과 프로세스가 만든다
성능 개선은 일회성 프로젝트가 아니라 문화다. 디자인, 프론트엔드, 백엔드, 마케팅이 같은 숫자를 본다. 코어 웹 바이탈의 목표치와 현재 p75 값을 위젯으로 공유하고, 주간 스탠드업에서 지표 변화를 짧게라도 짚는다. 배포 체크리스트에 성능 리스크 항목을 추가한다. 이번 배포에 새로운 폰트가 추가되는가, 이미지 포맷 정책을 바꾸는가, 서드파티가 들어가는가. 이 세 가지 질문만 습관화해도 큰 사고를 대부분 피할 수 있다.
신규 기능은 조금 느리게 나가도 된다. 대신 초기부터 성능 예산을 잡고, 그 예산 안에서 구현한다. 페이지당 자바스크립트 150KB, 초기 CSS 14KB, 폰트 2개, LCP 2.5초, 이런 식의 구체적인 숫자가 중요하다. 예산을 어길 경우 기능을 쪼개거나 대안을 찾는다. 기획서에 성능 섹션을 추가하면 퀄리티가 전반적으로 올라간다.
간단한 현장 체크리스트
- 모바일 뷰의 LCP 대상 요소가 무엇인지, 이미지인지 텍스트인지 식별하고 eager 로딩과 fetchpriority를 올바르게 적용했는가 INP 상위 문제 상호작용 세 가지를 정리하고, 긴 태스크 분할과 서드파티 격리를 적용했는가 CLS를 유발하는 이미지, 광고, 폰트, 무한스크롤의 placeholder가 모두 공간을 예약하고 있는가 서버, CDN, 브라우저 캐시 정책이 역할에 맞게 설정되어 있는가, HTML은 SWR, 정적 자산은 해시 기반 장기 캐시인가 RUM 수집이 p75 기준으로 대시보드화되어 있고, 기기/네트워크 별 분포를 분리해서 볼 수 있는가
사례에서 얻은 교훈
한 미디어 사이트에서 모바일 트래픽이 70%에 육박했지만 LCP가 꾸준히 3초대를 맴돌았다. 히어로 이미지는 WebP로 바꾸었고, lazy 로딩도 적용되어 있었다. 그래도 개선이 막혔다. 원인은 의외로 서버 측 include로 합쳐지는 헤더 메뉴였다. 메뉴를 그리기 위해 API를 한 번 더 호출했고, 그 결과 TTFB가 400ms 늘어났다. 헤더를 캐시 가능한 JSON으로 분리하고, 페이지 빌드 단계에서 병렬 요청으로 합쳐 내려주자 LCP는 2.1초까지 떨어졌다. 이미지가 아니라 HTML 경로에서의 작은 막힘이 전체를 늦춘 사례다.
반대로 SEO해킹 커머스 앱의 웹뷰에서는 LCP는 충분히 빠른데 전환이 낮았다. INP가 원인이었다. 장바구니 버튼이 화면 하단에 고정되어 있었는데, 스크롤 이벤트와 리사이즈가 동시에 발생하며 레이아웃 계산이 중첩됐다. 변동 폭을 줄이고 스로틀링을 16ms로 맞춘 뒤, 클릭 핸들러에서 검증 로직을 웹워커로 분리하자 INP가 절반 이하로 줄었다. 사용자 반응은 즉각적이었고, 전환율이 7에서 9% 사이로 상승했다. 숫자 변화는 미미해 보이지만 매출로 환산하면 큰 차이다.
변화하는 기준에 대응하는 방법
구글은 지표를 가끔 바꾼다. FID가 INP로 대체된 것처럼, 정의가 바뀌면 최적화 중심도 달라진다. 과거에는 페인트 시점과 정적 자산 압축이 중심이었다면, 이제는 상호작용 품질과 프레임 안정성이 핵심이다. 팀의 대응력은 실험과 관측의 민첩성에서 나온다. 모듈형 계측, 비침투형 실험, 프록시 수준의 캐시 제어는 지표 변화에 흔들리지 않는 기반을 제공한다. 바뀌지 않는 원칙도 있다. 사용자가 보게 될 것을 먼저, 작게, 안정적으로 제공한다. 가치가 있는 상호작용에는 자원을 몰아준다. 나머지는 천천히, 필요할 때만 제공한다.
마무리 생각
모바일 퍼스트 SEO는 결국 인간 중심의 빠른 회복력에 관한 이야기다. 네트워크가 나쁠 때도 핵심 정보는 즉시 보이고, 손가락이 닿는 즉시 반응하며, 화면은 뜻밖에 흔들리지 않는다. 코어 웹 바이탈은 그 상태를 측정하는 최소 단위의 규칙이다. 지표를 외우는 것보다, 조직이 그 규칙을 자연스럽게 지키도록 만드는 설계와 습관이 더 중요하다. 작은 비효율을 줄이는 일이 모여 성능을 만들고, 성능이 쌓여 신뢰를 만든다. 그 신뢰가 결국 검색성과와 매출로 돌아온다. 모바일에서 먼저, 사용자가 체감하도록, 지표와 현실을 끈끈하게 연결하고 운영하자.