프론트엔드 개발

Next.js로 배우는 실전 웹 바이탈 최적화 완벽 가이드

쫌수 2025. 1. 11. 02:29

안녕하세요! 오늘은 Next.js에서 제공하는 기능들을 활용해 웹 바이탈을 최적화하는 방법을 상세히 알아보겠습니다. 특히 Next.js 13 이상에서 사용할 수 있는 최신 기능들을 중심으로 설명드리겠습니다.

1. LCP(Largest Contentful Paint) 최적화

LCP는 사용자가 보는 화면에서 가장 큰 이미지나 텍스트가 표시되는 시간을 측정합니다. Next.js의 Image 컴포넌트를 활용하면 쉽게 최적화할 수 있습니다.

Next.js Image 컴포넌트 최적화

// app/page.tsx
import Image from 'next/image'

function HeroSection() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1200}
      height={600}
      priority={true}  // LCP 이미지는 priority 필수!
      quality={75}     // 품질과 파일 크기 최적화
      placeholder="blur"  // 로딩 중 blur 효과
      blurDataURL="data:image/jpeg;base64,..."  // 미리보기 이미지
    />
  )
}

Next.js 설정 최적화

// next.config.js
module.exports = {
  images: {
    deviceSizes: [640, 750, 828, 1080, 1200, 1920],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    formats: ['image/avif', 'image/webp'],
  },
  experimental: {
    optimizeCss: true,  // CSS 최적화
  }
}

2. FID(First Input Delay) 최적화

FID는 사용자의 첫 상호작용(클릭, 탭 등)에 대한 응답 시간을 측정합니다.

App Router의 Dynamic Imports

// app/dashboard/page.tsx
import { Suspense } from 'react'
import dynamic from 'next/dynamic'

// 무거운 컴포넌트 동적 로딩
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
  loading: () => <LoadingSkeleton />,
  ssr: false  // 클라이언트 사이드에서만 로드
})

export default function DashboardPage() {
  return (
    <Suspense fallback={<LoadingSkeleton />}>
      <HeavyChart />
    </Suspense>
  )
}

서버 컴포넌트 활용

// app/products/page.tsx
import { ProductList } from '@/components/ProductList'

// 서버 컴포넌트로 데이터 로딩
async function getProducts() {
  const res = await fetch('https://api.example.com/products')
  return res.json()
}

export default async function ProductsPage() {
  const products = await getProducts()

  return <ProductList products={products} />
}

3. CLS(Cumulative Layout Shift) 최적화

CLS는 페이지 로드 중 발생하는 레이아웃 변화를 측정합니다.

Next/Image를 활용한 이미지 CLS 방지

// app/components/ProductCard.tsx
import Image from 'next/image'

export function ProductCard({ product }) {
  return (
    <div className="relative aspect-square w-full">
      <Image
        src={product.image}
        alt={product.name}
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        className="object-cover rounded-lg"
      />
    </div>
  )
}

Next/Font로 폰트 최적화

// app/layout.tsx
import { Inter } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  preload: true,
})

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

Loading UI와 Streaming

// app/products/loading.tsx
export default function Loading() {
  return (
    <div className="grid grid-cols-3 gap-4">
      {[...Array(6)].map((_, i) => (
        <div key={i} className="animate-pulse">
          <div className="bg-gray-200 aspect-square rounded-lg" />
          <div className="h-4 bg-gray-200 rounded mt-2 w-3/4" />
          <div className="h-4 bg-gray-200 rounded mt-2 w-1/2" />
        </div>
      ))}
    </div>
  )
}

4. Next.js 성능 분석 및 모니터링

내장 Analytics 활용

// app/layout.tsx
import { Analytics } from '@vercel/analytics/react'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  )
}

웹 바이탈 측정

// app/components/WebVitals.tsx
'use client'

import { useReportWebVitals } from 'next/web-vitals'

export function WebVitals() {
  useReportWebVitals(metric => {
    console.log(metric)

    // 애널리틱스로 전송
    switch (metric.name) {
      case 'LCP':
        console.log('LCP:', metric.value)
        break
      case 'FID':
        console.log('FID:', metric.value)
        break
      case 'CLS':
        console.log('CLS:', metric.value)
        break
    }
  })

  return null
}

결과 및 모범 사례

실제 프로젝트에 위 최적화를 적용한 결과:

  • LCP: Next/Image + priority로 2초 이하 달성
  • FID: 서버 컴포넌트 + 동적 임포트로 100ms 이하 유지
  • CLS: Next/Image fill + 스켈레톤 UI로 0.1 이하 달성

추가 팁

  1. 페이지별 최적화
    // app/[slug]/page.tsx
    export async function generateMetadata({ params }) {
    return {
     title: `${params.slug} - My Site`,
     description: '...',
    }
    }
    

export const dynamicParams = true


2. **이미지 자동 최적화**
```jsx
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'your-domain.com',
      },
    ],
  },
}
  1. 라우트 그룹화로 성능 최적화
    app/
    (marketing)/ # 마케팅 페이지 그룹
     page.tsx
     about/page.tsx
    (dashboard)/ # 대시보드 그룹
     layout.tsx
     page.tsx

결론

Next.js는 웹 바이탈 최적화를 위한 다양한 기능을 기본적으로 제공합니다. Image, Font, Analytics 등의 컴포넌트와 서버 컴포넌트, Suspense 등의 기능을 적절히 활용하면 손쉽게 최적화된 웹사이트를 구축할 수 있습니다.

특히 App Router와 서버 컴포넌트를 활용하면, 클라이언트 번들 크기를 줄이고 초기 로딩 성능을 크게 개선할 수 있습니다.

다음 포스트에서는 Next.js 13의 새로운 기능인 Server Actions과 Parallel Routes를 활용한 고급 최적화 기법에 대해 다루도록 하겠습니다.