Next.js 에서 router.query 사용시 type 정의해서 사용하기

@p-iknow 🎹 · March 12, 2023

next-js-logo

Code

코드는 링크의 샌드박스에서 확인하실 수 있습니다.

Background

nextjs 에서 pathParam 과 queryParam 값에 접근 하고자 한다. URI는 다음과 같다.

http://doamin/examples/params/1?queryKey=queryKeyValue

pathParam과 queryParam에 접근하는 코드는 다음과 같다.

export const UseQueryParamPage:React.FC<Props> = () => {
  const router = useRouter();
  const queryValue = router.query.queryKey;
  const id = router.query.id;
	return <div tw='flex justify-center items-center h-screen' >
    <div css={tw`text-center m-auto`}>id: {id}<br />queryKey2: {queryKey2}</div>
  </div>
}

pathParam 인 id 와 queryParam 인 queryValue 는 아래와 같은 타입으로 추론된다.

image-20230312224721441 image-20230312224745117

pathParam 의 경우 명시적으로 url 에 포함되어야 하고, SSR을 사용할 경우 router.isReady 상태와 무관하게 router.query 에는 pathParam 값이 담겨 있다.

queryParam 의 경우 queryParam 의 사용 용도에 따라 string | undefined , string[]| undefiend , string | string[] | undefined 로 사용될 수 있다.

Problem

pathParam 값이 undefined 가 아님이 보장됨에도 불구하고 router.query 의 값이 string | string [] | undefined 으로 추론되기 때문에 type narrowing을 위해 불필요한 분기문이 생긴다.

export const UseQueryParamPage:React.FC<Props> = () => {
  const router = useRouter();
  const id = router.query.id;
  const queryValue = router.query.queryKey;

  if (!isString(id) || !isArray(queryValue)) {
    return null
  }

	return <div tw='flex justify-center items-center h-screen' >
    <div css={tw`text-center m-auto`}>id: {id}<br />queryKey2: {queryValue}</div>
  </div>
}

image-20230312224857453

NextJs 의 타입은 router.isReadyfalse 될때, pathParam이 undefined 가 될 수 있는 점을 고려하여 string | string [] | undefined 타입으로 추론된다.

Solution

목표는 다음과 같다.

각 queryKey가 특정 타입으로 추론될 것이 확실한 경우 각 key에 맞는 타입으로 추론되게 한다.

usePathParam

import { useRouter } from 'next/router';

/**
* @description In the case of pathParam, there is no chance of it being undefined,
* so we can cast it to a string type.
*/
export type ParamKeys = 'id'|'id2';
export const usePathParam = (paramKey: ParamKeys) => {
  const { query } = useRouter();
  return query[paramKey] as string;
};

pathParam 의 경우 string 타입이 보장되므로 string 으로 타입 캐스팅해준다. image-20230312224929352 usePathParam 을 사용할 땐 위와 같이 key가 suggestion 된다. image-20230312224947458 pathParamidstring 으로 추론되고 불필요한 분기 코드를 삭제할 수 있다.

useQueryParam

export type UrlQueryParams = {
  'queryKey'?: stirng[];
};
export const useQueryParam = <T extends keyof UrlQueryParams>(queryKey: T) => {
  const { query } = useRouter();
  return (query as UrlQueryParams)[queryKey];
};

queryParam 의 경우 각 key에 따라 리턴타입이 달라져야 한다. 따라서 generic(T) 을 써서 각 key 에 따라 미리 설정한 타입이 추론되도록 한다. image-20230312225006625 useQueryParam 을 사용할 때 위와 같이 key가 suggest 된다. image-20230312225032602 queryValueUrlQueryParams 에서 정의한 대로 stirng[] 으로 추론된다. image-20230312225040666 결과적으로 불필요한 분기문을 삭제하고, 의도한대로 타입을 추론하여 사용할 수 있다.

Caveat

usePathParam, useQueryParam 은 query 의 타입이 확실하게 보장될 때 사용할 수 있다. 타입이 확실 보장되지 않는 경우에 위 hook의 사용은 런타임 오류로 이어진다. query 타입이 보장되지 않는 상황은 다음과 같다.

페이지에 getServerSideProps 또는 getInitialProps가 없는 경우 Next.js는 페이지를 정적 HTML로 prerendering 하여 page를 statically optimize 한다. prerendering 이 진행되는 시점에는 router query object 가 비어있게 된다. 따라서 prerendering 시점에 router.query.queryKey 는 undefined 가 될 수 있다. 따라서 이때는 router.isReady 상태를 확인하여 true 가 되었을 때 비어있는 queryKey가 업데이트 됬음을 보장받아야 한다. 보다 자세한 내용은 Next.js 의 문서를 참고하자.

@p-iknow 🎹
많은 것을 이해하고 싶습니다. 더 이해하기 위해 노력합니다.