실무에서 Next js를 사용하는 프론트 개발자들은 SSG나 SSR을 많이 써봤을 것이고 물론 나도 그렇다! 하지만 getStaticPaths라는 Dynamic Routing에 대해서는 최근에 알게 되었고, 이 부분에 대해서 좀 더 깊이 얘기를 얘기를 나누어 볼까 한다.
getStaticPaths는 동적 경로를 사용하는 page에서 지정된 모든 경로를 정적으로 사전 렌더링하게 도와준다. 즉 다시 말하면 url을 동적으로 입력하여 쓸 수 있다는 것이다.
말이 어려우니 예시를 통해 확인해보자
// getStaticPaths
export const getStaticPaths: GetStaticPaths = async () => {
const paths = [{ params: { name: 'test'} }]
return { paths, fallback: false }
// getStaticProps
export const getStaticProps: GetStaticProps = async ({ params }) => {
return { props: { } }
}
getStaticPaths 함수를 만들고 paths를 return 하게 되면 name이 test인 하나의 페이지가 만들어지게 되고 경로를 test가 아닌 다른 값으로 페이지에 접근하려고 하면 404 page가 나오게 된다. 즉 다시말하면 getStaticPaths 에서 선언한 paths라는 배열에 있는 name 값에 해당하는 경로만 실제로 페이지가 만들어지게 되어서 위의 예시와 같은 상황이 2개로 나누어진 거라고 볼 수 있다. 마지막으로 fallback 값은 뒤에가서 설명을 하겠다.
getStaticPaths의 역활
미리 렌더링할 페이지의 경로를 반환해서 동적 경로를 생성할 때 사용한다.
getStaticProps의 역활
미리 렌더링할 페이지에 필요한 데이터를 가져오고 페이지가 정적 파일로 미리 생성할 때 사용한다.
주의사항
getStaticPaths는 무조건!!! getStaticProps와 같이 사용해야하고 getServerSideProps와는 같이 사용할 수 없다.
이유는 동적 경로를 가진 페이지를 미리 렌더링해야 하기 때문에 getStaticProps 와 같이 쓰이고 getServerSideProps는 빌드 타임 때 미리 만들어놓은 HTML 및 JSON 파일을 사용하는게 아니여서 그런 것 같다!
위의 간단한 예제로 연습을 해보았으니 이제 실제 프로젝트에 getStaticPaths를 사용해보겠다.
// getStaticPaths
export const getStaticPaths: GetStaticPaths = async () => {
const { data: storeInfo } = await clientAxios.get('/api/stores')
const paths = storeInfo.map((info: StoreInfo) => ({
params: { name: info.name },
}))
return { paths, fallback: false }
}
// getStaticProps
export const getStaticProps: GetStaticProps = async ({ params }) => {
const { data: storeInfo } = await clientAxios.get('/api/stores')
const targetStoreInfo = storeInfo.find(
(info: StoreInfo) => info.name === params.name
)
return { props: { targetStoreInfo } }
}
동적 경로가 제대로 동작이 되면 위의 이미지와 같이 화면에 잘 렌더링 된다!
사실 연습 코드와 실제 프로젝트에 적용한 코드의 차이점은 서버와 통신해서 data 값을 가져와 가공해 주는 부분 정도만 차이가 있어 별다를 게 없긴 하다! 정확하게 정리하자면 아래와 같이 말할 수 있다.
- getStaticPaths 함수가 실행되면 동적 경로를 생성하고 어떤 경로로 페이지를 미리 렌더링할지 결정한다.(반환된 path 값이 동적 경로가 된다.)
- 동적 경로가 정해지면 그 후에 getStaticProps 함수가 호출되어 getStaticPaths에서 반환된 값을 getStaticProps에서 params로 받아서 사용할 수 있고 해당 페이지에 필요한 데이터를 미리 가져와 페이지를 렌더링한다.
간단하게 정리!
- getStaticPaths로 경로 정의
- getStaticProps로 페이지 렌더링
- 짜잔!
(간단하게 보일진 몰라도 처음에는 함수 이름도 비슷하고 params는 어디서 나오는건지도 모르고 동작 순서도 좀 헷갈렸다.. ㅎㅎ)
getStaticPaths는 위에서 말했다시피 페이지의 경로를 정적으로 생성해둔다.
즉 이 과정들은 모두 빌드 타임에 동작하므로 필요한 모든 경로에 대해서 페이지를 미리 프리렌더링 해 둔다고 생각하면 된다.
그렇다면 서버에서 새로운 data가 추가될 때 마다 next를 다시 빌드하는 과정을 거쳐서 새로운 경로를 매번 만들어줘야하는 걸까? 라는 의문이 생긴다. 위에서 분명 getStaticPaths는 getServerSideProps와 같이 사용하는것이 아닌 getStaticProps와 사용해야된다고 했으니까 말이다..
이제 그러한 상황을 대비해 위한 방법을 알아보자
fallback은 위에 언급했던 상황이 발생했다는 가정하에 대비책으로 쓸 수 있는 값이다.
fallback에 들어가는 값은 boolean과 'blocking'이 있고 각각 어떻게 동작을 하는지 알아보자!
fallback 값이 false 이면 빌드 타임에 모든 경로를 만들고 만약 찾을 수 없는 경로에 도달한다면 바로 404 page를 띄우게 된다.
fallback 값이 true 이면 false와 마찬가지로 빌드 타임에 경로를 만들지만 존재하지 않는 경로에 접근하더라도 바로 404 page를 띄우지 않고 getStaticProps를 호출한다. 빌드 타임이 아니라 새로운 경로의 접근을 했을 때도 getStaticProps를 호출하여 해당 함수를 수행하고 존재하지 않는 경로가 있다면 그 때 404 page로 이동하게 된다. 그래서 getStaticProps가 실행되는 동안에는 useRouter 함수에 있는 isFallback 이 true가 되어 fallback UI가 보여지게 된다.
이해가 안될 수 있으니 코드로 확인해보자.
const StoreDetail: NextPage<Props> = ({ targetStoreInfo }) => {
const router = useRouter()
if(router.isFallback) {
return <div>Loading...</div>
}
return <div>{targetStoreInfo?.name}</div>
export default StoreDetail
export const getStaticProps: GetStaticProps = async ({ params }) => {
const { data: storeInfo } = await clientAxios.get('/api/stores')
const targetStoreInfo = storeInfo.find((info: StoreInfo) => info.name === params.name)
if(!targetStoreInfo) {
return {
notFound: true
}
}
return { props: { targetStoreInfo } }
}
}
- 확인되지 않은 새로운 경로를 입력한다
- getStaticProps 내부에 로직들이 실행되는 동안에는 isFallback 값이 true 가 되어 Loading 문구가 보이게 된다.
- 결국에 targetStoreInfo 값이 undefined가 되어 notFound의 값이 true 가 되어 404 page로 이동하게 된다.
- 또는 새로운 경로에 대한 값이 존재한다면 404 page가 아닌 targetStoreInfo.name에 대한 값이 보여지게 된다.
fallback값이 'blocking' 이면 새롭게 접근한 경로에 대해 HTML이 생성 될 때까지 즉 getStaticProps 함수가 return 될 때 까지 화면에 보여지는 UI를 가만히 blocking 한다는 뜻이다.
나머지 부분들은 fallback값이 true일 때와 비슷하다!