nextjs cache 총정리
nextjs cache 총정리

nextjs cache 총정리

작성일
2024년 09월 15일
태그
카테고리
nextjs
Last edited time
Last updated October 23, 2024
날짜
 
Next.js는 React 애플리케이션을 위한 강력한 프레임워크로, 데이터 페칭과 렌더링 최적화를 위한 다양한 전략을 제공합니다. 이러한 전략들은 사용자 경험을 크게 향상시키고, 개발자들에게 효율적인 도구를 제공합니다. 다음은 Next.js에서 사용되는 주요 데이터 페칭 및 렌더링 전략들입니다.
 

case: react app 에서의 데이터 페칭

 
notion image
우선 리엑트에서의 페이지 요청전략을 살펴보자, 기본적으로 react app에서는 위와 같이 유저가 요청을 하면 우선 index.html을 보낸 후
js를 브라우저에서 실행해야만 사용자는 화면을 볼 수 있습니다.
코드 스플리팅이 되어있지 않다면 모든 페이지에 해당하는 js 번들을 브라우저에서 로딩해야하기 때문에
사용자는 긴 시간동안 빈화면을 보게됨.
하지만 초기 실행 이후에는 다른페이지로의 이동은 쾌적하게 가능.
notion image
하지만 브라우저에서 사용자가 데이터를 백엔드 서버에 요청할 경우 또 기다려야 하는 상황이 발생.
언제까지 어깨춤을 추게할꺼야..
물론 코드 스플리팅, lazyloading을 통해 js 번들을 줄이고 웹팩으로 최적화, 패칭할 경우 로딩상태를 보여줌으로써 이를 완화할 수 있다.
 

case: nextjs에서의 데이터 페칭

notion image
  • nextjs
nextjs는 이를 서버사이드 랜더링을 통해 해결 서버에서 랜더링한 html을 먼저 내려주고
이후 해당 페이지의 js번들을 hydration후 상호작용이 가능하게 동작함
 
Link컴포넌트가 존재하면 (link컴포넌트를 사용하지 않는prefetch할 경로를 router.prefetch 로 설정가능) 해당 페이지의 jsbundle을 미리 받아옴으로써 다음 페이지의 tti를 줄일 수 있음.
notion image
 
notion image
위처럼 Link의 prefetch옵션을 false로 했을경우에는
이동시 해당 번들을 받는 시간이 추가적으로 걸렸는데
 
true로 했을경우
페이지 hydration이 끝난후 추가적으로 뷰포트에 존재하는 링크컴포넌트의 번들을 받아오는것을 확인가능.
notion image
받는 시간은 조금 오래 걸렸을지 몰라도
true인 경우 번들을 받아오는데 걸리는 시간이 없기때문에 매우빠르게 이동하는것을 체감할 수 있었음. (RSC payload를 받아오고 클라이언트 사이드의 js번들은 받지 않고있는데 이는 해당 path가 dynamic 라우팅이기 때문에 자바스크립트 번들이 아니라 RSC payload만 미리 받아놓는것.)
 
 

 

사전 렌더링의 종류.

 

1. SSR

notion image
notion image
서버에서 html을 랜더링!
 
여기서 백엔드 서버의 요청이 너무 늦을경우
로딩화면이라도 보여주는 리엑트가 더 좋아보일 수 있다.
그래서 SSG나 ISR 같은 개념이 등장
 

page router의 경우

// 서버사이드 랜더링 export const getServerSideProps = () { const data = await fetch(); return {props:{data }} };
page router의 경우 단점: 현재 getServerSideProp이 선언된 컴포넌트에서
자식 컴포넌트로 props drilling을 해야함.

app router의 경우

// async 키워드로 서버컴포넌트로 만들 수 있다. export async function Page () { const data = await fetch() return <div>...</div> }
클라이언트 컴포넌트에서는 메모이제이션등 여러 문제가 발생할 수 있어서 async 로 컴포넌트를 생성할 수 없었으나
서버컴포넌트에서는 그럴필요가 없다. 그렇기에 async 키워드를 사용가능.
notion image
nextjs의 공식문서에서도 필요한곳에서 직접 데이터를 불러와라는 문구가 있다. 이는 후술할 리퀘스트 메모이제이션(requets memoization) 기능에 의해 달성가능. 간단히 설명하면 한번 패치한 데이터를 캐싱하므로써 하위 컴포넌트에서 데이터 요청을 하면 캐싱된 데이터를 가져오는 방식. 이로써 pagerouter의 불필요한 props drilling을 제거하고 필요한곳에서 데이터를 호출하는
간단한 형식이 되었음
  • 나는 react query와 같은 방식으로 이해함.
  • react query를 사용할 경우 react query의 hydration provider를 사용하면 서버에서 패칭한 데이터를 클라이언트로 전역객체에 할당해 내려줌으로써 서버사이드에서 초기 랜더링시 중복 패칭은 nextjs의 requests memoization의 캐싱으로 해결. 클라이언트사이드에서는 서버에서 패칭한 데이터를 전역으로 설정, 캐싱하는 방식이 가능.
 
 
 
 

