NextJS13에서 axios와 tanstack query는 유효할까

작성일: 2023.09.24

AI 요약

최근 프로젝트는 NextJS 13 app directory를 사용하며 공식 문서의 데이터 패칭 사례, caching 및 tanstack query 등을 다루고 있다.axios 사용 시 cache와 revalidate가 까다로워질 수 있음.

Contents

개요

최근 진행하는 프로젝트는 NextJS13 app directory를 사용하여 진행하고 있다.

부딪혀가며 배우는 중인데, 까면 깔 수록 자체 제공 기능이 다양하고 서버 컴포넌트가 디폴트이다 보니 기존 라이브러리나 접근법에서 더 신경 써 주어야 하는 것들이 많다고 느낀다.

그 중에서 최근 잘 못하기도 했고 정리도 안 된 데이터 패칭에 대해서 알아보고자 한다.

공식 문서

공식 문서에서 제안한 네 가지 데이터 패칭 사례를 살펴보자.

  1. 서버에서 fetch
  2. 서버에서 타사 라이브러리 사용
  3. 클라이언트에서 route handler 사용
  4. 클라이언트에서 타사 라이브러리 사용

1. 서버에서 fetch 사용

Caching

NextJS는 기본으로 제공하는 fetch API를 확장하여 사용한다. 즉, 기본 fetch API를 사용하면서 caching이나 revalidating을 커스텀 할 수 있다.

여기서 중요한 점은 React가 fetch에 대해 동일한 URL과 옵션을 가진 요청을 자동으로 memo한다는 점이다.

230924-213514

If you need to use the same data (e.g. current user) in multiple components in a tree, you do not have to fetch data globally, nor forward props between components.

때문에 NextJS 공식 문서에서는 위와 같이 렌더링 트리의 여러 구성 요소에서 동일한 데이터(예를 들면 현재 사용자 같은)를 요청하는 경우 전역적으로 데이터를 가져오거나, props로 전달하는 등의 노력이 불필요하다고 지적한다.

230924-213529

  • 첫 요청에 대해 MISS 처리되어 외부 데이터를 가져오는 과정을 거치고, 메모리에 SET 된다.

  • 동일한 렌더링 경로에서 후속으로 처리되는 함수 호출은 요청을 실행하지 않고 메모리에서 반환한다.

  • 렌더링 된 후에는 메모리가 reset되어 모든 request memoization이 지워진다.

  • 해당 기능은 NextJS 기능이 아닌 React의 기능이다.

  • 메모이제이션은 fetch request의 GET 메서드에만 해당한다.

    • 그러나 NextJS에서 확장이 되어서 그런지 POST 요청을 하는 fetch도 자동으로 caching 된다고 언급되어 있다.
    • 다만 메서드가 Route Handler 내부에 있는게 아니라면 해당되지 않는다고 한다.
    • 요청을 nextjs api handler로 처리해야 해당 캐싱의 이점을 볼 수 있다고 이해했다.

Revalidation

  • Time-based

    • 일정 시간이 지나면 데이터를 재검증
    • 최신성이 중요하지 않은 경우
    fetch('https://...', { next: { revalidate: 3600 } })
    
  • On-demand

    • 양식 제출 등의 이벤트 기반으로 재검증
    • 태그 기반 또는 경로 기반하여 데이터 그룹을 한 번에 재검증
    • 가능한 빨리 최신 데이터가 필요한 경우
    const res = await fetch('https://...', { next: { tags: ['collection'] } })
    
    // api route에 해당 태그를 심어두고 revalidate를 할 때 같은 태그에 대한 요청을
    // 한 번에 revalidate 할 수 있다는 것
    
    • 이를 위해서 태그와 웬만하면 자체 토큰을 세팅하고, 검증할 필요가 있다.
    // 토큰과 태그 검증 절차가 추가된 api route 예시
    import { NextRequest, NextResponse } from 'next/server'
    import { revalidateTag } from 'next/cache'
    
    // e.g a webhook to `your-website.com/api/revalidate?tag=collection&secret=<token>`
    export async function POST(request: NextRequest) {
      const secret = request.nextUrl.searchParams.get('secret')
      const tag = request.nextUrl.searchParams.get('tag')
    
      if (secret !== process.env.MY_SECRET_TOKEN) {
        return NextResponse.json({ message: 'Invalid secret' }, { status: 401 })
      }
    
      if (!tag) {
        return NextResponse.json(
          { message: 'Missing tag param' },
          { status: 400 },
        )
      }
    
      revalidateTag(tag)
    
      return NextResponse.json({ revalidated: true, now: Date.now() })
    }
    
    • 또한 path 기반으로도 해당 동작이 가능하다.
    import { NextRequest, NextResponse } from 'next/server'
    import { revalidatePath } from 'next/cache'
    
    export async function POST(request: NextRequest) {
      const path = request.nextUrl.searchParams.get('path')
    
      if (!path) {
        return NextResponse.json(
          { message: 'Missing path param' },
          { status: 400 },
        )
      }
    
      revalidatePath(path)
    
      return NextResponse.json({ revalidated: true, now: Date.now() })
    }
    
  • 아래와 같이 cache를 아예 처리하지 않거나 하는 커스텀이 가능하다.

