← 전체 글로 돌아가기

웹 개발

릴리즈 빌드에서 사용자 입력이 느릴 때

프로덕션 빌드에서 상호작용이 느린 건 자주 JavaScript 크기가 크거나 스크립트 실행 비용 때문이다. 빌드 분석부터 해야 한다.

배포 후 사용자들이 '입력할 때 화면이 멈춘다'고 보고했다. 로컬에서는 빠른데 프로덕션에서만 느리다. 이런 건 대부분 번들 크기나 실행 성능 때문이다.

빌드 시간과 번들 크기 확인

먼저 기본 빌드 명령으로 얼마나 오래 걸리고 뭐가 큰지 봐야 한다:

npm run build

Next.js 빌드 로그에 각 페이지 크기가 나온다. 5MB 이상이면 뭔가 커진 거다.

번들 분석

bundle-analyzer 같은 도구로 뭐가 크기를 차지하는지 확인할 수 있다:

npx next-bundle-analyzer

또는 빌드 후 .next/static 디렉토리를 직접 본다:

du -sh .next/static

자주 커지는 이유

  1. 큰 라이브러리: 전체를 import할 때. 필요한 함수만 import하자.
   // 나쁜 예
   import _ from 'lodash'; // 전체 라이브러리

   // 좋은 예
   import { debounce } from 'lodash-es'; // 필요한 함수만
  1. 동적 import 활용 안 함: 페이지별로 필요한 코드만 로드하자.
   const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
     loading: () => <div>로딩 중...</div>
   });
  1. 프로덕션 모드 설정: NODE_ENV=production으로 빌드하면 최소화와 트리셰이킹이 적용된다.

로컬과 프로덕션 성능 차이

로컬 개발 서버는 source map을 포함하고 최적화를 건너뛴다. 프로덕션에서는 트리셰이킹, 코드 분할, 최소화가 모두 적용된다.

실제 프로덕션처럼 테스트하려면:

NODE_ENV=production npm run build
NODE_ENV=production npm run start

또는 더 정확하게, 최소화와 최적화가 완전히 적용된 상태:

npm run build
serve -s out  # 또는 npm run start

입력 성능 개선

입력이 느린 건 보통 onChange 핸들러가 비효율적이어서다:

// 나쁜 예: 매번 리렌더링
const [input, setInput] = useState('');

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setInput(e.target.value);
  // 동시에 무거운 작업
  processData(e.target.value);
};

// 좋은 예: 디바운싱
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setInput(e.target.value);
  // processData는 debounce로 감싸기
};

크롬 DevTools로 측정

Production 빌드에서:

  1. Chrome DevTools → Performance 탭
  2. 입력하는 동안 녹화
  3. Main 스레드의 CPU 사용률과 블로킹 작업 확인
  4. 어느 함수가 오래 걸리는지 찾기

환경 변수와 빌드 설정

때때로 환경 변수가 프로덕션 최적화를 방해할 수 있다:

# .env.production
NEXT_PUBLIC_DEBUG=false  # 디버그 모드 비활성화
NEXT_PUBLIC_ANALYTICS=true

점진적 개선

  1. 현재 번들 크기 측정
  2. 가장 큰 항목부터 개선 (파레토 원칙)
  3. 각 개선 후에 체감 성능 확인
  4. 사용자 피드백 수집

한 번에 많은 변경을 하면 뭐가 효과있는지 알 수 없다. 작은 개선들이 모이면 사용자 경험이 크게 달라진다.