ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Next.js의 fetch는 "일반적인 fetch"가 아니다?!
    Front-end/Next.js 2026. 2. 2. 22:57
    반응형

    - 해당 글에 나오는 모든 Next.js에 대한 내용은 App Router 기준이다
    - 해당 글에 나오는 모든 React는 Vite, CRA, Webpack 등 기반의 SPA를 뜻한다

    Next.js의 fetch는 "일반적인 fetch"가 아니다?!

     

    Next.js에서는 왜 axios보다 fetch를 사용하는가?

    React에 익숙한 분들이라면 더 잘 알겠지만 주로 axios를 사용해서 통신을 하게 된다. 그 이유는 fetch가 제공하지 않는 DX를 axios가 보완하기 때문일 것이다. 그 차이는 라이브러리 선호도의 차이가 아니라 "누가 네트워크 요청을 통제하느냐"의 차이이다.

    Next.js를 사용하다보면 공식문서, 예제, 베스트 프랙티스 등을 접할 수 있는데 대부분이 axios를 사용하기 보다는 fetch를 사용하는 경우가 압도적으로 많다.

    이는 Next.js와 React의 Data fetching에 대한 철학 차이일 것이다.

    같은 React 기반임에도 서로 다른 이유는 렌더링 모델과 책임 주체가 다르기 때문이다.

    1. Next.js - 프레임워크가 요청을 관리한다

    Next.js에서 데이터 패칭은 렌더링 파이프라인의 일부이다.

    await fetch(url, {
      cache: 'force-cache',
      next: { revalidate: 60 }
    });

    이 코드 한 줄로 인해서 Next.js는 다음을 수행한다.

    • 요청 캐싱
    • ISR / SSR 제어
    • 서버 컴포넌트 요청 deduplication
    • 빌드 타임 / 런타임 구분
    • Edge / Node 실행 환경 최적화

    이걸 가능하게 하는 전제는 "Next.js가 fetch 호출을 직접 인지하고 분석할 수 있어야 한다" 이다.

    axios는 이 전제와 맞지 않는 것이다.

    2. React - 요청 관리는 개발자의 책임

    React에서는 어떻게 다를까?

    • SSR 없음 (또는 제한적)
    • 빌드 타임 데이터 패칭 없음
    • 프레임워크 레벨 캐싱 없음

    즉, HTTP 요청은 단순한 "Side effect" 일 뿐이다.

    그래서 React에서는 다음이 중요해진다.

    • 에러 처리 일관성
    • 인터셉터
    • 인증 토큰 자동 주입
    • 응답 포맷 통일
    • 재시도 / 타임아웃

    이렇게 봤을 때 fetch는 원시적(Low Level)으로 느껴지는 것이다.

    서버 컴포넌트에서 axios가 불리한 이유?

    Next.js에서 Server Component의 목표는 무엇인가?

    바로 "이 페이지를 만들기 위해 어떤 데이터가 필요하고, 그 데이터는 언제, 몇 번 요청해야하는지 프레임워크가 미리 알고 통제하겠다" 라는 것이다.

    그렇기 떄문에 다음을 전제로 한다.

    // 예시 1
    await fetch('/api/user');
    
    // 예시 2
    await fetch('/api/posts', {
      next: { revalidate: 60 }
    });
    • 같은 코드는 항상 같은 요청이다
      • [예시 1] 코드가 있다면 Next.js는 다음과 같이 생각한다
        1. 이 컴포넌트는  /api/user 를 요청한다
        2. 조건에 따라 바뀌지 않는다
        3. 그러면 미리 캐싱해도 되겠다!
    • 같은 fetch 요청은 한 번만 실행
      • [예시 1] 코드가 부모, 자식, 다른 Server Component 등 여러 곳에 있다면
        • 여러 곳에 있어도 Next.js는 "어차피 같은 요청이네? 한 번만 요청하고 결과는 공유하자!" 이렇게 생각한다
    • 캐싱 여부는 프레임워크가 판단
      • [예시 2] 코드를 보면 아래와 같이 Next.js는 생각한다. 즉, 개발자가 아니라 프레임워크가 판단한다
        1. 이 요청은 60초 캐시 가능
        2. 빌드 타임에 가져와도 됨
        3. 다음 요청은 재사용

    하지만 axios는

    • 내부 구현이 보이지 않는다. 그러다보니 Next.js 시점에서는 "지금 이 axios 호출이 언제, 어떻게, 몇 번 실행되는지 알 수 없다"
      • 내부에서 fetch를 쓸 수도 있고
      • http 모듈을 쓸 수도 있고
      • 언제 요청이 나가는지도 내부 로직에 달려있다
    • dedeupication / caching hook 불가
      • 요청 시점을 Next.js가 알 수 없다.
        • 만약 await fetch(url); 이라고 사용하면 호출 즉시 네트워크 요청을 한다. 그러나 axios.get(url); 이라고 사용하면 axios 내부에서 인터셉터 실행, 설정 병합, 조건 처리, 그 후 요청이 들어가는데 Next.js가 이 과정을 훔쳐볼 수 없다.

     

    Next.js의 fetch는 "일반적인 fetch"가 아니다?!

    제목에서 본 것처럼 Next.js의 fetch는 일반적인 fetch와 다르다.

    다시 말해, 표준 fetch API를 확장한다고 볼 수 있다.

    await fetch(url, {
      cache: 'force-cache',
      next: { revalidate: 60 }
    })

     

    이 한 줄의 코드로 다음을 동시에 처리할 수 있기 때문이다.

    • 요청 캐싱
    • ISR(Revalidation)
    • Server Component 간 요청 deduplication
    • 빌드 타임 / 런타임 요청 최적화

    그럼에도 불구하고 axios를 쓰는 이유는 명확하다.

    • 자동 JSON 파싱
    • 인터셉터
    • 에러 핸들링 통일
    • 구형 브라우저 호환

     

    그래서 등장한 ky

    ky란 fetch를 기반으로 한 초경량 HTTP Client 이다.

    사용방법은 아래와 같다.

    import ky from 'ky';
    
    const data = await ky.get('/api/posts').json();

     

    내부적으로는 fetch 사용, Promise 체인 정리, 자주 쓰는 패턴을 API로 캡슐화 등을 하고 있다.

     

    axios 사용에 편리함을 느꼈는데 Next.js를 사용하면서 fetch를 사용하다보면 불편함이 느껴질 때가 있다.

    이 때 ky를 사용하면 fetch의 불편함을 줄여준다.

     

    1. JSON Parsing을 명확히 분리

    const data = await ky.get(url).json();

     

    여전히 두 단계지만 그래도 의도가 더 명확하게 보인다.

     

    2. 에러를 자동으로 throw 해준다. fetch와 비교해보자.

    // fetch
    const res = await fetch(url);
    if (!res.ok) throw new Error();
    
    // ky
    const data = await ky.get(url).json();

     

    4xx / 5xx 의 경우 자동 reject

     

    3. 인터셉터(Hooks) 지원

    ky.create({
      hooks: {
        beforeRequest: [
          request => request.headers.set('Authorization', 'Bearer token')
        ]
      }
    });

     

    axios와 가장 비슷한 지점이다.

     

    ky는 fetch를 대체할 수 있을까?

    클라이언트 컴포넌트 / 일반 SPA 기준으로는 대체 가능하고 오히려 더 깔끔한 선택일 수 있을 것이다.

    그러나 Next.js의 Server Component 기준에서는 완전한 대체는 불가능해보인다.

    그 이유는 ky는 Next.js의 fetch 확장 옵션을 전달할 수 없기 때문이다.

    그러니 이런 결론을 내릴 수 있다.

    • 서버 컴포넌트 / 데이터 패칭 : fetch
    • 클라이언트 / API Wrapper : ky

     


     

    또 하나 fetch를 사용하다보면 axios를 사용할 때와 가장 차이가 나는 것 중 하나가 바로 await을 두 번 써야한다는 것이다.

    fetch를 사용할 때 await을 두 번 사용해야 하는 이유

    const response = await fetch(url);
    const data = await response.json();

     

    우리는 관성처럼 이런 방법으로 사용하고 있다. 하지만 이러한 구조는 우연이 아니다.

    왜 불편하게 두 번씩 사용하도록 만들었을까?

     

    이유1. fetch는 두 단계의 비동기 API 다

    Step1 : HTTP 요청

    await fetch(url)

     

    네트워크 통신을 하고 Response 객체 반환을 했지만 body는 아직 읽지 않은 상태

     

    Step2 : body 소비 + 파싱

    await response.json()

     

    Read Stream, JSON 파싱, 또 다른 비동기 작업

     

    .

    .

    .

     

    그렇다면 왜 이렇게 설계가 되었을까?

    응답 body는 스트림이고 큰 데이터도 처리 가능해야한다. 

    그리고 json / text / blob 등 소비 방식 선택이 가능하다 그래서 fetch는 의도적으로 Low Level API로 만들었다.

    ky, axios 등은 이를 "감싸는 역할"을 할 뿐이다.

     

    반응형
Designed by Tistory.