fetch('https://...', { cache: 'no-store' })

2. 서버에서 타사 라이브러리 사용

Next.js is working on an API, unstable_cache, for configuring the caching and revalidating behavior of individual third-party requests.

  • NextJS에서는 타사 라이브러리의 cache 및 revalidate 동작을 처리하기 위한 API를 개발 중이라고 한다.

Tanstack Query

tanstack query를 nextjs13 app directory에서 사용하는 데에는 약간의 사전 준비가 요구된다.

아래 링크에서 그 내용을 다루고 있다.

SSR | TanStack Query Docs

  • initialData 방법을 이용하면 API를 요청하고 응답을 page에 전달한 다음 props → initialData 순으로 넣어주어야 한다. props drilling이 발생할 수 있고, 응답이 필요한 query가 여러개인 경우 일일이 넣어주어야 해 세팅이 간단하나 권장하지 않는다.
import { dehydrate, Hydrate } from '@tanstack/react-query'
import getQueryClient from './getQueryClient'

export default async function HydratedPosts() {
  const queryClient = getQueryClient()
  await queryClient.prefetchQuery(['posts'], getPosts)
  const dehydratedState = dehydrate(queryClient)

  return (
    <Hydrate state={dehydratedState}>
      <Posts />
    </Hydrate>
  )
}

두 번째 방법은 위와 같이 dehydrate와 Hydrate를 사용해 주어야 한다.

queryClient를 싱글톤 인스턴스로써 정의하고, 쿼리를 prefetch하고 이를 dehydrate 하여 page에 dehydrateState로 전달하면 된다.

위와 같이 설정하면 중첩 컴포넌트에서도 prefetch 했던 query key와 같은 key를 사용하면 SSR 시에 데이터가 유지된다.

즉 클라이언트에서 hydrate가 진행될 때 queryClient에서 dehydrate한 데이터를 바탕으로 hydrate를 하는 것이다.

tanstack query에서는 dehydrate를 시킨 다음 dehydrated state로 값을 넘겨주어야 cache로써 세팅이 되고 이를 서버사이드 렌더링에서 반환하여 동작한다.

이를 클라이언트에서도 hydrate할 때 dehydrated 된 query를 찾아 각각 hydrate 시켜준다.

Axios

위에서 알아 본 내용에 기반하여, axios를 사용하면 nextjs에서 제공하는 cache와 revalidate를 사용하기 까다로워진다.

import { cache } from 'react'

export const revalidate = 3600 // revalidate the data at most every hour

export const getItem = cache(async (id: string) => {
  const item = await db.item.findUnique({ id })
  return item
})

위 예시처럼 별도의 처리가 요구된다.

NextJS 13에서 별도의 fetch 라이브러리는 필요 없을 수 있다.

NextJS 처럼 고도화 된 프레임워크에서는 위와 같은 설정에 드는 노력이 과연 필요한가에 대한 물음이 생길 수 밖에 없다.

물론 Infinity Scroll 같은 특정 기능에 대해서 강력한 기능을 제공하는 경우(Tanstack Query)에 대해서 일부 도입을 고려할 수 있다.

fetch 라이브러리가 axios에서 제공하는 default config, interceptor 등의 활용을 위해서는 부가적인 노력이 요구될 수 있으나, 현재 단계에서 각 서드파티 라이브러리가 제공하는 SSR에 대한 지원은 NextJS와 자연스럽게 호환된다고 보기는 힘들 것 같다.

하지만 여전히 Tanstack Query와 RSC를 기반한 NextJS의 역할이 다르기 때문에 그 사용에 충분한 의의가 있다는 의견도 있다.

즉 중요한 점은, 구현하고자 하는 특정 부분에서 어떤 전략이 필요할 지를 사전의 정의한 후 적절한 기술을 도입할 필요가 있다는 것… 그런 점에서 내가 이미 작성한 여러 코드 중에 굳이 axios나 tanstack query를 도입할 필요가 없던 내용이 다수 존재한다는 것이다.

참고

React Query 와 SSR - React Query 라이브러리 코드보며 이해하기

Next.js v13 App Router로 제품 만들기: 이제 더 이상 Axios를 쓰지 않기로 했습니다

You Might Not Need React Query

next.js에서 react query가 필요할까?

Data Fetching: Fetching, Caching, and Revalidating

태그: