리스트로 돌아가기
실무에서 바로 써먹는 웹사이트 성능 최적화 실전 가이드 thumbnail

실무에서 바로 써먹는 웹사이트 성능 최적화 실전 가이드

Core Web Vitals 최신 동향부터 Next.js와 Tailwind CSS에서 바로 적용할 수 있는 성능 최적화 기법까지, 실무에서 바로 써먹는 체크리스트를 정리했습니다.

2026-01-08 00:00

실무에서 바로 써먹는 웹 성능 최적화 실전 가이드

안녕하세요! 포테코입니다.

웹사이트를 만들 때 가장 중요한 것 중 하나는 바로 성능이에요. 사용자가 페이지를 열었을 때 몇 초나 기다려야 하는지, 스크롤할 때 버벅거림이 있는지, 이미지가 갑자기 튀어나와서 레이아웃이 깨지는지... 이런 모든 것이 웹 성능에 포함돼요.

이번 글에서는 최신 Core Web Vitals 기준프로젝트에서 바로 적용할 수 있는 실전 최적화 기법을 체크리스트 형태로 정리했어요. 이론보다는 실무에서 바로 써먹을 수 있는 팁과 코드 예시에 집중했습니다.

웹 성능 최적화는 한 번에 끝나는 작업이 아닙니다. 하지만 올바른 방법을 알고 체계적으로 접근하면, 사용자 경험과 SEO 모두 크게 개선할 수 있어요.

웹 성능... 정확히 뭐로 판단하는데?

웹 성능은 사용자 경험과 SEO에 직접적인 영향을 미쳐요. 특히 Google이 Core Web Vitals를 검색 랭킹 요소로 사용하면서, 성능 최적화는 선택이 아닌 필수가 되었어요.

단순히 "빠르다"는 느낌만으로는 부족해요. Google은 세 가지 핵심 지표로 웹 성능을 객관적으로 측정해요:

  • LCP (Largest Contentful Paint): 가장 큰 콘텐츠가 화면에 표시되는 시간
  • FID/INP (First Input Delay / Interaction to Next Paint): 사용자 상호작용에 대한 응답 시간
  • CLS (Cumulative Layout Shift): 레이아웃이 예상치 못하게 이동하는 정도

이 세 가지 지표가 모두 기준치를 만족해야 Google이 "좋은 사용자 경험"을 제공하는 사이트로 인정해요.

Core Web Vitals는 Google이 정의한 웹 페이지 사용자 경험의 핵심 지표예요. LCP, FID, CLS 세 가지 메트릭으로 구성되며, 검색 엔진 최적화(SEO)에도 영향을 미쳐요.

Core Web Vitals 이해하기

Core Web Vitals는 사용자 경험을 측정하는 세 가지 핵심 지표예요. 이름이 다소 어려워 보일 수 있지만, 실제로 우리가 매일 경험하는 것들을 숫자로 나타낸 것이라고 생각하시면 돼요.

지표설명좋은 기준측정 방법
LCP (Largest Contentful Paint)가장 큰 콘텐츠가 화면에 표시되는 시간2.5초 이하이미지, 비디오, 텍스트 블록
FID (First Input Delay)사용자 상호작용에 대한 응답 시간100ms 이하클릭, 탭, 키보드 입력
CLS (Cumulative Layout Shift)레이아웃 이동 정도0.1 이하예상치 못한 요소 이동

2025년부터는 FID 대신 INP(Interaction to Next Paint)를 사용해요. INP는 모든 상호작용을 종합적으로 측정하기 때문에 더 정확한 사용자 경험 지표예요.

각 지표별 최적화 전략

각 지표를 개선하는 방법을 정리했어요. 실제 프로젝트에 적용하면 성능 개선 효과를 바로 체감할 수 있어요.

LCP 개선 방법:

