Next.js의 Link
컴포넌트와 Dynamic Routing 기능을 통해 해커톤 사이트의 각 참가자 페이지(/users/:id
)와 각 프로젝트결과물 페이지(/projects/:id
) 등을 아주 편리하게 개발했다.
그런데 특정 참가자 페이지나 특정 프로젝트 결과물 페이지를 Link
컴포넌트를 통해서 들어가면 정상적으로 모든 내용이 잘 뜨는데, 다음과 같은 경우 모든 내용이 다 undefined로 변하는 오류를 발견했다.
Link
컴포넌트를 통해 클릭하여 접속하지 않고 주소창에 바로 /user/유저아이디 를 입력하여 접속하려는 경우일단 원인부터 찾아야 해결 방법을 알 수 있으므로 원인을 찾아보았는데, 찾기까지 좀 오래걸렸다.
상황은 영상으로 보시죠!
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의 공식문서에 다음과 같이 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].tsx
에 getServerSideProps
를 사용하지 않아서 Next.js는 이 페이지를 자동으로 static하다고 판단한 것이다.
그리고 더 밑으로 내려서 How it works 파트에 보면 다음과 같은 설명이 적혀 있다.
- 만약 페이지 내부에
getServerSideProps
나getInitialProps
가 있다면, 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
추가하기 전 - falsegetServerSideProps
추가한 후 - truegetStaticProps
, getStaticPaths
에 대해서 알아보기