[#1 - Pre-Rendering & Data Fetching]
우리가 이전에 React를 사용해서 Data Fetching을 하는 과정을 되짚어보면 다음과 같이 정리할 수 있다.
1. 호출한 데이터를 보관할 State 생성
2. Data Fetching을 위한 함수 생성
3. Component의 Mount 시점에 Data Fetching 함수 호출
4. Data Loading 과정의 예외 처리
React는 CSR 방식으로 동작하고 위의 과정은 Component가 Mount 되는 시점에 Data Fetching을 실행하기 때문에 사용자가 데이터를 받기까지 시간이 오래 걸린다는 단점이 있었다. 첨언하자면 CSR로 인해 느린 FCP에 더해 그 이후 Data Fetching을 실행하기 때문에 데이터를 받기까지 시간이 오래 걸리게 되는 것이다.
하지만 NextJS에서는 SSR 방식과 더불어 Pre-Rendering 과정에서 Data Fetching을 통해서 React의 느린 FCP를 보완하고 Data Fetching을 미리 실행하기 때문에 더 빠르게 화면을 보고 데이터를 더 빠르게 받아볼 수 있게 된다.
간단히 정리해보자면 다음과 같다.
React App의 Data Fetching | Next App의 Data Fetching |
Component Mount 이후에 발생 | Pre-Rendering 중에 발생 |
Data Fetching 시점이 늦어짐 | Data Fetching 시점이 매우 빨라짐 |
하지만 여기서 의문을 가질 수 있는 부분이 존재한다. NextJS가 아무리 빠른 시점에 데이터를 가져온다고 하여도 프로젝트 내에서 받아와야 하는 데이터의 양이 정말 방대하다면 Pre-Rendering 과정에서 많은 시간을 소요하여 React 보다 더 비효율적인 상황이 발생할 수도 있지 않을까라는 의문을 가질 수 있는 것이다.
NextJS는 이런 상황도 방지할 수 있도록 프로젝트를 빌드하는 시점에 미리 Pre-Rendering을 통해 Data Fetching을 실행하여 두는 방식이 존재한다. 사용자로부터 웹 페이지 접속 요청이 들어왔을 때 빠르게 Pre-Rendering을 통해서 데이터를 가져오는 방법도 있는 반면 데이터의 양이 방대한 경우 사용자의 요청이 있기 전에 프로젝트 자체를 빌드하는 시점에 Pre-Rendering을 통해 데이터를 가져오는 방법 또한 존재한다. 이 방법을 SSG라고 하는데 이 부분은 추후에 다루어 보도록 하자.
정리하자면 NextJS는 다음과 같이 3가지의 Pre-Rendering 방식이 존재한다.
SSR(Server Side Rendering) | SSG(Static Site Generation) | ISR(Incremental Static Regeneration) |
서버 사이드 렌더링 | 정적 사이트 생성 | 증분 정적 재생성 |
요청이 있을 때 마다 사전 렌더링 방식 | 빌드 타임에 페이지 사전 렌더링 방식 | - |
[#2 - SSR(Server Side Rendering]
SSR(Server Side Rendering)
- SSR이란 CSR 방식의 단점을 보완한 Pre-Rendering 방식으로 사용자가 페이지 접속 요청을 보낼 때마다 서버에서 JS파일을 실행하여 렌더링을 해주는 방식이다. React의 동작 방식인 CSR은 빈 HTML 파일에 JS 파일을 다운 받아 브라우저에서 렌더링을 해주는 과정을 거치기 때문에 동작 시간이 오래걸리는 반면, SSR은 접속하고자 하는 페이지의 JS 파일을 미리 다운 받아 HTML 파일을 생성하여 브라우저에 보내주기 때문에 사용자가 더 빠르게 완성된 웹 페이지를 받아볼 수 있는 장점이 있다. 또한 페이지 요청이 올 때마다 서버측에서 페이지를 제작하여 보내주기 때문에 페이지 내부의 데이터를 항상 최신으로 유지할 수 있다는 장점도 존재한다. 하지만 백엔드 서버로부터 데이터를 가져오는 과정이 길어지는 경우 페이지 전체의 동작이 지연된다는 단점이 있다.
getServerSideProps
getServerSideProps란 NextJS 내의 특정 컴포넌트를 SSR 방식으로 Pre-Rendering 하고자 할 때 사용하는 함수이다. 특정 컴포넌트의 위에 getServerSideProps를 선언해주며 export 처리해주면 NextJS Framework 자체적으로 인식하여 해당 함수를 본 컴포넌트보다 먼저 실행되면서 컴포넌트에 필요한 데이터를 미리 받아올 수 있도록 해준다. getServerSideProps는 Pre-Rendering 과정에서 단 한 번만 실행이 되고 서버측에서만 실행이 되는 함수이다. 따라서 window 객체 같이 브라우저에서 사용 가능한 객체는 사용이 불가능하다. 또한 getServerSideProps 함수를 통해 필요한 데이터를 미리 받아오는 Pre-Rendering이기 때문에 본 함수는 props 객체를 반환해야한다. 코드 사용 방식은 다음과 같다.
import { InferGetServerSidePropsType } from "next";
export const getServerSideProps = () => {
const data = "Hello";
return {props: {data}};
};
export default function Home({data}: InferGetServerSidePropsType<typeof getServerSideProps>){
return (
<div>{data}</div>
)
}
위의 코드 예시에서는 임의로 data 변수를 생성하였지만 실제로는 getServerSideProps를 통해서 data를 미리 호출 받아 props 객체로 반환해준다. 이후 props 객체를 Component에서 props로 받아(위의 예시에서는 구조분해할당을 사용하였다) 사용할 수 있는 것이다.
추가적으로 위의 예시에서 볼 수 있듯이 props로 받는 객체에 대한 Type도 지정을 해주어야 한다. Type은 일반적으로 NextJS에서 기본적으로 제공해주는 Type을 사용하면 되는데 SSR을 통해 받은 데이터는 위와 같이 InferGetServerSidePropsType의 Generic 문법을 통해 InferGetServerSidePropsType<typeof getServerSideProps>와 같이 정의해줄 수 있다.
Server(Client Server) & Browser
NextJS에서 Page 역할을 하는 Component는 Server에서 한 번 사용자의 요청이 발생했을 때 Pre-Rendering이 되고 Browser에서 또 한 번 JS Bundle 형태로 전달이 될 때(Hydration) Rendering이 되어 총 2번 렌더링이 진행된다. 그래서 Component 내에서 console.log를 통해 데이터를 출력하면 터미널에 한 번 브라우저에 한 번 총 2번 출력이 되는 것을 알 수 있다. 따라서 앞서 언급하였듯이 Component에서도 마찬가지로 Server에서 렌더링이 되는 경우가 있기 때문에 window 객체와 같이 브라우저에서 사용이 가능한 객체들을 사용하면 오류가 발생한다.
하지만 사용이 불가능한 것은 아니다. 브라우저 측에서 사용할 수 있는 객체들을 사용하기 위해서는 Component가 Mount 되고 나서 사용하면 Server 측에 렌더링이 되었을 때는 사용하지 않을 수 있기 때문에 오류가 발생하지 않을 것이다. 따라서 브라우저에서만 사용이 가능한 객체를 사용하기 위해서는 Component 내에 useEffect 훅을 사용해서 Component가 Mount 되는 시점에 동작하도록 의존성 배열을 빈 배열로 부여하면서 window 객체 같이 브라우저용 객체를 사용하면 오류가 발생하지 않고 정상적으로 사용이 가능하게 된다.
getServerSideProps 활용하기
특정 Component를 SSR 방식으로 Pre-Rendering하기 위해서 getServerSideProps 함수를 사용한다고 앞에서 살펴보았다. 본 함수를 활용하는 방식의 예시와 Type 정의 등 몇가지를 알아보도록 하자.
1. context: GetServerSidePropsContext
getServerSideProps 함수를 사용할 때 NextJS에서 기본 제공되는 context 객체를 사용할 수 있다. context 객체 안에는 정말 많은 속성이 존재하지만 우리가 지금 단계에서 알아야 할 속성은 query와 params이다. 검색 기능을 구현하였을 때 Query String을 사용하게 되는데 사용자가 검색한 단어를 context.query.q를 통해 접근하여 url 요청 같은 기능에서 사용이 가능하다.
또한 Dynamic Routing을 구현하였을 때 URL Parameter을 볼 수 있는데 이것 또한 context.params.id를 통해 URL Parameter에 접근하여 해당 페이지에 대한 url 요청을 보내거나 고유 id 값으로 사용을 하는 것도 가능하다.
이 때 우리는 현재 TS를 사용중이기 때문에 context의 Type을 정의해주어야 하는데, context의 Type은 NextJS에서 제공해주는 GetServerSidePropsContext로 정의해주면 된다.
2. InferGetServerSidePropsType<typeof getServerSideProps>
앞서 살펴본 정보이기도 하지만 다시 한 번 학습하자면 우리는 getServerSideProps 함수를 통해서 Pre-Rendering을 통해 데이터를 받은 후 Component에 props로 전달해주어서 데이터를 활용하게 된다. 이 때 props의 타입을 정의해주게 되는데 NextJS에서 기본적으로 props에 대한 Type을 제공해주는데 InferGetServerSidePropsType와 Generic 문법을 합쳐서 전달받은 데이터인 props 객체의 타입을 정의해주어 사용하게 된다.
3. Promise<T[ ]>
이 Type 자체는 getServerSideProps 함수와는 직접적으로 연결되어 있지는 않지만 별도로 우리가 Data Fetching을 위한 TS 파일을 생성하여 데이터 호출을 위한 함수를 사용할 때 반환값에 대한 Type으로 정의되는 Type이다. async/await을 통해 Data Fetching을 하는 함수가 있을 때 해당 함수의 반환값은 Promise가 되므로 NextJS에서 제공해주는 Promise Type을 정의해주면 되지만 데이터를 올바르게 받아온 경우와 에러가 발생한 경우 다른 값을 반환하게 되기 때문에 Promise와 Generic 문법을 사용하여 Promise<T>와 같은 형태로 사용해주면 된다.
[#3 - SSG(Static Site Generation)]
SSG(Static Site Generation)
- SSG란 정적 사이트 생성이라는 뜻으로 SSR의 단점을 해결하는 Pre-Rendering 방식 중에 하나이다. 여기서 다시 SSR의 장점과 단점을 간단하게 살펴보면 SSR은 빠른 페이지 이동과 데이터의 빠른 최신화가 가능하다는 장점이 있지만 백엔드 서버로부터 데이터 요청이 지연되는 경우 웹 페이지 전체의 동작이 지연되고 멈추게 되는 단점이 있다. 이런 SSR의 단점을 보완하기 위해 SSG 방식을 사용하게 된다.
SSG는 프로젝트의 빌드 타임에 페이지를 미리 Pre-Rendering 해주는 방식을 말한다. 사용자가 웹 페이지에 접속을 하고 특정 페이지로의 이동 요청을 포함한 모든 요청이 발생하기 전, 프로젝트 자체가 빌드하는 시점에 백엔드 서버로부터 필요한 데이터를 모두 받아오고 JS Bundle 파일을 모두 Pre-Rendering 해두기 때문에 사용자의 요청이 발생하고 나서 추가적으로 서버에 요청을 보낼 필요가 없어지기에 굉장히 빠른 속도로 응답을 해줄 수 있게 된다. 따라서 FCP는 물론 TTI까지 굉장히 빠른 속도로 처리가 가능하게 되어 Pre-Rendering에 많은 시간이 소요되더라도 사용자의 요청에는 매우 빠른 속도로 응답이 가능하다는 엄청난 장점을 보유하고 있는 방식이다.
하지만 정적 사이트 생성이라는 이름에서 알 수 있듯이 한 번 렌더링 된 페이지에 대한 요청은 다시 보내지 않기 때문에 새로운 데이터의 반영에는 어려움이 있다는 단점이 존재한다. 빌드 시점에 페이지를 모두 정적인 상태로 렌더링 해두었기 때문에 빠르게 응답이 가능한 반면 새로운 응답에는 어려움이 있기 마련인 것이다.
따라서 SSG 방식은 페이지 내에 데이터의 변화가 거의 없는, 데이터의 요청이 필요하지 않은 페이지에서 사용하기에 용이하다고 할 수 있다.
getStaticProps
특정 컴포넌트가 SSR 방식으로 동작하게 해주기 위해서 우리는 해당 컴포넌트가 들어있는 파일의 상단에 getServerSideProps라는 함수를 사용해서 Data-Fetching과 같은 동작을 구현했다. SSG 방식으로 동작을 하기 위해서 굉장히 비슷한 방식으로 적용하면 된다. getStaticProps라는 함수를 SSR과 같은 방식으로 사용해주면 된다. Data-Fetching과 같은 로직은 그대로 유지하되 SSR로 동작을 하고 싶으면 getServerSideProps, SSG로 동작을 하고 싶으면 getStaticProps를 사용하면 되는 것이다. SSR을 적용해 보았기에 쉽게 이해가 가능하다.
하지만 우리는 TSX를 사용하고 있기 때문에 getStaticProps 함수가 반환하여 컴포넌트로 전달해주는 props의 타입을 정의해주어야 하는데 앞서 SSR에서의 props의 타입은 InferGetServerSidePropsType<typeof getServerSideProps>로 정의해주었다면 SSG의 props 타입은 InferGetStaticPropsType<typeof getStaticProps>으로 정의하여 NextJS와 TS가 자체적으로 Props의 타입을 추론하도록 Generic 문법을 사용해주면 된다.
이 때 올바르게 SSG 방식으로 동작하는지 확인하기 위해 getStaticProps 함수 내에 console.log로 임시 문구를 출력해보면 기존 SSR 방식과 동일하게 터미널에 문구가 출력되는 것을 볼 수 있다. 이러한 이유는 SSG는 프로젝트가 빌드되는 시점에 적용이 되기 때문에 올바른 동작을 확인하기 위해서는 npm run build로 프로젝트를 빌드하고 npm run start를 통해 제공 환경인 Production 환경에서 실행을 시켜보아야 확인이 가능하다. 프로젝트를 빌드시키면 다음과 같은 기록들이 터미널에 뜨게 되는데 사진을 보고 하나씩 살펴보자.
console.log로 getStaticProps 함수 내에서 확인차 출력해둔 문구가 빌드 시점에 출력이 된 것을 확인할 수 있다.
프로젝트가 빌드되며 각 파일, 페이지들에 대한 정보를 터미널에 출력 시켜주었는데 각 페이지 별로 앞에 기호가 붙은 것을 볼 수 있다. F, ○(빈 원), ●(채워진 원)이 있는데 각각이 의미한 요소는 하단에 보면 알 수 있다.
●(채워진 원)
- SSG로 동작하는 파일을 의미한다. 터미널에 prerendered as static(uses getStaticProps)로 의미를 표시해주고 있으며 getStaticProps 함수를 사용하여 SSG 방식으로 동작하는 파일을 의미한다.
F(Function)
- SSR로 동작하는 파일을 의미한다. 터미널에 정보에 따르면 server-rendered on demand라는 의미를 가진 기호로 요청에 따라 서버가 렌더링 해주는 파일이라는 의미이다.
○(빈 원)
- 아무것도 설정해두지 않은 파일을 의미한다. NextJS의 Default 설정 방식이 페이지에 적용된 것인데 여기서 prerendered as static content라는 문구를 보면 알 수 있듯이 NextJS는 기본적으로 페이지 Rendering을 SSG 방식으로 하는 것을 알 수 있다.
NextJS Default SSG
우리가 Query String을 사용하는 검색 기능이 있는 페이지의 경우 getStaticProps 함수를 사용할 수 없다. 그 이유는 Query String에 접근하기 위해서는 함수의 매개변수로 context 객체를 사용하는데 getStaticprops 함수의 context 객체에는 query 속성이 존재하지 않기 때문이다. 그 이유는 사용자가 요청하기 전에 페이지를 생성하기 때문에 사용자의 요청에 포함되는 검색 기능 자체가 SSG 방식에서는 적용이 불가능하기 때문에 애초에 SSG방식의 context 객체의 query 속성은 존재하지 않는 것이다.
따라서 NextJS는 페이지를 기본값으로 SSG 방식으로 렌더링을 해주기 때문에 이런 경우는 아무 렌더링 방식 설정 없이 컴포넌트만 생성을하고 컴포넌트 내에 로직을 생성하여 Client 측에서 Data-Fetching을 진행하면 결론적으로는 SSG 방식으로 렌더링이 가능하게 된다. 다만 별도의 로직은 useEffect 훅을 사용하기 때문에 Component가 Mount 되는 시점에 데이터를 불러오게 된다는 점은 참고해야 한다.
정리하자면 빌드 시점에 Data-Fetching이 불가한 페이지 같은 경우에는 Data-Fetching을 Client 측에서 진행해주면 된다. 또한 NextJS는 기본적으로 모든 페이지를 SSG 방식으로 렌더링을 한다.
SSG on Dynamic Route Pages
앞서 우리가 살펴 본 SSG 방식의 적용 유형은 Next의 Router에 따라 구현한 정적 경로 페이지들에 대해 적용한 방식이였다. getStaticProps 함수를 사용하여 NextJS에게 해당 파일과 컴포넌트는 SSG 방식으로 동작할 것이라고 알려주는 방식으로 처리하였는데 [id].tsx와 같이 동적 경로로 생성된 페이지들에게 동일하게 getStaticProps를 사용하였더니 페이지에서 오류가 발생하였다. Component의 props Type도 올바르게 지정해주었고 getStaticProps 함수 내에서 사용하는 URL Parameter가 담긴 context 객체의 타입 또한 GetStaticPropsContext로 올바르게 지정해주었음에도 오류가 발생하였다. 그 이유는 무엇일까?
동적 경로를 통해 이동한 페이지에서 발생한 오류를 살펴보면 "getStaticPaths is required for dynamic SSG pages~..."와 같이 명시되어 있다. 동적 경로 페이지에서 SSG 방식을 사용하기 위해서는getStaticPaths라는 함수가 필수라고 알려주고 있다. 여기서 알 수 있듯이 동적 경로 라우팅을 설정해둔 페이지에서 SSG 방식을 적용하기 위해서는 getStaticProps 함수뿐만 아니라 getStaticPaths를 통해서 이동할 경로를 빌드 시점에 미리 설정을 해주고 Pre-Rendering을 진행한다. 따라서 프로젝트의 동적 경로로 생성된 페이지의 getStaticProps 상단에 getStaticPaths 함수를 선언 후 사용해주도록 하자.
getStaticPaths
getStaticPaths는 객체를 반환한다. 반환되는 객체에는 두가지 속성이 존재한다.
paths
paths 속성은 배열을 속성값을 갖고 있다. 속성값인 배열 내에는 다시 객체들이 들어있는데 들어있는 객체는 params 속성을 갖고 {id: "1"}과 같이 객체 형태의 URL Parameter 값을 속성값으로 갖는다. 이 때 id 값이 동적 경로를 통해 접근 가능한 URL Parameter 값을 의미하고 그 값은 반드시 문자열 형태로 입력이 되어야 한다. 그래야만 NextJS Framework 자체가 인식이 가능하기 때문이다. paths 속성으로 반환해준 값에 대해서는 npm run build를 통해 프로젝트를 빌드한 후 빌드 메세지를 살펴보면 paths 속성으로 반환한 값은 SSG 방식으로 미리 Pre-Rendering 된 것을 확인할 수 있다. 아래 사진을 참고해보자.
fallback
fallback 속성은 특정 옵션 값을 갖고 있는 속성으로 앞서 정의해 준 paths 속성에 포함되어 있지 않은 경로 값에 대해서 페이지 이동이 발생하면 대체재로 사용되는 속성이다. false로 선언해주면 paths 속성에 없는 경로로 접근하였을 때 바로 에러 페이지(404.tsx)를 보여주게 된다. false 외에도 true, blocking Option도 존재하는데 fallback Option에 대해서는 다음에 다루어 보도록 하자.
Fallback Option
False
앞 단락에서 살펴본 옵션으로 paths 속성에 없는 경로를 요청하게 되는 경우 에러 페이지를 반환해주는 옵션이다.
Blocking
True,False 속성과 다르게 문자열 형태로 "blocking" 형태로 부여해주어야 하는 옵션으로 본 옵션이 설정된 경우 paths에 없는 페이지를 요청하였을 때 그 즉시 서버 측에 페이지 요청을 보내서 SSR 방식으로 처리를 해주는 방식이다. False 옵션은 존재하지 않는 요청이 들어왔을 때 NotFound 페이지를 반환하는 반면 Blocking 옵션은 요청이 들어온 경우 즉시 서버에 요청을 보내서 데이터를 받아오는 옵션인 것이다. 하지만 본 옵션을 사용할 때 주의해야 할 점이 있다. 만약 서버에 요청한 데이터가 응답을 주는데까지 오랜 시간이 걸리는 경우 사용자는 아무 동작도 할 수 없이 빈 화면을 보고 있어야 한다. 이런 경우 대체할 수 있는 옵션이 바로 다음에 나올 True 옵션이다.
True
False 옵션과 같이 Boolean 형태로 입력하는 속성이며 경로에 없는 페이지를 요청하였는데 요청한 페이지의 데이털르 받아오는데 오랜 시간이 소요되는 경우 사용하는 옵션이다. 본 옵션이 설정되었을 때에는 getStaticProps 함수의 반환값을 받지 않고 생략한 뒤 먼저 Component를 호출하여 렌더링 시켜준 후 Data-Fetching이 완료된 후 Component에 Props를 전달해주어 해당 페이지에 대한 정보를 렌더링해주는 방식으로 동작하게 된다. 정리하자면 Blocking 옵션과 동일하게 SSR 방식으로 동작하지만 props가 없는 Fallback 상태의 페이지를 먼저 반환하고 이후에 Data-Fetching이 완료된 후 Props를 후속으로 보내주어 페이지를 완성시키는 과정으로 진행이 된다고 보면 될 것 같다.
fallback 상태 : Component가 서버로부터 데이터를 전달받지 못한 상태
fallback 상태의 페이지 : getStaticProps로부터 받은 Props 즉, 데이터가 없는 페이지
isFallback(useRouter)
웹 페이지를 그려주는 Component에서 useRouter 훅을 사용할 때 useRouter의 isFallback 속성을 사용하면 해당 Component가 Fallback 상태일 때 반환해 줄 요소를 부여할 수 있다. 간단한 코드 예시로 살펴보자.
import {useRouter} from 'next/router';
export default function Home(){
const router = useRouter();
if(router.isFallback) return "로딩중입니다.";
return (
<div>Home</div>
)
}
위의 코드 예시와 같이 useRouter의 isFallback 속성에 반환값을 설정해주면 해당 웹 페이지가 데이터 로딩중과 같이 응답을 기다리고 있는 상태인 경우 설정한 값을 사용자에게 보여줄 수 있게 된다. React의 Suspense와 동일한 기능을 한다고 생각하면 이해가 쉬울 것 같다.
따라서 isFallback 속성은 getStaticPaths 함수에서의 fallback 옵션이 True인 경우 데이터의 요청이 완료되지 않은 페이지를 사용자가 받은 시점에 오류 상태가 아닌 로딩 상태라는 것을 알려주기 위해 사용하기에 용이한 속성이다.
export default function Book({book}: InferGetStaticPropsType<typeof getStaticProps>) {
if (!book) return "존재하지 않는 페이지입니다.";
return (
<div>{book.title}</div>
);
}
추가적으로 하나 더 살펴보자면 만약 데이터가 반환되지 않아서 위 코드와 같이 에러 문구를 반환하도록 구현해둔 Component에서 위와 같이 별도의 문구를 출력하지 않고 바로 프로젝트 내에 생성해둔 에러 페이지로 이동시켜주고 싶은 경우에 getStaticProps 함수 내에서 구현하는 방법이 존재한다. 함수 내에서 반환된 데이터가 없는 경우 {notFound: true}라는 객체를 반환해주면 되는데 코드로 확인해보도록 하자.
위의 코드와 같이 notFound 객체를 true 속성값과 함께 반환해주면 getStaticPaths의 fallback 옵션이 true인 경우에 데이터를 반환받지 못했을 경우 에러 페이지를 쉽게 보여줄 수 있게 되는 것이다.
[#4 - ISR(Incremental Static Regeneration)]
ISR(Incremental Static Regeneration)
- ISR이란 증분 정적 재생성이라는 뜻으로 웹 페이지의 렌더링 방식중에 하나이다. CSR, SSR, SSG에 비해 생소한 개념이기에 어렵게 느껴질 수 있지만 개념 자체는 어렵지 않은 방식이다. 결론부터 말하자면 ISR 방식은 SSR과 SSG의 장점들을 합쳐서 페이지의 렌더링을 더 최적화하고 효율적으로 하는 방식이다. 과정을 간단하게 설명하자면 처음 페이지의 렌더링 과정은 SSG와 동일하다. 하지만 SSG로 생성된 페이지는 사용자가 아무리 재요청을 보내도 서버 측에 새로운 페이지 요청을 보내지 않고 빌드 시점에 미리 생성해둔 페이지를 계속 반환해주는 방식이기 때문에 굉장히 빠른 속도로 페이지 렌더링이 가능하지만 새로운 데이터를 업데이트 하는데에 어려움이 있다는 단점이 있었다. 이러한 SSG의 단점을 보완하고자 사용하는 방식이 ISR이라고 이해해도 좋다.
ISR의 동작 원리에 대해 살펴보면 기본 동작은 SSG와 동일하다. 하지만 시간의 흐름에 따라 일정 시간이 지난 뒤에 사용자가 웹 페이지에 대한 요청을 보내면 SSR 방식으로 동작하여 새로운 데이터를 반영하는 것이 가능하도록 동작한다. 정리하자면 SSG 방식으로 빌드 시점에 생성된 웹 페이지를 사용하는 사용자가 새로운 데이터를 요청하였을 때 일정 시간까지는 정적인 페이지를 반환해주다가 특정 시점이 지난 후에 들어오는 요청부터는 SSR 방식으로 서버에서 데이터를 받아 SSR 방식으로 웹 페이지를 렌더링 해주는 것이다.
따라서 SSG 방식의 장점인 데이터의 양이 많아도 초기 렌더링이 매우 빠르다는 점과 SSR 방식의 장점인 최신 데이터 반영에 더해 빠른 페이지 이동을 유지하면서 장점들을 모아놓은 렌더링 방식이다.
ISR을 페이지에 적용하는 것은 매우 간단하다. SSG로 생성할 Component의 getStaticProps 함수의 반환값 객체에 한가지 속성을 추가해서 반환해주면 된다. 바로 revalidate라는 속성이다. revalidate는 재생성하다,재검증하다라는 뜻을 가진 영단어로 ISR 방식을 위해 직관적으로 설정이 가능한 속성이라고도 볼 수 있다. revalidate 속성의 속성값으로는 주기를 입력해주면 되는데 숫자형태의 값을 넣어주면 입력한 초 주기로 웹 페이지가 새로운 데이터를 받아와 브라우저에 렌더링 해주게 되는 것이다. 코드 예시를 살펴보면 다음과 같이 작성할 수 있다.
export const getStaticProps = async () => {
const response = await fetch(URL);
const data = await response.json();
return {
props: {data},
revalidate: 3
}
}
편의상 getStaticProps 함수만 작성하였는데 위 코드와 같이 반환하는 객체에 Component에 전달해줄 props 데이터와 더불어 revalidate 속성까지 추가해주면 해당 페이지는 3초 주기로 데이터를 렌더링 하게 된다.
ISR로 빌드되어 생성된 페이지의 빌드 메세지를 살펴보면 다음과 같이 ISR이 어느 주기로 적용되는지 알 수 있다.
페이지를 정적인 상태로 브라우저에게 제공함으로써 굉장히 빠른 속도로 렌더링이 가능하다는 장점과 더불어 일정 시간 주기로 최신 데이터를 갱신할 수 있다는 장점이 있기에 가장 강력하고 추천하는 Pre-Rendering 방식이라고 할 수 있겠다.
주문형 재검증(On-Demand-ISR)
앞서 ISR 방식의 장점과 더불어 사용 방식에 대해 알아보았다. 하지만 시간 기반의 ISR을 적용하기 어려운 페이지들이 존재한다. 예를 들어 커뮤니티의 게시글과 같이 시간의 흐름과 관계 없이 사용자의 행동에 따라 데이터가 업데이트 되는 페이지는 ISR을 적용하기 어려운 부분이 존재한다. 우리가 revalidate로 설정해둔 시간 내에 사용자의 동작이 발생하지 않을 수도 있고 시간 내에 새로운 데이터를 필요로하는 사용자의 요청이 발생할 수 있기 때문에 ISR을 적용하기 어려운 것이다. 이런 경우 사용할 수 있는 렌더링 방식이 주문형 재검증 방식이다.
주문형 재검증 방식이란 요청을 받을 때마다 정적 페이지를 재생성하는 방식을 의미한다. 동작 원리를 간단히 살펴보면 사용자가 데이터 업데이트에 관한 행동을 했을 때 revalidate 요청을 보내서 그 순간 페이지 재생성하게 된다. 결론적으로 주문형 재검증 방식을 사용하면 대부분의 페이지를 최신 데이터 상태를 유지하면서 정적인 페이지로 생성이 가능하게 되어 빠른 렌더링과 빠른 페이지 이동까지 모두 겸비한 최적화 된 페이지를 생성할 수 있게 되는 것이다.
적용 방식은 새로운 api 파일을 생성하면서 적용하게 된다. 기존에 적어두었던 revalidate 속성을 지운 뒤 api 폴더 내에 새로운 TS 파일을 생성하여 다음과 같은 코드를 작성해준다.
//revalidate.ts
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res:NextApiResponse
){
try {
await res.revalidate('/') //요청을 받았을 때 재생성할 페이지 경로
return res.json({revalidate: true}) //요청 성공 시 재성성 완료를 의미하는 객체 반환
} catch (error) {
res.status(500).send("revalidatioin Failed"); //요청 실패 시 서버 에러 500 반환과 문구 반환
}
}
올바르게 적용되었는지를 확인하려면 마찬가지로 npm run build를 통해 프로젝트를 빌드한 후 npm run start로 Production 환경을 열어준 뒤 확인해야 한다. 만약 프로젝트 내에 커뮤니티 같이 주문형 재검증 방식을 사용하고자 하는 페이지가 있다면 사용자가 데이터 요청을 하는 로직에 다음 API를 localhost:3000/api/revalidate로 설정하여 요청을 보낸다면 요청의 성공과 실패에 따라 특정 동작을 실행해 줄 것이다. 만약 요청이 성공하였다면 위 코드의 await res.revalidate('/') 코드가 동작하여 지정해둔 페이지를 재생성하여 재렌더링 해주는 방식으로 동작하게 된다.
임의로 위의 코드를 테스트하고자 하면 브라우저에 위의 API 주소를 입력하여 요청 성공 시 응답 객체인 { revalidate: true }를 받고 나서 적용할 페이지를 새로고침 해보면 페이지가 재생성되는 것을 알 수 있다. 주의해야할 점은 앞서 말했듯이 주문형 재검증 방식 역시 ISR이기 때문에 기본적으로는 SSG 방식으로 동작하기 때문에 반드시 프로젝트를 빌드를 해야 모든 과정이 확인이 가능하고 적용이 가능하다는 점이다.
'NextJS' 카테고리의 다른 글
[NextJS v.13~] - (1) App Router (1) | 2024.09.26 |
---|---|
[NextJS] - (4) SEO, Deploy (6) | 2024.09.19 |
[NextJS] - (2) Page Router (2) | 2024.09.10 |
[NextJS] - (1) NextJS (1) | 2024.09.10 |
[NextJS(v.14)] - (3) Data Fetching (0) | 2024.08.20 |