웹 성능 주요 지표와 실무 최적화 방법

2025. 10. 17. 02:22·Frontend/Perfomance Optimization

웹 성능을 제대로 이해하고 개선하려면 핵심 지표(Core Web Vitals)를 먼저 알아야 한다. Google에서 정의한 Core Web Vitals는 사용자가 실제로 느끼는 웹 경험을 정량적으로 측정할 수 있도록 설계되었다. 여기서는 FCP, LCP, CLS, TTI의 의미와 측정 방법, 그리고 실무에서 개선할 수 있는 전략을 자세히 살펴본다.

 

1. FCP (First Contentful Paint)

FCP는 사용자 화면에 처음으로 텍스트, 이미지, SVG 등 콘텐츠 요소가 렌더링되는 시점을 의미한다. 즉, 브라우저가 빈 화면에서 벗어나 “사이트가 살아 있다”는 신호를 주는 순간이다.

  • 권장 기준: 1.8초 이하
  • 중요성: 사용자는 화면에 무언가 뜨기를 기대하며, 이를 통해 첫인상이 결정된다.

예시: 사용자가 블로그 글을 클릭했을 때, 헤더나 제목이 화면에 나타나는 순간이 FCP다.

 

실제 서비스 환경용 Web Vitals 측정 코드 예시

// web-vitals-report.js
import { getCLS, getFID, getLCP, getFCP, getTTFB } from 'web-vitals';

// 성능 측정 결과를 서버로 전송하거나, 로그 시스템에 기록하는 함수
function sendToAnalytics(metric) {
  // 예시: 서버로 데이터 전송 (Google Analytics, Firebase, 혹은 자체 API)
  fetch('/api/web-vitals', {
    method: 'POST',
    body: JSON.stringify(metric),
    keepalive: true, // 페이지 언로드 중에도 전송 유지
    headers: { 'Content-Type': 'application/json' },
  });
}

// 각 Web Vital 측정값을 수집
export function reportWebVitals() {
  getCLS(sendToAnalytics);  // 누적 레이아웃 이동량 (Cumulative Layout Shift)
  getFID(sendToAnalytics);  // 첫 입력 지연 (First Input Delay)
  getLCP(sendToAnalytics);  // 가장 큰 콘텐츠 렌더링 시점 (Largest Contentful Paint)
  getFCP(sendToAnalytics);  // 첫 콘텐츠 렌더링 시점 (First Contentful Paint)
  getTTFB(sendToAnalytics); // 첫 바이트까지의 시간 (Time to First Byte)
}

크롬 Performance 탭에서도 동일한 결과를 확인할 수 있다.

 

2. LCP (Largest Contentful Paint)

LCP는 시각적으로 가장 큰 콘텐츠 요소(이미지, 텍스트 블록 등)가 화면에 렌더링 완료되는 시점을 의미한다. 페이지의 핵심 콘텐츠가 언제 사용자에게 표시되는지를 측정하며, UX 평가에 중요한 지표다.

  • 권장 기준: 2.5초 이하
  • 중요성: 페이지의 핵심 콘텐츠가 나타나는 속도가 빠를수록 사용자의 만족도가 높다.

예시: 뉴스 사이트에서 기사 제목과 썸네일 이미지가 완전히 나타나는 순간이 LCP다.

 

개선 전략:

1. 이미지 최적화(WebP, AVIF)

JPEG/PNG 대신 WebP나 AVIF 사용 → 이미지 용량 감소, 로딩 속도 향상

<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="메인 이미지">
</picture>

 

2. Critical CSS 적용

페이지 상단 콘텐츠에 필요한 최소 CSS만 인라인으로 삽입하고, 나머지 CSS는 비동기 로딩

<style>
  /* 상단 헤더, 타이틀 등 핵심 스타일만 인라인 적용 */
  header { display: flex; align-items: center; justify-content: space-between; }
  h1 { font-size: 2rem; margin: 0; }
</style>
<link rel="stylesheet" href="main.css" media="print" onload="this.media='all'">

3. 폰트 지연 로딩 제거

중요한 텍스트는 font-display: swap 사용.

swap은 웹 폰트가 아직 로드되지 않았을 때 시스템 폰트로 먼저 텍스트를 표시하고, 웹 폰트가 로드되면 이를 교체하는 방식이다. 이 과정에서 FOUT(Flash of Unstyled Text, 스타일 없는 텍스트 깜빡임)이 발생하는데, 이는 텍스트가 보이지만 처음에는 다른 폰트로 표시되었다가 교체되는 현상을 의미한다. 반면, FOIT(Flash of Invisible Text, 텍스트 안 보임)과 달리 사용자에게 빈 화면을 보여주지 않기 때문에 LCP 개선에 도움이 된다.

