6개월 동안 Core Web Vitals를 추적한 한 팀의 이야기는 불편한 진실로 시작한다. Lighthouse 점수는 92~98점을 오갔고, 팀은 스스로 잘하고 있다고 믿었다. 그러다 PM이 자기 폰으로 필터를 탭했을 때 화면이 '멈추는 느낌'이 난다고 보고했다. 실제로 그랬다. 단지 팀의 측정 도구가 그걸 보지 못했을 뿐이다.
이 사례가 가리키는 핵심은 하나다. Lighthouse는 시뮬레이션이고, CrUX(Chrome User Experience Report)가 진짜 점수판이다. Google이 신경 쓰는 건 실제 기기, 실제 네트워크, 실제 사용자의 75번째 백분위 값이다. 그것도 28일 롤링 평균으로. 개발 머신에서 돌린 퍼포먼스 패널은 참고 자료일 뿐, 최종 심판이 아니다. AI가 생성한 코드든 사람이 손수 짠 코드든, 이 원칙은 동일하게 적용된다.
INP: 가장 많은 팀이 놓치고 있는 지표
2024년 3월 FID를 대체한 INP(Interaction to Next Paint)는 클릭 한 번의 딜레이가 아니라, 클릭에서 화면 업데이트까지의 전체 왕복을 측정한다. 현재 전체 사이트의 43%가 200ms 기준을 통과하지 못하고 있다. 앞서 언급한 팀의 사례에서 필터 탭 하나가 480ms를 기록한 이유는 번들 크기 문제가 아니었다. 하나의 setState가 40개 컴포넌트를 리렌더링하고 정렬을 재계산한 뒤에야 UI를 업데이트하는 아키텍처 문제였다.
해법은 scheduler.yield()를 활용한 태스크 분할이었다. '시각적 피드백 먼저, 나머지 연산은 그다음'으로 실행 순서를 쪼개는 것만으로 INP p75가 480ms에서 170ms로 떨어졌다. React의 useDeferredValue로 무거운 리스트 리렌더링을 백그라운드로 미루는 것도 유효했다. 더 놀라운 건 서드파티 애널리틱스 스크립트가 클릭마다 동기 JSON 직렬화를 돌리고 있었다는 사실이다. 웹 워커로 옮기자 INP가 추가로 40ms 줄었다. 팀은 그 스크립트가 문제인 줄 몰랐다.
INP가 아키텍처 문제인 이유는 여기에 있다. 이벤트 핸들러는 동기적으로 거의 아무것도 하지 않아야 한다. 필터링, 정렬, 로깅, 무거운 연산은 전부 yield하거나, defer하거나, 오프로드해야 한다. 이건 코드 최적화가 아니라 설계 관점의 변화다.
LCP와 CLS: 단순하지만 자주 틀리는 곳
LCP(Largest Contentful Paint)는 대부분 히어로 이미지 문제다. 하지만 '이미지 최적화'를 했는데도 LCP가 3초를 넘는다면, 최적화 대상이 틀렸을 가능성이 높다. web-vitals 라이브러리로 실제 LCP 엘리먼트를 콘솔에 찍어보면 거의 항상 특정 이미지 하나가 범인이다. 그 이미지에 <link rel="preload">, fetchpriority="high", 올바른 srcset을 적용하고, 결정적으로 loading="lazy"를 제거하는 것—이 네 가지가 LCP 개선 플레이북의 전부다. 2026년에도 히어로 이미지에 lazy loading을 달아 배포하는 프로덕션 사이트가 있다는 사실이 이 플레이북이 여전히 유효한 이유다.
CLS(Cumulative Layout Shift)는 세 가지 규칙만 지키면 된다. 이미지·비디오·아이프레임에 명시적 width/height, 늦게 삽입되는 요소(광고 슬롯, 쿠키 배너)에 사전 공간 확보, 그리고 폴백 폰트와 웹폰트의 메트릭 불일치를 size-adjust로 해소하는 것. 이 팀은 CLS 0.02를 달성했다. 어렵지 않다. 다만 알아야 한다.
AI가 코드를 빠르게 짜는 시대, 성능 품질 판단은 누가 하는가
Claude Code 실무 활용 팁을 정리한 글에서 Anthropic의 Boris Cherny는 흥미로운 원칙을 제시한다. 코드 생성부터 시작하지 말고, 먼저 코드베이스에 질문하라는 것이다. Claude Code가 git 히스토리를 분석하고 왜 그 함수가 그 시그니처를 갖게 됐는지 설명하는 건 인상적이다. 하지만 그가 강조하는 핵심 패턴은 다르다. 계획을 먼저 받고, 검토하고, 승인한 뒤에 구현하게 하라. 피드백 루프를 만들어 AI가 스스로 반복 개선하게 하되, 최종 게이트는 사람이 쥐어야 한다.
이 원칙은 AWS·Anthropic 개발자 컨퍼런스에서 나온 Vibe Coding 논의와 맞닿아 있다. 오늘날 전체 커밋의 42%가 AI 지원으로 작성되지만, 개발자의 96%는 AI 결과물을 완전히 신뢰하지 않는다. CodeRabbit의 분석에 따르면 AI 생성 코드를 포함한 PR은 인간이 단독으로 작성한 코드보다 약 1.7배 더 많은 이슈를 가진다. 코드는 멀쩡해 보이고 해피 패스에선 잘 돌아간다. 문제는 엣지 케이스와 성능 경계에서 나중에 터진다.
컨퍼런스에서 제시된 핵심 프레임은 명쾌하다. "코드는 존재하지 않는다. 제품만 존재한다." 당신은 더 이상 모든 줄의 저자가 아니라, 결과물의 오너다. 컴파일러를 처음 도입했을 때 어셈블리 출력을 손으로 검증하려 했던 개발자들처럼, 이제 우리는 '모든 줄 이해'에서 '동작 검증'으로 검증의 추상화 레벨을 올려야 한다. 아키텍처와 데이터 흐름은 사람이 수호하고, 리프 노드 구현은 AI에게 맡기되, 인수 테스트와 스트레스 테스트로 동작을 확인하는 것이 현실적인 분업이다.
성능 품질은 도구가 아니라 판단에서 결정된다
Core Web Vitals 개선의 가장 큰 교훈은 측정 방법의 전환이다. 로컬 Lighthouse를 넘어 실사용자 데이터(CrUX, web-vitals SDK)를 라우트·기기 등급·국가별로 쪼개서 보기 시작했을 때 비로소 실제 문제가 보였다. AI 워크플로우도 마찬가지다. 생성 속도는 이미 AI가 해결했다. 지금 병목은 '내가 만든 것이 실제 사용자에게 어떻게 동작하는가'를 판단하는 능력이다.
AI가 코드를 빠르게 생성할수록, 성능 품질을 측정하고 해석하고 결정하는 사람의 역할은 더 선명해진다. 실측 INP가 480ms인 페이지를 Lighthouse 98점으로 착각하는 일은 AI가 코드를 짜기 전에도, 짠 이후에도 반복될 수 있다. 차이를 만드는 건 도구의 선택이 아니라, '터미널의 숫자가 아니라 실제 사용자의 75번째 백분위 값이 진짜 점수다'라는 판단을 팀이 내면화하고 있느냐다.