티스토리 뷰
1. Next.js란?
Next.js는 React 기반의 프레임워크로, 리액트에는 없는 서버 사이드 렌더링server-side rendering(SSR), 정적 사이트 생성static site generation(SSG), 증분 정적 재생성incremental static regeneration(ISR)과 같은 기능을 제공한다.
그렇다면 Next.js가 등장하게 된 배경은 무엇일까?
Next.js 등장 배경
1. Static Site에서 SPA로
과거의 대부분의 웹사이트들은 SSR과 MPA (Multi Page Application) 방식으로 동작했다. 하지만 페이지 전환시 깜박거리는 이슈로인해 사용자 경험이 좋지 못했고 동시에 스마트폰의 시대가 도래하면서 웹사이트를 사용할 때도 모바일 앱처럼 부드럽고 빠른 사용 경험을 원하는, 앱과 같은 사용성의 수요가 커져갔다.
이후 1998년부터 fetch API의 원조인 XMLHttpRequest API가 개발되어 HTML문서 전체가 아니라 JSON과 같은 포맷으로 서버에서 가볍게 필요한 데이터만 받아올 수 있게 되었다.
이 방식은 받아온 데이터를 자바스크립트를 이용해서 동적으로 HTML 요소를 생성해서 페이지에서 업데이트 하는 방식으로, 공식적으로 2005년에 AJAX라는 이름을 가지게된다.
여기서 더 나아가 클라이언트 측에서 단순히 데이터를 받아오는 것에 그치지 않고, 웹페이지의 화면까지 클라이언트에서 렌더링하자는 요구가 생겨났다. 즉, 서버에서 HTML을 전부 만들어 보내는 대신, 서버는 데이터만 보내고 클라이언트는 이 데이터를 이용해서 화면을 구성하는 방식이 도래하게 되었다.
2. SPA (Single Page Application)의 등장
이런 요구에 응하면서 등장한 것이 흔히 SPA 3대장이라고 일컫는 React, Angular, Vue와 같은 라이브러리 및 프레임워크이다. 이들로 인해 최근까지 CSR(Client Side Rendering)이 가능한 SPA(Single Page Application)의 전성기가 이어져왔다.
하지만 욕심은 끝이 없는 법!
이런 CSR + SPA 방식도 몇 가지 문제가 발생하기 시작한다.
1. 초기 로딩이 느리다. (TTV 시간 증가)
2. SEO(Search Engine Optimization)이 잘 안된다.
(구글, 네이버등의 검색엔진은 서버에 등록된 웹사이트를 하나씩 검사하면서 html 문서를 분석해 해당 웹사이트의 내용을 판단하고 사용자가 빠르게 웹사이트를 검색할수있도록 도와준다.
이때 CSR에서 사용되어지고있는 html body는 텅텅 비어져있기때문에 검색엔진들이 CSR로 작성된 웹페이지를 분석할때 아무런 정보도 남겨주지 않게 된다.).
3. SSR(Server Side Rendering) 도입
이후 CSR의 문제점을 극복하고자 이전의 static sites의 장점을 기반으로 한 SSR이 도입된다.
SSR은 웹사이트에 접속하면 서버에서 미리 필요한 데이터들을 모두 가져와서 만든 html 파일을 보내주어 사용자가 웹사이트를 볼수있게 한다. 이후 서버에 html 파일을 동적으로 제어할수있는 js파일을 요청해 받아오게되면 이때부터 사용자의 클릭과 같은 인터렉션을 처리 할 수 있게 된다.
SSR의 장점은 다음과 같다.
1. 페이지 로딩 시간이 빨라진다.
2. 모든 컨텐츠가 html에 담겨있기때문에 좀 더 효율적인 SEO(검색엔진최적화)가 가능하다.
SSR의 단점은 다음과 같다.
1. static sites에서 발생했던것과 동일하게, blinking(깜빡임) 이슈가 발생한다.
2. 사용자 인터렉션 (클릭 등) 이 발생하면 전체 웹사이트를 서버에서 다시 받아와야 하므로 좋지 않은 사용자 경험을 겪을 수 있다.
3. 클릭 시 매번 서버에 요청해 필요한 데이터로 html을 만들어야하므로 사용자가 많으면 서버에 과부하가 걸리기 쉽다.
4.사용자가 빠르게 웹사이트를 확인할수는 있지만, 동적으로 데이터를 처리하는 자바스크립트를 아직 다운로드 받지 못해 여기저기 클릭했는데 반응이 없는 경우가 발생할 수 있다. (TTI 시간이 증가한다)
*그렇다면 앞서 언급한 TTI 와 TTV은 무엇일까?
TTV(Time To View)는 사용자가 웹사이트를 보는데까지 걸리는 시간을 말한다.
TTI(Time To Interact)는 사용자가 클릭을 하거나 인터렉션이 가능하게 되는데 걸리는 시간을 말한다.
TTV, TTI 둘다 웹사이트 성능을 측정하는 데 중요한 지표로 사용된다.
1. CSR의 경우
CSR은 사용자가 웹사이트를 볼 수 있음과 동시에 인터렉션이 가능하다.
따라서 최종적으로 번들링 결과물로 사용자에게 보내주는 자바스크립트 파일을 어떻게 효율적으로 분산할수있는지, 어떻게 하면 사용자가 첫 페이지를 보기 위해 필수적으로 꼭 필요한 것들만 보낼수 있는지 고민해봐야 한다.
2. SSR의 경우
SSR의 경우 사용자가 웹사이트를 볼수있는 시기와 인터렉션이 가능한 시기 사이의 공백이 꽤 존재하게 된다.
그러므로 이 시간의 단차를 줄이고 어떻게 더 깜빡임없이 매끄러운 UI와 UX를 제공할수있을지 고민해봐야한다.
4. Next.js 등장 (SSG (Static site generation))
이후 CSR과 SSR의 장점을 잘 혼합하여 좀 더 강력하고 유연한 웹사이트를 만들 수 있도록 SSG를 지원하는 Next.js가 등장한다.
2. Next.js 동작 과정과 Pre-Rendering
Next.js를 더 잘 이해하기 위해 Next.js 에서 사용되는 렌더링 방식인 SSR, SSG, ISR의 차이에 대해 살펴보자.
Next.js는 렌더링 할 때 기본적으로 모든 페이지를 pre-rendering(사전 렌더링)을 수행한다.
pre-rendering이란 서버단에서 DOM 요소들을 build 하여 HTML 문서를 렌더링 해주는 것을 말한다.
프리렌더링 방법은 SSG와 SSR로 나뉘는데 차이는 HTML 생성시기이다.
우선 SSG는 빌드시에 HTML을 만들고 각각의 요청을 재사용한다. SSR은 각각의 요청 시에 HTML을 만든다.
기본적으로 SSG가 SSR보다 높은 성능을 가지고 있어 Next.js에서는 SSG를 사용하는 것을 권장하고있다.
1. SSR (Server-Side-Rendering)
SSR은 유저가 페이지를 요청할 때 마다 HTML 문서가 생성된다. SSR을 사용하기 위해서는 getServerSideProps 함수사용한다.
어떤 상황에서 사용될까?
항상 최신 상태를 유지해야하는 웹 페이지나, 분석 차트 등 사용자의 요청마다 동적으로 페이지를 생성해서 다른 내용을 보여주어야 하는 경우에 사용된다.
getServerSideProps
Next.js는 pre-rendering 중 getServerSideProps 함수를 발견하면 컴포넌트 함수 호출 전에 getServerSideProps를 먼저 호출한다.
이후 API 통신을 통해 데이터를 받아온 후 컴포넌트에 props로 데이터를 전달하고, 함수는 매 요청마다 호출되어 서버에서 데이터를 가져온다. 따라서 정보들은 항상 최신상태로 유지될 수 있다.
export async function getServerSideProps(context) {
const res = await fetch(`https://.../data`);
const data = await res.json();
return {
props: {
listData: data,
},
};
}
const Main = ({ listData }) => {
<ul>
{listData.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>;
};
2. SSG (Static-Site-Generation)
Next.js에서는 SSR보다 SSG가 높은 성능을 가지기 때문에 SSG를 사용하는 것을 권장한다. 이유가 뭘까?
앞서 SSG와 SSR의 차이를 설명하면서 SSG는 빌드시에 HTML이 생성된다고 했다. 받아온 HTML은 퍼포먼스 향상을 위해 CDN에 캐싱되며 매 요청마다 생성되었던 HTML을 재사용한다. 따라서 서버에서 데이터가 바뀌어도 다시 통신을 하지않기 때문에 hydration 이후나 interaction이 있어도 같은 페이지를 보여준다.
(만약 블로그 글 같은 정적인 페이지를 제공하지만, 페이지를 업데이트 해야 할 일이 있다면 SSR을 고려하기 보다 성능상 이점(속도 최적화)를 위해 revalidate 옵션을 사용해주는 것도 좋은 방법이다.
(참고: https://byseop.com/post/@da66c257-ab86-4b2e-83c4-3fa455c21a3b))
SSG와 반대로 SSR은 매 요청마다 HTML을 생성하기 때문에 응답속도가 느리고 서버에 더 많은 부담이 가게 된다.
어떤 상황에서 사용될까?
상품 설명이나 블로그 같이 자주 내용이 업데이트 될 필요가 없는 정적인 컨텐츠에서 사용된다.
getStaticProps
Next.js에서 SSG를 사용하여 데이터를 받아오려면 getStaticProps를 사용하면 된다.
서버 측에서만 실행되는 함수로 클라이언트에서 실행되지 않는다. 이 함수는 API와 같은 외부 데이터를 받아서 Static Generation 하기 위한 용도이며 빌드 시에 딱 한 번만 호출되며, static file로 빌드된다.
아래 같이 getStaticProps를 사용해서 build를 하면 사전에 서버에서 API 호출을 해서 데이터를 담고, 그 데이터가 담긴 HTML을 생성하게 된다.
export async function getStaticProps() {
const res = await fetch(`https://.../data`);
const data = await res.json();
return {
props: {
listData: data,
},
};
}
const Main = ({ listData }) => {
<ul>
{listData.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>;
};
getStaticPaths
만약 동적 라우터(Dynamic Route)를 사용해서 pages/posts/[id].js 라는 파일을 만들었을 때,id에 따라서 다른 글을 보여주고 싶을 때는 어떻게 하면 좋을까?
id가 1인 글을 추가한다면 우리는 빌드 시에 id가 1인 post data를 불러와서 pre-render 해야한다. 만약 id가 2로 바뀐다면 id가 2인 글을 불러와서 pre-render 하는 것이 필요하다.
이러한 예시가 바로 page의 path가 외부 데이터에 의존하는 경우이다. 이를 구현하기 위해서는 getStaticPaths를 사용해서 pre-render 되기 원하는 path들을 명시해주면 된다.
export const getStaticPaths = async () => {
return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } },
{ params: { id: '3' } },
],
fallback: true,
};
};
export const getStaticProps = async ({ params }) => {
const id = params.id;
const res = await axios.get(`https://url/${id}`);
return {
props: {
listData: res.data,
},
};
};
const Detail = ({ listData }) => {
<ul>
{listData.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>;
};
호출 순서는 getStaticPaths -> getStaticProps -> Detail 순이다. getStaticPaths에서 리턴값으로 path에 1,2,3 페이지 번호를 지정한 후 getStaticProps에서 params.id를 읽어서 해당 게시글에 대한 데이터를 가져와서 페이지를 생성한다.
또한 getStaticPaths에서 정적으로 지정했기 때문에 1,2,3 페이지는 static file로 생성된다.
fallback
getStaticPaths 반환 값 중 fallback 값이 있다. 동적 페이지의 경우 빌드 시에 생성되지 않은 주소로 사용자가 요청을 보내는 경우가 존재하는데, 이러한 경우에 fallback 값에 따라 다른 대응을 할 수 있다.
fallback은 다음과 같이 true | false | blocking을 값으로 가질 수 있다.
1. true
- 빌드 시에 생성되지 않은 정적 페이지를 요청하는 경우, fallback 페이지를 제공한다.
- 서버에서 정적 페이지를 생성하고, 생성되면 사용자에게 해당 페이지를 제공한다.
2. false
- 빌드 시에 생성되지 않은 정적 페이지를 요청하는 경우, 404 페이지를 응답한다.
3. blocking
- 빌드 시에 생성되지 않은 정적 페이지를 요청하는 경우, SSR 방식으로 제작한 정적 페이지를 사용자에게 제공한다.
이후에 해당 주소로 요청이 들어오면 정적 페이지를 응답한다.
true와 blocking은 정적 페이지를 생성하고 사용자에게 제공한다는 부분이 유사하지만, true는 정적 페이지가 생성되는 동안 fallback 페이지를 제공한다는 점에 차이가 있다.
3. ISR (Incremental-Static-Regeneration)
SSG에 포함되는 개념이다. SSG와의 차이는 설정한 시간마다 페이지를 새로 렌더링 한다는 점이다.
SSG는 빌드 시에 페이지를 생성하기 때문에 데이터가 변경되면 다시 빌드를 해야하지만, ISR은 일정 시간마다 특정 페이지만 다시 빌드하여 페이지를 업데이트 한다.
어떤 상황에서 사용될까?
블로그와 같이 컨텐츠가 동적이지만 자주 변경되지 않는 사이트인 경우 ISR을 사용하는 것이 좋다.
export const getStaticProps = async ({ params }) => {
const id = params.id;
const res = await axios.get(`https://url/${id}`);
return {
props: {
list: res.data,
},
revalidate: 20,
};
};
const Detail = ({ list }) => {
<ul>
{list.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>;
};
SSG와 getStaticProps 사용 방법은 같지만 revalidate에 명시된 숫자(초)마다 페이지가 새로 렌더링 되는 차이가 있다.
정리
지금까지 SPA 환경의 React에서 Next.js 를 사용해서 SSG, SSR 렌더링 기법을 적용하는 법에 대해 배워보았다.
Next.js의 SSR, SSG, ISR
- Next.js의 큰 특징은 pre-rendering 방식을 사용하여 서버에서 미리 HTML 문서를 렌더링을 해주는 것이다.
- SSG와 SSR의 차이는 SSG는 HTML을 빌드 시에 생성하여 재사용하고, SSR은 요청 시마다 생성하는 것이다.
- ISR은 SSG에 포함되는 개념이며 설정한 시간마다 페이지를 새로 렌더링한다는 차이가 있다.
상황마다 적절한 렌더링 방식
- SEO 적용이 크게 중요하지 않거나 데이터 pre-rendering이 필요없다면 CSR
- 정적 문서로 충분한 화면이면서 빠른 HTML 문서 반환이 필요하다면SSG
- 컨텐츠가 동적이지만 자주 변경되지 않는 경우 ISR
- 매 요청마다 화면이 달라지면서 서버 사이드로 렌더링을 하고자 한다면 SSR
참고
https://medium.com/weekly-webtips/next-js-on-the-server-side-notes-to-self-e2170dc331ff
https://velog.io/@wiostz98kr/SSR-CSR-SSG-TTV-TTI-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC
'Client > Next.js' 카테고리의 다른 글
- Total
- Today
- Yesterday
- Target
- html
- react
- arguments
- tanstackquery
- GitHub
- 비제어 컴포넌트
- javascript
- currentTarget
- 코드잇 스프린트
- 코드잇스프린트
- 프론트엔드
- 스프린트프론트엔드6기
- 리액트
- 제어 컴포넌트
- 배열
- Next.js
- rest parameter
- CSS
- map
- 동기
- js
- innerhtml
- 중급 프로젝트
- hydrationboundary
- 유사배열객체
- 취업까지달린다
- 비동기
- Git
- 객체
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |