icon안동민 개발노트

성능 병목 현상 식별 및 해결


 Next.js App Router를 사용한 애플리케이션의 성능을 최적화하기 위해서는 성능 병목 현상을 정확히 식별하고 효과적으로 해결해야 합니다.

 이 절에서는 주요 성능 지표와 최적화 전략에 대해 알아보겠습니다.

성능 지표 및 측정 도구

 1. Core Web Vitals

  • Largest Contentful Paint (LCP)
  • First Input Delay (FID)
  • Cumulative Layout Shift (CLS)

 2. 측정 도구

  • Lighthouse
  • Chrome DevTools Performance 탭
  • Next.js Analytics

렌더링 성능 최적화

 1. 서버 컴포넌트 활용

  • 서버 컴포넌트를 사용하여 초기 로드 시간을 단축하고 클라이언트 측 JavaScript를 줄입니다.
app/components/ServerComponent.js
export default function ServerComponent({ data }) {
  return <div>{data.map(item => <p key={item.id}>{item.name}</p>)}</div>
}
app/page.js
import ServerComponent from './components/ServerComponent'
 
export default async function Page() {
  const data = await fetchData()
  return <ServerComponent data={data} />
}

 2. 동적 임포트

  • 필요한 시점에 컴포넌트를 로드하여 초기 번들 크기를 줄입니다.
import dynamic from 'next/dynamic'
 
const DynamicComponent = dynamic(() => import('../components/HeavyComponent'))

 3. 이미지 최적화

  • Next.js의 Image 컴포넌트를 사용하여 자동 이미지 최적화를 수행합니다.
import Image from 'next/image'
 
export default function OptimizedImage() {
  return <Image src="/large-image.jpg" width={500} height={300} />
}

데이터 페칭 최적화

 1. 증분 정적 재생성 (ISR) 활용

  • 자주 변경되지 않는 데이터에 대해 ISR을 사용하여 성능과 최신성의 균형을 맞춥니다.
app/products/[id]/page.js
export default async function ProductPage({ params }) {
  const product = await getProduct(params.id)
  return <div>{/* 제품 정보 표시 */}</div>
}
 
export async function generateStaticParams() {
  const products = await getProducts()
  return products.map((product) => ({
    id: product.id,
  }))
}
 
export const revalidate = 3600 // 1시간마다 재검증

 2. SWR 또는 React Query 사용

  • 클라이언트 사이드 데이터 페칭을 최적화합니다.
import useSWR from 'swr'
 
export default function Profile() {
  const { data, error } = useSWR('/api/user', fetcher)
 
  if (error) return <div>Failed to load</div>
  if (!data) return <div>Loading...</div>
  return <div>Hello {data.name}!</div>
}

번들 크기 분석 및 최적화

 1. @next/bundle-analyzer 사용

  • 번들 크기를 분석하고 최적화 지점을 찾습니다.
next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({})

 2. Tree Shaking 최적화

  • 사용하지 않는 코드를 제거하여 번들 크기를 줄입니다.
// 최적화 전
import { format, formatDistance } from 'date-fns'
 
// 최적화 후
import format from 'date-fns/format'
import formatDistance from 'date-fns/formatDistance'

서버 사이드 성능 개선

 1. 데이터베이스 쿼리 최적화

  • 필요한 데이터만 정확히 쿼리하여 서버 응답 시간을 단축합니다.

 2. 캐싱 전략 구현

  • Redis 또는 Memcached를 사용하여 자주 요청되는 데이터를 캐싱합니다.
import { Redis } from '@upstash/redis'
 
const redis = new Redis({
  url: process.env.REDIS_URL,
  token: process.env.REDIS_TOKEN,
})
 
export async function getData(key) {
  const cachedData = await redis.get(key)
  if (cachedData) return JSON.parse(cachedData)
 
  const data = await fetchDataFromAPI(key)
  await redis.set(key, JSON.stringify(data), { ex: 3600 }) // 1시간 캐시
  return data
}

클라이언트 사이드 성능 개선

 1. 메모이제이션 활용

  • 불필요한 리렌더링을 방지합니다.
import { useMemo } from 'react'
 
export default function ExpensiveComponent({ data }) {
  const processedData = useMemo(() => expensiveCalculation(data), [data])
  return <div>{processedData}</div>
}

 2. 가상화 (Virtualization) 구현

  • 대량의 데이터를 효율적으로 렌더링합니다.
import { FixedSizeList } from 'react-window'
 
export default function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={400}
      width={300}
      itemSize={50}
      itemCount={items.length}
      itemData={items}
    >
      {({ index, style, data }) => (
        <div style={style}>{data[index]}</div>
      )}
    </FixedSizeList>
  )
}

12장 성능 최적화와의 연관성

 18장의 성능 병목 현상 식별 및 해결은 12장에서 다룬 성능 최적화 기법을 실제 상황에 적용하는 과정을 다룹니다.

 12장에서 학습한 이미지 최적화, 코드 분할, 그리고 렌더링 최적화 기법들을 실제 성능 병목 지점에 적용하여 전체적인 애플리케이션 성능을 개선합니다.

실습 : Next.js 성능 프로파일링 및 최적화

 1. 성능 프로파일링

  • Lighthouse를 사용하여 초기 성능 점수 측정
  • Chrome DevTools Performance 탭을 사용하여 렌더링 성능 분석
  • Next.js Analytics를 설정하여 실제 사용자 경험 데이터 수집

 2. 주요 병목 지점 식별

  • LCP (Largest Contentful Paint) 지연 확인
  • 불필요한 리렌더링 발생 여부 확인
  • 대용량 번들 파일 식별

 3. 최적화 적용

  • 서버 컴포넌트로 전환하여 초기 로드 시간 단축
app/components/ServerComponent.js
export default async function ProductList() {
  const products = await getProducts()
  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  )
}
  • 이미지 최적화 적용
import Image from 'next/image'
 
export default function OptimizedProductImage({ product }) {
  return (
    <Image 
      src={product.image} 
      width={300} 
      height={200} 
      alt={product.name} 
      placeholder="blur"
      blurDataURL={product.blurDataURL}
    />
  )
}
  • 코드 분할 및 지연 로딩 구현
import dynamic from 'next/dynamic'
 
const DynamicChart = dynamic(() => import('../components/Chart'), {
  loading: () => <p>Loading chart...</p>,
  ssr: false
})

 4. 결과 분석 및 추가 최적화

  • 최적화 후 Lighthouse 점수 재측정
  • Core Web Vitals 개선 여부 확인
  • 사용자 경험 메트릭 (Time to Interactive, Total Blocking Time 등) 분석

 5. 지속적인 모니터링 및 최적화 계획 수립

  • 성능 모니터링 도구 설정 (예 : Vercel Analytics)
  • 정기적인 성능 검토 및 최적화 일정 수립

 이 실습을 통해 실제 Next.js App Router 프로젝트의 성능을 종합적으로 분석하고 최적화하는 과정을 경험할 수 있습니다.

 성능 최적화는 지속적인 과정이며, 사용자 경험 향상을 위해 꾸준한 노력이 필요합니다.

 Next.js App Router 프로젝트의 성능 최적화는 사용자 경험과 비즈니스 성과에 직접적인 영향을 미치는 중요한 과정입니다.

 성능 병목 현상을 정확히 식별하고, 적절한 최적화 전략을 적용함으로써 빠르고 반응성 좋은 웹 애플리케이션을 구축할 수 있습니다.

 지속적인 모니터링과 최적화를 통해 변화하는 사용자 요구사항과 기술 환경에 대응하며, 항상 최상의 성능을 유지하는 것이 중요합니다.