페이지가 빠르게 표시되도록 하는 것이 핵심이에요. 프로젝트에서 자주 사용하는 방법은 다음과 같아요:

  1. 서버 응답 시간 단축: Next.js 서버 컴포넌트를 적극 활용해요. 서버에서 미리 렌더링하면 클라이언트 부담이 줄어들어요
  2. 리소스 로딩 최적화: 이미지는 반드시 최적화하고, 폰트는 미리 로드해두는 것이 좋아요
  3. 렌더링 차단 리소스 제거: 사용하지 않는 CSS나 JavaScript는 제거해요. 번들 크기가 줄어들면 로딩 속도가 개선돼요
  4. CDN 활용: 정적 파일을 CDN에 배포하면 전 세계 어디서나 빠르게 제공할 수 있어요

INP 개선 방법:

사용자 상호작용에 즉시 반응하도록 최적화하는 것이 중요해요:

  1. JavaScript 실행 시간 단축: 무거운 코드는 코드 스플리팅과 동적 import를 활용해서 필요할 때만 로드해요
  2. 이벤트 핸들러 최적화: 스크롤이나 리사이즈 같은 이벤트는 디바운싱이나 쓰로틀링으로 제어해요
  3. 메인 스레드 블로킹 방지: 무거운 작업은 Web Worker로 분리해서 사용자 인터랙션이 끊기지 않도록 해요
  4. 긴 작업 분할: 한 번에 처리하기 어려운 작업은 requestIdleCallback을 활용해서 나눠서 처리해요

CLS 개선 방법:

레이아웃이 예상치 못하게 이동하는 것을 방지하는 것이 핵심이에요:

  1. 이미지/비디오 크기 지정: width와 height를 반드시 지정해요. 브라우저가 미리 공간을 예약할 수 있어요
  2. 폰트 로딩 최적화: font-display: swap을 사용하면 폰트가 로드되기 전에도 텍스트가 표시돼요
  3. 동적 콘텐츠 공간 예약: API에서 데이터를 받아오는 동안 스켈레톤 UI를 표시하면 레이아웃 시프트가 줄어들어요
  4. 애니메이션 최적화: transform이나 opacity만 사용하면 GPU 가속을 받아 부드럽게 동작해요

Next.js 성능 최적화 체크리스트

실제 프로젝트에서 바로 적용할 수 있는 최적화 방법들을 정리했어요. 하나씩 체크해보시면 돼요.

1. 이미지 최적화

이미지는 웹사이트에서 가장 무거운 리소스 중 하나예요. 제대로 최적화하면 성능이 확실히 개선돼요.

체크리스트:

  • Next.js Image 컴포넌트 사용 (일반 <img> 태그보다 훨씬 효율적이에요)
  • AVIF/WebP 포맷 사용 (JPEG보다 파일 크기가 작아요)
  • 적절한 sizes 속성 설정 (반응형 이미지를 위해 필수예요)
  • priority 속성으로 중요한 이미지 우선 로딩 (첫 화면 이미지에 적용)
  • loading="lazy"로 지연 로딩 적용 (화면에 보일 때만 로드)

실전 예시:

import Image from "next/image";
 
// ✅ 좋은 예
<Image
  src="/hero-image.jpg"
  alt="Hero Image"
  width={1200}
  height={630}
  priority // 첫 화면 이미지는 우선 로딩
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 1200px"
  quality={85}
  placeholder="blur" // 블러 플레이스홀더
/>
 
// ❌ 나쁜 예
<img src="/hero-image.jpg" alt="Hero Image" />

next.config.ts 설정:

const nextConfig: NextConfig = {
  images: {
    formats: ["image/avif", "image/webp"], // 최신 포맷 우선
    deviceSizes: [640, 750, 828, 1080, 1200, 1920],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 60, // 캐시 TTL 설정
  },
};

2. 폰트 최적화

폰트도 성능에 큰 영향을 줘요. 특히 한글 폰트는 용량이 커서 더 신경 써야 해요.

체크리스트:

  • next/font 사용 (Next.js가 자동으로 최적화해요)
  • display: "swap" 설정 (폰트 로딩 중에도 텍스트가 표시돼요)
  • 필요한 weight만 로드 (100~900 전체를 가져올 필요는 없어요)
  • preload로 중요 폰트 우선 로딩 (첫 화면에 보이는 폰트는 미리 로드)
  • variable로 CSS 변수 활용 (테마 전환 시 유용해요)