@font-face {
  font-family: 'Pretendard';
  src: url('/fonts/Pretendard.woff2') format('woff2');
  font-display: swap;
}

 

 

4. 이미지 로딩 최적화 (Lazy Load 제외, LCP 요소 우선 로딩)

LCP 요소(메인 이미지)는 loading="eager"로 즉시 로딩하고, 그 외 부수 이미지만 loading="lazy" 적용

<img src="hero.jpg" alt="메인 이미지" loading="eager">
<img src="thumbnail.jpg" alt="썸네일" loading="lazy">​

 

3. CLS (Cumulative Layout Shift)

CLS는 페이지 로딩 중 레이아웃이 얼마나 많이 이동했는지를 측정한다. 불필요한 레이아웃 이동은 UX에 큰 악영향을 끼친다.

  • 권장 기준: 0.1 이하
  • 중요성: 버튼 클릭이나 콘텐츠 스크롤 중 레이아웃이 밀리면 클릭 오류나 구매 오류를 유발할 수 있다.

예시: 텍스트를 읽고 있는데 갑자기 이미지가 로드되면서 글이 밀리는 현상.

개선 전략: 

1. 이미지와 미디어 영역에 크기 명시

  • HTML에서 <img width="600" height="400">처럼 이미지의 가로/세로 크기를 지정하거나, CSS aspect-ratio를 사용해 미리 공간을 확보한다.
  • 광고, 동영상 등 외부 콘텐츠도 로드 전에 고정된 영역을 설정하여 레이아웃 이동을 방지한다.

2. 폰트 교체 시 FOUT 대비

  • font-display: swap을 사용해 텍스트가 보이지 않는 FOIT 현상을 방지하고, FOUT으로 인한 CLS를 최소화한다.
  • 중요한 텍스트는 시스템 폰트를 우선 표시하고, 웹 폰트가 로드되면 자연스럽게 교체되도록 설정한다.

3. 동적 콘텐츠 삽입 시 공간 확보

  • 자바스크립트로 동적 요소를 추가할 때, 예상되는 크기만큼 미리 공간을 만들어 두어 레이아웃 이동을 줄인다.
  • 예: 댓글 섹션, 추천 상품 리스트 등.

이런 전략을 적용하면 CLS 점수를 개선하고, 사용자 경험을 안정적으로 유지할 수 있다.

 

4. TTI (Time To Interactive)

TTI는 페이지가 완전히 상호작용 가능한 상태가 되는 시간을 의미한다. 겉으로는 페이지가 보이지만, 버튼 클릭이나 스크롤이 동작하지 않으면 사용자 경험이 크게 저하된다.

  • 권장 기준: 5초 이하
  • 중요성: JavaScript 번들 로딩/파싱/실행 지연으로 인해 TTI가 늦어질 수 있다.

예시: 로그인 페이지가 표시되지만 버튼 클릭이 즉시 반응하지 않는 상황.

개선 전략:

1. 코드 스플리팅(Code Splitting)

목적: 초기 로딩 시 불필요하게 큰 JS 번들을 로딩하지 않고, 필요한 모듈만 로딩하여 페이지 상호작용 속도 향상

 

React 예시

import React, { Suspense } from 'react';

// 느리게 로드되는 무거운 컴포넌트 - 코드 스플리팅 + 지연로딩
// Suspense와 React.lazy를 함께 사용해서, 컴포넌트가 실제로 렌더링될 때까지 로드를 지연한다.
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <div>
      <h1>로그인 페이지</h1>      
      <Suspense fallback={<div>Loading...</div>}>
        <HeavyComponent />
      </Suspense>
      <button onClick={() => alert('로그인 클릭!')}>로그인</button>
    </div>
  );
}

 

초기 렌더링 시 HeavyComponent는 로딩되지 않아, 버튼 클릭 등 인터랙션이 빠르게 동작한다.

 

2. 지연 로딩(Lazy Loading)

목적: 화면에 바로 필요하지 않은 리소스(JS, 이미지 등)를 나중에 로드하여 TTI를 단축

 

이미지 지연 로딩 예시 - 이미지가 화면에 나타날 때까지 실제 src를 로드하지 않음

import React, { useEffect, useRef } from 'react';

function LazyImage({ src, alt }) {
  const imgRef = useRef();

  useEffect(() => {
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          entry.target.src = entry.target.dataset.src;
          observer.unobserve(entry.target);
        }
      });
    });
    observer.observe(imgRef.current);
    return () => observer.disconnect();
  }, []);

  return <img ref={imgRef} data-src={src} alt={alt} />;
}

export default function App() {
  return (
    <div>
      <h1>Lazy Loading 이미지 예제</h1>
      <LazyImage src="https://example.com/large-image.jpg" alt="예시 이미지" />
    </div>
  );
}

 

 

3. JS 최적화

목적: 불필요한 JS 실행, 번들 크기, DOM 처리 지연 최소화

// Debounce 사용으로 이벤트 처리 최적화
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

window.addEventListener('resize', debounce(() => {
  console.log('리사이즈 이벤트 처리');
}, 200));

 

이벤트 처리 부담을 줄여 페이지 초기 상호작용 속도 향상

 

4. 성능 지표가 중요한 상황

지표 중요한 상황
FCP 사용자에게 로딩 시작 피드백 제공, 이탈 방지
LCP 메인 콘텐츠를 빠르게 보여주는 페이지 (뉴스, 쇼핑몰 등)
CLS 정적 레이아웃 유지가 중요한 페이지 (결제, 양식)
TTI 상호작용이 많은 앱(SPA, 대시보드, 폼)에서 UX 품질 관리

 

5. 정리

  • FCP/LCP 개선: 이미지 최적화, Critical CSS, 폰트 지연 로딩 제거
  • CLS 개선: 이미지/광고 영역 크기 지정, 폰트 FOUT 대응
  • TTI 개선: 코드 스플리팅, Lazy Loading, JS 최적화

 

웹 성능 최적화는 사용자가 느끼는 속도와 경험을 기준으로 계획하는 것이 핵심이다. Core Web Vitals를 기반으로 성능을 측정하고, 필요한 전략을 단계적으로 적용하면 더 빠르고 안정적인 웹 경험을 제공할 수 있다.

'Frontend > Perfomance Optimization' 카테고리의 다른 글

Webpack에서 Vite까지: 프론트엔드 빌드 툴의 진화  (0) 2025.11.03
[프론트엔드 성능 최적화 가이드] 4장 이미지 갤러리 최적화  (0) 2023.03.24
[프론트엔드 성능 최적화 가이드] 3장 홈페이지 최적화  (0) 2023.03.20
[프론트엔드 성능 최적화 가이드] 2장 올림픽 통계 서비스 최적화  (0) 2023.03.20
[프론트엔드 성능 최적화 가이드] 1장 블로그 서비스 최적화  (0) 2023.02.02
'Frontend/Perfomance Optimization' 카테고리의 다른 글
  • Webpack에서 Vite까지: 프론트엔드 빌드 툴의 진화
  • [프론트엔드 성능 최적화 가이드] 4장 이미지 갤러리 최적화
  • [프론트엔드 성능 최적화 가이드] 3장 홈페이지 최적화
  • [프론트엔드 성능 최적화 가이드] 2장 올림픽 통계 서비스 최적화
JTB
JTB
웹/앱 개발 정보를 공유하고 있습니다.
  • JTB
    JTechBlog
    JTB
  • 전체
    오늘
    어제
    • All About Programming;)
      • Computer Science
        • Terminology and Concepts
        • Network
        • Operating System
        • Database
        • Data Structure
        • Web Development
      • Frontend
        • Javascript Essentials
        • Perfomance Optimization
        • JS Patterns
        • React
        • Next.js
        • Flutter
        • Testing
      • Backend
        • Node.js
      • DevOps
        • Docker & Kubernetes
      • Coding Test
        • LeetCode
        • Programmers
      • Tech Books & Lectures
        • Javascript_Modern JS Deep d..
        • Network_IT 엔지니어를 위한 네트워크 입문
      • Projects
        • PolyLingo_2025
        • Build Your Body_2024
        • JStargram_2021
        • Covid19 Tracker_2021
        • JPortfolio_2021
      • BootCamp_Codestates
        • TIL
        • TILookCloser
        • Pre Tech Blog
        • IM Tech Blog
        • Daily Issues and DeBugging
        • First Project
        • Final Project
        • Sprint Review
        • Good to Know
        • Socrative Review
        • HTML &amp; CSS
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 글쓰기
    • 관리
  • 공지사항

  • 인기 글

  • 태그

    프론트엔드 성능 최적화 가이드
    DOM
    structure of os
    polylingo
    Binary Tree BFS
    VoiceJournal
    자바스크립트 딥다이브
    Operating System
    Javascript Essentials
    database
    How memory manage data
    TCP/IP
    leetcode
    Network
    딥다이브
    커리어
    mobile app
    모던 자바스크립트 Deep Dive
    Threads and Multithreading
    need a database
    이벤트
    CPU scheduling algorithm
    스코프
    자바스크립트
    Time complexity and Space complexity
    Data Structure
    testing
    indie hacker
    js pattern
    Shared resources
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
JTB
웹 성능 주요 지표와 실무 최적화 방법
상단으로

티스토리툴바