애드블럭 종료 후 보실 수 있습니다.

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • NextJS Data Fetching (feat. CSR, SSR, SSG, ISR)
    Next.js 2022. 8. 29. 16:43
    728x90
    반응형

    1. Data Fetching이란

    SPA가 Web 세상에서 새로운 시대를 열었다는 것에 의문을 제시할 사람은 없을 것이다.

     

    기존에 페이지를 이동할 때마다 새로고침이 되며 빈 화면을 봐야 했던 이용자들은 이제 로딩이 없는 것과 비슷한 환경에서 사이트를 사용할 수 있게 되었기 때문이다.

     

    이는 확실히 획기적이었고 대다수의 웹사이트가 이제는 SPA로 되어있고 변화하고 있다.

     

    하지만 SPA에도 치명적인 문제가 있었다.

    웹사이트의 운영자들은 어떻게든 네이버나 구글 등의 검색 결과에 자신의 웹사이트가 노출되기를 원하는데 SPA의 경우 페이지를 모두 그린 뒤 데이터를 가지고 오는 CSR방식이 기본이기에 검색 사이트들의 Bot이 웹사이트에 접속했을 때 아무것도 없는 빈 페이지로 판별해 버렸기 때문이다.

    (물론 최근 구글의 Bot의 경우 이런 SPA의 단점을 인식하고 해당 사이트에 잠깐 머무는 방식으로 실제 빈 페이지인지를 확인한다고 한다.)

     

    이런 문제 때문에 나온 프레임워크가 바로 Next이다.

    물론 Pure React만으로도 SSR을 사용할 수 있지만 그 방법이 간단하지 않고 그 이외에도 많은 부분이 갖춰져 있기에 Next의 사용빈도가 점점 늘어나고 있는 것이다.

     

    그럼 이제 아래에서 NextJS의 Data Fetching에 대해 설명하겠다.

    2. CSR

    일반적으로 NextJS에서 Data를 가지고 오면 실행되는 방법으로 useEffect나 react-query, swr을 이용해서 데이터를 가지고 오는 것을 말한다.

     

    이 방법의 경우 SEO가 상관없고 내용이 자주 업데이트되는 경우 유용한 방법으로 데이터의 변경이 있을 경우에만 Page가 업데이트된다.

    const Test = () => {
      const [data, setData] = useState();
      const [isLoading, setLoading] = useState(false);
      
      useEffect(()=> {
        setLoading(true);
        fetch('/api/test')
        .then((res) => res.json())
        .then((data) => setData(data));
        setLoading(false);
      }, []);
      
      if(isLoading) return <p>Loading...</p>
      if(!data) return <p>Data Not Found!</p>
      
      return (
        <>
          <h2>{data?.title}</h2>
          <p>{data?.contents}</p>
        </>
      );
    };
    
    export default Test

    3. SSR

    Data를 페이지를 그리기 전인 서버 측에서 가지고 오는 방식이다.

     

    기본적으로 SEO를 위해 자주 사용하는 방식이지만 Data의 크기가 크거나 서버와의 통신이 느린 경우 페이지를 생성하는데 긴 시간이 소요될 수 있는 단점이 존재한다.

    const Test = ({ data }) => {
    
      if(!data) return <p>Data Not Found!</p>
      
      return (
        <>
          <h2>{data?.title}</h2>
          <p>{data?.contents}</p>
        </>
      )
    };
    
    export const getServerSideProps = async () => {
      const data = await fetch('/api/test').then((res) => res.json());
      return { props: { data } }
    };
    
    export default Test;

    4. SSG

    사이트를 배포할 때 Data를 가지고 오는 방식으로 SSR처럼 SEO를 구현하는데 문제가 없으며, 배포할 때 한 번만 서버와 통신하기 때문에 배포한 뒤에는 정적 페이지로 보여 CSR이나 SSR보다 더욱 빠른 속도를 자랑한다.

     

    단점으로는 배포한 뒤에는 데이터의 변경을 인지하지 못하기 때문에 데이터의 신선도를 유지할 수 없다는 단점이 존재하기에 변경되는 일이 없는 데이터를 대상으로 하는 것이 좋다.

     

    그리고 동적인 데이터의 경우에서 사용하는 경우에도 문제가 될 수 있는데 빌드를 할 때 미리 모든 Data를 가지고 온 뒤에 배포되기 때문에 동적인 route path들을 가지고 올 수 없기 때문에 getStaticPaths와 함께 이용되는 경우가 많다.

    // pages/tests.tsx
    const Tests = ({ posts }) => {
    	if(!posts.lenght) return <p>Posts Not Found!</p>
        
        return (
          <>
            {posts.map((post) => <Link key={post?.id} href={`/tests/${post?.id}`}>{post.title}<a></a></Link>
          </>
    }
    
    export const getStaticProps = async () => {
      const posts = await fetch('/api/posts').then((res) => res.json());
      return { props: { posts } }
    };
    
    export default Tests;
    
    // pages/tests/[id].tsx
    const Test = ({ data }) => {
    
      if(!data) return <p>Data Not Found!</p>
      
      return (
        <>
          <h2>{data?.title}</h2>
          <p>{data?.contents}</p>
        </>
      )
    };
    
    export const getStaticPaths = async () => {
      const postIds = await fetch('/api/postIds').then((res) => res.json());
      const paths = postIds.map((id) => ({ params: { id } });
      return { paths, fallback: false }
    };
    
    export const getStaticProps = async (ctx) => {
      const { id } = ctx.req.params;
      const data = await fetch(`/api/test/${id}`).then((res) => res.json());
      return { props: { data } }
    };
    
    export default Test;
    // old
    export const getStaticPaths = async () => {
      const postIds = await fetch('/api/postIds').then((res) => res.json());
      const paths = postIds.map((id) => ({ params: { id } });
      return { paths, fallback: false }
    };
    
    // new
    export const getStaticPaths = () => {
      return { paths:[], fallback: 'blocking' || true }
    };

    본래라면 위와 같은 방법으로 모든 paths를 가지고 와야 했지만 첫 접속에서의 약간의 손해를 감수하고 무척 좋은 방법으로 가지고 올 수 있게 되었다.

     

    위의 방법을 사용하면 사용자가 접속했을 때 path가 존재하지 않는다면 그때 Data를 가지고 와서 파일을 생성해 주는 방법이다.

    fallback true와 'blocking'의 차이는 크지 않은데 true의 경우 CSR로 첫 Data를 가지고 오고, blocking의 경우 SSR로 첫 Data를 가지고 온다는 차이가 있다.

     

    사용자에게 스켈레톤 등을 보여주는 등의 loading 처리를 하고 싶다면 true로 설정한 뒤 router.isFallback 값을 이용해 Loading을 처리해 줄 수 있다.

    5. ISR(On Demand Revalidation)

    SSR은 랜더링 속도가 느리다는 단점이 존재하고, SSG의 경우 새로운 Data을 업데이트하고 싶다면 새롭게 빌드를 실행해야 된다는 단점이 존재한다.

     

    이런 단점을 보안하기 위해 ISR이라는 개념이 Next@9.5.0에서 처음 도입되었으며 Next@12.1.0에서 Beta로 등장한 On Demand Revalidation이 Next@12.2.0에서는 정식으로 지원하기 시작했다.

     

    ISR에 대해 먼저 설명하자면 SSG의 단점을 완벽하지는 않지만 보안한 방법이다.

    revalidate 값을 return에 추가해 주면 설정한 시간이 지난 뒤에 요청이 들어왔을 때 새롭게 Data를 가지고 오는 방식이다.

     

    이는 확실히 기존의 SSG보다는 좋았다.

    배포 뒤에는 새로운 Data를 업데이트할 방법이 없던 상황에서 큰 도움이 됐기 때문이다.

     

    그리고 Next@12에서 등장한 On Demand Revalidation은 이런 문제를 없애 주었다.

    아직은 Next의 api에서 생성한 api에서만 되지만 새로운 글을 등록하거나 했을 경우 res.revalidate('/path')를 하는 것으로 revalidate값을 입력하지 않아도 최신의 Data를 유지할 수 있도록 변경된 것이다.

     

    이제 SSG로 생성된 Data를 업데이트한 경우 해당 Data에 대해 항상 최신의 Data를 제공할 수 있게 변경되었다.

    ...
    res.revalidate('/any-path')
    return res.json({ data });
    ...

    최근 나의 경우에는 post의 경우 CSR로 react-query를 통해 가지고 오고 상세 페이지의 경우는 ISR(On Demand Revalidation)을 통해 이용하는 경우가 많다.

     

    물론 Backend를 Next에 의존해야 하는 만큼 사용에 제한이 있을 수 있지만 상황에 따라 유용하게 사용했으면 좋겠다.

     

    반응형

    댓글

Designed by Tistory.