[Next.js] 동적 라우팅 새로고침 에러 (router.query가 undefined일 때)

hzn·2023년 4월 12일
0

PROJECT🏝

목록 보기
15/24
post-thumbnail

문제 상황

상세 페이지를 동적 라우팅([id].tsx)으로 만들고
각 페이지에 해당하는 데이터를 받아오는 API 요청에 페이지 경로( router.query 객체)를 이용하려고 했다.

pages/nearby/[id].tsx

export default function ProductDetail() {
  const router = useRouter();
  const id = router.query.id;

  const { data } = useQuery(['productDetail'], axios.get(`${REQUEST_URL}/api/boards/${id})
...

그러나 페이지에 접속하거나 새로고침 할 때 데이터를 불러오는 데 실패했다.
router.query.id가 undefined로 나오며 500 에러가 뜬다.

원인

비어있는 router의 query 객체

해당 페이지 컴포넌트에서 console.log로 router 객체를 찍어보면 router.query 객체가 비어있다.

  • router.query는 빈 객체({})
  • isReady는 false

Next.js의 Pre-rendering

  • 기본적으로 Next.js에서는 모든 페이지Pre-rendering 된다.
  • Pre-rendering에는 Static GenerationServer-side Rendering 두 가지 방식이 있다.

Static Generation : 요청 전에 이미 생성된 페이지

공식 문서에 따르면

During prerendering, the router's query object will be empty since we do not have query information to provide during this phase. After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.

  • Next.js는 페이지에 getServerSideProps 또는 getInitialProps가 있는 경우 페이지를 요청할 때마다 렌더링한다. (Server-side Rendering.)
  • 그 외의 경우에는 페이지를 정적 HTMLPre-rendering해서 자동으로 정적 최적화(statically optimize)를 시킨다. (Static Generation)

Static Generation 방식의 페이지

  • 요청 전에 build 단계에서 페이지를 미리 렌더링해서 요청이 올 때마다 재사용한다.
  • 데이터 역시 build 시점에 받아온 데이터를 그대로 재사용한다.
  • Static Generation 방식으로 만들어진 페이지는 렌더링하는 동안(Hydration이 끝날 때까지도) 쿼리 정보를 알 수가 없다. (어떤 페이지를 요청할 지 아직 알 수 없으므로)

  • 따라서 데이터 요청 시 쿼리 정보를 담아서 요청을 보내야 하는 해당 페이지인데, 아직 쿼리 정보가 없는(어떤 페이지를 요청할 지 알 수 없는) build 시점에 데이터 요청을 보내서 만들어지는 방식으로 페이지가 제공되고 있기 때문에 발생한 문제이다.

참고 : query 객체 업데이트

다음과 같은 경우, Hydration이 끝난 후 query 객체에 경로 매개변수를 제공하기 위해 업데이트(렌더링)가 trigger된다.

  • 페이지가 동적 경로인 경우
  • 페이지의 URL에 쿼리 값이 있는 경우 등..

➡️ 실제로 쿼리 객체가 업데이트 되는 것을 볼 수 있지만, 보여지는 페이지는 여전히 요청 전 build 단계에서 만들어진 페이지이므로 브라우저 상에서는 제대로 받아온 데이터가 있는 페이지를 볼 수는 없다

해결 방법

  • SSR 방식을 사용하면 페이지 요청 시마다 서버로 데이터 요청을 보낼 수 있다.

  • getServerSideProps 함수의 context parameter를 사용해 쿼리 정보를 컴포넌트에 props(id)로 넘겨주고 컴포넌트에서 이를 이용해 상세 페이지 데이터를 요청 할 수도 있고, 아예 서버 쪽에서 상세 페이지 데이터를 요청해서 받아온 후 그대로 컴포넌트에 props로 넘겨줄 수도 있다.

  • 나는 react-query를 사용해 여러 옵션들을 사용하고 페이지 상세 데이터의 캐시를 관리하고 싶었으므로 쿼리 정보(id)와 페이지 상세 데이터(detailData) 모두 props로 넘겨주었다.

    • getServerSideProps 함수의 context parameter를 사용해 쿼리 정보를 컴포넌트에 props(id)로 넘겨주었다.
    • getServerSideProps 함수를 사용해 상세 페이지의 데이터를 서버 측에서 요청해 받아와서 컴포넌트에 props(detailData)로 넘겨주었다.

pages/nearby/[id].tsx

export async function getServerSideProps(context) {
  const { id } = context.params; // context를 사용해 만든 쿼리 정보

  try {
    const res = await getProductDetail(id); // 상세 페이지 데이터

    if (res.status === 200) {
      const datailData = res.data;
      return { props: { id, datailData } }; // 컴포넌트에 넘겨줄 props
    }
    return { props: { id } };
  } catch (error) {
    console.log(error);
    return { props: { id } };
  }
}                            
                           
export default function ProductDetail({ id, datailData }: productDetailType) { // 서버에서 받아온 props
  const { data } = useQuery(['productDetail'], () => getProductDetail(id), {
    initialData: datailData,
  });
...

getServerSideProps

  • 페이지에서 getServerSideProps 함수를 export하는 경우 Next.js는 getServerSideProps가 반환하는 데이터를 사용하여 페이지를 pre-render한다.
  • 이때 함수는 페이지 요청마다 실행되고 함수가 반환하는 데이터는 페이지 컴포넌트의 props로 전달된다.
  • 서버 측에서만 실행되며 브라우저에서는 실행되지 않는다.

레퍼런스

0개의 댓글