올해부터 마음을 다잡기 위해 개인 블로그를 만드는 프로젝트를 하고 있었다.
기획 단계를 건너뛰고 개발부터 들어가다보니 별 생각없이 React + vite 환경으로 진행했는데
로고 사진과 manifest.json
등 홈페이지에서 필요한 것들을 구성하다 보니 SEO 관련 걱정을 하지 않을 수 없었다.
2022년 후반부 쯤 누군가에게 "Google의 검색 엔진에서 SPA도 SEO가 되는 것 같더라" 라는 말을 들었다.
그 당시 아무렇지 않게 일 잘하는 구글을 칭찬하고 넘어갔는데 문득 궁금해서 해당 내용을 찾아보기로 했다.
Google 검색 센터에서 해당 내용에 대한 힌트를 조금 얻을 수 있었는데 상위 빨간색 문구를 보고 Next.js로의 Migration을 결심하게 되었다.
NextJS 공식 홈페이지를 참고해서 Next.js 13버전으로 진행하려 했는데 예전에 Next를 사용했을 때와는 많이 다른 구조를 가지고 있는 것 같아서 기초부터 공부하고자 해당 글을 남기기로 했다.
Next.js는 클라이언트가 웹 페이지를 요청하면 서버에서 미리 웹 페이지를 Pre-Rendering 하고 이에 대한 결과물인 HTML을 클라이언트에게 전송한다.
이후 번들링 된 리액트코드를 클라이언트에게 전송하며 클라이언트는 이를 받아 렌더링하며 Hydrate하게 된다.
하지만 웹 페이지를 Pre-Rendering 할 때 필요한 정보가 클라이언트에게 있거나 데이터 서버에서 받아와야 할 때 등 페이지 정보마다 다르게 진행해야 할 경우가 있다.
이를 위해 Next.js에선 Data-Fetching 기능을 내부에서 제공하게 되는데 자세히 보도록 하자.
내가 이해한 해당 함수의 진행은 다음과 같다.
요청 후 실행되어서 반환되는 데이터 값을 사용해 렌더링하므로 각 사용자에 대한 데이터를 렌더링해야 할 때 사용할 수 있다.
하지만 매 요청마다 실행되는걸 기다리기 때문에 성능 저하를 걱정해야 한다.
기본적으로 CDN에 캐싱되지 않으며 캐싱 기능을 사용하려면 따로 설정해주어야 하는데
캐싱 기능을 적용하는 상황이라면 아래의 함수들을 고려해 볼 필요가 있다.
페이지에서
function Page({ data }) {
return <>~~</>
}
// import type { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from "next";
// Function Type = GetServerSideProps
export async function getServerSideProps(context) {
// Prop (context) Type = GetServerSidePropsContext
const res = await fetch(`https://.../data`)
const data = await res.json()
// Return Type = GetServerSidePropsResult
return { props: { data } }
}
export default Page
내가 이해한 해당 함수의 진행은 다음과 같다.
function Page({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// import type { GetStaticProps, GetStaticPropsContext, GetStaticPropsResult } from "next";
// Function Type = GetStaticProps
export async function getStaticProps(context) {
// Prop Type = GetStaticPropsContext
const res = await fetch('https://.../posts')
const posts = await res.json()
// Return Type = GetStaticPropsResult
return {
props: {
data,
},
}
}
export default Blog
이 기능은 Pre-Render를 위한 기능이 아니라, Static Generation 을 위한 기능이다
내가 이해한 해당 함수의 진행은 다음과 같다.
paths 값으로 빈 배열을 넘겨서 페이지 빌드를 연기할 수 있다고 한다.
// pages/posts/[id].js
// import { GetStaticPaths, GetStaticPathsContext, GetStaticPathsResult } from "next";
// Function Type = GetStaticProps
export async function getStaticPaths(context) {
// Prop Type = GetStaticPathsContext
// Return Type = GetStaticPathsResult
return {
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
fallback: false, // can also be true or 'blocking'
}
}
export async function getStaticProps(context) {
return {
props: { post: {} },
}
}
export default function Post({ post }) {
return <ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
}
공식 홈페이지에 따르면 아래와 같은 말이 있다.
pages 폴더에서 각 페이지 파일에서 독립 실행형 기능으로 내보내야한다.
=> _app, _document, _error에서 내보낼 수 없다
Next.js 개발 중 모든 페이지에 대해서 getServerSideProps를 실행해야 할 경우 어떻게 해결해야 할까?
Next.js의 예전 버전에서 이 getInitialProps 함수를 이용해서 컴포넌트에 필요한 Props를 해결해왔었다.
_app, _document, _error와 같은 페이지의 컴포넌트가 동작할 때 여전히 getInitialProps 함수가 있다면 실행되기 때문에 해당 함수를 지정해주어 모든 페이지에 대해서 실행할 수 있다.
Next 9.3 이후 버전에서 getServerSideProps, getStaticProps, getStaticPaths등의 기능이 나왔는데, Next.js에서 getInitialProps보다 위 함수들을 사용하길 권고하는 이유가 있다.
이 기능들은 Automatic-Static-Optimization 기능을 지원해준다고 한다
If getServerSideProps or getInitialProps is present in a page, Next.js will switch to render the page on-demand, per-request (meaning Server-Side Rendering).
If the above is not the case, Next.js will statically optimize your page automatically by prerendering the page to static HTML.
요약하자면 getServerSideProps 혹은 getInitialProps가 각 페이지에서 사용되지 않을 경우 Next.js가 이를 최적화하기 위해 Static Generation 페이지를 생성하고 제공하게 된다는 의미이다.
더 자세한 내용에 대해선 공식문서에서 확인할 수 있다.
만약 개발하는 프로젝트 중 Static Generation 을 통한 서비스 제공이 중요하다고 생각된다면 전역에서 getInitialProps를 선언해주는 것을 고려해주는게 좋다.