실전 예시:

import { Inter } from "next/font/google";
 
// ✅ 좋은 예
const inter = Inter({
  subsets: ["latin"],
  weight: ["400", "500", "600", "700"], // 필요한 weight만
  variable: "--font-inter",
  display: "swap", // FOIT 방지
  preload: true, // 우선 로딩
});
 
// ❌ 나쁜 예
// <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900" />

레이아웃에서 사용:

export default function RootLayout({ children }) {
  return (
    <html lang="ko" className={inter.variable}>
      <body className={inter.className}>
        {children}
      </body>
    </html>
  );
}

3. 번들 크기 최적화

JavaScript 파일이 크면 다운로드하는데 시간이 오래 걸려요. 특히 모바일 환경에서는 더 느려질 수 있어요.

체크리스트:

  • 동적 import로 코드 스플리팅 (필요한 것만 나중에 로드)
  • 서버 컴포넌트 기본 사용 (클라이언트 번들에 포함되지 않습니다)
  • 불필요한 의존성 제거 (사용하지 않는 라이브러리는 제거)
  • Tree-shaking 확인 (사용하지 않는 코드 자동 제거)
  • 번들 분석기로 크기 모니터링 (무엇이 무거운지 확인)

실전 예시:

// ✅ 동적 import로 코드 스플리팅
"use client";
 
import dynamic from "next/dynamic";
 
// 무거운 컴포넌트는 동적 로드
const HeavyChart = dynamic(() => import("@/components/HeavyChart"), {
  loading: () => <div>차트 로딩...</div>,
  ssr: false, // 서버 사이드 렌더링 불필요 시
});
 
export default function Dashboard() {
  return (
    <div>
      <h1>대시보드</h1>
      <HeavyChart />
    </div>
  );
}

번들 분석:

# @next/bundle-analyzer 설치
pnpm add -D @next/bundle-analyzer
 
# next.config.ts
const withBundleAnalyzer = require("@next/bundle-analyzer")({
  enabled: process.env.ANALYZE === "true",
});
 
module.exports = withBundleAnalyzer(nextConfig);
 
# 분석 실행
ANALYZE=true pnpm build

4. 서버 컴포넌트 활용

Next.js 13+ App Router의 가장 큰 장점 중 하나가 서버 컴포넌트예요. 클라이언트로 보내는 코드가 줄어들어서 성능이 개선돼요.

체크리스트:

  • 기본적으로 서버 컴포넌트 사용 (별도 설정 없이 기본값입니다)
  • 클라이언트 컴포넌트는 필요한 경우만 ("use client"는 꼭 필요한 곳에만 사용)
  • 데이터 페칭은 서버에서 (클라이언트에서 fetch할 필요가 없습니다)
  • API 라우트 최소화 (서버 컴포넌트에서 직접 데이터를 가져올 수 있습니다)

실전 예시:

// ✅ 서버 컴포넌트 (기본)
export default async function BlogPage() {
  // 서버에서 직접 데이터 페칭
  const posts = await fetch("https://api.example.com/posts").then(
    (res) => res.json()
  );
 
  return (
    <div>
      {posts.map((post) => (
        <article key={post.id}>{post.title}</article>
      ))}
    </div>
  );
}
 
// ✅ 클라이언트 컴포넌트 (필요한 경우만)
"use client";
 
import { useState } from "react";
 
export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

5. 메타데이터 및 SEO 최적화

성능도 중요하지만, 검색 엔진에서 잘 보이게 하는 것도 중요해요. Next.js의 metadata API를 사용하면 쉽게 설정할 수 있어요.

체크리스트:

  • metadata API 사용 (타입 안전하게 메타데이터 관리)
  • Open Graph 태그 설정 (SNS 공유 시 미리보기가 표시됩니다)
  • 구조화된 데이터(JSON-LD) 추가 (검색 엔진이 이해하기 쉽습니다)
  • robots.txtsitemap.xml 설정 (검색 엔진 크롤링을 돕습니다)

실전 예시:

