Next.js SSG, SSR, ISR
코로나 격리 기간에 할 게 없어서 간만에 Next.js 문서 정독을 했다. 모든 기능을 설명하기에는 글이 너무 길어질 것 같고, Next.js에서 가장 핵심이라고 할 수 있는 SSG, SSR, ISR에 대해 정리해보려고 한다.
Next.js
Next.js는 리액트 프레임워크다. 서버 사이드 렌더링이나 다이나믹 라우팅 등 리액트 어플리케이션을 개발할 때 필요한 기능들을 쉽게 사용할 수 있도록 도와주는 역할을 한다. 이는 Next.js에서 요구하는 조건에 맞춰서 코드를 작성하기만 하면 된다. 서버 사이드 렌더링과 클라이언트 사이드 렌더링의 차이는 아래 이미지로 확인할 수 있다.
출처 The Benefits of Server Side Rendering Over Client Side Rendering
Pre-rendering
Next.js에서 가장 중요한 컨셉 중 하나가 Pre-rendering이다. 각 페이지의 HTML을 미리 만들어 놓는다는 뜻이다. pre-rendeing이 발생하는지는 간단한 테스트를 통해 확인해 볼 수 있다.
-
먼저
npx create-next-app nextjs-example
명령어를 통해 Next.js 기본 프로젝트를 만들어주고, 실행한다. -
크롬 개발자 모드를 켜고, command + shift + p를 눌러서 disable javascript를 누르고 새로고침을 한다. 그러면 자바스크립트를 실행하지 않아도 미리 만들어둔 Html을 보여줌으로써 어플리케이션의 ui를 확인할 수 있습니다.
-
똑같은 방식으로
create-react-app
프로젝트를 만들어서 비교해 보면 둘의 차이를 알 수 있다.
Static Generation, Server-side Rendering
Pre-rendering은 크게 두 가지 방식으로 나뉠 수 있는데 Static Generation과 Server-side Rendering이다. 공식 문서에 아래와 같이 설명하고 있다.
Static Generation
is the pre-rendering method that generates the HTML atbuild time
. The pre-rendered HTML is then reused on each request.
Server-side Rendering
is the pre-rendering method that generates the HTML oneach request
.
다시 설명하면 Static Generation은 빌드 할 때 HTML을 생성해놓고, 요청이 들어오면 빌드 할 때 만들어둔 HTML을 보여준다. Server-side Rendering은 매 요청 시 HTML을 생성해서 보여준다.
getStaticProps
Next.js에서는 Static Generation 사용을 권장한다. 사용자의 요청과는 관계없이 보여줄 수 있는 페이지라면 Static Generation을 사용하면 된다. Static Generation 사용 방식은 간단하다.
아래와 같이 getStaticProps을 작성하면 Next.js는 빌드 할 때 getStaticProps로부터 리턴된 props를 사용하여 해당 페이지를 pre-render 한다.
export default function Home(props) { ... }
export async function getStaticProps() {
const data = { name: 'haha' };
return {
props: {
data
}
}
}
getStaticProps는 서버사이드에서만 실행된다. javascript bundle에도 포함되지 않는다. 따라서 브라우저를 통하지 않고, getStaticProps 내에서 바로 쿼리를 호출하여 데이터를 가져올 수 있다.
getServerSideProps
Server-side Rendering의 사용법도 비슷하다. getStaticProps 대신 getServerSideProps를 사용하면 된다.
아래와 같이 getServerSideProps를 작성하면 매 요청 시마다 getServerSideProps에서 리턴된 데이터를 가지고 해당 페이지를 pre-render 한다. getServerSideProps 또한 서버사이드에서만 실행된다.
getServerSideProps에서 받고 있는 context에는 다양한 데이터가 담겨있는데 자세한 건 API 문서를 살펴보자.
export async function getServerSideProps(context) {
const data = { name: 'haha' };
return {
props: {
data,
},
};
}
Incremental Static Regeneration(ISR)
Static Generation은 빌드 할 때 HTML을 만들어 두기 때문에 보통 정적인 데이터를 보여주고자 할 때 사용된다. 하지만 Incremental Static Regeneration을 사용하면 빌드 후에도 변경된 데이터를 가져올 수 있다. getStaticProps에 revalidate prop을 추가하면 된다.
import styles from '../styles/Home.module.css';
import axios from 'axios';
export default function Home({ data }) {
return (
<div className={styles.container}>
<h1>{data}</h1>
</div>
);
}
export async function getStaticProps() {
// 현재 시간을 가져오는 API
const res = await axios.get('https://worldtimeapi.org/api/ip');
return {
props: {
data: res.data.datetime,
},
};
}
위와 같은 코드를 작성하고 빌드 후 실행시키면 시간을 화면에 보여주는데 페이지를 새로고침해도 처음 빌드할 때 getStaticProps를 통해 데이터를 가져왔기 때문에 시간은 바뀌지 않는다.
이번엔 revalidate prop을 추가해 보자.
export async function getStaticProps() {
const res = await axios.get('https://worldtimeapi.org/api/ip');
return {
props: {
data: res.data.datetime,
},
revalidate: 60,
};
}
revalidate
를 추가하면 Next.js는 요청이 들어왔을 때, 최대 10초에 한 번 페이지 regeneration을 수행할 것이다.
revalidate를 추가했을 때 동작 방식은 다음과 같다.
-
페이지 요청이 들어오면 캐시 된 페이지를 보여준다.
-
최초 페이지 요청 후 60초 내로 들어오는 요청들도 캐시 된 페이지를 보여준다.
-
10 초 후 요청이 들어왔을 때도 저장된 페이지를 보여주고, Next.js는 백그라운드에서 페이지 regeneration을 수행한다.
-
페이지 regeneration이 성공적으로 됐을 경우 이전의 캐시를 없애고 새로운 페이지를 보여준다.
출처 Incremental Static Regeneration
이처럼 getStaticProps에 revalidate를 사용하면 데이터가 변경되더라도 계속해서 새로운 데이터를 보여줄 수 있다.
On-Demand Revalidation (Beta)
ISR를 통해 SSG에서 정적인 데이터만 보여준다는 단점을 보완할 수 있다. 하지만 revalidate
를 설정하면 특정 시간 내에는 무조건 동일한 데이터를 가진 페이지를 볼 수밖에 없다. 그 사이에 데이터가 변경된다고 하더라도 새로운 데이터를 확인할 수 있는 유일한 방법은 revalidate에 설정한 시간이 지난 후 누군가 페이지를 요청을 보내는 것뿐이다.
하지만 Next.js v12.1.0
부터는 On-Demand Revalidation을 지원한다. 원하는 시점에 캐시 된 페이지를 지우고 페이지 regeneration이 가능하다.
사용 방법은 기존에 있던 revalidate를 삭제하고, unstable_revalidate를 사용하면 된다. 먼저 pages/api/revalidate.js 파일에 아래와 같은 코드를 넣어준다.
unstable_revalidate에는 revalidate할 페이지를 넣어준다. 여기서는 인덱스 페이지에 적용할 것이기 때문에 '/'를 넣어줬다
// pages/api/revalidate.js
export default async function handler(req, res) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' });
}
try {
await res.unstable_revalidate('/');
return res.json({ revalidated: true });
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating');
}
}
그리고 Home 컴포넌트에서는 revalidate를 수행할 버튼을 만들고 위에서 만든 걸 호출하면 된다.
import styles from '../styles/Home.module.css';
import axios from 'axios';
export default function Home({ data }) {
function revalidate() {
fetch('/api/revalidate');
}
return (
<div className={styles.container}>
<h1>{data}</h1>
<button onClick={() => revalidate()}>Revalidate</button>
</div>
);
}
export async function getStaticProps() {
const res = await axios.get('https://worldtimeapi.org/api/ip');
return {
props: {
data: res.data.datetime,
},
// revalidate: 5
};
}
그럼 아래와 같이 동작한다. 버튼을 누르지 않고 새로고침을 하면 계속 동일한 시간이 나온다. revalidate 버튼을 누르고, 다시 새로 고침을 누르면 변경된 시간을 확인할 수 있다.
마무리
Next.js에서 가장 핵심이라고 할 수 있는 SSG, SSR, ISR 그리고 가장 최근에 나온 On-Demand Revalidation (Beta)까지 알아보았다. 참고로 전부 page 내에서만 사용이 가능하고, 페이지별로 다르게 적용할 수 있다.
예전에 처음 Next.js로 프로젝트를 시작했을 때는 getInitialProps밖에 없었던 걸로 기억하는데 그동안 많이 바뀐 것 같다. Next.js를 사용해 보지 않았다면 한번쯤 사용해 보는 것을 추천한다. 사용법도 간단하고 문서도 굉장히 잘 되어있기 때문에 초보자가 접근하기에도 무리가 없을 거라고 생각한다.