ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Next.js - Incremental Static Regeneration (ISR) 과 SSR caching
    개발 2022. 10. 30. 23:16


    Static Site Generation (SSG)

     

     

    Next.js 에서 지원하는 Static Site Generation을 사용하면 빌드 시점에 클라이언트에 제공되어야 하는 HTML을 미리 생성하여

    사용자가 페이지 접근 시 로딩 없이 빠르게 화면을 볼 수 있어, 내용의 변경이 없는 정적 페이지를 제공 할 때 높은 사용자 경험을 제공한다.

     


    Incremental Static Regeneration (ISR)

    직역하면 증분 정적 재생..?

    ISR을 한 문장으로 정리하면 "런타임 중에 정적 페이지를 재생성할 수 있는 기능"을 의미 한다.

    정적인 페이지를 생성하면서 revalidate를 통해 새로운 페이지를 재생성하는 기능을 갖는다는 점에서

    SSG와 SSR의 하이브리드 기능처럼 볼 수도 있다.

     

     

    사용방법

    사용방법은 생각보다 간단하다.

    정적인 페이지를 만드는 기능은 이미 SSG를 사용하기 위한  getStaticProps 함수를 통해 사용할 수 있다.

    우리는 이 함수에서 요청해야 할 데이터를 이미 받아 온 후 컴포넌트로 전달 한다.

    이때 return 하는 객체에 revalidate 라는 키를 통해 얼마만의 간격으로 re-generate 를 시도 할 지 설정한다.

     

    function Blog({ posts }) {
      return (
        <ul>
          {posts.map((post) => (
            <li key={post.id}>{post.title}</li>
          ))}
        </ul>
      )
    }
    
    // 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
      }
    }
    
    // This function gets called at build time on server-side.
    // It may be called again, on a serverless function, if
    // the path has not been generated.
    export async function getStaticPaths() {
      const res = await fetch('https://.../posts')
      const posts = await res.json()
    
      // Get the paths we want to pre-render based on posts
      const paths = posts.map((post) => ({
        params: { id: post.id },
      }))
    
      // We'll pre-render only these paths at build time.
      // { fallback: blocking } will server-render pages
      // on-demand if the path doesn't exist.
      return { paths, fallback: 'blocking' }
    }
    
    export default Blog

     

    ISR을 설명하는 그림을 살펴보면 최초 생성된 페이지를 캐싱한 후 설정해둔 revalidate 시간동안은 캐싱된 페이지를 사용하여 클라이언트에 제공한다. 이후 revalidate time이 지난 후에 사용자가 요청한 데이터가 변경되었다면 다시 새로운 정적 페이지를 생성해 캐싱 데이터를 업데이트하고 이 정보를 클라이언트에 제공하는 방식으로 사용되고있다.

     

     

    SSR과 비교했을때 사용자가 페이지에 접근했을때 매번 데이터를 요청하고 기다리는 시간을 소요해야하는 단점을 보완하여

    revalidate 옵션을 사용해 변경된 데이터로 정적 페이지를 생성하여 첫 번째 요청 이후에는 불필요하게 통신하는 절차를 생략할 수 있다.

     

    ISR은 "stale-while-revalidate" 캐싱 전략을 사용해 애플리케이션의 성능을 향상 키시고 방문자에게 더 빠른 경험을 제공 할 수 있다.


    ISR이 만능일까?

     

    ISR의 장점들을 보자면 SSG와 비슷하게 블로그, 뉴스 기사 등의 정적인 페이지를 제공하는 서비스에 적합해 보이며, 혹여나 이러한 정적인 페이지라도 정적 시간에 따라 내용을 새로 갱신해야되는 서비스라면 SSG보다 훨씬 나은 기술이다.

     

    아무리 ISR이 좋다고해도, 새로운 내용을 갱신할 수 있게 하더라도 과연 잦은 인터랙션으로 데이터 변화가 빈번한 CSR이나 SSR을 사용하는 서비스에도 적합하다고 할 수 있을까?

     

    revalidate time을 1초로 줄이고 매번 새로운 정적 페이지를 제공해야 한다면 오히려 지속적으로 정적 파일을 생성해야한다는 점에서

    효율적이라고 할 수 없을것 같다.

     

    출처: https://youthfulhps.dev/nextjs/next-isr/

     

    또 ISR에서는 revalidate time을 사용해서 새로운 정적 페이지를 만드는 시점이 실제로 데이터가 변화하였을때 바로 새로운 정적 페이지를 만들어 주는것이 아니라 revalidate 주기 동안 계속 캐싱된 데이터를 보여줬다가 다음 재검증 시점때 새로운 화면을 보여준다는 점에서

    실제 최신화된 데이터를 보여주는 시점에 오차가 발생 할 수 있다는 점에서 서비스에 성격에 따라 크리티컬한 이슈가 될 수 있다.

     


    그럴땐 SSR이 맞는거 같은데?

     

     

    앞서 설명했던 상황처럼 매번 새로운 데이터를 확인해 사용자에게 가장 최신의 정보를 빠르게 보여줘야 하고, SEO나 CSR에서 사용자가 초기 화면 깜빡임을 경험하지 않도록해야 한다면 SSR이 적합해 보인다.

     

    Next.js 에서는 SSR을 사용하기 위해서 getServerSideProps 함수를 사용한다. 

    function Page({ data }) {
      // Render data...
    }
    
    // This gets called on every request
    export async function getServerSideProps() {
      // Fetch data from external API
      const res = await fetch(`https://.../data`)
      const data = await res.json()
    
      // Pass data to the page via props
      return { props: { data } }
    }
    
    export default Page

     

    하지만 이 함수를 사용했을때 사용자가 매번 페이지에 접근 할때마다 데이터를 패칭하는 작업을 수행해야 한다.

    데이터가 변경되는 시점이 언제인지는 알 수 없지만 매번 데이터 패칭을 해야 할 필요없이 이전 데이터와 새롭게 넘겨받은 데이터가 같다면 굳이 요청을 매번 해야 할까?

     

    SSR에서도 캐싱할 수 있는 방법이 있는지가 궁금해진다.

     


     

    SSR - Caching

    공식문서가 정답 해설지라고 누가 그러더라..

    Next.js 공식문서를 살펴보면 우리가 사용하는 SSR에서도 캐싱을 지원하는 방법을 잘 설명해주고 있다.

     

     

    Going to Production | Next.js

    Before taking your Next.js application to production, here are some recommendations to ensure the best user experience.

    nextjs.org

     

    // This value is considered fresh for ten seconds (s-maxage=10).
    // If a request is repeated within the next 10 seconds, the previously
    // cached value will still be fresh. If the request is repeated before 59 seconds,
    // the cached value will be stale but still render (stale-while-revalidate=59).
    //
    // In the background, a revalidation request will be made to populate the cache
    // with a fresh value. If you refresh the page, you will see the new value.
    export async function getServerSideProps({ req, res }) {
      res.setHeader(
        'Cache-Control',
        'public, s-maxage=10, stale-while-revalidate=59'
      )
    
      return {
        props: {},
      }
    }

     

    코드를 간단하게 살펴보면 response Header 옵션으로 CDN과 같은 중간 서버가 특정 리소스를 캐시 할 수 있는지 여부를 지정하기 위해 'Cache-Control' 옵션을 세팅한다.

     

    각각의 옵션을 살펴보면

    • public: 모든 사람과 중간 서버가 캐시를 저장 할 수 있음을 나타낸다. (private는 사용자 브라우저만 캐시를 저장할수있음)
    • s-maxage: 일반적으로 revalidate 시 n초 이내에 발생하는 반복적인 요청에 대해서 캐싱된 데이터를 보내주기 위한 주기로 max-age를 사용하는데 쉽게 말하면 해당 주기동안 일어난 요청은 낡은 컨텐츠로 인식하여 캐싱된 데이터를 보내준다 라고 말할 수 있다.
      이러한 max-age 옵션을 중간 서버 (CDN)와 같은 영역에만 설정하기 위해 s-maxage를 사용한다.

      s-maxage와 관련된 내용은 토스테크 블로그에서 정말 잘 설명해둔 내용이 있으니 참고하면 좋을것 같다.
      https://toss.tech/article/smart-web-service-cache
     

    웹 서비스 캐시 똑똑하게 다루기

    웹 성능을 위해 꼭 필요한 캐시, 제대로 설정하기 쉽지 않습니다. 토스 프론트엔드 챕터에서 올바르게 캐시를 설정하기 위한 노하우를 공유합니다.

    toss.tech

     

     

     

    댓글

onul-hoi front-end developer limchansoo