// app/page.tsx
export const metadata: Metadata = {
  title: "홈페이지",
  description: "페이지 설명",
  openGraph: {
    title: "홈페이지",
    description: "페이지 설명",
    images: ["/og-image.png"],
  },
  robots: {
    index: true,
    follow: true,
  },
};

Tailwind CSS 성능 최적화

Tailwind CSS는 편리하지만, 설정을 제대로 하지 않으면 사용하지 않는 클래스까지 포함될 수 있습니다. 프로덕션 빌드에서는 자동으로 제거되지만, 설정을 확인하는 것이 좋습니다.

1. PurgeCSS 설정

Tailwind는 기본적으로 사용하지 않는 클래스를 자동으로 제거해요. 하지만 content 경로를 정확히 설정해야 해요.

체크리스트:

  • content 경로 정확히 설정 (어디서 클래스를 찾을지 지정해야 합니다)
  • 사용하지 않는 클래스 자동 제거 (프로덕션 빌드에서 자동 처리)
  • 프로덕션 빌드에서 최적화 확인 (빌드 후 CSS 파일 크기 확인)

tailwind.config.ts:

export default {
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    // 정확한 경로 지정으로 불필요한 CSS 제거
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

2. CSS 변수 활용

테마를 관리할 때 CSS 변수를 사용하면 런타임에 스타일을 변경할 수 있어 편리해요. 다만 너무 자주 변경하면 성능에 영향을 줄 수 있으니 주의해야 해요.

체크리스트:

  • 테마 색상을 CSS 변수로 관리 (한 곳에서 관리하면 편리합니다)
  • 런타임 변경 최소화 (자주 변경하면 리렌더링이 많아집니다)
  • 인라인 스타일 최소화 (CSS 변수나 Tailwind 클래스 사용)

실전 예시:

/* globals.css */
@theme {
  --color-primary: #6b5d4f;
  --color-secondary: #cab7a0;
  --font-inter: var(--font-inter);
}
// 컴포넌트에서 사용
<div className="bg-primary text-foreground">
  {/* CSS 변수로 빠른 렌더링 */}
</div>

실전 성능 측정 도구

성능을 개선하려면 먼저 현재 상태를 정확히 파악해야 해요. 자주 사용하는 도구들을 소개할게요.

1. Lighthouse

Chrome DevTools에 내장된 Lighthouse는 매우 유용한 도구예요. Core Web Vitals를 포함해서 성능 점수를 한눈에 확인할 수 있어요.

프로덕션 환경에서 측정하는 것이 중요해요. 개발 환경과 프로덕션 환경의 성능 차이는 상당히 커요.

사용 방법:

  1. Chrome DevTools 열기 (F12 또는 우클릭 → 검사)
  2. Lighthouse 탭 선택
  3. "Performance" 체크 (다른 항목도 함께 체크해볼 수 있어요)
  4. "Analyze page load" 클릭

목표 점수:

프로젝트에서 목표로 하는 점수예요. 이 정도면 사용자 경험이 좋다고 볼 수 있어요:

  • Performance: 90점 이상 (100점 만점)
  • LCP: 2.5초 이하 (첫 화면이 2.5초 안에 표시)
  • FID/INP: 100ms 이하 (버튼 클릭 후 0.1초 안에 반응)
  • CLS: 0.1 이하 (레이아웃이 거의 움직이지 않아야 함)

실제 측정 예시:

Lighthouse 리포트 결과:
 
Performance: 95
├─ LCP: 1.8s ✅ (목표: 2.5s)
├─ FID: 45ms ✅ (목표: 100ms)
├─ CLS: 0.05 ✅ (목표: 0.1)
└─ TBT: 50ms ✅
 
개선 제안:
- 이미지 최적화로 LCP 개선 가능
- JavaScript 번들 크기 감소로 FID 개선 가능

2. Web Vitals 라이브러리

실제 사용자들이 경험하는 성능을 측정하려면 web-vitals 라이브러리를 사용하는 것이 좋습니다. Lighthouse는 한 번 측정하는 것이지만, 이 라이브러리는 실제 사용자들의 데이터를 지속적으로 수집할 수 있습니다.

설치 및 사용:

pnpm add web-vitals
// app/layout.tsx 또는 _app.tsx
import { onCLS, onFID, onLCP, onINP } from "web-vitals";
 
function sendToAnalytics(metric: any) {
  // Google Analytics로 전송
  if (typeof window !== "undefined" && (window as any).gtag) {
    (window as any).gtag("event", metric.name, {
      value: Math.round(metric.value),
      event_label: metric.id,
      non_interaction: true,
    });
  }
 
  // 또는 Vercel Analytics
  console.log(metric);
}
 
export function reportWebVitals() {
  onCLS(sendToAnalytics);
  onFID(sendToAnalytics);
  onLCP(sendToAnalytics);
  onINP(sendToAnalytics); // INP도 측정
}

Next.js 13+ App Router를 사용하는 경우를 위한 팁이에요. Pages Router에서는 reportWebVitals 함수를 export하면 되지만, App Router에서는 클라이언트 컴포넌트에서 useEffect를 사용해야 해요.

App Router에서 사용:

// components/WebVitals.tsx
"use client";
 
import { useEffect } from "react";
import { onCLS, onFID, onLCP, onINP } from "web-vitals";
 
export function WebVitals() {
  useEffect(() => {
    onCLS(console.log);
    onFID(console.log);
    onLCP(console.log);
    onINP(console.log);
  }, []);
 
  return null;
}
 
// app/layout.tsx
import { WebVitals } from "@/components/WebVitals";
 
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <WebVitals />
      </body>
    </html>
  );
}

