F-1: 웹 성능 기초 — 우리 사이트가 얼마나 빠른지 숫자로 재고, 어디를 고칠지 찾는다
목차 45
안녕하세요, 홍순구 튜터입니다. 지난 시간(E-3)에 우리는 Vite라는 빌드 도구로 흩어진 모듈을 하나로 묶고, 압축하고, 안 쓰는 코드까지 덜어낸 배포 묶음을 만들었어요. 그때 제가 마무리에서 한 가지를 흘렸죠. "오늘 만든 배포 묶음이 얼마나 빠른지를 숫자로 측정하기 시작해요. 화면이 얼마 만에 뜨는지(LCP), 클릭에 얼마나 빨리 반응하는지(INP) 같은 지표를 Lighthouse라는 도구로 재요"라고요.
오늘 그 약속을 회수해요. 지금까지 우리는 "이 코드를 저장하면 화면이 이렇게 변해요"를 눈으로 확인해 왔어요. 이게 이 과목의 가장 큰 장점이었죠. 그런데 "빠르다 / 느리다"는 눈으로만 보면 사람마다 말이 달라요. 누구는 "쓸 만한데?" 하고, 누구는 "답답해 죽겠네" 해요. 그래서 오늘부터는 느낌이 아니라 숫자로 이야기해요.
지금까지 (느낌) 오늘부터 (숫자)
┌───────────────────────────┐ ┌────────────────────────────┐
│ "음... 좀 느린 것 같은데?"│ │ LCP 3.1초 │
│ "내 폰에선 괜찮던데?" │ ──▶ │ INP 250ms │
│ → 사람마다 말이 다름 │ │ CLS 0.04 │
└───────────────────────────┘ │ → 누구나 같은 기준으로 대화│
└────────────────────────────┘
💡 오늘 수업의 핵심 — "추측하지 말고 측정한다. Lighthouse로 우리 인스타그램 피드의 성능 점수를 직접 받아 보고, LCP·INP·CLS 세 지표로 어디가 약한지 읽고, 브라우저가 화면을 그리는 과정을 들여다본 뒤, 무엇을 고치면 빨라지는지 찾아낸다." 🎯
🎯 학습 목표
- 웹 성능을 왜 재야 하는지, "빠르다"가 사실은 세 가지 다른 이야기라는 걸 이해합니다.
- Lighthouse를 열어 우리 피드의 성능 점수를 직접 측정하고, 점수가 환경마다 달라지는 이유를 압니다.
- Core Web Vitals(LCP·INP·CLS) 세 지표의 뜻과 "좋음" 기준을 알고, 랩 측정과 필드 측정의 차이를 이해합니다.
- 브라우저가 HTML·CSS·JS를 받아 화면을 그리기까지의 렌더링 파이프라인 5단계와, 첫 화면을 막는 Critical Rendering Path를 이해합니다.
- Lighthouse가 주는 개선 제안을 읽고, 우리 사이트에서 무엇을 이미 잘했고 무엇을 고쳐야 하는지 찾아냅니다.
Step 1: "빠르다"는 게 대체 뭔데?
성능 이야기를 본격적으로 하기 전에, 가장 헷갈리는 단어부터 정리하고 갈게요. 바로 "빠르다"예요. 이 말은 사실 한 가지가 아니라 세 가지 다른 질문이 뭉쳐 있는 말이에요.
빠르다는 세 가지 질문이다
여러분이 인스타그램을 연다고 상상해 보세요. 화면을 켠 순간부터 마음속으로 이런 평가를 해요.
① 화면이 언제 떠? ② 눌렀을 때 반응이 빨라? ③ 보는데 안 흔들려?
┌──────────────────┐ ┌───────────────────┐ ┌──────────────────┐
│ 켰는데 하얀 화면 │ │ 좋아요 눌렀는데 │ │ 글 읽는 중에 │
│ 만 한참... │ │ 한 박자 늦게 │ │ 갑자기 화면이 │
│ → 답답 │ │ 빨개짐 → 찝찝 │ │ 쑥 내려감 → 짜증 │
└──────────────────┘ └───────────────────┘ └──────────────────┘
로딩 속도 반응 속도 화면 안정성
첫째, 화면이 언제 뜨는가예요. 켰는데 하얀 화면만 오래 보이면 "이 사이트 죽었나?" 싶죠. 둘째, 눌렀을 때 빨리 반응하는가예요. 좋아요를 눌렀는데 하트가 한 박자 늦게 빨개지면 뭔가 찝찝해요. 셋째, 보는 도중에 화면이 안 흔들리는가예요. 글을 읽는데 위에서 이미지가 뒤늦게 떠서 글이 쑥 밀려 내려가면, 누르려던 버튼을 잘못 누르기도 해요.
이 세 가지는 서로 달라요. 화면은 빨리 뜨는데 클릭 반응이 느릴 수도 있고, 반대일 수도 있어요. 그래서 "우리 사이트 빨라요?"라는 질문엔 항상 "어떤 빠름이요?"라고 되물어야 해요. 오늘 우리가 잴 세 지표가 정확히 이 세 질문에 하나씩 답해요.
느린 사이트는 왜 손해일까
성능이 단순히 "기분 문제"라면 이렇게까지 신경 쓰지 않을 거예요. 그런데 느린 화면은 사람을 떠나게 해요. 화면이 뜨는 데 1초가 더 걸릴 때마다 떠나는 사람이 눈에 띄게 늘어요. 그리고 검색 엔진도 느린 사이트를 뒤로 미뤄요. 같은 내용이라면 빠른 사이트를 위에 보여주거든요. 즉 성능은 사용자 경험이면서 동시에 사람을 모으는 일과 직접 이어져요.
추측하지 말고 측정하자
여기서 가장 중요한 태도 하나를 짚을게요. 성능은 절대 눈대중으로 판단하지 않아요. 우리 개발자들의 컴퓨터는 대체로 좋고, 인터넷도 빨라요. 그래서 "내 화면에선 빠른데?"가 실제 사용자의 느린 폰을 전혀 대변하지 못해요.
지난 시간에 Vite로 번들 크기를 줄였죠. 그게 정말 사이트를 빠르게 만들었을까요? "그럴 것 같아요"는 답이 아니에요. 오늘 우리는 그걸 숫자로 확인할 거예요. 측정 도구를 켜서 점수를 받고, 그 점수가 좋아졌는지 나빠졌는지로 이야기해요. 자, 그럼 도구부터 켜 볼게요.
Step 2: 일단 한 번 돌려보자 — Lighthouse로 첫 점수 받기
이론은 잠깐 미뤄두고, 먼저 우리 피드의 점수부터 받아 봐요. 백문이 불여일견이니까요. 도구 이름은 Lighthouse(라이트하우스, 등대) 예요. 캄캄한 바다에서 배의 길을 비추는 등대처럼, 우리 사이트의 성능을 비춰 점수로 알려줘요. 크롬 개발자 도구 안에 이미 들어 있어서 따로 설치할 게 없어요.
측정 전 준비
우리 피드는 게시물을 가짜 API 서버(json-server)에서 받아와 그려요. 그러니 측정하기 전에 그 서버부터 켜야 해요. 안 켜면 게시물이 안 떠서, 진짜 화면이 아닌 빈 화면을 재게 돼요. 터미널 두 개를 준비하세요.
# 터미널 1: 가짜 API 서버 (게시물 데이터)
npm run mock
# 터미널 2: 우리 사이트 개발 서버
npm run dev
npm run dev가 알려주는 주소(보통 http://localhost:5173)로 feed.html을 브라우저에서 여세요.
⚠️ Lighthouse는 깨끗한 환경에서 재야 정확해요. 크롬 확장 프로그램(광고 차단기 같은 것)이 측정에 끼어들 수 있으니, 시크릿 창에서 열거나 확장을 잠시 꺼 두는 걸 권해요.
Lighthouse 돌리기
피드 화면을 띄운 상태에서, 키보드 F12(또는 마우스 오른쪽 → "검사")로 개발자 도구를 열어요. 위쪽 탭 줄에서 Lighthouse를 찾아 누르세요. 그러면 이런 패널이 나와요.
┌─ Lighthouse ────────────────────────────────────┐
│ Mode: ● Navigation (페이지 로드) │
│ Device: ● Mobile ○ Desktop │
│ Categories: │
│ ☑ Performance (성능 ← 오늘의 주인공) │
│ ☑ Accessibility (접근성) │
│ ☑ Best Practices(권장 사례) │
│ ☑ SEO (검색 최적화) │
│ │
│ [ Analyze page load ] │
└─────────────────────────────────────────────────┘
기본값 그대로 두고 "Analyze page load"(페이지 로드 분석) 버튼을 누르세요. 그러면 Lighthouse가 페이지를 처음부터 다시 불러오면서 몇 초간 측정해요. 잠시 기다리면 동그란 점수 네 개가 나와요.
점수를 어떻게 읽나
점수는 0~100점이고, 색으로 한눈에 보여줘요. 우리는 그중 Performance(성능) 에 집중해요.
🔴 빨강 (0~49) 🟡 노랑 (50~89) 🟢 초록 (90~100)
고쳐야 함 개선 여지 있음 좋음
지금 여러분의 점수가 몇 점인지는 제가 미리 못 알려드려요. 왜냐하면 점수는 환경마다 달라지거든요. 컴퓨터 성능, 인터넷 속도, 그 순간 외부 서버(우리는 이미지를 외부에서 받아와요)의 상태에 따라 같은 사이트도 점수가 출렁여요. 한 번은 82점, 다시 돌리면 78점, 이런 식이에요. 그러니 "몇 점이 나왔다"보다 "어느 지표가 빨갛더라" 가 훨씬 중요해요.
🙋 학생 질문 — "튜터님, 점수가 매번 달라지면 이걸 어떻게 믿어요?"
좋은 질문이에요. 점수 하나하나의 정확한 숫자에 집착하지 마세요. Lighthouse는 "성적표"라기보다 "건강 검진"에 가까워요. 검진 결과의 콜레스테롤 수치가 매번 1~2 차이 나도, "높은 편이다 / 정상이다"라는 큰 그림은 변하지 않잖아요. 마찬가지로 점수가 78이든 82든, "이 지표가 빨갛다 = 여기가 약점이다"라는 진단은 그대로예요. 우리가 볼 건 빨간 지표가 뭐고, 왜 빨간가예요. 그리고 같은 환경에서 고치기 전·후를 비교하면, 점수 출렁임 속에서도 좋아졌는지 나빠졌는지가 또렷이 보여요.
점수 동그라미 아래를 보면 Metrics(지표) 라는 영역에 LCP, CLS 같은 영어 약자들이 줄지어 있어요. 지금은 외계어처럼 보이죠? 다음 Step부터 이 약자들의 정체를 하나씩 밝힐게요.
Step 3: 3대 지표 큰 그림 — Google이 콕 집은 LCP·INP·CLS
방금 본 그 약자들은 아무거나 막 모아 놓은 게 아니에요. 구글이 "웹 페이지의 사용자 경험은 결국 이 세 가지로 압축된다"고 정해 둔 핵심 지표예요. 이름이 멋있어요. Core Web Vitals(코어 웹 바이탈), 우리말로 "핵심 웹 건강 지표"예요. 병원에서 맥박·혈압·체온을 핵심 활력 징후로 보듯, 웹 페이지의 활력을 이 셋으로 봐요.
세 지표, 한 줄 요약
기억하기 쉽게 Step 1의 세 질문과 짝지어 볼게요.
지표 뭘 재나 질문 좋음 기준
─────────────────────────────────────────────────────────────────
LCP 가장 큰 콘텐츠가 언제 떴나 ① 화면이 언제 떠? 2.5초 이하
INP 클릭에 얼마나 빨리 반응하나 ② 눌렀을 때 빨라? 200ms 이하
CLS 화면이 얼마나 흔들렸나 ③ 보는데 안 흔들려? 0.1 이하
- LCP(Largest Contentful Paint, 가장 큰 콘텐츠가 그려진 시각) — 화면에서 제일 큰 덩어리(보통 큰 이미지나 제목)가 떠오른 시점이에요.
- INP(Interaction to Next Paint, 상호작용에서 다음 그림까지) — 사용자가 누른 뒤 화면이 반응하기까지 걸린 시간이에요.
- CLS(Cumulative Layout Shift, 누적 레이아웃 이동) — 로딩 중에 요소들이 예상치 못하게 밀린 정도예요. 이것만 시간이 아니라 "흔들림 양"이라 단위가 없어요.
각 지표의 자세한 이야기는 Step 4·5·6에서 하나씩 풀게요. 지금은 큰 그림 하나만 더 잡고 가요. 아주 중요한 거예요.
랩에서 재기 vs 실제 사용자에게서 재기
방금 Lighthouse를 돌렸죠. 그런데 Lighthouse는 여러분 컴퓨터 한 대에서, 사람 없이 페이지를 한 번 불러본 거예요. 이렇게 깨끗한 실험실 환경에서 재는 걸 랩(lab) 측정이라고 해요. 장점은 언제든 똑같은 조건으로 다시 잴 수 있다는 거예요. 고치기 전·후를 비교하기에 딱이죠.
반대로, 전 세계 실제 사용자들의 진짜 폰에서 모은 데이터를 필드(field) 측정이라고 해요. 구글은 크롬 사용자들의 실제 성능 데이터를 모아 둬요. 이게 진짜 우리 사용자가 겪는 경험이에요.
랩(lab) 측정 필드(field) 측정
┌───────────────────────────┐ ┌────────────────────────────┐
│ 내 컴퓨터 한 대 │ │ 전 세계 실제 사용자 폰 │
│ 사람 없이 자동으로 한 번 │ │ 진짜로 클릭하고 스크롤 │
│ 언제든 똑같이 재현 가능 │ vs │ 진짜 경험이지만 모으기 느림│
│ → Lighthouse │ │ → 구글이 수집(CrUX) │
└───────────────────────────┘ └────────────────────────────┘
여기서 결정적인 사실 하나. INP는 랩에서 잴 수가 없어요. 왜냐하면 INP는 "사용자가 클릭했을 때 반응 속도"인데, Lighthouse는 사람이 클릭을 안 하거든요. 사진 한 장 찍듯 페이지 로드만 자동으로 보는 거라, 클릭이라는 행동 자체가 없어요. 그래서 Lighthouse 점수표엔 INP가 안 나와요. 대신 TBT(Total Blocking Time, 총 차단 시간)라는 대체 지표를 보여줘요. "지금 자바스크립트가 화면을 이만큼 붙잡고 있으니, 사용자가 클릭하면 이 정도로 버벅일 것 같다"를 미리 추정한 값이에요.
진짜 INP를 보고 싶으면 우리가 직접 클릭해서 재야 해요. 그 방법은 Step 5에서 다뤄요.
⚠️ 예전에는 클릭 반응 지표로 FID(First Input Delay)라는 걸 썼어요. 지금은 더 정확한 INP로 공식 교체됐어요. 오래된 글에서 FID를 보면 "아, 지금의 INP 이전 지표구나" 하고 넘기면 돼요. 오늘 우리는 INP만 봐요.
Step 4: LCP — 가장 큰 콘텐츠가 언제 떴나
이제 세 지표를 하나씩 깊이 볼게요. 첫 번째는 LCP, "가장 큰 콘텐츠가 그려진 시각"이에요. Step 1의 "화면이 언제 떠?"에 답하는 지표죠.
왜 하필 "가장 큰" 콘텐츠일까
화면이 뜨는 과정을 생각해 보세요. 처음엔 하얀 화면, 그다음 글자 몇 개, 그다음 작은 아이콘들, 마지막에 큰 사진이 쿵 하고 떠요. 사용자가 "아, 이제 화면이 떴다"고 느끼는 순간은 보통 그 큰 덩어리가 보일 때예요. 작은 아이콘 몇 개 떴다고 "다 떴네!" 하진 않잖아요.
그래서 LCP는 화면 안에서 가장 큰 콘텐츠 하나가 그려진 시점을 재요. 그게 사용자 체감에 가장 가깝거든요. 큰 이미지일 수도 있고, 큰 제목 글자 덩어리일 수도 있어요.
페이지 로드 타임라인
0초 ──────────────────────────────────────▶ 시간
│
├─ 하얀 화면
├─ 0.8초: 글자 조금, 스토리 아이콘 (작음)
├─ ...
└─ ★ 2.x초: 첫 게시물 600×600 사진 등장 ← 이게 LCP!
(화면에서 제일 큰 덩어리)
우리 피드의 LCP는 무엇일까
우리 인스타그램 피드를 떠올려 보세요. 위에 작은 스토리 동그라미들이 있고, 그 아래 첫 게시물의 큰 600×600 사진이 있어요. 화면에서 가장 큰 콘텐츠는 누가 봐도 그 첫 게시물 사진이에요. 그러니 우리 피드의 LCP는 보통 그 첫 사진이 뜨는 시점이에요.
그런데 우리 피드엔 작은 함정이 있어요. 그 첫 사진은 자바스크립트(feed.js)가 서버에서 게시물을 받아온 뒤에야 그려져요. 즉 "HTML 로드 → JS 실행 → 서버에서 데이터 받기 → 그제서야 사진 요청 → 사진 도착" 이라는 긴 줄을 거쳐요. 게다가 그 사진에는 이런 속성이 붙어 있어요.
// instagram-clone-frontend/js/post-card.js (발췌)
<img src="${post.image}" alt="${post.alt}" width="600" height="600" loading="lazy">
loading="lazy"는 "이 이미지는 급하지 않으니 천천히, 필요할 때 받아와"라는 뜻이에요. 화면 아래쪽 이미지엔 아주 좋은 설정이에요. 그런데 가장 큰 첫 사진(=LCP 후보)에 "천천히"를 붙이면 오히려 LCP가 늦어질 수 있어요. 빨리 보여줘야 할 주인공한테 "천천히 와도 돼"라고 한 셈이죠. 이건 다음 시간(F-2)에 제대로 고칠 거예요. 오늘은 "아, 여기가 개선할 곳이구나"를 발견하는 게 목표예요.
LCP 직접 확인하기
Lighthouse 결과의 Metrics 영역에 LCP 값이 초 단위로 나와 있어요. 좋음 기준은 이래요.
🟢 좋음 🟡 개선 필요 🔴 나쁨
2.5초 이하 2.5 ~ 4.0초 4.0초 초과
더 정확히 보고 싶으면, Lighthouse 결과에서 LCP 항목을 펼쳐 보세요. "어느 요소가 LCP였는지"를 직접 짚어 줘요. 정말 그 첫 게시물 사진이 LCP로 잡혔는지 눈으로 확인해 보세요.
🙋 학생 질문 — "우리 이미지는 외부(picsum.photos)에서 받아오던데, 그게 LCP에 영향을 주나요?"
크게 줘요. 우리 게시물 사진은 전부 외부 서비스(https://picsum.photos/...)에서 받아와요. 우리 손이 닿지 않는 남의 서버라, 그 서버가 그날따라 느리면 우리 LCP도 같이 느려져요. 그래서 LCP 값이 측정할 때마다 출렁이는 거예요. 실무에서는 이런 "가장 큰 첫 이미지"를 가능하면 내 서버에 두거나, 미리 받아오도록 손을 써요. 외부 의존이 곧 외부 변수라는 걸 LCP가 똑똑히 보여주는 셈이에요.
Step 5: INP — 눌렀을 때 얼마나 빨리 반응하나
두 번째 지표는 INP, "클릭에서 다음 화면 갱신까지의 시간"이에요. Step 1의 "눌렀을 때 빨라?"에 답해요. LCP가 "화면이 처음 뜨는 속도"라면, INP는 페이지가 뜬 뒤 사용자와 대화하는 속도예요.
반응이 느리다는 느낌의 정체
좋아요 버튼을 눌렀다고 해봐요. 누른 순간 하트가 즉시 빨개지면 "오, 반응 좋네"예요. 그런데 눌렀는데 0.3초쯤 멍하니 있다가 빨개지면, 그 짧은 틈에 "어? 안 눌렸나?" 하고 또 누르게 돼요. 이 "누름과 반응 사이의 멍한 틈"을 재는 게 INP예요.
클릭! ──────────────────────▶ 화면 반응
│ │
└──── 이 틈이 INP ───────────┘
짧을수록 좋음 (200ms 이하)
왜 이런 틈이 생길까요? 브라우저의 자바스크립트는 한 번에 한 가지 일만 해요. 만약 클릭한 순간에 다른 무거운 작업이 돌고 있으면, 그게 끝날 때까지 클릭 처리가 줄을 서서 기다려요. 그동안 화면은 멍하니 멈춰 있죠.
우리 피드의 인터랙션들
우리 피드에서 사용자가 누르는 것들을 떠올려 보세요. 다행히 대부분 아주 가벼워요. 예를 들어 좋아요 토글은 하트 클래스 하나 바꾸고 숫자 하나 고치는 게 전부예요.
// instagram-clone-frontend/js/like.js (발췌)
const likeBtn = article.querySelector(".icon-btn-like");
const liked = likeBtn.classList.toggle("is-active");
// ... 숫자만 갱신 ...
이런 가벼운 작업은 INP가 거의 즉시예요. 반면 댓글 입력칸 옆 😊 이모지 버튼은 처음 누를 때 살짝 달라요. 지난 시간에 우리는 이모지 고르개를 "필요할 때만 받아오도록" 동적 import()로 미뤄 뒀어요. 그래서 첫 클릭 때는 그 코드 조각을 받아오느라 아주 잠깐 틈이 생길 수 있어요. 두 번째부터는 이미 받아왔으니 즉시 열리고요. "미루기"가 첫 화면을 가볍게 해 주는 대신 첫 클릭에 작은 대가를 치르는, 트레이드오프의 좋은 예예요.
INP는 직접 클릭해서 재야 한다
Step 3에서 말했듯, Lighthouse 점수표엔 INP가 없어요. 사람이 클릭을 안 하니까요. 진짜 INP를 보려면 개발자 도구의 Performance(성능) 탭을 써요.
Performance 탭을 열면, 요즘 크롬은 화면 위쪽에 Live metrics(실시간 지표)를 보여줘요. 페이지에 떠 있는 LCP·CLS가 실시간으로 보이고, 여러분이 좋아요·댓글·이모지 버튼을 직접 클릭하면 그제서야 INP 값이 잡혀요. 마구 눌러 보세요. 가장 느렸던 반응이 INP로 기록돼요. 우리 사이트는 인터랙션이 가벼워서 대체로 초록일 거예요.
🟢 좋음 🟡 개선 필요 🔴 나쁨
200ms 이하 200 ~ 500ms 500ms 초과
그리고 Lighthouse 점수표에 INP 대신 보이던 TBT(총 차단 시간)를 기억하세요. TBT가 크다는 건 "자바스크립트가 화면을 오래 붙잡고 있다 = 클릭해도 버벅일 것 같다"는 랩에서의 경고예요. TBT가 작으면 실제 INP도 대체로 좋아요. 랩의 TBT와 필드의 INP가 이렇게 짝을 이뤄요.
Step 6: CLS — 화면이 덜컹거리지 않게
세 번째 지표는 CLS, "로딩 중에 화면이 흔들린 정도"예요. Step 1의 "보는데 안 흔들려?"에 답해요. 앞의 둘은 "시간"이었는데, 이건 "흔들림의 양"이라 단위가 없는 점수예요.
화면이 흔들리는 순간
글을 읽고 있는데, 위쪽에서 이미지가 뒤늦게 떡 하고 나타나면서 읽던 글이 아래로 쑥 밀린 경험 있죠? 또는 "구매" 버튼을 누르려는 순간 광고가 끼어들어 버튼이 내려가는 바람에 엉뚱한 걸 누른 적도요. 이 갑작스러운 밀림이 레이아웃 이동(Layout Shift) 이에요. 페이지가 뜨는 동안 이게 얼마나 자주, 얼마나 크게 일어났는지를 모두 더한 게 CLS예요.
❌ 흔들리는 화면 (CLS 나쁨) 🟢 안정된 화면 (CLS 좋음)
┌─────────────────┐ ┌─────────────────┐
│ 제목 │ │ 제목 │
│ 본문 읽는 중... │ 이미지 도착! │ ┌────────────┐ │ 이미지 자리를
│ ┌────────────┐ │ ──▶ 본문이 │ │ (이미지) │ │ 미리 비워 둠
│ │ (이미지) │ │ 쑥 밀림 😫 │ └────────────┘ │
│ └────────────┘ │ │ 본문 그대로 😊 │
└─────────────────┘ └─────────────────┘
우리 피드는 이미 잘하고 있어요
여기서 기쁜 소식. 우리 피드는 CLS가 아주 좋을 거예요. 왜냐하면 우리가 만든 모든 이미지에 width와 height를 미리 적어 뒀거든요.
<!-- instagram-clone-frontend/feed.html (발췌) -->
<img class="story-avatar" src="https://picsum.photos/seed/story1/120/120"
alt="jiwoo_log 스토리" width="56" height="56">
width="56" height="56"처럼 크기를 미리 적어 두면, 브라우저는 이미지가 도착하기 전부터 "여기에 56×56짜리 그림이 들어올 거야"라며 그 공간을 미리 비워 둬요. 그러니 나중에 이미지가 도착해도 주변이 밀리지 않아요. 게시물의 600×600 사진도 마찬가지로 크기를 적어 뒀어요. 우리가 그동안 무심코 적어 온 width/height가 사실은 성능을 지키는 든든한 습관이었던 거예요.
실험: 일부러 흔들리게 만들어 보기
말로만 들으면 와닿지 않으니 직접 깨뜨려 봐요. 개발자 도구 Elements(요소) 탭에서 게시물 이미지(<img ...>)를 하나 골라, width와 height 속성을 지워 보세요. 그리고 페이지를 새로고침해 보세요.
이제 이미지가 도착하기 전엔 높이가 0이었다가, 도착하는 순간 600px만큼 쑥 펼쳐지면서 아래 글들이 와르르 밀려 내려갈 거예요. 바로 이게 CLS가 나빠지는 순간이에요. 확인했으면 새로고침해서 원래대로(우리 코드엔 width/height가 있으니까) 되돌리세요. "직접 망가뜨려 보고 다시 고치기"만큼 확실한 공부가 없어요.
🟢 좋음 🟡 개선 필요 🔴 나쁨
0.1 이하 0.1 ~ 0.25 0.25 초과
Step 7: 렌더링 파이프라인 — 브라우저가 화면을 그리는 5단계
지금까지 "무엇을 재는가"(LCP·INP·CLS)를 배웠어요. 이제 한 걸음 안으로 들어가, 브라우저가 화면을 그리는 과정 자체를 들여다봐요. 이걸 알아야 "왜 느린지"를 이해하고 고칠 수 있거든요.
새 기술 전에: 화면은 한 번에 안 그려진다
우리는 브라우저에 HTML·CSS·JS를 건네줘요. 그런데 브라우저는 이걸 받아서 마법처럼 한 방에 화면을 띄우는 게 아니에요. 요리처럼 정해진 순서의 단계를 거쳐요. 이 과정을 렌더링 파이프라인(rendering pipeline) 이라고 해요. 큰 단계가 다섯 개예요.
① 파싱 ② 스타일 계산 ③ 레이아웃 ④ 페인트 ⑤ 합성
Parsing Style Layout Paint Composite
┌────────┐ ┌───────────┐ ┌───────────┐ ┌──────────┐ ┌───────────┐
│HTML→DOM│ │어떤 규칙이│ │어디에, │ │무슨 색, │ │겹친 층을 │
│CSS→ │──▶│어디에 │──▶│얼마 크기로│──▶│어떤 모양 │──▶│합쳐서 │
│CSSOM │ │적용되나 │ │ 놓이나 │ │ 으로 칠함│ │화면에 출력│
└────────┘ └───────────┘ └───────────┘ └──────────┘ └───────────┘
- ① 파싱(Parsing) — 글자로 된 HTML을 읽어 가계도(DOM) 로 만들고, CSS도 읽어 규칙 묶음(CSSOM)으로 만들어요. (DOM은 D-1에서 만난 그 가계도예요.)
- ② 스타일 계산(Style) — 각 요소에 어떤 CSS 규칙이 적용되는지 따져요. "이
<p>는 글자색 회색, 크기 0.9rem이구나" 하고 정해요. - ③ 레이아웃(Layout) — 각 요소가 화면의 어디에, 얼마만 한 크기로 놓일지 계산해요. 박스 모델(B-2), Flexbox(B-4), Grid(B-5)에서 배운 게 다 여기서 계산돼요.
- ④ 페인트(Paint) — 계산된 자리에 실제로 색·글자·테두리·그림자를 칠해요.
- ⑤ 합성(Composite) — 여러 겹(레이어)으로 칠한 것들을 차곡차곡 겹쳐서 최종 화면으로 내보내요.
🙋 학생 질문 — "합성(Composite)이 왜 따로 필요해요? 그냥 칠하면 되지 않나요?"
포토샵의 "레이어"를 떠올리면 쉬워요. 배경 레이어, 캐릭터 레이어, 글자 레이어를 따로 그려 두고 마지막에 포개서 한 장으로 만들죠. 브라우저도 비슷해요. 어떤 요소(특히 움직이는 것)는 별도 층으로 떼어 두면, 그 층만 살짝 옮겨도 나머지를 다시 안 그려도 돼요. B-7에서 애니메이션은 transform으로 하라고 했던 것 기억나요? transform은 바로 이 합성 단계만 건드려서 가볍게 움직이거든요. 반대로 너비·위치를 바꾸면 ③ 레이아웃부터 다시 계산해야 해서 무거워요. 그때 흘렸던 이야기가 이 파이프라인 위에서 정확히 맞아떨어지는 거예요.
Performance 탭으로 직접 들여다보기
이 단계들이 실제로 도는 모습을 눈으로 볼 수 있어요. 개발자 도구 Performance(성능) 탭을 열고, 녹화 버튼(●)을 누른 뒤 페이지를 새로고침하고, 잠시 뒤 멈추세요. 그러면 시간 순서대로 무슨 일이 있었는지가 길게 펼쳐져요.
요즘 크롬은 오른쪽에 Insights(인사이트) 사이드바를 함께 보여줘요. 예전엔 일일이 타임라인을 뒤져야 했던 것들 — "LCP가 왜 늦었나", "어떤 파일이 화면을 막았나" — 을 알아서 짚어 줘요. LCP 항목을 펼치면 그 안을 또 몇 단계로 쪼개 "서버 응답 기다림 / 이미지 받기 / 그리기" 중 어디서 시간을 까먹었는지까지 보여줘요. 우리 피드를 녹화해서, 시간을 가장 많이 잡아먹은 단계가 어디인지 한번 찾아보세요.
Step 8: Critical Rendering Path — 첫 화면을 막는 길목
파이프라인을 배웠으니, 이제 가장 중요한 질문에 답해요. "그래서 첫 화면을 빨리 띄우려면 뭘 신경 써야 하죠?" 이 첫 화면까지의 결정적 경로를 Critical Rendering Path(크리티컬 렌더링 패스, 중요 렌더링 경로) 라고 해요. 줄여서 CRP예요.
첫 픽셀이 뜨기까지 꼭 거치는 길
브라우저가 화면에 첫 그림을 그리려면, 최소한 이만큼은 끝나야 해요.
HTML 받기 ──▶ HTML 파싱(DOM) ─────────────┐
├──▶ 렌더 트리 ──▶ 레이아웃 ──▶ 페인트 ──▶ 첫 화면!
CSS 받기 ──────────▶ CSS 파싱(CSSOM) ──────┘
↑
이게 다 도착하고 읽힐 때까지
브라우저는 화면을 못 그리고 기다림 (render-blocking)
핵심은 이거예요. CSS는 화면 그리기를 막아요(render-blocking). 브라우저는 "스타일을 다 알기 전엔 화면을 그릴 수 없다"고 봐요. 색도 모르고 크기도 모르는 채로 그렸다간 곧바로 다시 그려야 하니까요. 그래서 CSS 파일을 다 받아서 읽을 때까지 첫 화면을 미뤄요.
우리 feed.html의 길목을 보자
우리 피드의 <head>를 보면, 이렇게 생겼어요.
<!-- instagram-clone-frontend/feed.html (발췌) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Grand+Hotel&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/layout.css">
<link rel="stylesheet" href="css/components.css">
<link rel="stylesheet" href="css/responsive.css">
CSS 파일이 여섯 개예요(reset·variables·base·layout·components·responsive). 거기에 외부 폰트까지요. 이게 다 화면 그리기를 막는 길목이에요. 브라우저는 이 여섯 개를 다 받고 읽을 때까지 첫 화면을 못 그려요. 파일이 흩어져 있을수록 왕복이 늘어 첫 화면이 늦어져요.
좋은 점도 보여요. 폰트 앞에 preconnect가 있죠? "이 외부 주소에 곧 연결할 거니 미리 악수해 둬"라는 귀띔이에요. 또 폰트 주소의 display=swap은 "폰트 받는 동안 글자를 숨기지 말고 기본 글꼴로 먼저 보여줘"라는 뜻이라, 글자가 안 보이는 시간을 줄여 줘요. 이미 잘 깔아 둔 장치들이에요.
지난 시간 번들이 바로 이걸 고친다
이제 E-3에서 한 일이 왜 성능과 직결되는지 또렷해져요. Vite로 npm run build를 하면, 흩어진 파일들을 하나로 묶고 압축해요. 여섯 번 왕복하던 걸 줄여 길목을 짧게 만드는 거예요. 정말 그런지 숫자로 확인해 봐요.
# 1) 빌드해서 배포용 묶음을 만들고
npm run build
# 2) 그 묶음을 띄운 뒤
npm run preview
npm run preview가 알려주는 주소로 feed.html을 열고, Step 2와 똑같이 Lighthouse를 다시 돌려 보세요. (json-server는 계속 켜 둬야 해요.) 개발용으로 띄웠을 때(npm run dev)와 빌드해서 띄웠을 때의 점수를 비교하는 거예요. 같은 컴퓨터·같은 환경에서 재면, 출렁임 속에서도 빌드한 쪽이 더 나은 경향이 보일 거예요. 지난 시간 번들 작업이 추측이 아니라 측정 가능한 개선이었다는 걸, 오늘 비로소 숫자로 확인하는 거예요.
💡 CRP를 한 문장으로: 첫 화면은 "HTML과 CSS가 다 도착해 읽힐 때까지" 기다린다. 그러니 화면을 막는 파일을 줄이고·합치고·가볍게 하는 게 첫 화면을 빠르게 하는 길이다.
Step 9: 개선 포인트 찾기 — Lighthouse가 알려주는 할 일 목록
마지막 Step이에요. 지금까지 측정하고 원리를 이해했으니, 이제 무엇을 고칠지 정리해요. 고맙게도 Lighthouse는 점수만 주고 끝나지 않아요. 점수 아래로 쭉 내려가면 "이걸 이렇게 고치면 빨라져요"라는 개선 제안과 진단 목록을 줘요.
Lighthouse 제안 읽는 법
Lighthouse 결과 아래쪽에는 개선 기회(절약 가능한 시간 추정치 포함)와 진단 항목들이 나와요. 요즘 크롬은 이걸 Insights 형태로 더 친절하게 묶어 보여주기도 해요. 각 항목을 펼치면 "어떤 파일이, 왜 문제이고, 얼마를 아낄 수 있는지"가 적혀 있어요. 우리 피드를 측정하면 대체로 이런 항목들이 보일 거예요.
우리 사이트에 나올 법한 제안 오늘 배운 어느 이야기?
───────────────────────────────────────────────────────────
· 화면을 막는 리소스(CSS·폰트)를 줄여라 → Step 8 (CRP, render-blocking)
· 차세대 이미지 포맷을 써라 → 다음 시간 (F-2)
· LCP 이미지를 우선 로드하라 → Step 4 (lazy 붙은 첫 사진)
· 폰트 표시 전략을 점검하라 → Step 8 (display=swap)
우리가 이미 잘한 것 vs 더 고칠 것
오늘의 진짜 수확은 우리 사이트의 건강 상태를 정확히 아는 거예요. 정리하면 이래요.
이미 잘하고 있는 것 — 이미지마다 width/height를 적어 화면 흔들림(CLS)을 막아 뒀고, 화면 아래 이미지엔 loading="lazy"로 급하지 않은 건 천천히 받게 했고, 지난 시간 Vite 번들링으로 흩어진 파일을 묶어 길목(CRP)을 줄였어요. 인터랙션도 가벼워서 클릭 반응(INP)이 좋아요.
더 고칠 것 — 가장 큰 첫 게시물 사진(LCP 후보)에 loading="lazy"가 붙어 있어 주인공이 늦게 떠요. 이미지가 전부 외부 서버에서 와서 LCP가 출렁이고요. 큰 이미지를 더 가벼운 포맷으로 바꿀 여지도 있어요. 이것들이 다음 시간의 숙제예요.
💡 측정의 목적은 점수 자랑이 아니라 할 일 목록을 얻는 것이에요. "어디가 약한지" 알았으니, 다음 시간엔 실제로 손을 대요.
마무리
오늘 우리는 "빠르다"라는 막연한 느낌을 측정 가능한 숫자로 바꾸는 법을 배웠어요.
- 세 가지 빠름: 화면이 뜨는 속도 / 클릭 반응 속도 / 화면 안정성 — 서로 다른 이야기예요.
- Lighthouse: 크롬에 내장된 성능 등대. 점수는 환경마다 출렁이니, 숫자보다 "어느 지표가 약한가"를 봐요.
- Core Web Vitals: LCP(가장 큰 콘텐츠, ≤2.5초) · INP(클릭 반응, ≤200ms) · CLS(화면 흔들림, ≤0.1).
- 랩 vs 필드: Lighthouse는 사람 없는 랩 측정이라 INP 대신 TBT를 보여줘요. 진짜 INP는 Performance 탭에서 직접 클릭해 재요.
- 렌더링 파이프라인: 파싱 → 스타일 계산 → 레이아웃 → 페인트 → 합성.
- Critical Rendering Path: 첫 화면은 HTML·CSS가 다 읽힐 때까지 기다려요. 막는 파일을 줄이고 합치는 게 핵심. 지난 시간 번들링이 바로 이걸 고쳤다는 걸 점수로 확인했어요.
- 개선 포인트: 우리가 이미 잘한 것(width/height·lazy·번들링)과 더 고칠 것(LCP 이미지·외부 이미지·포맷)을 찾아냈어요.
다음 시간 예고
다음 시간(F-2)부터는 오늘 찾아낸 할 일들을 실제로 고쳐요. 가장 큰 첫 이미지에는 loading="lazy" 대신 "먼저 받아 와"라고 일러 주고(우선 로드), 화면 밖 이미지는 더 똑똑하게 미루고, 무거운 이미지를 WebP·AVIF 같은 더 가벼운 포맷으로 바꿔요. 같은 사진을 훨씬 작은 용량으로요. 그리고 지난 시간 맛본 코드 스플리팅을 본격적으로 써서, 큰 코드 묶음을 더 잘게 쪼개 첫 화면을 가볍게 만들어요. 오늘 우리가 받은 점수가, 다음 시간 고친 뒤 얼마나 올라가는지 직접 비교해 볼 거예요.
과제
오늘은 코드를 새로 짜기보다 직접 재 보는 게 핵심이에요. json-server(npm run mock)를 켜 둔 채로 진행하세요.
[구현] 개발용 vs 빌드 결과, 점수로 비교하기
Step 8에서 한 비교를 직접 해 보고 기록하세요.
npm run dev로 띄운feed.html에서 Lighthouse를 3번 돌려 Performance 점수와 LCP 값을 적으세요.npm run build후npm run preview로 띄운 같은 페이지에서 또 3번 돌려 적으세요.- 두 묶음의 평균을 비교해 보세요. 빌드한 쪽이 나은가요? 한 번이 아니라 여러 번 재서 평균으로 보는 이유도 함께 적어 보세요.
[구현] CLS를 일부러 망가뜨렸다 되살리기
Step 6의 실험을 직접 해 보세요.
- 개발자 도구 Elements 탭에서 게시물 이미지의
width·height속성을 지우고 새로고침해, 화면이 밀리는 걸 확인하세요. - Performance 탭으로 그 상태를 녹화해 CLS 값이 올라가는지 보세요.
- 다시 새로고침해 원래대로(우리 코드엔 속성이 있으니) 돌아오는 걸 확인하세요. 무엇이 흔들림을 막아 줬는지 한 문장으로 정리하세요.
[탐구] 우리 LCP의 정체와 늦는 이유 밝히기
Lighthouse 결과에서 LCP 항목을 펼쳐, 어느 요소가 LCP로 잡혔는지 확인하세요.
- 정말 첫 게시물 사진이 맞나요? 그 사진이 화면에 늦게 뜨는 이유를 오늘 배운 것으로 설명해 보세요. (힌트: 자바스크립트가 언제 그 사진을 요청하는지, 그 사진에 어떤 속성이 붙어 있는지, 사진을 어디서 받아오는지)
- "그럼 이걸 어떻게 고치면 될까?"를 한두 줄로 상상해 적어 보세요. 다음 시간에 맞춰 볼 거예요.
생각해볼 주제
정답을 적는 문제가 아니에요. 오늘 배운 것의 "왜"를 곱씹어 보는 질문들이에요. 스스로 답을 만들어 본 뒤, 예시답안과 비교해 보세요.
1. Performance 100점이 정말 목표일까?
성능 점수가 100점이면 더할 나위 없이 좋아 보여요. 그런데 100점을 위해 화면을 너무 단순하게 만들거나, 큰 이미지를 다 빼 버린다면 어떨까요? 사용자가 보고 싶은 건 결국 그 사진들인데 말이죠. "빠르지만 볼 게 없는 사이트"와 "조금 느리지만 풍성한 사이트" 사이에서, 우리 인스타그램은 어디쯤에 서야 할까요? 점수와 사용자가 원하는 경험이 부딪힐 때 무엇을 우선해야 할지 생각해 보세요.
2. 내 컴퓨터에선 빠른데 사용자는 느리다고 하면, 누구 말이 맞을까?
여러분의 컴퓨터에서 Lighthouse를 돌리면 점수가 꽤 잘 나올 수 있어요. 그런데 실제 사용자는 "느려요"라고 불평해요. 둘 다 거짓말은 아니에요. 오늘 배운 랩 측정과 필드 측정의 차이를 떠올려 보세요. 개발자의 좋은 컴퓨터·빠른 인터넷에서 잰 값이 왜 실제 사용자를 대변하지 못할까요? 그렇다면 우리는 무엇을 더 믿고, 어떻게 실제 사용자에 가깝게 측정할 수 있을까요?
3. 측정할 때마다 점수가 달라진다면, 무엇을 믿어야 할까?
Lighthouse는 돌릴 때마다 점수가 조금씩 달라져요. 우리 이미지가 외부 서버에서 오는 탓도 크고요. 이렇게 흔들리는 측정값을 가지고 "좋아졌다 / 나빠졌다"를 어떻게 판단해야 할까요? 한 번 잰 값을 믿어야 할까요, 여러 번 잰 값을 믿어야 할까요? 고치기 전·후를 비교할 때, 측정의 출렁임에 속지 않으려면 어떤 습관이 필요할지 정리해 보세요.
✅ 예시 답안정답 보기
과제와 생각해볼 주제의 예시답안이에요. 오늘 과제는 코드를 새로 짜기보다 직접 재 보고 해석하는 게 핵심이에요. 정답 숫자가 정해져 있지 않아요 — 여러분 컴퓨터·인터넷·그날 외부 서버 상태에 따라 점수가 다르게 나와요. 중요한 건 측정을 제대로 했는가, 결과를 오늘 배운 개념으로 설명할 수 있는가 예요. 측정 내내 가짜 API 서버(
npm run mock)를 켜 두세요.
과제 예시답안
🎯 [과제 1 예시답안] 개발용 vs 빌드 결과, 점수로 비교하기
핵심 접근
지난 시간 Vite 번들링이 정말 사이트를 빠르게 했는지를 숫자로 확인하는 과제예요. 같은 페이지를 두 가지 방식으로 띄워 비교해요 — 개발용으로 그냥 띄운 것(npm run dev)과, 빌드해서 띄운 것(npm run preview). 핵심은 세 가지예요 — ①두 측정의 환경을 똑같이 맞출 것, ②한 번이 아니라 여러 번 재서 평균으로 볼 것, ③점수만이 아니라 LCP 값도 함께 적을 것.
측정 절차
먼저 가짜 API 서버를 켜 두고 시작해요. 이게 꺼져 있으면 게시물이 안 떠서 엉뚱한 빈 화면을 재게 돼요.
# 터미널 1: 가짜 API 서버 (계속 켜 둠)
npm run mock
개발용으로 띄워 재요.
# 터미널 2: 개발 서버
npm run dev
npm run dev가 알려주는 주소로 feed.html을 시크릿 창에서 열고, Lighthouse를 3번 돌려 Performance 점수와 LCP 값을 적어요. 그다음 빌드해서 띄워 똑같이 재요.
# 같은 터미널 2: 빌드한 뒤 그 결과를 띄움
npm run build
npm run preview
npm run preview 주소로 feed.html을 열고, 다시 Lighthouse를 3번 돌려 적어요. 표로 정리하면 비교가 쉬워요.
1회 2회 3회 평균
─────────────────────────────────────
dev 점수
LCP
─────────────────────────────────────
preview 점수
LCP
예상 결과와 해석
대개 preview(빌드) 쪽이 점수가 조금 더 높고 LCP가 조금 더 빨라요. 빌드가 흩어진 파일을 묶고 압축해서 첫 화면을 막는 길목(CRP)을 줄였기 때문이에요. 다만 차이가 아주 크진 않을 수 있어요. 우리 사이트의 가장 큰 이미지는 외부 서버에서 오는데, 그건 빌드해도 그대로라서 LCP 출렁임이 남거든요. 그래서 한 번만 재면 우연히 dev가 더 잘 나오는 경우도 있어요. 여러 번 재서 평균으로 봐야 진짜 경향이 보이는 이유가 이거예요.
채점 포인트
| 포인트 | 설명 | 배점 가중 |
|---|---|---|
| 환경 통제 | 같은 페이지를 같은 조건(시크릿 창·json-server 켜둠)에서 쟀는가 | 상 |
| 여러 번 측정 | 한 번이 아니라 여러 번 재서 평균/추세로 비교했는가 | 상 |
| preview 사용 | dev가 아니라 build 결과를 preview로 띄워 비교했는가 |
상 |
| LCP 동반 기록 | 점수만이 아니라 LCP 값도 함께 적었는가 | 중 |
| 해석 | 차이가 작거나 출렁이는 이유를 외부 이미지·CRP로 설명했는가 | 중 |
흔한 실수
- json-server를 안 켜고 측정 — 게시물이 안 떠서 가장 큰 이미지가 없는 빈 화면을 재게 돼요. LCP가 비현실적으로 좋게 나와 비교가 무의미해져요.
- 한 번씩만 재고 결론 — 점수는 출렁여요. 한 번 잰 값으로 "빌드가 더 나쁘네?"라고 단정하면 안 돼요. 여러 번의 평균으로 봐야 해요.
dev끼리 비교 — 빌드 효과를 보려면 반드시preview(빌드 결과)와 비교해야 해요.dev는 묶지 않은 개발용이라 빌드의 이점이 안 들어가 있어요.
실무 개선 포인트 (심화)
- 실무에서는 사람이 매번 손으로 재지 않고, 코드를 올릴 때마다 자동으로 Lighthouse를 돌려 점수를 기록해 둬요. 그러면 "이번 변경으로 점수가 떨어졌다"를 바로 알아채고 되돌릴 수 있어요. 성능을 한 번 잘 맞춰 놓는 것보다, 떨어지는 걸 빨리 잡는 게 더 중요하거든요.
- 점수 하나의 절댓값보다 변화의 방향을 봐요. "지난주보다 LCP가 0.3초 늦어졌다" 같은 추세가 실무에선 더 쓸모 있어요. 그래서 측정값을 그때그때 버리지 않고 쌓아 둬요.
🎯 [과제 2 예시답안] CLS를 일부러 망가뜨렸다 되살리기
핵심 접근
우리 사이트의 CLS가 왜 좋은지를 직접 깨뜨려 봐서 이해하는 과제예요. 이미지의 width·height를 지우면 화면이 흔들리고, 되돌리면 안정돼요. 핵심은 "크기를 미리 적어 두면 브라우저가 자리를 먼저 비워 둔다"를 눈으로 확인하는 거예요.
관찰 절차
feed.html을 열고(json-server 켜 둔 채로), 게시물이 떠 있는 상태에서 개발자 도구 → Elements(요소) 탭을 열어요.- 게시물 이미지(
<img ... width="600" height="600" ...>)를 찾아,width와height속성을 지워요. - 페이지를 새로고침해요. 이미지가 도착하기 전엔 높이가 0이었다가, 도착하는 순간 600px만큼 쑥 펼쳐지면서 아래 글들이 와르르 밀려 내려가는 게 보여요.
- 이번엔 개발자 도구 Performance(성능) 탭에서 녹화(●)를 켜고 새로고침한 뒤 멈춰, CLS 값이 올라가 있는지(빨개졌는지) 확인해요.
- 다시 새로고침하면 우리 원래 코드(
post-card.js엔width/height가 있으니)대로 돌아와 흔들림이 사라져요.
무엇이 흔들림을 막았나
한 문장으로 정리하면 이래요 — 이미지에 width·height를 미리 적어 두면, 브라우저가 그림이 도착하기 전부터 그 크기만큼 공간을 비워 두기 때문에, 나중에 이미지가 도착해도 주변이 밀리지 않는다. 크기를 안 적으면 브라우저는 그림이 올 때까지 높이를 0으로 두다가, 도착하는 순간 갑자기 펼쳐서 주변을 밀어내요. 그게 CLS가 나빠지는 순간이에요.
채점 포인트
| 포인트 | 설명 | 배점 가중 |
|---|---|---|
| 흔들림 재현 | width/height를 지워 화면이 밀리는 걸 직접 봤는가 |
상 |
| CLS 수치 확인 | Performance 탭에서 CLS 값이 올라가는 걸 확인했는가 | 상 |
| 원인 설명 | 크기를 미리 적으면 자리를 비워 둔다는 원리를 설명했는가 | 상 |
| 되돌리기 | 원래대로(속성 복구) 돌아오는 걸 확인했는가 | 중 |
흔한 실수
- CSS로 크기를 줬으니 괜찮다고 생각 — CSS로 크기를 주더라도, CSS는 받아서 읽기 전까진 적용이 안 돼요. HTML 속성
width/height는 브라우저가 그림 받기 전부터 비율을 알아 자리를 잡아 줘서 더 확실해요. 둘을 함께 쓰는 게 가장 좋아요. - 흔들림만 보고 끝 — 화면이 밀리는 것까지만 보고 마치기 쉬워요. CLS 값이 실제로 올라가는 숫자까지 확인하고, 되돌려서 다시 좋아지는 것까지 봐야 완성이에요.
- 수정한 걸 안 되돌림 — Elements 탭에서 지운 속성은 새로고침하면 원래 코드로 복구돼요(코드 자체를 고친 게 아니니까요). 혹시 파일을 직접 고쳤다면 꼭 되돌리세요.
실무 개선 포인트 (심화)
- CLS를 만드는 흔한 범인은 이미지 말고도 많아요. 뒤늦게 끼어드는 광고, 늦게 도착하는 폰트로 글자 모양이 바뀌는 것, 자바스크립트가 화면 위쪽에 뭔가를 꽂아 넣는 것 등이에요. 공통 해법은 같아요 — 들어올 자리를 미리 비워 두기예요.
- 우리 피드는 게시물을 자바스크립트로 그려 넣죠. 그런 동적 콘텐츠도 "들어올 영역의 최소 높이"를 미리 잡아 두면 흔들림을 줄일 수 있어요. 자리를 먼저 잡고 내용을 채우는 순서가 안정된 화면의 비결이에요.
🎯 [과제 3 예시답안] 우리 LCP의 정체와 늦는 이유 밝히기
핵심 접근
우리 피드에서 "가장 큰 콘텐츠(LCP)"가 정확히 무엇인지 도구로 확인하고, 그게 왜 늦게 뜨는지를 오늘 배운 것으로 설명하는 탐구 과제예요. 핵심은 LCP 요소를 직접 짚고, 늦는 이유 세 가지를 코드와 연결하는 거예요.
분석 절차와 결과
feed.html에서 Lighthouse를 돌리고, 결과의 LCP 항목을 펼쳐요. "Largest Contentful Paint element"라며 어느 요소가 LCP였는지 직접 짚어 줘요.- 대개 첫 게시물의 600×600 사진이 잡혀 있을 거예요. 화면에서 제일 큰 덩어리니까요. (스토리 동그라미들은 작아서 후보가 안 돼요.)
- 그 사진이 늦게 뜨는 이유는 세 가지가 겹쳐 있어요.
첫 게시물 사진이 늦게 뜨는 이유
① JS 렌더 타이밍 — HTML만으론 안 그려짐.
feed.js 가 서버에서 데이터를 받아온 뒤에야 사진을 요청함
(HTML → JS 실행 → 서버 응답 → 그제서야 사진 요청)
② loading="lazy" — 가장 큰 첫 사진에 "천천히"가 붙어 있음
(주인공한테 천천히 와도 된다고 한 셈)
③ 외부 이미지 — picsum.photos 라는 남의 서버에서 받아옴
(그 서버가 느리면 우리 LCP 도 같이 느려짐)
그럼 어떻게 고칠까 (다음 시간 미리보기)
세 가지 원인에 하나씩 답을 상상해 보면 이래요.
- ②에 대해 — 가장 큰 첫 사진에서는
loading="lazy"를 빼고, 오히려 "이건 중요하니 먼저 받아 와"라고 일러 줘요. 화면 아래 사진들만lazy로 두고요. - ③에 대해 — 정말 중요한 첫 이미지는 우리 서버에 두거나, 미리 받아오도록(preload) 손을 써요.
- 그리고 무거운 이미지는 더 가벼운 포맷(WebP·AVIF)으로 바꿔 받는 양 자체를 줄여요.
이건 다음 시간(F-2)에 실제로 고치면서 점수가 오르는 걸 확인할 거예요. 오늘은 "여기가 문제다"를 정확히 짚은 것만으로 충분해요.
채점 포인트
| 포인트 | 설명 | 배점 가중 |
|---|---|---|
| LCP 요소 확인 | Lighthouse로 LCP가 첫 게시물 사진임을 직접 확인했는가 | 상 |
| 늦는 이유 설명 | JS 렌더·loading="lazy"·외부 이미지 중 둘 이상을 짚었는가 |
상 |
| 코드 연결 | 이유를 feed.js·post-card.js의 실제 동작과 연결했는가 |
중 |
| 개선 상상 | "어떻게 고칠지"를 한두 줄로 합리적으로 적었는가 | 중 |
흔한 실수
- 스토리 동그라미를 LCP로 착각 — 여러 개라 커 보이지만 하나하나는 작아요. LCP는 "가장 큰 콘텐츠 하나"라 보통 첫 게시물 사진이에요. 도구가 짚어 주는 요소를 믿으세요.
loading="lazy"는 무조건 좋다고 생각 — 화면 밖 이미지엔 좋지만, 가장 큰 첫 사진(LCP)에 붙이면 오히려 손해예요. "어디에 쓰는가"가 핵심이에요.- 늦는 이유를 하나로만 봄 — 우리 경우엔 세 원인이 겹쳐 있어요. 하나만 고쳐선 크게 안 빨라질 수 있다는 것도 함께 이해하면 좋아요.
실무 개선 포인트 (심화)
- 가장 큰 첫 이미지를 빨리 띄우는 건 LCP에서 가장 효과 큰 작업이에요. 그래서 실무에선 첫 화면에 보이는 핵심 이미지에 "우선 받아 와"라는 표시를 명확히 달아요. 반대로 화면 밖의 것은 확실히 미루고요. 같은
lazy한 가지도 자리에 따라 약이 되고 독이 돼요. - 더 근본적으로는, 첫 게시물을 자바스크립트로 그리지 않고 처음부터 HTML에 담아 보내는 방법도 있어요. 그러면 JS 실행과 서버 응답을 기다리지 않고 곧바로 사진을 받기 시작할 수 있거든요. "무엇을 처음부터 보내고, 무엇을 나중에 그릴지"가 성능 설계의 큰 갈림길이에요.
생각해볼 주제 예시답안
1. Performance 100점이 정말 목표일까?
[문제 상황 요약]
성능 점수 100점은 더할 나위 없이 좋아 보여요. 그런데 100점을 위해 화면을 너무 단순하게 만들거나 큰 이미지를 다 빼 버린다면, 정작 사용자가 보고 싶은 사진이 사라져요. 점수와 사용자가 원하는 경험이 부딪힐 때 무엇을 우선해야 할까요?
[튜터의 가이드 및 해설]
핵심은 "점수는 목표가 아니라 수단" 이라는 거예요. 점수는 사용자 경험을 대신 재 주는 도구일 뿐, 점수 자체가 우리 사이트의 가치는 아니에요.
극단적으로 생각해 보면 분명해져요. 사진을 한 장도 안 띄우는 텅 빈 페이지는 100점이 나와요. 하지만 그건 아무도 안 쓰는 사이트죠. 인스타그램은 사진을 보러 오는 곳이니, 사진을 빼서 점수를 올리는 건 본말이 뒤바뀐 거예요.
그래서 현실적인 기준은 100점이 아니라 "좋음" 기준을 통과하는 것이에요. LCP 2.5초 이하, INP 200ms 이하, CLS 0.1 이하를 넘기면, 사용자는 충분히 빠르다고 느껴요. 거기서 더 짜내는 노력은 들이는 품에 비해 사용자가 체감하는 차이가 작아요. 그 노력을 차라리 더 좋은 사진, 더 편한 기능에 쓰는 게 나아요.
현업에서는 이렇게 정리해요 — "충분히 빠르면서, 보여줄 게 풍성한" 균형을 찾아요. 큰 이미지를 빼는 대신 더 가볍게 만들고(포맷·크기 조절), 무거운 기능은 나중에 불러와요. 점수를 위해 경험을 깎는 게 아니라, 경험을 지키면서 점수를 올리는 방법을 찾는 거예요.
🎯 면접관을 홀리는 핵심 멘트
"성능 점수는 목표가 아니라 사용자 경험을 대신 재는 수단이에요. 사진을 다 빼면 100점이 나오지만 아무도 안 쓰는 사이트가 되죠. 그래서 100점이 아니라 Core Web Vitals의 '좋음' 기준(LCP 2.5초·INP 200ms·CLS 0.1) 통과를 현실적 목표로 잡고, 그 위로는 경험을 깎지 않는 선에서 개선해요. 경험을 지키면서 빠르게 만드는 게 핵심입니다."
2. 내 컴퓨터에선 빠른데 사용자는 느리다고 하면, 누구 말이 맞을까?
[문제 상황 요약]
내 컴퓨터에서 Lighthouse를 돌리면 점수가 잘 나오는데, 실제 사용자는 "느려요"라고 해요. 둘 다 거짓말은 아니에요. 개발자의 좋은 환경에서 잰 값이 왜 실제 사용자를 대변하지 못할까요? 무엇을 더 믿어야 할까요?
[튜터의 가이드 및 해설]
이건 오늘 배운 랩 측정과 필드 측정의 차이 그대로예요.
내 컴퓨터에서 잰 건 랩 측정이에요. 그런데 우리 개발자의 환경은 보통 좋아요 — 빠른 컴퓨터, 빠른 인터넷, 가까운 위치. 반면 실제 사용자는 몇 년 된 폰으로, 느린 모바일 데이터로, 신호가 약한 곳에서 들어와요. 그러니 같은 사이트도 사용자에겐 훨씬 느려요. 내 화면에서 빠른 건 나한테만 빠른 거예요.
그래서 진짜로 믿어야 할 건 필드 측정, 즉 실제 사용자들의 데이터예요. "내 화면에선 빠른데?"는 가장 빠지기 쉬운 함정이에요. 우리가 만든 걸 우리만의 좋은 환경에서 보면 늘 괜찮아 보이거든요.
그럼 어떻게 사용자에 가깝게 잴까요? 두 가지예요. 첫째, 느린 환경을 흉내 내요. Lighthouse는 기본적으로 느린 모바일·느린 네트워크를 가정하고 재는데(그래서 점수가 생각보다 낮게 나와요), 이걸 일부러 켜 두는 거예요. 둘째, 실제 사용자 데이터를 모아 봐요. 구글은 크롬 사용자들의 진짜 성능 데이터를 모으는데, 우리 사이트가 어느 정도 쌓이면 그걸로 실제 경험을 확인할 수 있어요.
🎯 면접관을 홀리는 핵심 멘트
"둘 다 사실이에요. 제 컴퓨터에서 잰 건 랩 측정인데, 개발자 환경은 빠른 기기·빠른 망이라 실제 사용자의 느린 폰·느린 네트워크를 대변하지 못해요. 그래서 더 믿어야 할 건 실제 사용자 데이터인 필드 측정이에요. 사용자에 가깝게 재려면 Lighthouse의 느린 환경 시뮬레이션을 켜고, 실사용자 데이터(CrUX 같은 필드 데이터)를 함께 봐요. '내 화면에선 빠른데'가 가장 위험한 말입니다."
3. 측정할 때마다 점수가 달라진다면, 무엇을 믿어야 할까?
[문제 상황 요약]
Lighthouse는 돌릴 때마다 점수가 조금씩 달라져요. 우리 이미지가 외부 서버에서 오는 탓도 크고요. 이렇게 흔들리는 값으로 "좋아졌다 / 나빠졌다"를 어떻게 판단할까요? 한 번 잰 값을 믿어야 할까요, 여러 번 잰 값을 믿어야 할까요?
[튜터의 가이드 및 해설]
핵심은 "한 번의 숫자가 아니라 여러 번의 추세를 본다" 예요.
점수가 출렁이는 건 정상이에요. 컴퓨터가 그 순간 다른 일을 하고 있었을 수도, 인터넷이 잠깐 느렸을 수도, 외부 이미지 서버가 그때 굼떴을 수도 있어요. 그래서 한 번 잰 78점, 다시 잰 84점 중 어느 하나가 "진짜"가 아니에요. 둘 다 출렁임 속의 한 점일 뿐이에요.
믿을 만한 값을 얻는 방법은 두 가지예요. 첫째, 여러 번 재서 가운데 값(또는 평균)을 봐요. 한 번의 운 좋은 점수나 운 나쁜 점수에 휘둘리지 않아요. 둘째, 고치기 전과 후를 같은 환경에서 비교해요. 절대 점수 "84점"보다, "고친 뒤 LCP가 평균 0.4초 빨라졌다"는 비교가 훨씬 믿을 만해요. 출렁임은 양쪽에 똑같이 끼니까, 차이를 보면 출렁임이 상쇄돼요.
그리고 출렁임을 줄이는 노력도 같이 해요. 측정할 땐 시크릿 창에서 확장을 끄고, 다른 무거운 프로그램을 닫고, 외부 의존(우리는 외부 이미지)을 줄여요. 변동 요인을 통제할수록 측정이 또렷해져요.
현업에서는 그래서 측정값 하나에 일희일비하지 않아요. 여러 번의 값을 쌓아 추세를 보고, "이번 변경이 추세를 올렸나 내렸나"로 판단해요. 측정의 목적은 완벽한 한 숫자가 아니라 방향을 아는 것이니까요.
🎯 면접관을 홀리는 핵심 멘트
"한 번의 점수는 출렁임 속 한 점일 뿐이라 믿지 않아요. 여러 번 재서 중앙값이나 평균을 보고, 고치기 전·후를 같은 환경에서 비교해 차이로 판단해요. 출렁임은 양쪽에 똑같이 끼니 차이를 보면 상쇄되거든요. 동시에 시크릿 창·확장 끄기·외부 의존 줄이기로 변동 요인을 통제하고요. 절대 점수가 아니라 추세와 방향을 보는 게 핵심입니다."