TIL

Next.js 의 Dynamic server usage 빌드 에러

indeeah 2024. 8. 19. 17:39

문제

client-side에서 데이터를 패칭 할 때 쿼리 스트링 등 동적으로 데이터 패칭이 필요 할 때 빌드 시 해당 API는 server-side 데이터를 가져 올 수 없으므로 빌드 에러가 나게 된다.

문제 코드

// index.tsx
const fetchOrders = useCallback(async () => {
    try {
      if (
        !user ||
        searchConditions.merchantId === undefined ||
        searchConditions.sellerId === undefined
      )
        return;

      const { totalCount, orders: fetchedOrders } = await getOrders({
        merchantId: searchConditions.merchantId,
        sellerId: searchConditions.sellerId,
        status:
          searchConditions.status !== '전체'
            ? [getStatusEng(searchConditions.status!) as OrderItemStatus]
            : null,
        startDate: searchConditions.startDate,
        endDate: searchConditions.endDate,
        page: pageInfo.page,
        perPage: pageInfo.perPage,
      });

      setOrders(fetchedOrders);
      setTotalCount(Number(totalCount));
    } catch (error) {
      handleErrorWithModal(
        error,
        '주문 내역을 불러오는 중 오류가 발생했습니다.',
        openModal,
        fetchOrders,
      );
    }
  }, [
    openModal,
    setOrders,
    searchConditions.merchantId,
    searchConditions.sellerId,
    searchConditions.status,
    searchConditions.startDate,
    searchConditions.endDate,
    pageInfo.page,
    pageInfo.perPage,
    user,
  ]);

  useEffect(() => {
    if (!user) return;

    fetchOrders();
  }, [user, fetchOrders]);

const exportDataToExcel = async () => {
    try {
      if (isDownloading) return;
      setIsDownloading(true);

      if (
        !user ||
        searchConditions.merchantId === undefined ||
        searchConditions.sellerId === undefined
      ) {
        showToastMessage('잠시 후 다시 시도해주세요.', 'error', true);
        return;
      }

      const { headers, data } = await getOrdersExcelData({
        merchantId: searchConditions.merchantId,
        sellerId: searchConditions.sellerId,
        status:
          searchConditions.status !== '전체'
            ? [getStatusEng(searchConditions.status!) as OrderItemStatus]
            : null,
        startDate: dayjs(searchConditions.startDate).format('YYYYMMDD'),
        endDate: dayjs(searchConditions.endDate).format('YYYYMMDD'),
      });

      const fileName = `${dayjs(searchConditions.startDate).format('YYYYMMDD')}_${dayjs(searchConditions.endDate).format('YYYYMMDD')}_주문내역`;
      const sheetName = '주문내역';

      exportToExcel({ headers, data, fileName, sheetName });
    } catch (error) {
      handleErrorWithToast(
        error,
        '주문 내역을 엑셀로 다운로드하는 중 오류가 발생했습니다.',
        showToastMessage,
      );
    } finally {
      // NOTE: 2초 후에 다운로드 가능한 상태로 변경
      setTimeout(() => setIsDownloading(false), 2000);
    }
  };

  // route.ts
  export async function GET(req: NextRequest) {
  try {
    const { searchParams } = new URL(req.url);

req.url을 가져오는 데 문제가 생김

설명

렌더링 방식

  1. 정적 생성 (Static Generation, SSG)
    • 정적 HTML 생성: 빌드 타임에 HTML 파일을 생성하고, 이 파일을 정적 서버에 배포
    • 빠른 응답 시간: 서버는 미리 생성된 HTML 파일 즉시 제공
    • SEO 친화적: 완전히 렌더링된 HTML을 제공하므로 검색 엔진 크롤러가 쉽게 인덱싱
    • 제한점: 빌드 타임에 모든 데이터를 필요로 하며, 이후에 데이터 변경 사항은 반영되지 않음
  2. 동적 렌더링 (Dynamic Rendering, SSR)
    • 서버 사이드 렌더링: 각 요청 시마다 서버가 최신 데이터를 기반으로 HTML을 생성하여 반환
    • 실시간 데이터: 항상 최신 데이터를 사용하여 페이지를 렌더링
    • SEO 친화적: 서버가 완전히 렌더링된 HTML을 제공하므로 SEO에 유리
    • 서버 부하: 각 요청 시마다 서버가 렌더링 작업을 수행하므로 서버 부하가 증가 할 수 있음
  • 클라이언트 사이드 데이터 패칭:
    • React 컴포넌트가 브라우저에서 실행
    • API는 동적으로 처리: 서버는 클라이언트로부터의 요청에 따라 실시간으로 데이터를 처리하고 응답 반환

빌드 타임과 런타임 차이

  1. 빌드 타임: 정적 사이트 생성 시점
    • 이때 Next.j는 서버 사이드 함수나 API 라우트를 실행 할 수 없음
    • 모든 데이터는 정적으로 존재해야 함
  2. 런타임: 사용자가 페이지를 요청하는 시점. 동적 렌더링의 경우, 이 시점에서 서버가 요청을 받아 데이터를 가져와 페이지를 렌더링
    • 이때 Next.js는 서버 사이드 함수나 API 라우트를 실행할 수 있음
    • 최신 데이터를 가져와 페이지를 동적 생성

API 요청

  1. 정적 요청
    • getStaticProps 를 이용해 요청 시 API 요청은 정적 요청이 된다.
  2. 동적 요청
    • server-side 및 client-side에서 요청 시 API 요청은 동적 요청이 된다.

해결 방법

// route.ts
export const dynamic = 'force-dynamic';
  • 빌드 타임에 해당 API 라우트를 정적으로 생성하지 않고, 런타임에 처리하도록 하는 설정
  • 빌드 시점에 접근할 수 없는 리소스나 환경 변수를 필요로 할 때 사용
  • API가 데이터베이스 쿼리, 외부 API 호출 등 빌드 타임에 실행될 수 없는 작업을 포함하는 경우 유용
  • 모든 동적 요청 API에 처리할 필요는 없고, 동적 데이터가 필요한 API에만 처리