2. SSG

  • 정적 사이트 생성
  • 빌드타임에 미리 페이지를 사전 랜더링해둠
  • 하지만 정적인 사이트만 보여줄 수 있는 단점이 존재함. 매번 똑같은 페이지만 응답하기에 최신 데이터 반영은 어려움
 

page router의 경우

notion image
// page router // 동적경로 export const getStaticPaths = () => { return { paths : [ { params: {id: "1"} }, { params: {id: "2"} }, { params: {id: "3"} }, ], fallback : true // path에 없는 경로로 접근했을 경우 } } // 정적페이지 생성 export const getStaticProps = async ( constext: GetStaticPropsContext // 페이지의 query, params ) { const id = context.params!.id; const book = await fetchOneBook(Number(id)); return { props: { book, } } };
page router 에서 정적페이지를 만드는법은
getStaticProps 를 사용
동적 경로에 대해서는 getStaticPaths를 사용
fallback옵션을 통해 조정가능
 
false: 정적생성하지 않은 페이지에 대해서 접근거부 404 반환 blocking: 즉시생성 (Like SSR) true: 즉시생성 + 페이지만 미리반환 (props 가 없는 페이지)
 
💡
페이지만 미리 반환했을 경우 isFallback 으로 로딩상태인지 감지가능
const router = useRouter(); if (router.isFallback) return '로딩중' if (!props) return '문제발생! '
 
 
 

3. ISR

  • 증분정적 재생성
notion image
기본적으로 만들어진 페이지를 반환하다가
지정된 시간이후 접속요청이 발생하면 일단은 이전의 페이지를 반환하고
다시 빌드한 후 다음 요청부터 새롭게 빌드된 페이지를 반환하는 형식
ssg의 장점과 ssr의 장점을 결합
 
하지만 사용자의 행동에 따라 페이지가 업데이트 되는 형식이면 시간으로는 문제가 발생
ex) 게시판
이경우 on-demand ISR을 사용함.

on-Demand ISR

On-Demand ISR(증분 정적 재생성)은 Next.js의 기능으로, 요청 시 정적 페이지를 동적으로 재생성할 수 있게 해줍니다. 이 기능은 전체 사이트를 다시 빌드하지 않고도 실시간 콘텐츠 업데이트를 가능하게 합니다.
 

page router의 경우

