본문 바로가기

[Nextjs] 맨날 햇갈리는 Data Fetching 정리

Nextjs를 하면서 맨날 햇갈리는 ㅠㅠ Data Fetching 부분을 정리하였다.

(번역투 다수)

 

Prerequisite

정리 전에 Nextjs의 pre-rendering을 알고 가야한다.

 

Each generated HTML is associated with minimal JavaScript code necessary for that page. When a page is loaded by the browser, its JavaScript code runs and makes the page fully interactive. (This process is called hydration.)

 

생성된 각 HTML은 최소한의 자바스크립트 코드와 함께 생성되고, 그 js코드는 페이지가 완전히 인터렉티브하도록 한다. (ReactDOM.hydrate) 이것을 hydration이라고 한다.

 

* hydration에 관해 알기 쉽게 정리해주신 글 : 리액트의 hydration이란?

 

 

Pre-rendering에는 Static Generation과 Server-side rendering이 있다.

 

Nextjs에서는 Static Generation > Server-side Rendering > Client-side Rendering 순으로 권장한다. (성능 측면에서)

 

1. Static Generation (정적 생성)

정적 생성은 개발자가 next build 를 실행했을 때 HTML을 생성한다. (빌드 타임에 생성)

 

보통 데이터에 의존하지 않는 (즉, Data Fetching하지 않는 페이지들) 페이지들은 모두 이에 해당한다.

또한 페이지의 컨텐츠나 경로가 외부 데이터에 의존할 경우, getStaticPropsgetStaticPaths 를 활용하여 HTML을 정적 생성할 수 있다.

 

정적 생성을 사용하는 대표적인 사례로는 인터렉션에 관계없이 똑같은 정보를 제공해야 하는 프로모션 페이지를 들 수 있다.

 

You should ask yourself: "Can I pre-render this page ahead of a user's request?" If the answer is yes, then you should choose Static Generation.

 

반면에 User Request에 따라 데이터가 달라져야한다면 Server-side rendering 혹은 Client-side rendering을 활용한다.

 

2. Server-side rendering (SSR)

매 요청마다 데이터를 fetching해야 한다면 getServerSideProps 를 사용하여 SSR한다.

 

 

Data Fetching

1. getStaticProps

페이지를 외부데이터를 받아 Static Generation하기 위한 용도이다.

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

 

args로는 다음을 받는다.

  • params : 동적 라우터의 params을 받는다. ([id].js -> {id})
  • preview : 페이지를 preview mode로 fetching (여기 읽어야 함)
  • previewData : preview mode일 때 받는 preview 데이터

리턴 값으로는 다음이 있다.

  • props (object) : 컴포넌트가 받을 props
  • revalidate (sec) : 페이지가 재생성될 지연 시간
  • notFound (boolean) : true일 때 404 리턴
  • redirect ({ destination: string, permanent: boolean }) : 리다이렉트할 페이지 경로

 

⚠️ 주의사항

Imports used in getStaticProps will not be bundled for the client-side.

 

getStaticProps에서 임포트된 모듈은 CSR 번들러에 포함되지 않는다.

 

Note: You should not use fetch() to call an API route in getStaticProps. Instead, directly import the logic used inside your API route. You may need to slightly refactor your code for this approach.

 

/api 는 호출하면 안된다. (외부 URL은 괜찮다.)

 

Note: Redirecting at build-time is currently not allowed and if the redirects are known at build-time they should be added in next.config.js.

 

빌드 타임 때 redirect는 현재 허용되지 않는다. 따라서 next.config.jsasync redirect() 로 설정한다.

 

💡 타입스크립트에서 사용하기

타입스크립트에서 getStaticProps가 리턴하는 Props의 타입을 추론할 수 있다.

function Info({ posts }: InferGetStaticPropsType<typeof getStaticProps>) {
}

 

Incremental Static Regeneration (ISR)

Incremental Static Regeneration은 정적 페이지가 생성된 후, 재빌드하지 않아도 페이지를 갱신할 때 유용하다.

앞에서 봤던 getStaticProps에서 revalidate 가 바로 ISR을 가능하게 해준다.

export async function getStaticProps() {
    const covidStats = await axios.get('/covid/stats')

    if (!covidStats) {
        return {
            notFound: true,
        }
    }

    return {
        props: {...covidStats},
        notFound: false,
        revalidate: 60 * 60
    }
}

예를 들어 /covid/stats 요청 후 리턴 값으로 revalidate: 60 * 60을 주었다고 가정한다.

nextjs는 다음과 같이 동작한다.

  • 1시간 전에 들어온 요청은 캐시에서 리턴한다.
  • 1시간이 지난 후, nextjs가 페이지 재생성을 요청하기 전까지는 캐싱된 데이터를 보여준다.
  • next가 페이지 재생성을 요청하면, 캐싱된 데이터를 invalidate하고 새로 요청한 데이터를 리턴한다.
  • 만약 새로 요청한 API가 실패하면 이전에 캐싱된 데이터를 리턴한다.

즉, 이전 페이지를 유지할 margin 시간을 설정할 수 있고, 시간이 지난 후 페이지 재생성을 요청가능하다.

 

ISR: Not just Caching 에서 다음처럼 버전 관리에도 유용하다고 말한다.