3. Next.js Analytics

설치:

pnpm add @vercel/analytics
// app/layout.tsx
import { Analytics } from "@vercel/analytics/react";
 
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  );
}

종합 체크리스트

프로젝트를 시작할 때나 리팩토링할 때 이 체크리스트를 확인해보세요. 하나씩 체크해가면서 개선하다 보면 성능이 확실히 개선돼요.

이미지 및 미디어

  • Next.js Image 컴포넌트 사용 (일반 img 태그 대신)
  • AVIF/WebP 포맷 사용 (최신 포맷이 더 작아요)
  • 적절한 이미지 크기 (원본 그대로 올리지 마세요)
  • sizes 속성으로 반응형 이미지 (모바일/데스크톱에 맞게)
  • priority로 중요한 이미지 우선 로딩 (첫 화면 이미지)
  • 비디오는 loading="lazy" 적용 (필요할 때만 로드)

폰트

  • next/font 사용 (자동 최적화)
  • display: "swap" 설정 (FOIT 방지)
  • 필요한 weight만 로드 (100~900 다 필요 없어요)
  • 폰트 preload 설정 (중요한 폰트는 미리 로드)
  • 폴백 폰트 지정 (폰트 로딩 실패 대비)

코드 및 번들

  • 서버 컴포넌트 기본 사용 (클라이언트 번들 줄이기)
  • 동적 import로 코드 스플리팅 (필요한 것만 로드)
  • 불필요한 의존성 제거 (package.json 정기적으로 확인)
  • Tree-shaking 확인 (사용하지 않는 코드 제거)
  • 번들 크기 모니터링 (무엇이 무거운지 파악)

렌더링 최적화

  • 불필요한 리렌더링 방지 (React.memo, useMemo 활용)
  • 큰 리스트는 가상화 (react-window 같은 라이브러리)
  • 애니메이션은 CSS/GPU 가속 활용 (transform, opacity)
  • 레이아웃 시프트 최소화 (크기 미리 지정)

네트워크

  • HTTP/2 또는 HTTP/3 사용 (최신 프로토콜)
  • CDN 활용 (정적 파일은 CDN에서)
  • 적절한 캐싱 전략 (브라우저 캐시 활용)
  • Gzip/Brotli 압축 (파일 크기 줄이기)
  • 리소스 preconnect/prefetch (미리 연결/로드)

모니터링

  • Core Web Vitals 측정 (정기적으로 확인)
  • 실시간 성능 모니터링 (Vercel Analytics 등)
  • 에러 트래킹 (Sentry 등)
  • 사용자 피드백 수집 (실제 느끼는 속도 확인)

