ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Next.js - SSG, getStaticProps
    React.js & Next.js 2022. 9. 18. 20:39

     

    Data Fetching: getStaticProps | Next.js

     

    Data Fetching: getStaticProps | Next.js

    Fetch data and generate static pages with `getStaticProps`. Learn more about this API for data fetching in Next.js.

    nextjs.org

    정의

    getStaticProps 메서드를 export 한다면, Next.js 는 getStaticProps 에 의해 반환되는 props를 빌드 타입에 프리-렌더할 것입니다.

    export async function getStaticProps(context) {
      return {
        props: {}, // will be passed to the page component as props
      }
    }

    언제 getStaticProps를 사용할까?

    우리는 이런 상황에서 getStaticProps 를 사용할 수 있습니다 :

    • The data required to render the page is available at build time ahead of a user’s request
    • The data comes from a headless CMS
    • The page must be pre-rendered (for SEO) and be very fast — getStaticProps generates HTML and JSON files, both of which can be cached by a CDN for performance
    • The data can be publicly cached (not user-specific). This condition can be bypassed in certain specific situation by using a Middleware to rewrite the path.
    • 빌드시 고정되는 값으로 빌드 이후에는 수정이 불가능합니다
    • data를 빌드 시에 미리 땡겨와 정적으로(static 하게) 제공합니다
    • 매 유저의 요청마다 fetch할 필요가 없는 데이터를 가진 페이지를 렌더링할 때 유리합니다
    • 유저에 구애받지 않고 퍼블릭하게 캐시할 수 있는 데이터
    • SEO 등의 이슈로 인해 빠르게 미리 렌더링해야만 하는 페이지

    언제 getStaticProps 메서드가 실행될까?

    getStaticProps 메서드는 오직 서버에서 동작하며 클라이언트 단에서 동작하지 않습니다. getStaticProps 메서드 내부에 validate 코드를 추가하여, client-side 에서 번들링되는 것을 제거할 수 있습니다.

    • getStaticProps always runs during next build
    • getStaticProps runs in the background when using [fallback: true](https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-true)
    • getStaticProps is called before initial render when using [fallback: blocking](https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-blocking)
    • getStaticProps runs in the background when using revalidate
    • getStaticProps runs on-demand in the background when using [revalidate()](https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation)

    어디서 getStaticProps를 사용할 수 있을까?

    getStaticProps 는 오직 페이지에서만 exported될 수 있습니다. _app, _document, or _error 과 같은 페이지에서는 사용할 수 없습니다. 이것 들이 제한되는 이유들 중 하나는 리액트는 페이지가 렌더링되기 전에 모든 데이터를 필요로 하기 때문입니다.

    CMS로 부터 데이터를 패칭하기위해 getStaticProps 사용하기

    // posts will be populated at build time by getStaticProps()
    function Blog({ posts }) {
      return (
        <ul>
          {posts.map((post) => (
            <li>{post.title}</li>
          ))}
        </ul>
      )
    }
    
    // This function gets called at build time on server-side.
    // It won't be called on client-side, so you can even do
    // direct database queries.
    export async function getStaticProps() {
      // Call an external API endpoint to get posts.
      // You can use any data fetching library
      const res = await fetch('https://.../posts')
      const posts = await res.json()
    
      // By returning { props: { posts } }, the Blog component
      // will receive `posts` as a prop at build time
      return {
        props: {
          posts,
        },
      }
    }
    
    export default Blog

    SSG 적용하기 예시

    사용할 디렉터리 구조는 다음과 같습니다

    - pages
        - movie
            - spiderman
                    - index.tsx 🔥
            - [name].tsx 
          - index.tsx      
    - next.config.js     

    spiderman/index.tsx

    이전 SSR 사용 시에는 build 된 정적 파일을 가지고 있는 것이 아닌, 매 요청 마다 정보를 가져오는 흐름이었습니다. 이번에는 이런 구조에서 spiderman 이라는 정적 파일을 가지고 있다는 것을 가정합니다.

    export async function getStaticProps() {
      const res = await fetch(
        `https://api.themoviedb.org/3/search/movie?api_key=${REACT_APP_API_KEY}&language=en-US&query=spiderman`
      );
      const data: Imovie = await res.json();
    
      return { props: { data } };
    }

    구조는 위에 설명한 구조와 같습니다. fetch 함수를 통해 api 요청을 보내고 해당 데이터가 온전히 전달되었다면, json으로 반환하여 props로 반환합니다.

    사용한 API 는 영화 데이터를 무료로 제공하는 사이트인 TMDB 입니다. 실제로 따라하고 싶은 경우 해당 사이트에 회원가입 이후 키 값을 넣어주세요. 우리가 봐야할 부분은 쿼리 부분입니다.

    &query=spiderman

    이전 SSR 을 사용할 때는 query 문에 context.params 내부에 있는 다이나믹 라우팅 경로 값을 넣어주었었습니다.

    &query=${context.params?.name}

    하지만 이번에는 빌드 시에 해당 요청에 대한 데이터를 가지고 있어야 하기 때문에 query 문에 원하는 값(spiderman) 을 직접 입력해줍니다.

     

    반환된 리턴 값을 받아 렌더링하는 부분은 다음과 같습니다.

    interface ISpiderMan {
      data: Imovie;
    }
    
    const SpiderMan = ({ data }: ISpiderMan) => {
      const { results } = data;
    
      return (
        <>
          <div>
            <h1>Hello Movie</h1>
    
            <div style={{ display: 'flex', flexWrap: 'wrap' }}>
              {results.map((movie) => (
                <div style={{ marginTop: 5, display: 'flex', marginRight: 10 }} key={movie.id}>
                  <div style={{ position: 'relative', color: 'white' }}>
                    <Image
                      width={250}
                      height={200}
                      src={
                        movie.backdrop_path
                          ? `https://image.tmdb.org/t/p/original/${movie.backdrop_path}`
                          : `https://freesvg.org/img/1645699345cat.png`
                      }
                      alt={movie.title}
                    />
                    <span
                      style={{
                        position: 'absolute',
                        top: 5,
                        right: 5,
                        textShadow: '#000 1px 0 10px',
                      }}
                    >
                      {movie.title}
                    </span>
                  </div>
                </div>
              ))}
            </div>
          </div>
    
          <button type='button' onClick={() => (location.href = `/movie_SP`)}>
            back to main
          </button>
        </>
      );
    };
    
    export default SpiderMan;

    이전 SSR 에 사용된 [name].tsx 와 구조가 동일한 것을 알 수 있습니다.

    이렇게 SSG 를 사용하면 우리가 HTML 로 클라이언트 단에서 하드코딩할 데이터를 직접 가지고 있을 필요가 없기 때문에 클라이언트 단의 부담이 덜어지게 됩니다. 또한 서버-사이드에서 HTML 파일 및 데이터를 정적으로 가지고 있기 때문에 SEO에도 유리합니다.

    전체 코드 보기

    // types/movie/index.ts
    
    interface ImovieResults {
      adult: boolean;
      backdrop_path: string;
      genre_ids: string[];
      id: number;
      original_language: string;
      original_title: string;
      overview: string;
      popularity: number;
      poster_path: string;
      release_date: string;
      title: string;
      video: boolean;
      vote_average: number;
      vote_count: number;
    }
    
    export interface Imovie {
      page: number;
      results: ImovieResults[];
      total_pages: number;
      total_results: number;
    }
    import Image from 'next/image';
    
    import { REACT_APP_API_KEY } from 'config';
    import { Imovie } from '@/types/movie';
    
    interface ISpiderMan {
      data: Imovie;
    }
    
    const SpiderMan = ({ data }: ISpiderMan) => {
      const { results } = data;
    
      return (
        <>
          <div>
            <h1>Hello Movie</h1>
    
            <div style={{ display: 'flex', flexWrap: 'wrap' }}>
              {results.map((movie) => (
                <div style={{ marginTop: 5, display: 'flex', marginRight: 10 }} key={movie.id}>
                  <div style={{ position: 'relative', color: 'white' }}>
                    <Image
                      width={250}
                      height={200}
                      src={
                        movie.backdrop_path
                          ? `https://image.tmdb.org/t/p/original/${movie.backdrop_path}`
                          : `https://freesvg.org/img/1645699345cat.png`
                      }
                      alt={movie.title}
                    />
                    <span
                      style={{
                        position: 'absolute',
                        top: 5,
                        right: 5,
                        textShadow: '#000 1px 0 10px',
                      }}
                    >
                      {movie.title}
                    </span>
                  </div>
                </div>
              ))}
            </div>
          </div>
    
          <button type='button' onClick={() => (location.href = `/movie_SP`)}>
            back to main
          </button>
        </>
      );
    };
    
    export default SpiderMan;
    
    export async function getStaticProps() {
      const res = await fetch(
        `https://api.themoviedb.org/3/search/movie?api_key=${REACT_APP_API_KEY}&language=en-US&query=spiderman`
      );
      const data: Imovie = await res.json();
    
      return { props: { data } };
    }

    댓글

Designed by Tistory.