ISR is designed to persist your generated pages between deployments. This means you are able to rollback instantly and not lose your previously generated pages.

 

기술적인 상세

  • Build Time에만 실행되어 query parameter, HTTP header 등 요청때에만 유효한 값들은 받을 수 없다.
  • Server-side 코드를 바로 작성할 수 있다. (SQL 쿼리 등)
  • 페이지 뿐만 아니라 JSON도 정적으로 생성하여, next/link 등으로 getStaticProps를 사용하는 페이지로 라우팅되어도 getStaticProps가 호출되는 것이 아니라 정적으로 생성된 JSON 데이터를 사용한다.
  • next dev 로 실행한 개발 환경에서는 매 요청마다 getStaticProps가 재호출된다.

 

2. getStaticPaths

동적 라우팅을 사용하고, getStaticProps를 사용하는 페이지라면 빌드 타임 때 렌더링할 경로를 지정해줘야 한다.

 

export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } } 
    ],
    fallback: true or false
  };
}

 

리턴값은 다음과 같다.

  • paths (필수) : 어떤 경로를 pre-rendering할 것인지 명시한다.
  • fallback (필수)
    • `false` : paths에 포함되지 않은 값은 404를 리턴한다.
    • `true` : paths에 포함되지 않은 페이지는 404를 리턴하는 대신, router.isFallback(useRouter)을 true로 리턴하여 별도의 Fallback Pages로 이동한다. 그 후, getStaticProps에서 데이터를 Fetching하고 props를 리턴하면 정상적으로 페이지를 정적 생성한다. 즉, true라면 getStaticPaths에서 pre-rendering하지 않고 getStaticProps에서 하겠다는 의미이다.
    • `blocking` : getStaticPaths에 포함되지 않은 경로는 SSR처럼 페이지가 생성되기를 기다렸다가 해당 페이지 경로를 캐싱한 후 다음 요청 시에는 캐싱된 데이터를 리턴한다. 즉, 처음에는 pre-rendering하지 않고 SSR하고, 다음요청시에는 정적 생성한다.

 

기술적인 상세

  • getStaticProps와 함께 사용한다. getServerSideProps와는 함께 사용하지 않는다.

 

3. getServerSideProps

매 요청마다 페이지를 pre-rendering한다.

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

 

getServerSideProps가 받은 주요 인자는 다음과 같다.

  • params : 동적 라우팅 사용 시 받는 params
  • req : HTTP IncomingMessage
  • res : HTTP Response
  • query : query string

리턴값은 다음과 같다.

  • props : 컴포넌트가 받을 props
  • notFound : true일 때 404 리턴
  • redirect : 리다이렉트할 페이지 경로
Time to first byte (TTFB) will be slower than getStaticProps because the server must compute the result on every request, and the result cannot be cached by a CDN without extra configuration.

getStaticProps와 유사하게 사용하지만 다른점은 캐싱하지 않고, 매요청마다 서버에서 계산하기 때문에 더 느리다.

`InferGetServerSidePropsType<typeof getServerSideProps>` 으로 props의 타입을 추론할 수 있다.

 

3. SWR (Client-side Rendering)

Pre-rendering하고 싶지 않다면 SWR을 사용하여 CSR한다.

 

4. (번외) getInitialProps

getInitialProps또한 getServerSideProps처럼 SSR로 동작하지만 SEO에 적합하다.

 

공식문서에서는 위의 getStaticProps, getServerSideProps 사용을 권장한다.

If you're using Next.js 9.3 or newer, we recommend that you use getStaticProps or getServerSideProps instead of getInitialProps.

 

응용

실무에서 사용한 예시이다.

export async function getStaticProps() {
    try {
        const product = await withAxios<Product>({
            url: `/product`,
            baseURL: STATIC_HOST_URL,
        })

        if (!product) {
            return {
                props: {
                    product: {} as Product
                   
                },
            }
        }

        return {
            props: {
                product
            },
        }
    } catch {
        return {
            props: {
                product: {} as Product
            },
        }
    }
}

 

⚠️ 주의

next.config.js에서 rewires 로 Api Url Proxy를 설정했는데, Client-side routing에 적용되므로 Static Generation은 적용되지 않는다.

Rewrites allow you to map an incoming request path to a different destination path.
Rewrites are applied to client-side routing, a <Link href="/about"> will have the rewrite applied in the above example.
// in next.config.js

module.exports = {
    // ... 중략 ...
    async rewrites() {
        return [
            {
                basePath: false,
                source: '/api/:path*',
                destination: `${API_ORIGIN}/api/:path*`
            }
        ]
    },
}

 

따라서 getStaticProps에서 baseURL을 별도로 넘겨주었다.

const product = await withAxios<Product>({
    url: `/product`,
    baseURL: STATIC_HOST_URL,
})

'Today I Learn > 프론트엔드' 카테고리의 다른 글

cypress를 써보자! (3)  (0) 2021.07.11
react-spring으로 구현해본 애니메이션 정리  (0) 2021.06.29
cypress를 써보자! (2)  (0) 2021.05.23
cypress를 써보자! (1)  (0) 2021.05.16
TDD로 서비스 개발하기  (2) 2021.04.18