[Next.js] getStaticProps, getServerSideProps 제대로 이해하기
nextjs의 큰 장점 중 하나는, SSR(Server Side Rendering)이 쉽다는 것이다.
서버 사이드 렌더링은 한마디로, 서버에서 html을 렌더링해서 클라이언트에 전송해주는 것을 뜻한다.
php나 jsp등 서버에서 템플릿 엔진을 이용해 html을 전송해주는 방식들이 대표적인 서버 사이드 렌더링이라고 볼 수 있겠다.
그와 반대로 클라이언트 사이드 렌더링은 브라우저(클라이언트 측)에서 html을 그리는 방식이다.
nextjs의 경우 좀 더 정확히 말하면, CSR(Client Side Rendering)이 필요한 곳에 CSR을 SSR이 필요한 곳에 SSR을 적용하기 쉽게 되어있다. (Pre-rendering이라고 한다.)
getStaticProps나 getServerSideProps같은 경우도 서버 사이드 렌더링이 필요할 때 활용할 수 있는 도구이다.
코드를 보자.
import { useState } from "react";
const URL =
"https://www.7timer.info/bin/astro.php?lon=113.2&lat=23.1&ac=0&unit=metric&output=json&tzshift=0" as const;
export default function Home({ product }: any) {
const [count, setCount] = useState(0);
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
}}
>
<span>{product}</span>
<span>{count}</span>
<button onClick={() => setCount((prev) => prev + 1)}>count</button>
</div>
);
}
export async function getServerSideProps() {
const res = await fetch(URL);
const data = await res.json();
return {
props: {
product: data.product,
},
};
}
해당 코드를 빌드한 후 실행하면 서버로부터 아래와 같은 html을 받는다.
서버 사이드 렌더링 요청(getServerSideProps)이 들어온 부분(astro)은 이미 렌더링되어 클라이언트로 전송되었고,
useState가 사용된 count 부분은 초깃값(0)이 렌더링 되어 전송되었다.
이제 button을 통해 증가하는 count는 브라우저가 렌더링을 담당할 것이다! (정말 멋진 기능이다)
이게 바로 nextjs의 큰 장점 중 하나인 적절하게 SSR과 CSR를 손쉽게 하이브리드 하여 사용할 수 있다는 점이다.
하지만 기본적으로 nextjs는 클라이언트로부터 요청이 올 때 서버에서 Pre-rendering을 하므로,
브라우저에만 존재하는 기능을 사용하려 할 때, 오류가 생기는 경우도 존재하기는 한다.
이 경우 dynamic import 같은 기능을 사용해야 하는데, 본 게시글과는 성격이 맞지 않기 때문에 설명은 생략하고,
SSR을 지원하지 않아, dynamic import를 사용해 import 해야 했던 라이브러리에 대한 게시글이 있어 링크만 남겨놓겠다.
https://hackids.tistory.com/139
위 같은 장점들을 모두 활용하려면, nextjs는 서버가 꼭 필요하다.
여기서 말하는 서버는 정적 파일(html, css, js)들을 서빙만 해주는 웹 서버를 말하는 것이 아니다.
물론 nextjs도 정적 빌드 기능(next export)이 있기 때문에, 정적 빌드 후 S3 같은 파일서버를 이용해 호스팅하거나, express와 같은 웹서버를 열어 직접 서빙할 수도 있다.
하지만 이 경우 서버 사이드 렌더링을 활용할 수가 없는데, 이미 만들어진 파일들을 단순 서빙해주는 서버가 아니라,
API를 호출해 데이터를 페칭한다던가, 연산한다던가 API 엔드포인트를 만들든가 하는 기능들은
nextjs 서버를 이용해야 한다. (즉, next build를 통해 나온 서버 파일들이 필요하다는 뜻이다.)
이제 각각의 예를 보도록 하자.
Client Side Rendering
먼저 클라이언트 측에서 API를 호출하는 방식이다. (리액트를 사용해보신 분들은 굉장히 익숙한 방식일 것이다.)
import { useEffect, useState } from "react";
const URL =
"https://www.7timer.info/bin/astro.php?lon=113.2&lat=23.1&ac=0&unit=metric&output=json&tzshift=0" as const;
export default function Home() {
const [product, setProduct] = useState();
useEffect(() => {
const getData = async () => {
const res = await fetch(URL);
const data = await res.json();
setProduct(data.product);
};
getData();
}, []);
return <>{product}</>;
}
사용자의 브라우저에서 API를 호출했기 때문에 역시나 네트워크 탭에 요청한 API의 정보가 뜨는 것을 볼 수가 있다.
그리고 서버에서 product에 대한 정보 없이 html을 렌더링해주기 때문에, API 호출이 완료될 때까지 product는 보이지 않는다.
실제 해당 코드를 작동시킨 후 새로고침을 눌러보면 깜빡거리는 것을 볼 수가 있다.
이제 서버에서 보내준 html파일(위 스샷의 경우 localhost)을보자.
아래 code beautify 같은 서비스를 이용하면 코드를 포맷팅하여 볼 수가 있다.
https://codebeautify.org/htmlviewer
역시 body 태그 내에 아무것도 존재하지 않는다.
Server Side Rendering (getServerSideProps)
이제 getServerSideProps를 써보자.
const URL =
"https://www.7timer.info/bin/astro.php?lon=113.2&lat=23.1&ac=0&unit=metric&output=json&tzshift=0" as const;
export default function Home({ product }: any) {
return <>{product}</>;
}
export async function getServerSideProps() {
const res = await fetch(URL);
const data = await res.json();
return {
props: {
product: data.product,
},
};
}
아까와는 다르게 network 탭에 호출한 API가 뜨지 않는다.
당연하다, 서버에서 API를 호출하고 그 결과를 미리 html에 넣어 클라이언트로 보내주었기 때문이다.
그렇다면, 서버에서 보내준 html을 열어보자.
컨텐츠가 html에 포함되어있는 것을 볼 수 있다.
Server Side Rendering (getStaticProps)
마지막으로 getStaticProps를 사용해보도록 하자.
const URL =
"https://www.7timer.info/bin/astro.php?lon=113.2&lat=23.1&ac=0&unit=metric&output=json&tzshift=0" as const;
export default function Home({ product }: any) {
return <>{product}</>;
}
export async function getStaticProps() {
const res = await fetch(URL);
const data = await res.json();
return {
props: {
product: data.product,
},
};
}
마찬가지로 네트워크 탭에 아무것도 뜨지 않는다.
역시나 html 파일에 컨텐츠가 포함되어있다.
getServerSideProps vs getStaticProps
그럼 getServerSideProps와 getStaticProps는 뭐가 다른 걸까?
getServerSideProps는 클라이언트가 서버에 요청할 때마다 서버가 새로운 html을 그려서 보내준다.
즉 항상 최신화된 데이터를 바라볼 수 있다는 것이다.
getStaticProps는 서버가 미리 파일을 생성해둔 후 클라이언트가 서버에 요청할 때마다 html을 재활용한다.
즉 데이터가 바뀌지 않는다는 소리다.
그렇기 때문에 정적 빌드에서도 getStaticProps는 여전히 유효하게 사용할 수가 있다. (next export 시에 데이터를 페칭해 파일을 만든다)
하지만 getStaticProps에는 revalidate라는 옵션이 있는데, 해당 옵션을 줄 경우 일정 시간이 지나면 서버에서 다시 html 파일을 생성한다.
즉, 어느 정도 데이터를 최신화할 수 있다는 얘기인데, 당연하게도 next export로 정적 빌드를 한 경우에는 적용할 수 없다.