← Dev Log

ky 라이브러리 세팅하기 / 서버 컴포넌트에서 prefetch하기

·4 min read

오늘 한 일

  • 카공실록 ky 세팅

카공실록 ky 세팅

ky에는 hooks로 요청 전처리, 응답 후처리 등 다양한 기능을 추가할 수 있다.

요청 전에 헤더에 토큰을 추가하고, 응답 후에 만약 토큰이 만료되었다면 다시 발급하거나 로그인 페이지로 이동하는 등의 기능을 추가하기 위해 훅을 사용했다.

// instance.ts
import { retryRequest } from './retryRequest'
import { setHeader } from './setHeader'
import ky from 'ky'

const api = ky.create({
  prefixUrl: process.env.NEXT_PUBLIC_API_URL,
  headers: {
    'Content-Type': 'application/json',
  },
  hooks: {
    beforeRequest: [setHeader], // 토큰을 헤더에 추가
    afterResponse: [retryRequest], // 토큰이 만료되었다면 재발급 or 로그인 페이지로 이동
  },
})

export default api

서버 컴포넌트에서 prefetch하기

서버 컴포넌트에서는 useQuery 같은 훅들을 사용할 수 없다. 그래서 서버 컴포넌트에서 prefetch로 데이터를 요청한 후 queryClient를 dehydrate해서 클라이언트로 보내줘야 한다.

이 과정을 간단하게 하기 위해 HydrationProvider 컴포넌트로 감사서 사용하기로 했다.

// HydrationProvider.tsx
import {
  dehydrate,
  Hydrate,
  QueryClient,
  type QueryKey,
  type QueryFunction,
} from '@tanstack/react-query'
import { cache, PropsWithChildren } from 'react'

type HydrationProviderProps = {
  queryKey: QueryKey
  queryFn: QueryFunction
}

export const HydrationProvider = async ({
  children,
  queryKey,
  queryFn,
}: PropsWithChildren<HydrationProviderProps>) => {
  const getQueryClient = cache(() => new QueryClient())

  const queryClient = getQueryClient()
  await queryClient.prefetchQuery(queryKey, queryFn)
  const dehydratedState = dehydrate(queryClient)

  return <Hydrate state={dehydratedState}>{children}</Hydrate>
}
사용법
// 서버 컴포넌트
import Child from '../components/Child'
import { Keys, getPlace } from '@/apis/place'
import { HydrationProvider } from '@/providers/HydrationProvider'

export default async function TestPage({ params: { id } }: { params: { id: string } }) {
  const placeId = Number(id)

  return (
    <div>
      <HydrationProvider queryKey={Keys.place(placeId)} queryFn={() => getPlace(placeId)}>
        <Child />
      </HydrationProvider>
    </div>
  )
}
// 클라이언트 컴포넌트
'use client'
import { useGetPlace } from '@/apis/place'

export default function Child() {
  const { data } = useGetPlace(1)

  return (
    <div>
      <h1>Child</h1>
    </div>
  )
}
주의할 점

HydrationProvider 컴포넌트는 비동기 서버 컴포넌트이다. 그래서 사용하게 되면 이런 에러가 뜰 수 있다.

'HydrationProvider' cannot be used as a JSX component.
Its return type 'Promise<Element>' is not a valid JSX element.
Type 'Promise<Element>' is missing the following properties from type 'ReactElement<any, any>': type, props, keyts(2786)

공식문서에 따르면, 타입스크립트 버전 5.1.3 이상, @types/react 버전 18.2.8 이상을 사용해야고 나와있다.

만약 버전을 업그레이드 할 수 없는 상황이라면, 이 컴포넌트 위에 /* @ts-expect-error Async Server Component */} 주석을 추가하면 된다.

/* @ts-expect-error Async Server Component */
<HydrationProvider>

우리 프로젝트에서는 yarn berry를 사용하고 있어서 타입스크립트 버전을 업그레이드 한 후 yarn dlx @yarnpkg/sdks vscode로 워크스페이스의 타입스크립트 버전을 업데이트 해서 해결했다.


내일 할 일

  • TS 스터디 진행
  • gloddy 개발