icon안동민 개발노트

브라우저 개발자 도구 활용


 Next.js App Router를 사용한 애플리케이션의 디버깅과 최적화를 위해 브라우저 개발자 도구를 효과적으로 활용하는 방법을 알아보겠습니다.

네트워크 탭을 통한 요청 분석

 네트워크 탭은 애플리케이션의 HTTP 요청을 모니터링하고 분석하는 데 사용됩니다.

  1. 요청 타임라인 분석 :
  • Waterfall 차트를 통해 요청의 우선순위와 차단 시간 확인
  • 특히 Next.js의 API 라우트 요청에 주목
  1. 불필요한 요청 식별 :
  • 중복 요청이나 과도한 API 호출 확인
  • 캐싱 전략 개선 필요성 파악

 예제

// app/api/data/route.js
import { NextResponse } from 'next/server';
 
export async function GET() {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();
  return NextResponse.json(data);
}

성능 탭을 이용한 렌더링 최적화

 성능 탭은 애플리케이션의 런타임 성능을 분석하고 최적화하는 데 사용됩니다.

  1. 렌더링 성능 분석 :
  • 프레임 차트를 통해 렌더링 병목 지점 식별
  • Long tasks 확인 및 최적화
  1. 컴포넌트 렌더링 최적화 :
  • 불필요한 리렌더링 식별
  • React DevTools와 연계하여 컴포넌트 분석

 예제

// app/components/ExpensiveComponent.js
'use client'
 
import { memo } from 'react'
 
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
  // 복잡한 렌더링 로직
  return <div>{/* 렌더링 결과 */}</div>
})
 
export default ExpensiveComponent

메모리 탭으로 메모리 누수 감지

 메모리 탭은 애플리케이션의 메모리 사용량을 모니터링하고 메모리 누수를 감지하는 데 사용됩니다.

  1. 힙 스냅샷 분석 :
  • 객체 보유 트리를 통해 메모리 누수 원인 파악
  • 분리된 DOM 노드 확인
  1. 메모리 할당 타임라인 분석 :
  • 시간에 따른 메모리 사용량 변화 관찰
  • 급격한 메모리 증가 지점 식별

 예제

// app/components/MemoryLeakComponent.js
'use client'
 
import { useEffect, useState } from 'react'
 
export default function MemoryLeakComponent() {
  const [data, setData] = useState([])
 
  useEffect(() => {
    const intervalId = setInterval(() => {
      setData(prev => [...prev, new Array(10000).fill('x')])
    }, 1000)
 
    return () => clearInterval(intervalId)
  }, [])
 
  return <div>Data length: {data.length}</div>
}

애플리케이션 탭에서의 스토리지 관리

 애플리케이션 탭은 로컬 스토리지, 세션 스토리지, 쿠키 등을 관리하는 데 사용됩니다.

  1. 로컬 스토리지 및 세션 스토리지 검사 :
  • 저장된 데이터 확인 및 수정
  • 스토리지 사용량 모니터링
  1. 쿠키 관리 :
  • 인증 관련 쿠키 검사
  • 불필요한 쿠키 식별 및 제거

 예제

// app/utils/storage.js
export function setItem(key, value) {
  if (typeof window !== 'undefined') {
    localStorage.setItem(key, JSON.stringify(value))
  }
}
 
export function getItem(key) {
  if (typeof window !== 'undefined') {
    const item = localStorage.getItem(key)
    return item ? JSON.parse(item) : null
  }
  return null
}

소스 탭을 활용한 디버깅 기법

 소스 탭은 JavaScript 코드를 디버깅하고 브레이크포인트를 설정하는 데 사용됩니다.

  1. 소스 맵을 통한 원본 코드 디버깅 :
  • Next.js 빌드 설정에서 소스 맵 활성화
  • 프로덕션 빌드에서도 효과적인 디버깅 가능
  1. 조건부 브레이크포인트 활용 :
  • 특정 조건에서만 실행을 중단하여 효율적인 디버깅

 예제

