nextjs에서도 기존 react에서 마찬가지로 client 환경에서의 데이터 fetch가 필요한데

해당 과정을 편하게 해줄 도구로

swr과 react query가 있다

이 둘의 특징을 표로 정리해보았다.

 

특징 SWR, React Query

개발사 Vercel (Next.js 개발사) TanStack
번들 크기 약 4KB (더 작음) 약 13KB
API 복잡도 간단하고 직관적 더 복잡하지만 기능이 풍부
설정 최소한의 설정 필요 더 많은 초기 설정 필요
캐싱 전략 단순한 stale-while-revalidate 전략 더 세밀한 캐싱 제어 가능
에러 처리 기본적인 에러 처리 더 강력하고 상세한 에러 처리
뮤테이션 기본 지원, 추가 설정 필요 내장 뮤테이션 지원
DevTools 공식 DevTools 없음 강력한 내장 DevTools 제공
Next.js 통합 더 나은 통합 (Vercel 제품) 좋은 통합, 약간의 추가 설정 필요
학습 곡선 낮음 중간 ~ 높음
성능 일반적으로 가벼움 기능이 많아 약간의 오버헤드 가능
사용 사례 간단한 데이터 fetching, 작은 프로젝트 복잡한 데이터 관리, 대규모 애플리케이션

 

위 특징들을 근거로 

  1. 간단한 API: SWR은 React Query에 비해 더 간단하고 직관적인 API를 제공합니다. 이는 빠른 개발과 유지보수에 도움이 됩니다.
  2. 가벼운 라이브러리: SWR은 React Query보다 더 작고 가벼운 라이브러리입니다. 이는 프로젝트의 번들 크기를 줄이는 데 도움이 됩니다.
  3. Next.js와의 통합: SWR은 Vercel에서 개발한 라이브러리로, Next.js와 매우 잘 통합됩니다. Next.js 프로젝트에서 특히 유용할 수 있습니다.
  4. 충분한 기능: 대부분의 일반적인 데이터 fetching 시나리오에 대해 SWR은 충분한 기능을 제공합니다. 캐싱, 자동 재검증, 페이지네이션 등의 기본적인 기능들을 포함하고 있습니다.
  5. 학습 곡선: SWR은 React Query에 비해 학습 곡선이 낮습니다. 이는 팀 전체의 생산성을 높이는 데 도움이 될 수 있습니다.

위와 같은 이유로 이번 프로젝트에서 SWR을 사용하기로 했다

실제로 현재 npm 사용량을 비교해보면

출처: https://npmtrends.com/react-query-vs-swr

 

와 같은 결과를 볼 수 있는데 이미 swr이 react-query의 사용자 수를 뛰어넘고 있었다

 

dev tool이 지원이 안되는게 아쉽지만 일단 한번 써봐야겠다


SWR의 캐시 구조

SWR은 기본적으로 메모리 내 캐시를 사용합니다1. 이 캐시는 JavaScript의 Map 객체를 기반으로 하며, 키-값 쌍으로 데이터를 저장합니다4. 현재 SWR의 캐시에는 크기 제한이 없습니다4.

가비지 컬렉션 부재의 영향

가비지 컬렉션 기능이 없으면 다음과 같은 상황이 발생할 수 있습니다:

  1. 메모리 사용량 증가: 사용하지 않는 데이터가 계속 캐시에 남아 있어 메모리 사용량이 증가할 수 있습니다.
  2. 성능 저하: 캐시 크기가 계속 커지면 캐시 검색 및 관리에 더 많은 시간이 소요될 수 있습니다.

대응 방안

SWR에서 가비지 컬렉션 기능이 없을 때 취할 수 있는 몇 가지 대응 방안이 있습니다:

1. 수동 캐시 관리

mutate 함수를 사용하여 더 이상 필요하지 않은 데이터를 수동으로 삭제할 수 있습니다

import { mutate } from 'swr'

// 특정 캐시 항목 삭제
mutate('/api/user', null, false)

// 여러 캐시 항목 삭제
const clearUserData = async () => {
  const keys = cache.keys()
  const userKeys = Array.from(keys).filter(key => key.startsWith('/api/user/'))
  await Promise.all(userKeys.map(key => mutate(key, null, false)))
}

2. 커스텀 캐시 프로바이더 구현

SWR의 provider 옵션을 사용하여 커스텀 캐시 프로바이더를 구현할 수 있습니다 이를 통해 캐시 크기 제한이나 만료 정책 등을 직접 구현할 수 있습니다.

const createCacheWithCleanup = (maxSize = 100) => {
  const cache = new Map()
  return {
    get: (key) => cache.get(key),
    set: (key, value) => {
      if (cache.size >= maxSize) {
        const oldestKey = cache.keys().next().value
        cache.delete(oldestKey)
      }
      cache.set(key, value)
    },
    delete: (key) => cache.delete(key)
  }
}

const config = {
  provider: () => createCacheWithCleanup(100)
}

3. 주기적인 캐시 정리

애플리케이션 레벨에서 주기적으로 캐시를 정리하는 로직을 구현할 수 있습니다

const cleanupCache = () => {
  const now = Date.now()
  cache.forEach((value, key) => {
    if (now - value.timestamp > MAX_AGE) {
      cache.delete(key)
    }
  })
}

// 주기적으로 cleanupCache 함수 실행
setInterval(cleanupCache, CLEANUP_INTERVAL)

4. 페이지 전환 시 캐시 정리

라우트 변경 시 특정 캐시 항목을 삭제하거나 전체 캐시를 초기화하는 방법도 고려할 수 있습니다.

import { useRouter } from 'next/router'
import { cache } from 'swr'

function MyApp({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    const handleRouteChange = () => {
      cache.clear() // 전체 캐시 초기화
    }

    router.events.on('routeChangeComplete', handleRouteChange)

    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router])

  return <Component {...pageProps} />
}

 

이러한 방법들을 통해 SWR에서 가비지 컬렉션 기능이 없더라도 효과적으로 캐시를 관리할 수 있습니다. 프로젝트의 규모와 특성에 따라 적절한 전략을 선택하여 구현하는 것이 중요합니다.

 

swr은 자동 gc기능이 지원이 안된다고 하는데 이런 환경에서 캐시가 계속 쌓이게 될 경우 성능의 저하가 발생할 수 있기 때문에 위와 같은 방법을 쓰면 해소할 수 있다고 한다. 작업하면서 신경써봐야겠다.

+ Recent posts