[Web] Next.js 새로고침 시 router.query undefined 되는 현상

sean·2022년 9월 16일
4

Web

목록 보기
8/22

🚨문제 상황

Next.js의 Link 컴포넌트와 Dynamic Routing 기능을 통해 해커톤 사이트의 각 참가자 페이지(/users/:id)와 각 프로젝트결과물 페이지(/projects/:id) 등을 아주 편리하게 개발했다.

그런데 특정 참가자 페이지나 특정 프로젝트 결과물 페이지를 Link 컴포넌트를 통해서 들어가면 정상적으로 모든 내용이 잘 뜨는데, 다음과 같은 경우 모든 내용이 다 undefined로 변하는 오류를 발견했다.

  • 해당 페이지에서 새로고침 을 하는 경우
  • Link 컴포넌트를 통해 클릭하여 접속하지 않고 주소창에 바로 /user/유저아이디 를 입력하여 접속하려는 경우

일단 원인부터 찾아야 해결 방법을 알 수 있으므로 원인을 찾아보았는데, 찾기까지 좀 오래걸렸다.
상황은 영상으로 보시죠!

😶‍🌫️문제의 원인 찾아나가기

Dynamic Routing 사용 방법

Next.js의 'Dynamic Routing' 기능을 사용하려면 우선 Next.js의 useRouter Hook을 사용하여 slug를 뽑아내야 한다. 제 프로젝트의 폴더 구조는 다음과 같습니다. (이 포스트에서 설명할 Dynamic Routing을 사용하는 부분만 적겠습니다!)

├──pages
│  ├── user
│       ├── [id].tsx
│  ├── projects
│       ├── [id].tsx
│       ├── edit.tsx
│       ├── list.tsx
│       ├── main.tsx
│  └── ... 다른페이지들

특정 유저정보 페이지는 다음 컴포넌트를 클릭하면 이동될 수 있게 해 놓았습니다.

<Link href="{`/user/${id}`}> View More </Link>

그리고 /user/[id].tsx에서는 useRouter를 사용하여 다음과 같이 id로 넘어온 slug를 뽑아내고, 이 id를 이용하여 서버에서 특정 유저의 정보를 가져옵니다.

import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';

function IndividualParticipant() {
  const router = useRouter();
  const { id } = router.query;
  
  const [individual, setIndividual] = useState<Participant>();
  
  useEffect(() => {
    try {
      axios.get(`/users/${id}`).then(res => setIndividual(res.data));
    } catch (e) {
      console.log(e);
    }
  }, []);
  
  ...
}
  
export default IndividualParticipant;

Next.js와 Server Side Rendering

공식 문서 보러가기

Next.js의 공식문서에 다음과 같이 Automatic Static Optimization 에 대한 설명이 적혀 있다.

Next.js automatically determines that a page is static (can be prerendered) if it has no blocking data requirements. This determination is made by the absence of getServerSideProps and getInitialProps in the page.

즉, 우리의 user/[id].tsx는 정적인(static한) 페이지가 아니라 id에 따라서 정보가 바뀌는 동적인 페이지인데, 내가 /user/[id].tsxgetServerSideProps 를 사용하지 않아서 Next.js는 이 페이지를 자동으로 static하다고 판단한 것이다.

그리고 더 밑으로 내려서 How it works 파트에 보면 다음과 같은 설명이 적혀 있다.

  • 만약 페이지 내부에 getServerSidePropsgetInitialProps가 있다면, Next.js는 페이지를 요청할 때마다 페이지를 렌더링 합니다. (Server-Side Rendering)
  • 페이지 내부에 위 두 함수가 없다면 Next.js는 자동으로 페이지를 static HTML로 prerendering하여 statically optimize 합니다.
  • Prerendering 하는 동안, router의 query 객체는 empty 상태일 것인데, 왜냐하면 prerendering 이라는 단계에서는 query 정보를 가지고 있지 않기 때문입니다.
  • Hydration 단계 이후에 Next.js는 query 객체의 route parameters에 대한 업데이트를 진행합니다.

(Next.js의 아주 특징적인 Hydration Phase도 다음에 포스팅 하겠습니다!)

원인을 찾았다

일단 위의 정보들을 종합해봤을 때, 다음과 같은 결론을 낼 수 있었습니다.

나는 getServerSideProps를 페이지 내부에 두지 않았다.

즉, prerendering의 결과로 router.query 객체가 비어있는 최소한의 html만을 받아서 id가 undefined인거고, 결국 id가 있어야 모든걸 서버에서 받아올 수 있는 구조이기 때문에 페이지 내부의 내용 모두 다 undefined가 된 것이다.

실제로 그렇다는 것을 useEffect 내부에 콘솔을 찍어서 볼 수 있었습니다.

🪄문제 해결하기

getServerSideProps 사용하기

관련 공식 문서 보러가기

페이지 컴포넌트 내부에서 getServerSideProps를 export하면 Next.js는 getServerSideProps 함수가 return한 데이터들을 이용하여 매 요청마다 pre-render 해 줍니다.

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

제일 놀라웠던 것은 백엔드에서 data를 fetching해오는 코드를 포함해서 server-side code 를 직접 getServerSideProps 내부에 작성해 줄 수 있다는 점이었습니다!

제 문제를 해결하기 위해서 getServerSideProps 함수의 파라미터로 무엇을 넣어줄 지를 또 공식문서를 보고 해결했는데, 저는 params 를 이용했습니다.
(context 파라미터의 종류 보러가기)

params : 만약 이 페이지가 'dynamic route'를 사용한다면, params 는 route parameters를 포함하고 있습니다! 만약 파일 이름이 [id].js라면, params{ id: ... } 와 같은 형식이 됩니다!

참고로, params 대신 query 를 이용해도 정상 작동합니다.

그래서 저는 다음과 같이 코드를 맨 밑에 추가하여 문제를 해결했습니다.

export async function getServerSideProps({ params: { id } }: { params: { id: string } }) {
    return {
        props: {},
    };
}

문제 해결 이후, 새로 고침을 몇백번 눌러도 그대로 원하는 데이터가 잘 뜬다😀
(아래 영상에서 이미지가 깨지는 이유는 이미지 서버를 내린 이후에 찍어서...ㅜ)

추가) router.isReady를 통해 확인하기

router.isReady는 router fields가 Client-Side에서도 update되어 사용 가능한 상태인지 아닌지를 boolean 형태로 리턴해주는데, useEffect 내부에서만 사용 가능합니다.

getServerSideProps 를 사용하여 문제를 해결하고 나서, 만약 제가 문제를 해결하지 않았을 때의 router.isReady와 문제를 해결한 이후의 router.isReady를 비교하고 싶어서 일부러 getServerSideProps 를 지우고 router.isReady를 콘솔에 찍어보았습니다.

  • getServerSideProps 추가하기 전 - false
  • getServerSideProps 추가한 후 - true

👀더 탐구해볼 영역

  • getServerSideProps와 함께, Next.js의 getStaticProps, getStaticPaths 에 대해서 알아보기
  • Next.js의 Hydration
profile
여러 프로젝트보다 하나라도 제대로, 깔끔하게.

0개의 댓글