// next.config.js
module.exports = {
  productionBrowserSourceMaps: true,
}

14장 테스팅과의 연관성

 18장의 브라우저 개발자 도구 활용은 14장에서 다룬 테스팅과 상호 보완적인 관계에 있습니다. 테스트로 발견하기 어려운 런타임 이슈나 성능 문제를 개발자 도구를 통해 식별하고 해결할 수 있습니다. 또한, 개발자 도구를 통해 발견한 문제에 대한 회귀 테스트를 작성함으로써 장기적인 코드 품질을 유지할 수 있습니다.

실습 : 성능 이슈가 있는 Next.js 페이지 최적화

 다음과 같은 성능 이슈가 있는 Next.js 페이지가 있다고 가정해 봅시다.

// app/page.js
'use client'
 
import { useState } from 'react'
import ExpensiveList from '../components/ExpensiveList'
 
export default function Home() {
  const [count, setCount] = useState(0)
 
  return (
    <div>
      <h1>Welcome to My App</h1>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ExpensiveList items={Array(10000).fill().map((_, i) => ({ id: i, name: `Item ${i}` }))} />
    </div>
  )
}
 
// app/components/ExpensiveList.js
export default function ExpensiveList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}

 이 페이지에는 다음과 같은 문제가 있습니다.

  1. 대량의 데이터를 한 번에 렌더링하여 성능 저하
  2. 불필요한 리렌더링 발생

 브라우저 개발자 도구를 사용하여 이 문제들을 식별하고 해결해보세요.

  1. 성능 탭을 사용하여 렌더링 성능 분석
  2. React DevTools를 사용하여 컴포넌트 리렌더링 확인
  3. 메모리 탭을 통해 메모리 사용량 분석

 문제 해결 방안

  1. 가상화(virtualization) 적용하여 렌더링 최적화
  2. useMemouseCallback을 사용하여 불필요한 리렌더링 방지

 최적화된 코드

// app/page.js
'use client'
 
import { useState, useMemo, useCallback } from 'react'
import ExpensiveList from '../components/ExpensiveList'
 
export default function Home() {
  const [count, setCount] = useState(0)
 
  const items = useMemo(() => 
    Array(10000).fill().map((_, i) => ({ id: i, name: `Item ${i}` })),
    []
  )
 
  const incrementCount = useCallback(() => setCount(prev => prev + 1), [])
 
  return (
    <div>
      <h1>Welcome to My App</h1>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment</button>
      <ExpensiveList items={items} />
    </div>
  )
}
 
// app/components/ExpensiveList.js
'use client'
 
import { memo } from 'react'
import { FixedSizeList as List } from 'react-window'
 
const ExpensiveList = memo(function ExpensiveList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  )
 
  return (
    <List
      height={400}
      itemCount={items.length}
      itemSize={35}
      width={300}
    >
      {Row}
    </List>
  )
})
 
export default ExpensiveList

 이 실습을 통해 브라우저 개발자 도구를 사용하여 Next.js App Router 애플리케이션의 성능 문제를 식별하고 해결하는 과정을 경험할 수 있습니다. 개발자 도구의 다양한 기능을 활용하여 애플리케이션의 동작을 심층적으로 분석하고, 최적화된 솔루션을 적용하는 능력을 기를 수 있습니다.

 브라우저 개발자 도구는 Next.js App Router 애플리케이션의 디버깅과 최적화에 필수적인 도구입니다. 네트워크 요청 분석, 렌더링 성능 최적화, 메모리 누수 감지, 스토리지 관리, 그리고 효과적인 디버깅을 위해 개발자 도구의 다양한 기능을 숙지하고 활용하는 것이 중요합니다. 지속적인 모니터링과 최적화를 통해 높은 성능과 안정성을 갖춘 웹 애플리케이션을 구축할 수 있습니다.