# next.js getInitialProps 사용법

서버사이드 렌더링을 하는 nextJs에서 컴포넌트는 각 페이지마다 사전에 불러와야할 데이터가 있습니다.(이하 data fetching) react, vue같은 Client Side Rendering (CSR)의 경우는 useEffect, created 함수를 이용하여 data fetching을 합니다. 서버사이드에서 실행하는 next에서는 getInitialProps를 이용하여 data fetching 작업을 합니다.

next v9 이상에서는 getInitialProps 대신 getStaticProps, getStaticPaths, getServerSideProps을 사용하도록 가이드 합니다.

# getStaticProps

Fetch data at build time, pre-render for Static Generation
getStaticPaths only runs at build time on server-side.

빌스시 고정되는 값, 빌드 이후 값 변경 불가

function Blog({ posts }) {
  return (
    <ul>
      {posts.map(post => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
}

export async function getStaticProps() {
  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;

docs 예제에서는 fetch를 통해 게시물을 가져오고 그 게시물의 title을 보여줍니다.

# getStaticPatch

  • 빌드 타임 때, 정적으로 렌더링할 경로 설정
  • 이곳에 정의하지 않은 하위 경로는 접근해도 페이지가 안뜸
    • 동적라우팅 : 라우팅 되는 경우의 수 따져서 하위로 넣음

/pages/dyna/[dynamic].js: /dyna/동적인값

// This function gets called at build time
export async function getStaticPaths() {
  return {
    //빌드 타임 때 아래 정의한  /dyna/1,  /dyna/2, ... /dyna/동적인값 경로만 pre렌더링.
    paths: [
      { params: { dynamic: 1 } },
      { params: { dynmic: 2 } }
      ......
      { params: { dynmic: 동적인값 } }
    ],
    // 만들어지지 않은 것도 추후 요청이 들어오면 만들어 줄 지 여부.
    fallback: true,
  }
}

# getServerSideProps

Fetch data on each request. pre-render for Server-side Rendering
function Page({ data }) {
  // Render data...
}

// This gets called on every request
export const getServerSideProps: GetServerSideProps = async context => {
  // 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;

각 요청에 따라 서버로부터 데이터를 가져옵니다.

# 언제 쓰는가?

getServerSideProps는 데이터 요청시 인출해야 페이지를 미리 렌더링해야하는 경우에만. TTFB (Time to First byte)는 getStaticProps서버가 모든 요청에 ​​대해 결과를 계산해야하고 추가 구성 없이는 결과를 CDN에 의해 ​​캐시 할 수 없기 때문에 더 느립니다.

데이터를 미리 렌더링 할 필요가없는 경우 클라이언트 측에서 데이터를 가져 오는 것을 고려해야합니다.

# 클라이언트 측에서 데이터 가져오기

페이지에 자주 업데이트되는 데이터가 포함되어 있고 데이터를 미리 렌더링 할 필요가없는 경우 클라이언트 측에서 데이터를 가져올 수 있습니다. 이에 대한 예는 사용자 별 데이터입니다. 작동 방식은 다음과 같습니다.

  • 먼저 데이터가 없는 페이지를 즉시 표시합니다. 페이지의 일부는 정적 생성을 사용하여 미리 렌더링 할 수 있습니다. 누락 된 데이터에 대한로드 상태를 표시 할 수 있습니다.
  • 그런 다음 클라이언트 측에서 데이터를 가져와 준비가 되면 표시합니다.

예를 들어 이 접근 방식은 사용자 대시 보드 페이지에 적합합니다. 대시 보드는 비공개 사용자 별 페이지이기 때문에 SEO는 관련이 없으며 페이지를 미리 렌더링 할 필요가 없습니다. 데이터는 자주 업데이트되므로 요청 시간 데이터 가져 오기가 필요합니다.

# SWR

next에서 만든 SWR을 사용하여 client side에서 데이터 패치를 합니다.

import useSWR from "swr";

function Profile() {
  const { data, error } = useSWR("/api/user", fetch);

  if (error) return <div>failed to load</div>;
  if (!data) return <div>loading...</div>;
  return <div>hello {data.name}!</div>;
}

seo 필요한 페이지면 getServerSideProps를 쓰고 비공개 페이지일 경우는 클라이언트 측에서 가져온다 (getServerSideProps를 남발하면 서버가 모든 요청을 계산하고, 값 캐쉬가 힘들기에 비효율적입니다)

현재 구현한 코드가 getInitialProps임으로 getInitialProps로 포스팅을 이어 가겠습니다.

# getInitialProps 이점

  1. 속도가 빨라집니다. 서버는 data fetching만, 브라우저는 렌더링만 함으로 연산을 브라우저와 서버가 각각 나누어 분담하게되어 그만큼 속도가 빨라집니다.
  2. 함수형 컴포넌트로 next를 코딩할 경우, 렌더링 하는 함수와 data fetching을 하는 함수가 분리됨으로 개발자의 입장에서 로직 파악이 쉽습니다. (예시 코드를 보면서 자세히 설명하겠습니다.)

# 사용법

일단 data fetching을 하지 않음으로 오류를 나는 코드를 작성하겠습니다.

data fetching 할 mok 데이터 (src/posts.json)를 먼저 정의합니다.

{
  "test": {
    "title": "test post",
    "content": "test content"
  },
  "second": {
    "title": "second post",
    "content": "second content"
  }
}

데이터를 보여줄 컴포넌트입니다.

혹시 동적 url(아래의 [id].tsx)에 대해 잘 모르신다면 동적url (opens new window) 이곳을 잠시 보고 와주세요 짧습니다!

// src/pages/[id].tsx
import { useEffect } from "react";
import { useRouter } from "next/router";
import posts from "../posts.json";

const Posts = () => {
  const router = useRouter();
  const post = posts[router.query.id];

  return (
    <>
      <h1>{post.title}</h1>
      <h1>{post.content}</h1>
    </>
  );
};

export default Posts;

위처럼 코드를 작성하고 yarn run dev 후에 localhost:3000/test로 접속합니다.

[id].tsx가 렌더링 될 때, post 값이 없기 때문에 title이 없다는 에러가 납니다.

CSR에서는 이럴때 if (!post) return <p></p>; 이러한 코드를 추가하여 data fetching이 되기를 기다립니다.









 











// src/pages/[id].tsx
import { useEffect } from "react";
import { useRouter } from "next/router";
import posts from "../posts.json";

const Posts = () => {
  const router = useRouter();
  const post = posts[router.query.id];
  if (!post) return <p></p>;

  return (
    <>
      <h1>{post.title}</h1>
      <h1>{post.content}</h1>
    </>
  );
};

export default Posts;

하이라이트한 부분을 추가하면 에러가 나지 않습니다.

ssr에서는 getInitalProps를 이용하여 데이터를 미리 받아오고, 렌더링 할 당시에는 이미 값이 있기 때문에 렌더링이 되는 방식으로 사용합니다.
















 
 
 
 
 
 
 


import { useEffect } from "react";
import { useRouter } from "next/router";
import posts from "../posts.json";

const Posts = (props: { post: { title: string; content: string } }) => {
  const router = useRouter();

  return (
    <>
      <h1>{props.post.title}</h1>
      <h1>{props.post.content}</h1>
    </>
  );
};

Posts.getInitialProps = context => {
  // context.query.id = 'test'
  return {
    post: posts[context.query.id]
  };
};

export default Posts;

위처럼 컴포넌트의 외부에 getInitialProps 함수를 선언만합니다. 실행은 렌더링 될때 알아서 실행됩니다.

getInitialProps 내부에는 context, component등 여러 객체가 있으며 그 중 query.id에 접근하여 우리의 url인 'test'를 받아오고, mok data에서 test 객체를 꺼내와 post에 담습니다.

post는 컴포넌트의 props에 담겨서 컴포넌트가 렌더링 될때 바로 사용 가능합니다.

# 주의사항

# 하나의 페이지에서는 하나의 getInitialProps만 실행됨

이 이슈는 대단히 중요합니다. 페이지가 렌더링 될때 next 내부에서 거치는 순서는 _app -> page component입니다. 즉, 예시 코드로 말씀드리면 _app.tsx가 실행되고 [id].tsx가 실행됩니다.

만약 _appgetInitialProps를 정의했다면, 하위 컴포넌트에서는 getInitialProps는가 실행되지 않습니다.

하위 컴포넌트에서도 getInitialProps 값을 반영하려면 아래와 같이 _app.tsx에 코드를 추가합니다.








 
 
 
 
 
 
 
 
 
 
 
 



// src/_app.tsx
import "./globals.css";

function MyApp({ Component, pageProps }) {
  return <Component ponent {...pageProps} />;
}

MyApp.getInitialProps = async ({ Component, ctx }) => {
  let pageProps = {};
  // 하위 컴포넌트에 getInitialProps가 있다면 추가 (각 개별 컴포넌트에서 사용할 값 추가)
  if (Component.getInitialProps) {
    pageProps = await Component.getInitialProps(ctx);
  }

  // _app에서 props 추가 (모든 컴포넌트에서 공통적으로 사용할 값 추가)
  pageProps = { ...pageProps, posttt: { title: 11111, content: 3333 } };

  return { pageProps };
};

export default MyApp;

위 예시 코드처럼 모든 컴포넌트에서 추가할 값을 _app.tsx에서 추가하고, 각 개별 컴포넌트에서 사용할 값은 개별 컴포넌트에서 추가하면 됩니다.

# getInitialProps는 서버에서 실행됨

즉, 브라우저 api (setTimeout, window.xxx, document.xxx)는 이곳에서 실행하면 안됩니다.

# Context Object

  • pathname - 현재 pathname (/user?type=normal-> /user)
  • queyr - 현재 query를 객체로 (http://localhost:3000/blog/test -> {id: 'test'}, /post?type=secret -> {type: 'secret'})
  • asPath - 전체 path (http://localhost:3000/blog/test -> /blog/[id], /blog/test)
  • req - HTTP request object (server only)
  • res - HTTP response object (server only)
  • err - Error object if any error is encountered during the rendering

# shallow route와 getInitialProps과 관계

아래 글들은 getServerSideProps, getStaticProps, getInitialProps 모두 동일하게 적용됩니다.

shallow routing은 getInitialProps를 이용하여 데이터를 가지오지 않고도 url을 변경 할 수 있습니다.

즉, 불필요한 서버 연산을 최소화 할 수 있고, 필요한 상태 값은 아래 예시코드 처럼 router 객체에 넣어서 전달합니다.

router.push(
  {
    pathname: "/cars?model=bmw",
    query: { ...values, page: 1 }
  },
  undefined,
  { shallow: true }
);

위의 예제로 보면 원래는 /cars 페이지에 있다가 어떤 이벤트를 통해 "/cars?model=bmw"로 바뀌었습니다.

페이지는 교체되지 않고, url만 바뀌는 경우 입니다. url이 변경되는 것은 componentDidUpdate, useEffect를 통해 감지 할수 있습니다.

componentDidUpdate(prevProps) {
  const { pathname, query } = this.props.router
  // verify props have changed to avoid an infinite loop
  if (query.counter !== prevProps.router.query.counter) {
    // fetch data based on the new query
  }
}

# 주의사항

shallow routing은 동일 페이지의 url의 변경에서만 작동합니다. 즉, 다른 페이지로 이동시 새 페이지가 로드하고, getInitialProps는 실행됩니다. 예시로 보자면 현재 url은 /cars입니다.

router.push(
  {
    pathname: "/users",
    query: { ...values, page: 1 }
  },
  undefined,
  { shallow: true }
);

위처럼 /users로 url이 바뀌면 페이지가 바뀌었기 때문에 새로운 페이지가 로드되고 getInitialProps가 실행됨으로 data fetching을 하게됩니다. 즉, shallow routing이 의미가 없는 것이죠.

같은 페이지에서 url이 바뀌는데 getInitialProps가 실행됨으로 굳이 같은 data fetching을 할 필요가 없을 때, shallow routing을 사용하면됩니다.

#react #next
노경환
이 글이 도움이 되셨다면! 깃헙 스타 부탁드립니다 😊😄
최근변경일: 5/14/2022, 8:52:31 AM