← 전체 글로 돌아가기

API

API 연동 중 빌드 시간이 갑자기 길어질 때

빌드 시간이 예상보다 오래 걸리면, 보통 API 호출이 병렬이 아닌 직렬로 처리되거나 타임아웃이 없어서다.

Next.js에서 정적 페이지 생성 중 데이터를 외부 API에서 가져올 때, 어느 순간부터 빌드가 엄청 오래 걸리는 경험을 했을 것이다. 특히 페이지 수가 많다면 더 심하다. 원인은 대부분 API 호출이 하나씩 순차적으로 실행되기 때문이다.

빌드 시간 측정하고 기록하기

먼저 빌드가 정확히 어디서 오래 걸리는지 확인한다.

time npm run build
# 또는 프로파일링
NODE_OPTIONS="--prof" npm run build
node --prof-process isolate-*.log > profile.txt

이전 빌드와 비교해서 어디서 시간이 증가했는지 찾는다.

API 호출이 병렬인지 직렬인지 확인

페이지 데이터를 가져올 때 가장 흔한 실수가 for 루프로 하나씩 호출하는 것이다.

// 나쁜 예: 직렬 호출 (10개의 API 호출 = N배 느림)
export async function generateStaticParams() {
  const ids = Array.from({ length: 10 }, (_, i) => i + 1);

  const pages = [];
  for (const id of ids) {
    const data = await fetch(`/api/items/${id}`);
    pages.push(id);
  }

  return pages;
}

// 좋은 예: 병렬 호출 (모든 호출이 동시 실행)
export async function generateStaticParams() {
  const ids = Array.from({ length: 10 }, (_, i) => i + 1);

  const pages = await Promise.all(
    ids.map(id => fetch(`/api/items/${id}`))
  );

  return pages;
}

동적 경로의 수 줄이기

모든 항목마다 페이지를 생성하지 않고, 필요한 것만 생성할 수도 있다.

export async function generateStaticParams() {
  // 상위 100개만 정적 생성, 나머지는 온디맨드
  const popular = await fetch('/api/items?popular=true&limit=100');

  return popular.map(item => ({
    id: item.id.toString(),
  }));
}

// next.config.js에서 ISR 활성화
module.exports = {
  experimental: {
    isrMemoryCacheSize: 50 * 1024 * 1024, // 50MB
  },
};

API 타임아웃 설정

느린 API 호출로 인해 전체 빌드가 멈추지 않도록 타임아웃을 설정한다.

export async function getStaticProps({ params }) {
  try {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 5000); // 5초 타임아웃

    const data = await fetch(`/api/items/${params.id}`, {
      signal: controller.signal,
    });

    clearTimeout(timeout);

    return {
      props: { data },
      revalidate: 3600, // 1시간마다 재검증
    };
  } catch (error) {
    // 실패하면 폴백
    return {
      notFound: true,
      revalidate: 60, // 1분 후 다시 시도
    };
  }
}

빌드 캐시 활용

Dependencies를 명시하지 않으면 매번 모든 페이지를 다시 빌드한다. Incremental Static Regeneration을 활용하면 변경된 것만 다시 빌드한다.

export async function getStaticProps() {
  const data = await fetch('/api/data');

  return {
    props: { data },
    revalidate: 1, // 매 요청마다 재검증 가능
  };
}

데이터 소스 고려

API 호출 대신 로컬 데이터베이스나 파일을 읽으면 빌드 시간을 크게 줄일 수 있다.

import { db } from '@/lib/db';

export async function generateStaticParams() {
  // 외부 API 대신 로컬 DB 쿼리
  const items = await db.items.findMany({ select: { id: true } });

  return items.map(item => ({
    id: item.id.toString(),
  }));
}

체크리스트

  1. time npm run build로 빌드 시간을 측정한다.
  2. 데이터 페칭이 Promise.all로 병렬 실행되는지 확인한다.
  3. 동적 경로의 수를 줄일 수 있는지 검토한다.
  4. API 호출에 타임아웃을 설정한다.
  5. ISR로 모든 페이지를 다시 빌드하지 않도록 한다.
  6. 가능하면 외부 API 대신 로컬 데이터를 사용한다.

빌드 시간이 길어지는 문제는 보통 직렬 호출을 병렬로 바꾸는 것만으로도 크게 개선된다.