NextJS

SSG를 on-Demand ISR 로 변경

변기원 2022. 11. 23. 14:32

SSG

Next를 사용하기 전에는 Next가 서버사이드 렌더링을 하기 위해 사용하는 프레임워크라고 생각했는데,

공식문서에서도 더 권장하기도 하며 성능상으로도 SSG(Static Site Generation)를 더 많이 사용하게 된다.

SSG는 Page폴더 안의 파일에서 getStaticProps라는 함수를 작성함으로써 구현할 수 있다.

SSG는 프로젝트 빌드 시점에 이 페이지를 프리랜더링 하게 된다.

유저의 요청보다 먼저 빌드되며 성능상의 이점을 위해 미리 생성한 HTML와 JSON파일을 CDN에 캐시 해놓는다.

덕분에 seo에도 탁월하고 매우 빠른 페이지를 가지게 된다. 단점이 있는데, 빌드 시점에 딱 한 번만 CMS를 통해 데이터 패칭을 하므로

빌드 이후에 데이터가 변해도 변한 데이터를 보여주지 못한다. 여전히 CDN에 캐시 되어 있는 과거의 HTML만 계속 보이게 된다.

그래서 static site라고 부르고, 데이터가 자주 변하는 페이지는 SSR(getServerSideProps)를 사용한다.

SSR은 빌드 시점에 CMS데이터를 가져와서 HTML을 만드는 게 아니라, 유저의 요청이 있을 때마다 데이터를 가져와서 HTML을 만든다.

유저의 요청이 있을때마다 데이터를 패칭 해서 HTML을 만들기 때문에 데이터가 항상 최신으로 유지된다.

그리고 여전히 SEO에 좋지만 매번 유저의 요청마다 이 과정을 반복해야 하므로 넥스트 서버의 자원을 소모하며,

CDN 캐시를 이용할 수 없기 때문에 SSG보다는 느릴 것이다.

SSR이 꼭 필요한 페이지도 있겠지만 SSG방식을 유지하면서 데이터가 변했을 때만 새로운 페이지를 다시 빌드해서 CDN을 이용하는 방법이 있다.

 

ISR

getStaticProps로 작성한 서버 코드에 revalidate time을 부여할 수 있다.

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
  }
}

빌드 시점에 넥스트 서버에서 https://.../posts api에서 받아온 데이터를 가지고 html을 만들 것이다. 

빌드로부터 10초가 지나기 이전에는 항상 캐시 된 페이지가 전달된다.

10초가 지난 후 요청이 있으면 여전히 캐시 된 페이지가 전달된다. 그리고 넥스트가 백그라운드에서 해당 페이지를 regeneration 한다.

페이지가 성공적으로 generate 되면 CDN 캐시를 invalide하고 새로운 페이지를 보여준다.

덕분에 CDN캐시를 사용하면서 프리랜더링 된 HTML을 받을 수 있게 된다.

 

근데 만약 사용자가 1초에도 여러 번 들어오는 서비스로 성장한다면?

심지어 데이터가 그렇게 자주 변하지도 않는다면?

실제 데이터는 변한 게 없는데도 매 10초마다 regeneration을 해야 되니 이것 또한 비효율적이 될 수 있다.

SSG를 적용한 개발자의 순수한 의도를 무시하는 느낌,,?

 

on Demand ISR

다음은 거의 공식문서에 나오는 내용이다.

https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration

 

Next v12.2.0부터 지원하는 on Demand ISR방식을 사용하면 훨씬 효율적으로 static site의 revalidation을 할 수 있다.

바로 특정 페이지에 대한 Next의 캐시를 수동으로 제거할 수 있는 주문형 방식이다.

언제 '나 지금 이 페이지 캐시 수동으로 날리고 싶어'라는 생각이 생길까? 당연히 데이터가 변했을 때이다.

getStaticProps 함수 내에서 revalidate time을 지정할 필요가 없어졌다.

대신 revalidate() 메서드를 실행함으로써 원하는 타이밍에만 재검증을 보낼 수 있게 된다.

 

Using on Demand Revalidation

일단 나의 Next앱에서만 알 수 있는 secret token이 필요하다. 왜냐하면

이 기능을 만들기 위해 내 express 서버가 re generation을 하면서 캐시를 초기화하도록 해야 하므로

Next 가 백그라운드에 만들어주는 Express서버에 api를 배포하고 그 api가 revalidate() 명령을 수행하도록 코딩해야 한다.

이 api에 허가되지 않은 사람의 액세스를 막아주기 위해 secret token이 필요한 것이다.(보안을 위해)

https://<your-site.com>/api/revalidate?secret=<token>

page/api 경로에 revalidate.js를 만들자. 그리고 위의 api주소로 요청을 보내자.

// pages/api/revalidate.js

export default async function handler(req, res) {
  const { body : { id } } = req;
  // Check for secret to confirm this is a valid request
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' })
  }
  try {
    // this should be the actual path not a rewritten path
    // e.g. for "/blog/[slug]" this should be "/blog/post-1"
    await res.revalidate(`/post/${id}`)
    return res.json({ revalidated: true })
  } catch (err) {
    // If there was an error, Next.js will continue
    // to show the last successfully generated page
    return res.status(500).send('Error revalidating')
  }
}

아까 만든 secret token을 환경변수에 등록해놓고 허가되지 않은 접근인 경우 401을 보낸다.

try문에서 간단하게 res.revalidate()를 실행하면 되는데, 나의 경우는 Id가 들어가는 path가 필요하므로

body에 id를 담아서 Post를 받기로 했다.

 

이제 필요한 곳에서 axios.post('/api/revalidate',{id:123})이런식의 네트워크를 함수에 담아 실행시켜주면 된다.

해당 함수가 실행될 때마다 /post/123이 revalidate 되어 백그라운드의 HTML이 재생성되고 CDN의 캐시를 초기화시켜줄 것이다.

 

트러블슈팅1

axios.post('/api/revalidate',{id:123})이 요청을 관리자 admin 페이지에서 앱으로 보내고 싶었다.

client.post('/api/revalidate',{id:123})를 보내자 CORS에러가 발생했다. CORS는 서로 다른 origin 간의 리소스 교환으로 인해

브라우저에서 발생시키는 에러이다. 

즉, 위 그림을 통해 알 수 있듯이, 백엔드 서버는 Access-Control-Allow-Origin에 정보를 담아 응답을 주고

우리 브라우저가 그 Access-Control-Allow-Origin과 Origin을 비교하여 규약을 위반한 리소스 교환인 경우

error를 발생시키는 것이다. 이를 해결하기 위해 나의 Next서버에서 admin 페이지의 요청을 허용할 수 있도록 하고 싶었지만

해결하지 못했다. 대신 CORS가 브라우저에서 발생시키는 에러라면, 서버끼리의 통신에는 문제가 없다는 것을 알게 되어

백엔드에 데이터가 update 되는 api가 실행될 때, 프론트엔드 Next서버에 배포한 revalidate api에 post를 보내주기를 요청했고

백엔드에서 update 될 때마다 해당 페이지를 revalidate 하는 api를 호출해서 SSG페이지가 최신화되도록 구현했다.