Next.js 튜토리얼 (2)

오호·2022년 3월 3일
1

지난 챕터에서는 Hello World 부터 Next의 라우팅까지 관련 기능들을 살펴보았다.

이번 챕터에서는 Next의 가장 중요한 Pre-rendering과 관련된 기능들을 살펴보자 🔥

전통적인? React에서의 방식과 Next의 방식을 비교하면서 알아보자.

1. React 방식

우선 CRA 방식으로 React 프로젝트를 하나 생성해보자.

npx create-react-app react-demo
yarn start

app을 실행시키고 우클릭해서 페이지 소스보기를 클릭해보면 <div id="root"></div>만 존재하는 것을 확인할 수 있다. 이는 CSR의 전통적인 방식을 따르고 있음을 확인할 수 있다.

필요한 데이터를 모두 로드시키고 스크립트를 통해 필요한 데이터와 DOM을 동적으로 생성시키는 것이다.

2. Next 방식

이번에는 Next방식을 살펴보자.

npx create-next-app next-demo
yarn dev

똑같이 우클릭하고 페이지 소스보기를 클릭한다.

코드가 minify되어 있어서 보기가 힘들지만 자세히보면 div외에도 main,h2 등 많은 태그가 이미 생성되어져 있는 것을 확인할 수 있다.

이게 바로 리액트와 next의 큰 차이점이다.

Next.js는 디폴트로 모든 페이지에 pre-rendering을 지원한다.

2.1 pre-rendering?

Next.js는 클라이언트 측 자바 스크립트로 모든 작업을 수행하는 대신 각 페이지에 대해 미리 HTML을 생성한다.

plain React 앱에서는 첫 로드 때 하얀 화면만 보이지만 hydration을 거쳐야 앱이 비로소 렌더링되고 interactive 한 동작을 할 수 있다.

반면에 Next에서는 pre-rendered된 HTML이 먼저 보여지고 htdration을 거치고 나서야 동적으로 동작하게된다.

2.2 why?

  • pre-redering은 HTML을 미리 생성하고 보여주기 때문에 훨씬 좋은 사용자 경험과 성능을 보여준다.

  • 미리 HTML이 렌더링되기 때문에 SEO에 큰 강점을 가지고 있다.

3. pre-rendering

Next.js는 두 가지의 프리렌더링을 지원한다.

  • Static Generation
  • Server-side Rendering

3.1 Static Generation

Static Generation은 HTML 페이지가 빌드 시 생성되는 프리 렌더링 방법이다.

웹 페이지의 콘텐츠를 구성하는 모든 데이터가 포함된 HTML은 애플리케이션을 빌드할 때 미리 생성된다.

공식문서에서는 데이터가 있든 없든, 가능하다면 Static Generation을 사용할 것을 추천한다. 왜냐면 build time에 한 번 만들어지고나서 CDN에 의해 served 되는데, 이 경우는 매 요청시마다 페이지를 렌더링하는 SSR보다 더 빠르기 때문이다.

  • Marketing pages (판매 페이지)
  • Blog posts (블로그 글)
  • E-commerce product listings (온라인 커머스 및 상품 나열)

Static Generation : HTML이 build time에 생성이 되고, 매 요청시마다 재사용된다.

유저의 요청이 있기 전에 사전 렌더링이 될 수 있는 페이지들에 적절한 방식이다. 추가적으로 데이터를 가져오기 위해 Client Side Rendering을 사용할 수도 있음!

3.2 how?

Next.js는 기본적으로 우리의 앱의 모든 페이지들에 pre-render를 지원해준다.

즉 빌드시에 자동으로 모든 페이지 HTML을 생성해준다는 것이다.

3.3 Static Generation & Data

그림처럼 데이터가 있는 경우에 HTML은 데이터를 가져온 이후에만 생성될 수 있는데 실습을 진행하면서 알아보자.

3.4 Static Generation with getStaticProps

npx create-next-app next-pre-rendering

새로 프로젝트를 생성해보자.

index.js

import React from 'react';

function Home() {
  return <h1>Home</h1>;
}

export default Home;

data-fetching을 테스트해보기 위해서 컴포넌트를 하나 생성한다.

users.js

import React from 'react';

function UserList() {
  return <h1>List of Users</h1>;
}

export default UserList;

Static Generation으로 구현하기 위해 Next.js는 메서드를 제공한다. getStaticProps이다.

하단에 다음 함수를 추가해보자.