시간을 기준으로 빌드.

 
기본적으로는
getStaticProps 의 리턴값에 revalidate 값을 넣어서 새로 빌드할 시간을 설정
return { props: { post }, // Next.js will invalidate the cache when a // request comes in, at most once every 60 seconds. revalidate: 60, }
 

on-Demand ISR.

notion image
api라우터를 사용해 캐시무효화 가능.
On-Demand ISR은 정적 생성의 이점과 서버 사이드 렌더링의 유연성을 결합하여, 자주 업데이트되는 콘텐츠에 특히 유용합니다. 이를 통해 개발자는 정적 페이지의 성능 이점을 유지하면서도 필요할 때 콘텐츠를 즉시 업데이트할 수 있습니다.
 

 

React Server Component

 

page router에서의 hydration 문제점!

 
notion image
nextjs에서는 클라이언트 컴포넌트와 서버컴포넌트가 존재함.
초기 랜더링시 nextjs에서는 사용자에게 모든 결과물을 미리 랜더링해서 내려주는데 이후에
hydration에는 이미 사용자가 획득한 컴포넌트는 다시 내려줄 필요가 없다. page router에서는 비효율적으로 모든 컴포넌트를 다시 내려줬다면 이를 app router에서는 server component를 사용해 해결, client component만 js bundle로 내려준다
 
notion image
위와같이 이미 내려준 불필요한 컴포넌트와 상호작용이 가능한 컴포넌트 둘다 내려줘야하는
불편함이 존재함.
notion image
app router에서는 이를 server컴포넌트라는 개념을 이용하여 불필요한 js번들의 크기를 최적화했음.
사전 랜더링시 딱 한번만 서버컴포넌트를 랜더링한후 이후 상호작용이 필요한 클라이언트 컴포넌트의 js 번들만 내려주는 식.
 
server compoent: 사전 랜더링시 1번만 실행
client compoenet: 사전랜더링할때 1번 하이드레이션할때 1번 총 2번 실행
 
 

주의점!

notion image
  1. 서버컴포넌트에서는 브라우저객체인 document가 없고 window 객체가 있기도하고 클라이언트의 상태를 모르기 때문에 브라우저에서 실행할 코드는 제외해야한다.
  1. 클라이언트 컴포넌트는 클라이언트에서만 실행되지 않고 서버에서도 한번 실행되기때문에 위와 같은 문제를 공유
  1. 자바스크립트 번들을 줄이기위해 서버컴포넌트는 자동으로 제외시키기 때문에 하이드레이션할때 문제발생 하지만 nextjs에서는 서버컴포넌트가 클라이언트 컴포넌트로 자동으로 변경됨.
💡
서버컴포넌트를 클라이언트 컴포넌트에 직접 import 하는것이 아니라 클라이언트 컴포넌트의 children으로 넘겨줄 경우 클라이언트 컴포넌트로 변경하지 않는다!
  1. 함수같이 직렬화가 어려운 props는 전달 불가능!
notion image
위와같이 사전랜더링시 RSC payload라는 서버컴포넌트들을 실행한후 직렬화한 결과물을 먼저 만든 후 클라이언트 컴포넌트를 실행해서 끼워넣는식.
 
notion image
notion image
위와같이 브라우저에서 rsc payload를 응답받은것을 확인가능함.
 
notion image
그렇기 때문에 서버컴포넌트를 실행하면서 함수를 클라이언트 컴포넌트에 prrop으로 전달불가능함
  • 함수는 직렬화가 불가능하기때문에
 
notion image
 
초기 접속이후 prefetch 시 js 번들과 RSC Payload를 미리 받아오게되는데,
RSC payload만 미리 받아오는 케이스가 존재함.
static page 같은경우 js번들도 같이 받아오나
dynamic page 같은경우 js번들은 받아오지 않는다.
dynamic page 같은 경우 쿼리스트링을 쓰는 등 빌드타임에 사용하기 어려운경우에 dynamic으로 설정됨.
 
 

app router cache

notion image

fetch를 확장한 cache 기능

 
 

request memoization

notion image
notion image
하나의 페이지를 랜더링할때만 유지되는 리퀘스트 메모이제이션, 중복된 요청만을 처리하기 위함.
 
데이터 캐시는 상술한 fetch를 확장한 옵션으로 설정가능하고 nextjs서버가 실행되는동안 백엔드 서버에서 불러온 데이터를 거의 영구저장하기위해 사용됨.
당연히 nextjs를 리빌드하면 사라짐.
 

풀 라우트 캐시

notion image

page router의 SSG와 동일한 기능을함.

빌드타임에 랜더링된 HTML을 풀라우트 캐시에 set하는 조건은 아래와 같음.
 
notion image
동적함수: (쿠키, 헤더, 쿼리스트링) 을 사용하는 컴포넌트
 
마지막 경우만 풀라우트캐싱이 적용된다!
 
 
동적 라우팅을 static 으로 설정하려면 아래의 함수를 위에 선언하면 빌드타임에 생성됨
  • ! 문자열로만 선언해야함
  • page router의 getStaticPath와 같은기능을함
  • 기본적으로 새로운 페이지에 대해서는 모두 캐싱한다. export const dynamicParams = false 을 선언하면 설정한 패스이외에는 접근을 허용하지 않는다
export const dynamicParams = false // 이 기능을 설정하면 아래의 경로이외에는 접근거부 404 리다이렉트 export function generateStaticParams() { return [{ id: '1' }, { id: '2' }, { id: '3' }] }
 
 
import { notFound } from 'next/navigation' notFound()
nextjs 의 notFound 함수를 호출하면 404페이지로 리다이렉트됨.
 
 
export const dynamic = 'auto' // 'auto' | 'force-dynamic' | 'error' | 'force-static'
해당설정으로 페이지의 동작을 정적 혹은 동적으로 강제로 변환시킬 수 있습니다.
error 같은 경우 정적 페이지로 변환하나 캐시되지 않은 페이지를 사용할 경우
에러메세지를 띄워줌
 
 

Streaming