[Nextjs] How getInitialProps Works

2022. 3. 28. 22:06Frontend

 

 

Overview

 

Nextjs 공식문서에 따르면 next v9 이후 버전부터는(현재 기준 release 버전은 12.1.1) getInitialProps 대신에 getStaticProps와 getServerSideProps를 사용하도록 권장합니다. 이는 getInitialProps가 Client Side와 Server Side에서 모두 동작하는 특징을 갖고 있기 때문입니다. 이번 포스팅에서는 Nextjs의 getInitialProps의 구현을 살펴보며 실제로 어떤 식으로 동작하는지를 살펴보도록 하겠습니다.

 

 

Official Documents

getInitialProps를 설명한 공식문서를 보면 다음과 같은 설명을 확인할 수 있습니다.

getInitialProps enables server-side rendering in a page and allows you to do initial data population, it means sending the page with the data already populated from the server. This is especially useful for SEO.

 

클라이언트 사이드(브라우저)가 아닌 서버사이드에서 Data Fetching을 하여 해당 데이터를 포함한 페이지를 생성해서 클라이언트로 내려줄 수 있도록 도와주는 역할을 하는 것임을 알 수 있습니다. 하지만 조금 더 아래로 내려가 보면 동일한 문서에 아래와 같은 내용도 포함되어 있는 것을 확인할 수 있습니다.

 

For the initial page load, getInitialProps will run on the server only. getInitialProps will then run on the client when navigating to a different route via the next/link component or by using next/router. However, if getInitialProps is used in a custom _app.js, and the page being navigated to implements getServerSideProps, then getInitialProps will run on the server.

 

문서에 따르면 getInitialProps는 실제로 서버사이드와 클라이언트 사이드 모두에서 동작할 수 있는데, 클라이언트 환경에서 next/link나 next/router를 사용하여 페이지를 이동할 경우, getInitialProps가 포함된 번들이 클라이언트 사이드로 내려와서 실제로 이 메서드는 클라이언트 사이드에서 동작하게 됩니다. 만약 URL을 통해 새로운 페이지가 처음 요청되는 상황이거나, next/link, next/router를 통한 navigation이 아닌 a tag의 href로 이동하는 경우 등에는 getInitialProps는 서버사이드에서 동작하게 됩니다.

 

 

How to Use

 실제로 getInitialProps는 경우에 따라 Client Side에서도 동작하기 때문에 Server Side에서만 동작할 것으로 가정하고 사용하면 불필요한 bundle이 내려가서 애플리케이션의 번들 사이즈를 키우는 원인이 됩니다. 따라서 서버사이드에서 데이터를 요청하는 것이 확실할 경우에는 nextjs 공식문서에서 권장하는 방식대로  getServerSideProps를 사용하는 것이 좋습니다. (단 next-redux-wrapper와 같은 라이브러리의 경우 내부적으로 getInitialProps를 사용하는 경우들이 있습니다.)

 

 

Code Implementations

실제로 nextjs의 _app.tsx 코드를 보면 getinitialProps가 class의 static method로 정의되어 있는 것을 확인할 수 있습니다

https://github.com/vercel/next.js/blob/canary/packages/next/pages/_app.tsx#L36

async function appGetInitialProps({
  Component,
  ctx,
}: AppContext): Promise<AppInitialProps> {
  const pageProps = await loadGetInitialProps(Component, ctx)
  return { pageProps }
}

export default class App<P = {}, CP = {}, S = {}> extends React.Component<
  P & AppProps<CP>,
  S
> {
  static origGetInitialProps = appGetInitialProps
  static getInitialProps = appGetInitialProps

  render() {
    const { Component, pageProps } = this.props as AppProps<CP>

    return <Component {...pageProps} />
  }
}

 

 

여기서 정의된 appGetInitialProps라는 함수는 실제로 loadGetInitialProps라는 async함수를 호출하며 이 함수는 유저가 Custom하게 정의해서 사용할 수 있습니다.

// This is not Nextjs Implementation
// This is Client Business Logic Example
function Page({ stars }) {
  return <div>Next stars: {stars}</div>
}