export async function getStaticProps() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await response.json();
  console.log(data);

  return {
    props: {
      users: data,
    },
  };
}

중간에 콘솔을 찍었는데 브라우저가 아닌 터미널에 데이터가 찍히는 것을 확인할 수 있다.

데이터를 컴포넌트로 전달해서 브라우저에 렌더링해야하는데 Next.js에서는 다음과 같은 포맷으로 데이터를 전달한다.

return {
	props : {
      users : data
    }
}
import React from 'react';

function UserList({ users }) {
  return (
    <>
      <h1>List of Users</h1>
      {users.map((user) => {
        return (
          <div key={user.id}>
            <p>{user.name}</p>
            <p>{user.email}</p>
          </div>
        );
      })}
    </>
  );
}

export default UserList;

전달한 데이터는 컴포넌트에 props로 전달되어진다.

3.5 pages vs components

위에서 작성한 공통되는 부분을 컴포넌트화 시키고 싶을 때 어떻게 해야할까? 다들 알다시피 간단하다.

Next.js에서 pages라는 폴더는 매우 스페셜하다. 라우팅을 처리하기 때문에 즉 components라는 폴더를 따로 생성해서 그 곳에 컴포넌트를 위치시키도록하자.

import React from 'react';

function User({ user }) {
  return (
    <>
      <p>{user.name}</p>
      <p>{user.email}</p>
    </>
  );
}

export default User;

3.6 getStaticProps 주의사항

  • 이 메서드는 서버사이드에서만 동작한다.
  • 절대 클라이언트 사이드에서 동작하지 않는다.
  • getStaticProps는 page에서만 유효하고 일반적인 컴포넌트에서는 동작하지 않는다.
  • 이 메서드는 프리렌더링에만 사용되고 client-side data fetching에는 사용되지 않는다.
  • 항상 객체를 리턴해야하고 객체는 props 라는 키를 가져야한다.

3.7 SSG with Dynamic Parameter

posts > index.js

import React from 'react';

function PostList({ posts }) {
  return (
    <>
      <h1>List of Posts</h1>
      {posts.map((post) => {
        return (
          <div key={post.id}>
            <h2>
              {post.id} {post.title}
            </h2>
          </div>
        );
      })}
    </>
  );
}

export default PostList;

export async function getStaticProps() {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  const data = await response.json();

  return {
    props: {
      posts: data.slice(0, 3),
    },
  };
}

Static Generation을 위해 새로운 컴포넌트를 생성하고 getStaticProps로 데이터를 새로 만들어 props로 넘겨주었다.

이번에는 동적 라우팅 그리고 Static Generation을 해주기 위해 컴포넌트를 새로 생성했다.

[postId].js

function Post({ post }) {
  return (
    <>
      <Link href={`posts/${post.id}`} passHref>
        <h2>
  			{post.id} {post.title}
      	</h2>
      </Link>
      <p>{post.body}</p>
    </>
  );
}

export default Post;

export async function getStaticProps(context) {
  const { params } = context;
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${params.postId}`,
  );

  const data = await response.json();

  return {
    props: {
      post: data,
    },
  };
}

에러가 난다.
일단 기존 리스트 페이지와는 다르게 Dynamic Params로 data-fetching 하는 이 페이지는 단 하나의 페이지가 아님을 알아야한다. 우리는 다른 데이터들로 이루어진 최소 2개 이상의 동적 페이지를 만들 때 이렇게 사용한다.

Next.js는 id가 1, 2, 3가 될 수도 있고 100000이 될 수 있기 때문에 빌드시에 어떤 파라미터를 허용할지를 결정해줘야 한다.

결론은 이런 상황에는 getStaticPaths를 사용하자

export async function getStaticPaths() {
  return {
    paths: [
      { params: { postId: '1' } },
      { params: { postId: '2' } },
      { params: { postId: '3' } },
    ],
    fallback: false,
  };
}

이렇게 string으로 postId를 직접 넘겨주었더니 제대로 작동한다.

3.8 none hard coding

이번에는 하드코딩하지 않고 여러 개의 라우팅을 구현해보자.

export async function getStaticPaths() {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts`);
  const data = await response.json();

  const paths = data.map((post) => {
    return {
      params: {
        postId: `${post.id}`,
      },
    };
  });

  return {
    // paths: [
    //   { params: { postId: '1' } },
    //   { params: { postId: '2' } },
    //   { params: { postId: '3' } },
    // ],
    paths,
    fallback: false,
  };
}
profile
오호

0개의 댓글