[Next.js] CSR 페이지에서 topLevelAwait 사용 Warning 해결하기

y1nlog·2025년 3월 17일

문제 발생

  • CSR(client side rendering) 페이지 구현 중
  • Route Handler를 사용하여 Next.js dev 환경에서 데이터가 잘 불러와지는 것까지 확인
  • topLevelAwait 관련 경고가 브라우저 콘솔에 출력됨.
  • 배포 환경에서 데이터가 정상적으로 출력되지 않음.

에러메세지

hook.js:608
./src/lib/constant.ts
The generated code contains 'async/await' because this module is using "topLevelAwait".
However, your target environment does not appear to support 'async/await'.
As a result, the code may not run as expected or may cause runtime errors.

(번역) 생성된 코드에는 'async/await'가 포함되어 있는데, 이 모듈은 "topLevelAwait"를 사용하고 있기 때문입니다.
그러나 대상 환경은 'async/await'를 지원하지 않는 것 같습니다.
그 결과, 코드가 예상대로 실행되지 않거나 런타임 오류가 발생할 수 있습니다.

원인 추론

  • CSR 페이지에서는 top-level await가 지원되지 않는다.
  • 현재 원인이 되는 코드는 ./src/lib/constant.ts 경로의 버전 정보를 호출하는 비동기 함수로 보여진다.

현재 프로젝트는

next-lol
├─ src
│  ├─ app
│  │  ├─ api
│  │  │  ├─ fetchData.ts
│  │  │  └─ rotation
│  │  │     └─ route.ts // route-handler
│  │  ├─ items
│  │  │  ├─ [id]
│  │  │  │  └─ page.tsx
│  │  │  └─ page.tsx
│  │  ├─ layout.tsx
│  │  ├─ page.tsx
│  │  └─ rotation
│  │     └─ page.tsx // CSR page
│  ├─ components
│  │  └─ Card.tsx
│  ├─ lib
│  │  ├─ constant.ts // warning 발생
│  └─ types
│     ├─ Champion.ts
│     ├─ ChampionRotation.ts
│     └─ Item.ts
...

위와 같은 구조를 사용하고 있는데,

rotation/page.tsx가 CSR방식으로 구현되었으나 VERSION정보를 비동기적으로 사용하고 있어서 문제가 되는 것으로 보인다.

물론 그것 뿐만 아니라 서버 컴포넌트에서 사용하는 비동기 함수를 가져다 쓴 것도 문제가 될 수 있을듯..

원인 코드

"use client";
import { useEffect, useState } from "react";
import Card from "@/components/Card";
import { VERSION } from "@/lib/constant";
import { Champion } from "@/types/Champion";

const Rotation = () => {
  interface Dataset {
    freeChampionIds: number[];
    freeChampionIdsForNewPlayers: number[];
    maxNewPlayerLevel: number;
  }

  const [dataset, setDataset] = useState<Dataset | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [champions, setChampions] = useState<Record<string, Champion>>({});

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await fetch("/api/rotation").then((res) => res.json());
        setDataset(response);

        const resChampion = await fetch(
          `https://ddragon.leagueoflegends.com/cdn/${VERSION}/data/ko_KR/champion.json`,
        );
        const allChampions = await resChampion.json();

        const rotationChampions: Record<string, Champion> = {};
        response.freeChampionIds.forEach((id: number) => {
          const champKey = Object.keys(allChampions.data).find(
            (key) => allChampions.data[key].key === String(id),
          );
          if (champKey) {
            rotationChampions[champKey] = allChampions.data[champKey];
          }
        });

        setChampions(rotationChampions);
      } catch (err) {
        console.error(err);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, []);

DOM요소를 반환하는 부분은 생략.

Client 컴포넌트로 선언을 해 두고, 비동기 함수들은 useEffect안에서 처리하고자 하였으나, 일단 첫번째로 문제가 되는 것은 VERSION.

import { getLatestVersion } from "@/app/api/fetchData";

export const VERSION = await getLatestVersion();

VERSION이라는 상수는 상수의 탈을 썼지만, 사실은 await getLatestVersion()을 실행시키는 것이므로 이 부분이 문제가 될 것이라고 예측할 수 있었다.

해결 방법

비동기 함수를 실행하고 있는 로직을 어떻게 바꾸면 버전 정보만을 상수로 전달하도록 바꿀 수 있을까?

빌드 타임이나 서버 사이드에서 비동기 함수를 실행, 그 결과값을 미리 받아서 클라이언트 컴포넌트에 전달하는 방식으로 바꿔야 한다.

만일 내가 구현하려는 rotation 페이지가 server component 안에 종속된다면, props로 내려주는 것이 비교적 간단했을 것 같다.

다만 현재 프로젝트 요구사항에서 rotation 페이지를 서버 컴포넌트에 넣어도 되는지는 명시되어 있지 않기 때문에, 최대한 CSR 방식을 유지하면서 최신 version 정보를 사용할 수 있도록 하기 위해서

아래와 같은 방법을 사용했다.

빌드 시점에 데이터를 가져와서 환경 변수에 주입

  • next.config.mjs 파일 수정
  • 환경 변수로 주입하여 클라이언트와 서버 모두에서 process.env.NEXT_PUBLIC_변수명으로 사용 가능하도록 만들기

그럼 프로젝트가 빌드되는 시점에 getLatestVersion 함수를 실행시켜 최신 버전정보를 프로젝트 내에 환경변수로 공유할 수 있다.

데이터의 버전 정보 같은 경우에는, 실시간으로 변경되는 데이터가 아니므로 빌드시점에만 업데이트해 두어도 주요 로직에 영향이 없다고 판단하였다.

해결 코드

/** @type {import('next').NextConfig} */

// 최신 버전 정보 fetch 함수 정의
async function getLatestVersion() { 
  const res = await fetch(
    "https://ddragon.leagueoflegends.com/api/versions.json",
  );
  const versions = await res.json();
  return versions[0];
}

const version = await getLatestVersion(); // 함수 실행결과 할당

const nextConfig = {
  images: {
    // remotePatterns ...
  },
  env: {
    NEXT_PUBLIC_VERSION: version, // 환경변수로 프로젝트에 공유
  },
};

export default nextConfig;

이제 기존에 발생하던 img 경고 외에, topLevelAwait 사용에 대한 경고문은 사라진 것을 확인할 수 있다.
하지만 여전히 배포환경에서는 안된다.... 뭐가 문제일까 ㅠㅠ

profile
FE / Data Science

0개의 댓글