[#1 - Full Route Cache]
Full Route Cache
Full Route Cache란 Next 서버 측에서 빌드 타임에 특정 페이지의 렌더링 결과를 캐싱하는 기능을 말한다. Page Router 버전의 SSG 방식과 유사한 느낌이다. 프로젝트 빌드 타임에 Pre-Rendering을 진행하며 백엔드 서버로부터 요청했던 데이터들을 모두 받아와 데이터 캐싱도 Set하여 완료해두고 페이지 렌더링을 모두 완료한 뒤 Full Route Cache로 렌더링 완료된 페이지 자체를 캐싱해두는 것이다. 그에 따라 사용자의 페이지 초기 접속 요청이 발생하였을 때 백엔드 서버에게로 데이터 요청과 응답받은 데이터를 캐싱해두고 페이지를 렌더링 해두는 과정 필요없이 바로 캐싱되어있던 완성된 페이지를 사용자에게 매우 빠른 속도로 보여줄 수 있는 것이다.
하지만 Next에 만들어둔 모든 페이지가 Full Route Cache가 진행되는 것은 아니다. 그 이유는 Next에서는 페이지가 어떤 기능을 사용하는지에 따라 Static Page(정적 페이지)와 Dynamic Page(동적 페이지)로 구분이 되는데 Full Route Cache는 Static Page에게만 적용이 되기 때문이다. 뒤에 이어서 Static Page와 Dynamic Page에 대해 자세히 알아보도록 하자.
Dynamic Page(동적 페이지)
- 특정 페이지가 사용자로부터 접속 요청을 받을 때 마다 매번 새로운 변화가 생기거나, 데이터가 달라지는 경우 Dynamic Page로 분류가 된다. Dynamic Page로 설정되는 기준은 다음과 같다.
1. 캐싱되지 않는 Data Fetching을 사용하는 경우(Fetch Method의 Cache 옵션이 없거나 "no-store"인 경우
2. 동적 함수(쿠키, 헤더, 쿼리스트링)를 사용하는 Component가 있는 경우
- 동적 함수 : 요청에 따라서 가변적인 형태를 갖는 값들을 꺼내오는 기능(Cookie, Header, Query String etc.)
따라서 사용자의 요청에 따라서 가변적으로 변하는 값이 포함되어 있기 때문에 페이지를 정적으로 생성하게 되면 데이터의 최신화가 이루어지지 않기 때문에 Full Route Cache 기능이 적용되지 않는다. 그에 따라 사용자의 접속 요청이 발생함과 동시에 페이지가 렌더링 되기 시작하며 상대적으로 Full Route Cache가 되었던 Static Page에 비해 느리게 렌더링이 완료된다.(실제로 매우 방대한 양의 데이터 호출이 있지 않는 한 사용자가 불편함을 느끼거나 눈에 띄게 느린 렌더링이 발생하지는 않는다.)
Static Page(정적 페이지)
- Dynamic Page로 구분이 되지 않는 모든 페이지는 Static Page로 구분이 된다. Next에서 Page의 Default 값이다. 페이지 내에 사용자의 요청이 발생할 때 마다 가변적으로 변하는 값이 없기 때문에 페이지를 정적으로 생성해도 되기 때문에 Full Route Cache 기능이 적용이 된다. 따라서 빌드 타임에 페이지가 정적으로 생성되어 캐싱된다.
아래 표를 통해서 Dynamic Page와 Static Page를 쉽게 구분할 수 있다.
페이지 분류 | 동적 함수(쿠키,헤더,쿼리스트링) | 데이터 캐시 |
Dynamic Page | Yes | No |
Dynamic Page | Yes | Yes |
Dynamic Page | No | No |
Static Page | No | Yes |
일반적으로는 가능만하다면 Dynamic Page보다는 Static Page로 생성하는 것이 권장되지만 각 페이지 별로 갖고 있는 기능과 성격이 다르기 때문에 페이지의 기능에 맞게 페이지를 생성하는 것이 올바르다. 또한 Dynamic Page는 Full Route Cache만 적용이 불가능하기에 페이지 전체의 캐싱만 불가능한 것이지 Data Caching과 그에 따른 Request Memoization은 모두 가능하기 때문에 필요에 맞게 페이지를 생성하고 캐싱 기능을 사용하면 좋다.
무엇보다 가장 중요한 점은 Next에서 페이지의 구분이 생기는 것은 Server Component에만 해당이 되는 점이다. Client Component는 브라우저에서 렌더링이 되기 때문에 페이지의 유형에 영향을 미치지 않는다.
Revalidate
Full Route Cache 또한 Revalidate 설정이 가능하다. Full Route Cache는 빌드 타임에 진행된다고 앞서 살펴보았다. 그 시점에 Fetch 요청을 보내면서 Revalidate 시간을 옵션으로 설정하여 요청을 보낼 수 있는데 옵션을 설정하여 보내어도 Data Caching과 Request Memoization, Full Route Cache는 모두 동일하게 진행된다. 하지만 Fetch 요청 때 함께 보낸 Revalidate 시간이 경과하고 나서 페이지 접속 요청이 발생하면 우선적으로 Stale(상한) 데이터가 포함되어 있고 Full Route Cache 되어있던 페이지를 렌더링 해줌과 동시에 즉시 서버측으로부터 캐싱되어 있던 데이터를 업데이트하여 새롭게 Data Caching을 완료한 후 바로 Full Route Cache까지 업데이트를 해둔다. 그 이후로부터 들어오는 페이지 접속 요청에 대해서는 업데이트 된 데이터와 그 데이터가 담긴 페이지를 렌더링 해줄 수 있게 되는 것이다. 이 동작은 Page Router의 ISR(증분 정적 재생성)과 유사한 느낌이라고 보면 이해가 쉬울 것이다.
적용하기
진행중인 프로젝트에서 Full Route Cache를 적용하기 위해서는 모든 페이지들을 살펴보면서 각 페이지가 Dynamic Page인지 Static Page인지 확인을 해보아야 한다. 이것을 코드로 확인을 일일이 하기엔 번거로움이 있기 때문에 현재 진행중인 프로젝트를 npm run build를 통해서 빌드를 해보면 빌드된 페이지들의 종류가 표시가 된다. 이 때 주의해야 할 점이 있다. 만약 상위 컴포넌트는 Static Page로 구성이 되어있지만 하위 컴포넌트에 Dynamic한 요소가 존재하여 Dynamic Page로 존재한다면 빌드 시에 오류가 발생한다. 에러 문구는 다음과 같은 형태로 출력된다.
"Generating static pages (3/n) [= ] x useSearchParams() should be wrapped in a suspense boundary at page "/" "
이 오류 문구가 의미하는 것이 바로 Static Page 하위에 Dynamic Page가 존재한다는 점이다. 이런 경우에는 하위 컴포넌트 내부에 동적으로 동작하는 컴포넌트에 Suspense 컴포넌트를 씌워주면 된다. Suspense 컴포넌트는 React에서 제공해주는 컴포넌트로 비동기적으로 처리되는 요소에 대해서 진행이 완료되지 않았을 경우 fallback 옵션을 통해서 대체를 해두고 처리가 완료되면 화면에 출력해주는 기능을 한다. 일반적으로 비동기적인 기능을 처리하는 컴포넌트에 Suspense 컴포넌트를 씌워주어 UX 측면을 개선시키는데에 주로 사용한다. 따라서 동적인 요소에 Suspense 컴포넌트를 씌워주면 정상적으로 빌드가 진행된다.
빌드가 진행되면 다음과 같은 결과가 터미널에 출력된다.
빌드가 완료되면 각 페이지 별로 f라는 단어가 붙은 페이지는 Dynamic Page, ○ 기호가 붙은 페이지는 Static Page로 구분이 되는 것을 확인할 수 있다. 앞서 말했듯이 페이지는 가능하다면 Static Page로 생성되는 것이 좋다고 했기 때문에 Dynamic Page의 기능들을 확인하면서 Static Page로 전환이 가능한 부분이 있다면 수정을 해주면 된다.
수정하는 대표적인 방법은 Fetch Method를 사용하는 로직에 옵션을 설정하여 페이지 기능에 영향이 없는 선에서 Data Caching을 하도록 설정을 바꾸어주는 것이다. {cache : "no-store"}로 설정되어있던 옵션을 {cache : "force-cache"}로 변경을 해주거나 Revalidate 설정을 위해서 {next : {revalidate: 3}}과 같이 설정을 해주어도 된다. 이와 같이 Static Page로 전환 후 빌드를 해주면 결과가 다음과 같이 바뀌는 것을 볼 수 있다.
Dynamic Page로 동작하던 최상위 페이지가 Static Page로 바뀐 것을 확인할 수 있다.
이에 더해 위와 같이 빌드만 완료했는데도 불구하고 next/server/app/index.html 과 같은 경로를 통해 들어가서 파일을 확인해보면 Next 서버를 실행하지도 않았는데 Full Route Cache를 통해 정적인 페이지가 생성이 되어 있는 것을 확인할 수도 있다. 그리고 서버를 실행시킨 뒤 페이지 내에서 다양한 동작이 실행되면 index.html 파일이 실시간으로 변경되면서 렌더링되는 것까지 확인할 수 있다.
generateStaticParams
앞서 우리는 다양한 페이지들을 Static Page로 생성하기 위해 Fetch Method의 Cache 옵션을 설정해주곤 했다. 하지만 아직 Dynamic Routing이 적용되는 페이지들에 대해서는 설정을 해주지 않았다. Dynamic Routing이 적용된 페이지들에게는 어떻게 해야 Static Page로서 생성이 되도록 해줄 수 있을까. 바로 generateStaticParams 함수를 사용하면 된다. Dynamic Routing이 적용된 컴포넌트의 상단에 본 함수를 작성한 뒤 URL Parameter 값이 담긴 객체들이 담긴 배열을 반환해주고 export 처리해주면 Next가 자동으로 인식하여 반환 받은 배열에 속한 URL Parameter 값이 포함된 경로로 생성된 페이지들은 Static Page로 생성하여 Full Route Cache가 적용된다. 사용 코드는 아래 예시와 같다.
export function generateStaticParams(){
return [{id: "1"},{id: "2"},{id: "3"}];
}
export default async function Page({params}: {params: {id: string|string[]}){
const response = await fetch(API);
if(!response.ok){ return <div>Error</div> }
const data = await response.json();
return (
<div>
Page {params.id}
<div>{data}</div>
</div>
)
}
위의 예시 코드는 Dynamic Routing이 적용된 페이지 로직이다. 컴포넌트 내부를 살펴보면 Fetch Method 내부에 Cache 옵션이 별도로 설정되어있지 않았다. 또한 컴포넌트 외부에 generateStaticParams 함수를 사용하고 반환값으로 URL Parameter 값이 담긴 객체들이 담긴 배열이 포함되어 있다. generateStaticParams 함수를 통해 Dynamic Routing으로 생성된 페이지들에 대한 Static Page 설정을 해주면 Fetch Method의 Cache 옵션이 설정되어 있지 않아도 자동으로 Static Page로 설정이 되어 Full Route Cache가 적용된다. 위와 같은 로직으로 작성하고 npm run build를 통해 프로젝트를 빌드한 후 출력된 빌드 메시지를 확인해보면 다음과 같은 내용을 확인할 수 있다.
위의 사진을 보면 /book 경로로 Dynamic Routing이 설정된 페이지들이 SSG, 즉 빌드 시점에 Static Page로서 생성이 된 것을 확인할 수 있다.
Static Page로 생성된 페이지 외에 URL Parameter을 통해 다른 Dynamic Routing 페이지에 접속하면 접속 즉시 페이지가 Dynamic Page로 생성되는 것도 확인이 가능하다.
dynamicParams
추가적으로 generateStaticParams에 반환값으로 설정해둔 URL Parameter 값들을 제외하고 다른 Dynamic Routing 경로를 통해 페이지 접속을 막고자 하는 경우에는 dynamicParams 변수 값을 false로 설정해주면 된다.
export const dynamicParams = false;
export function generateStaticParams(){
return [{id: "1"},{id: "2"},{id: "3"}];
}
export default async function Page({params}: {params: {id: string|string[]}){
const response = await fetch(API);
if(!response.ok){ return <div>Error</div> }
const data = await response.json();
return (
<div>
Page {params.id}
<div>{data}</div>
</div>
)
}
위 코드의 가장 최상단에 dynamicParams를 false로 설정을 해두면 generateStaticParams로 설정해둔 경로 이외의 동적 경로로 접속을 하였을 경우에는 DB에 데이터가 존재함에도 불구하고 404 Page를 반환해준다. 기본값은 true이기 때문에 이외의 경로로 접속하였을 때 즉시 Dynamic Page를 생성해주는 것이다.
위에서 살펴본 generateStaticParams 함수는 이전에 Page Router을 학습할 때 보았던 함수와 비슷한 기능을 한다. 바로 Pre-Rendering의 방식중에 SSG 방식에 사용되었던 함수인 getStaticPaths 함수와 동일한 기능을 한다. getStaticPaths 함수에서의 반환값 중 paths 옵션을 generateStaticParams 함수가 반환값으로 갖는다고 생각하면 된다. 또한 위의 dynamicParams의 값을 false로 설정을 해두는 것 역시 getStaticPaths 함수의 반환값 중 fallback 값의 옵션을 false로 설정한 것과 같은 맥락이다.
not-found.tsx(jsx)
App Router에서는 404 Page를 커스터마이징하거나 생성하는 방법이 매우 쉽다. App 폴더 바로 하위 경로에 not-found라는 이름으로 파일을 생성해주고 해당 컴포넌트의 UI를 생성하여 다른 페이지에서 서버 오류가 발생하였을 경우 본 컴포넌트를 넣어 사용하면 자동으로 not-found 파일이 페이지에 렌더링된다. Page Router 버전에서 404라는 파일명이 App Router에서 not-found라는 이름으로 바뀐 것 뿐이다.
[#2 - Route Segment]
Route Segment
앞서 우리는 빌드 타임에 페이지들이 Dynamic Page나 Static Page로 생성이 되도록 페이지 별로 설정을 해주는 방법을 학습했다. 하지만 별도의 설정 없이 앞서 살펴본 dynamicParams 같이 변수를 하나 선언하고 그 옵션 값을 설정해줌에 따라 강제적으로 Dynamic Page나 Static Page로 생성하는 방법이 존재하는데 그것이 바로 Route Segment이다. 정리하자면 페이지 내부에 Data Cache, dynamicParams, Revalidate,Server Regional 같이 별도의 설정을 모두 무시하고 페이지의 유형을 강제로 Dynamic Page나 Static Page로 설정하는 옵션이다.
Route Segment 사용법은 dynamic이라는 변수를 사용하는 것이다.
dynamic
dynamic 변수에 할당할 수 있는 옵션값은 4가지가 있다. 4가지를 차례차례 살펴보자.
1. auto
- auto 옵션은 dynamic 변수의 기본값이다. 강제적인 제어 없이 페이지에 설정되어있는 그대로 페이지를 생성하는 옵션이다.
export const dynamic = "auto";
2. force-dynamic
- force-dynamic 옵션은 해당 페이지를 강제로 Dynamic Page로 생성하는 옵션이다. 페이지 내부에 Static Page로 생성하도록 설정되어있는 옵션이 존재하더라도 본 옵션이 설정되면 강제적으로 Dynamic Page로 생성을 하게 된다.
export const dynamic = "force-dynamic";
3. force-static
- force-dynamic 옵션과 반대로 해당 페이지를 강제로 Static Page로 생성하는 옵션이다. 페이지 내부에 Dynamic Page로 생성하도록 설정되어있는 옵션이 존재하거나 useSearchParams, useParams 등 사용자의 동작에 따라 Dynamic하게 동작하는 요소들이 존재하더라도 해당 요소들을 undefined로 변환시켜버린 후 강제로 Static Page로 생성하는 옵션이다.
export const dynamic = "force-static";
4. error
- force-static 옵션과 동일하게 강제로 페이지를 Static Page로 생성하는 옵션이다. 하지만 force-static과의 차이점은 만약 본 옵션이 설정된 페이지에 Dynamic하게 동작하는 요소가 존재한다면 프로젝트를 빌드할 때 에러를 발생시키는 점이다. force-static 옵션은 Dynamic한 요소가 존재하더라도 무시하고 강제로 Static Page를 생성하는 반면 error 옵션은 페이지 빌드시 에러를 발생시켜 Static Page로서의 생성을 막아준다.
위와 같은 페이지 유형 강제 설정 방법을 알아보았지만 위의 방법은 그닥 권장하는 방법이 아니다. App Router 버전의 NextJS는 각 페이지, 각 컴포넌트 별로 동작하는 방식을 분석하여 자동적으로 Static Page, Dynamic Page로 구분을 지어주는 좋은 기능을 가지고 있기 때문에 강제로 NextJS의 동작을 제어하는 것은 그닥 좋은 방법이 아니기 때문이다. 다만 본 방법을 학습함으로써 프로젝트의 빠른 설정이나 테스트 시 유용하게 사용할 수 있기 때문에 알아두면 좋은 기능이다.
[#3 - Client Router Cache]
Client Router Cache
Client Router Cache란 브라우저에 저장되는 캐시로서 페이지 이동을 효율적으로 진행하기 위해 페이지의 일부 데이터를 브라우저 측에 보관하는 기능을 말한다. 가장 많이 사용되는 예시로 서로 다른 두개의 페이지가 갖고 있는 공통적인 레이아웃 파일이 존재하는데 레이아웃 같은 경우에는 데이터 호출이 존재하거나 변경되는 사항이 적은 요소이다. 따라서 변동되는 부분이 없는 요소임에도 불구하고 페이지의 이동이 발생하였을 때 해당 페이지 요소들과 같이 재호출이 발생하면서 불필요하게 잦은 호출이 발생하곤 한다. NextJS의 장점 중에 하나인 매우 빠른 페이지 이동에 맞게 최적화 되지 못한 부분이 발생하는 것이다. 그래서 NextJS는 자동으로 레이아웃 파일 같은 페이지 이동에 따라 변동 사항은 없지만 불필요하게 계속 호출이 되는 요소들은 최초 접속을 할 때 렌더링을 하고 Client Router Cache 기능을 통해서 브라우저 측에 보관을 해두고 페이지 이동이 발생하면 브라우저에서 캐싱되어있던 레이아웃 파일을 가져와 최적화 된 페이지 이동을 제공해주는 것이다. 별도의 설정할 필요 없이 자동적으로 제공이 되는 기능이다.
하지만 브라우저에 캐싱해두는 기능이기 때문에 페이지가 새로 고침되거나 브라우저 탭이 닫혔다가 다시 접속을 하는 등 브라우저가 초기화 되는 동작이 발생하면 캐싱 데이터는 사라지기 때문에 그 부분은 유의해야 한다.
'NextJS' 카테고리의 다른 글
NextJS[NextJS v.13~] - (5) Server Action (2) | 2024.11.19 |
---|---|
NextJS[NextJS v.13~] - (4) Streaming (4) | 2024.11.01 |
NextJS[NextJS v.13~] - (2) Data Fetching on App Router (1) | 2024.10.05 |
[NextJS v.13~] - (1) App Router (1) | 2024.09.26 |
[NextJS] - (4) SEO, Deploy (6) | 2024.09.19 |