Page.getInitialProps = async (ctx) => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const json = await res.json()
  return { stars: json.stargazers_count }
}

export default Page

 

 

서버사이드에서 nextjs는 위의 getInitialProps를 실행해서 가져온 props를 사용해서 HTML을 서버사이드에서 렌더합니다. 이때 _app.tsx에서도 getInitialProps를 실행할 수 있고 document.tsx에서도 getInitialProps를 실행할 수 있는데 이 경우, _app.tsx의 로직이 먼저 실행됩니다 (nextjs lifecycle 상으로도 _app.tsx가 먼저 실행됩니다.)

https://github.com/vercel/next.js/blob/canary/packages/next/server/render.tsx#L843

// renderHtml
...
 props = await loadGetInitialProps(App, {
    AppTree: ctx.AppTree,
    Component,
    router,
    ctx,
 })
 ...

 

 

클라이언트 사이드에서 next/router를 사용하게 되면 (next/link를 사용하는 경우에도 onClick Event Handler는 내부적으로 next/router를 사용하므로 원리는 동일합니다.) change event시에 getInitialProps를 호출하게 됩니다. 

https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/router/router.ts#L907

  private async change(
    method: HistoryMethod,
    url: string,
    as: string,
    options: TransitionOptions,
    forcedScroll?: { x: number; y: number }
  ): Promise<boolean> {
  ...
  
   if (!isLocalURL(as)) {
      if (process.env.NODE_ENV !== 'production') {
        throw new Error(
          `Invalid href: "${url}" and as: "${as}", received relative href and external as` +
            `\nSee more info: https://nextjs.org/docs/messages/invalid-relative-url-external-as`
        )
      }

      window.location.href = as
      return false
    }
  
  ...
  
  
  try {
      let routeInfo = await this.getRouteInfo(
        route,
        pathname,
        query,
        as,
        resolvedAs,
        routeProps,
        nextState.locale,
        nextState.isPreview
      )
      ...
  }
  
  ...
  }

 

 

컨텍스트를 조금 더 살펴보면, 외부 도메인으로 요청을 하는 경우에는 별도의 로직 없이 window.location.href로 바로 해당 도메인 페이지로 이동을 시키지만 외부 도메인이 아닌 경우, 즉 next가 컨트롤할 수 있는 페이지로의 이동을 하는 경우에는 routeInfo를 사전에 preflight Fetch를 하는 것을 확인할 수 있으며, 이 preflight fetch를 하는 과정에서 몇 가지 조건을 확인한 뒤 getInitialProps를 호출하는 것을 확인할 수 있습니다. 이 next/router가 동작하는 곳은 Client Side이기 때문에 공식 문서에 명시된 대로 Client Side에서 getInitialProps가 동작하고 있는 것입니다.

https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/router/router.ts#L1568

async getRouteInfo(
    route: string,
    pathname: string,
    query: any,
    as: string,
    resolvedAs: string,
    routeProps: RouteProperties,
    locale: string | undefined,
    isPreview: boolean
  ): Promise<PrivateRouteInfo> {
  ...
  const props = await this._getData<CompletePrivateRouteInfo>(() =>
        __N_SSG || __N_SSP
          ? fetchNextData(
              dataHref!,
              this.isSsr,
              false,
              __N_SSG ? this.sdc : this.sdr,
              !!__N_SSG && !isPreview
            )
          : this.getInitialProps(
              Component,
              // we provide AppTree later so this needs to be `any`
              {
                pathname,
                query,
                asPath: as,
                locale,
                locales: this.locales,
                defaultLocale: this.defaultLocale,
              } as any
            )
      )
  ...
  }

 

반응형

'Frontend' 카테고리의 다른 글

[Web] preload, prefetch, preconnect  (0) 2022.04.03
[Javascript] script async & defer  (1) 2022.04.02
Strict Mode  (0) 2022.03.27
[Web.dev] Network  (0) 2022.03.19
[Web.dev] Accessibility (2)  (0) 2022.03.19