실제 프로젝트 적용 예시

예시 1: 이미지 최적화

// components/HeroImage.tsx
import Image from "next/image";
 
export function HeroImage() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero"
      width={1920}
      height={1080}
      priority // 첫 화면이므로 우선 로딩
      sizes="100vw"
      quality={90}
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,..." // 작은 블러 이미지
    />
  );
}

예시 2: 동적 컴포넌트 로딩

// components/ChartSection.tsx
"use client";
 
import dynamic from "next/dynamic";
import { Suspense } from "react";
 
// 무거운 차트 라이브러리는 동적 로드
const Chart = dynamic(() => import("./Chart"), {
  loading: () => <ChartSkeleton />,
  ssr: false,
});
 
export function ChartSection() {
  return (
    <Suspense fallback={<ChartSkeleton />}>
      <Chart />
    </Suspense>
  );
}

예시 3: 폰트 최적화

// app/layout.tsx
import { Inter } from "next/font/google";
 
const inter = Inter({
  subsets: ["latin"],
  weight: ["400", "500", "600", "700"], // 필요한 것만
  variable: "--font-inter",
  display: "swap",
  preload: true,
});
 
export default function RootLayout({ children }) {
  return (
    <html lang="ko" className={inter.variable}>
      <head>
        {/* 폰트 preconnect */}
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link
          rel="preconnect"
          href="https://fonts.gstatic.com"
          crossOrigin="anonymous"
        />
      </head>
      <body className={inter.className}>{children}</body>
    </html>
  );
}

성능 측정 및 모니터링

Lighthouse CI 설정

# .lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: ["http://localhost:3000"],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        "categories:performance": ["error", { minScore: 0.9 }],
        "categories:accessibility": ["error", { minScore: 0.9 }],
        "categories:best-practices": ["error", { minScore: 0.9 }],
        "categories:seo": ["error", { minScore: 0.9 }],
      },
    },
  },
};

Web Vitals 리포트

// lib/analytics.ts
import { onCLS, onFID, onLCP, onFCP, onTTFB } from "web-vitals";
 
function sendToAnalytics(metric: any) {
  // Google Analytics, Vercel Analytics 등으로 전송
  console.log(metric);
}
 
export function reportWebVitals() {
  onCLS(sendToAnalytics);
  onFID(sendToAnalytics);
  onLCP(sendToAnalytics);
  onFCP(sendToAnalytics);
  onTTFB(sendToAnalytics);
}

마무리

웹 성능 최적화는 한 번에 끝나는 작업이 아닙니다. 하지만 체계적으로 접근하면 확실히 개선할 수 있어요.

경험상, 한 번에 모든 것을 최적화하려고 하면 오히려 지치게 돼요. 우선순위를 정해서 하나씩 개선해나가는 것을 추천해요. Core Web Vitals를 정기적으로 확인하고, 사용자들이 실제로 느끼는 속도도 함께 고려하면 좋아요.

다음 단계

프로젝트에 적용할 때는 다음 순서로 진행해보세요:

  1. 현재 성능 측정: Lighthouse로 현재 상태를 먼저 파악해요
  2. 우선순위 정하기: 가장 큰 영향을 주는 항목부터 시작해요
  3. 점진적 개선: 한 번에 하나씩 최적화하면 효과를 바로 확인할 수 있어요
  4. 모니터링: 개선 후에도 지속적으로 성능을 추적해요

추천 도구

자주 사용하는 도구들이에요:

  • Lighthouse: Chrome DevTools에 내장되어 있어 가장 쉽게 사용할 수 있어요
  • WebPageTest: 더 상세한 분석이 필요할 때 사용해요
  • Vercel Analytics: Next.js 프로젝트에 최적화되어 있어 편리해요
  • Google Search Console: 실제 사용자들의 Core Web Vitals 데이터를 확인할 수 있어요

웹 성능 최적화는 지속적인 과정이에요. 앞으로도 실무에서 바로 적용할 수 있는 성능 최적화 기법과 Core Web Vitals 개선 팁을 계속 공유할 예정이니 기대해 주세요!