NextJs #4 pre-rendering / data-fetching

TaejoonPark·2022년 5월 6일
0

Next.js

목록 보기
4/14

Next.js는 개발자 도구에서 javascript 로드를 꺼도 페이지가 열린다. 프리 렌더링 작업을 통해서 페이지가 이미 있기 때문이다. css만 불러와지지 않고 페이지 내용은 그대로 나온다.

  • Static Generation은 빌드 시 HTML을 생성하는 방식. pre-rendering된 HTML은 각 요청에서 재사용 된다.
  • Server-side Rendering은 매 요청 시 HTML을 생성하는 방식.

dev모드로 실행 중일때는 모든 페이지의 각 요청에서 HTML을 생성한다. (Static Generation을 사용하고 있어도 말이다.)


Next.js에서 권장하고 있는 방식

Static Generation을 추천한다. 매번 요청에 대해 서버가 HTML을 만드는 것보다 한번 만들고 CDN에 의해 제공하는 것이 훨씬 빠르기 때문이다.

  • static-generation
    만약 사용자의 요청보다 이 페이지를 먼저 로드해야 한다면 Static-generation을 선택해야 한다.
  • Server-side rendering
    만약 사용자의 요청 이전에 사전 렌더링을 할 필요가 없고, 높은 빈도로 갱신된 데이터를 보여줘야 한다면 Static-generation방식은 좋은 선택이 아니다. 이 때는 Server-side rendering을 사용해야 한다. 속도는 느리지만 사전 렌더링된 페이지는 항상 최신이다. 또는 사전 렌더링을 건너뛰고 Client측에서 JS를 이용해서 데이터를 최신화 할 수 있다.

static-generation - getStaticProps()

외부 데이터를 가져오지 않는 페이지들은 production용으로 빌드될 때 자동으로 정적으로 생성된다.

하지만, DB같은 곳에서 데이터를 불러와서 HTML페이지를 만들어줘야 할 수도 있다. next.js의 static generation으로도 가능하다. 이럴 때 getStaticProps()를 사용한다. 이 함수는 production 모드에서 빌드 때 실행된다. 함수 안에서 props로 받아온 데이터를 넘겨줄 수 있다.

export async function getStaticProps() {
	const data = ...
    
    return {
     	props: ... 
    }
}

getStaticProps를 사용하면 Next.js에게 렌더링하기 전에 데이터 종속성이 있다고 알려줄 수 있다. 개발모드에서는 각 요청에 대해 getStaticProps가 실행된다.

filesystem을 이용한 예시

src/pages/posts/pre-rendering.md, ssg-ssr.md 파일을 만들고
src/lib/posts.js파일에서 해당 마크다운 파일들을 파싱해서 title과 date를 배열에 저장하는 작성한다.

src/lib/posts.js

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const postsDirectory = path.join(process.cwd(), 'src/pages/posts');

export function getSortedPostsData() {
  const fileNames = fs.readdirSync(postsDirectory);
  const allPostsData = fileNames.map(fileName => {
    const id = fileName.replace(/\.md$/, '');

    const fullPath = path.join(postsDirectory, fileName);
    const fileContents = fs.readFileSync(fullPath, 'utf-8');

    const matterResult = matter(fileContents);

    return {
      id,
      ...matterResult.data,
    };
  });

  return allPostsData.sort(({ date: a }, { date: b }) => {
    if (a < b) {
      console.log(a, b);
      return 1;
    } else if (a > b) {
      console.log(a, b);
      return -1;
    } else {
      console.log(a, b);
      return 0;
    }
  });
}

아래 형태로 getSortedPostsData()는 반환한다.

[
  { id: 'ssg-ssr',
    title: 'When to Use Static Generation v.s. Server-side Rendering',
    date: '2020-01-02' 
  },
  { id: 'pre-rendering',
    title: 'Two Forms of Pre-rendering',
    date: '2020-01-01' 
  } 
]

다른 페이지에서 해당 데이터를 이용한다고 한다면 getStaticProps를 이용해서 아래와 같이 작성한다.
src/pages/index.js

import Head from 'next/head';
import Layout, { siteTitle } from '../components/layout';
import utilStyles from '../styles/utils.module.css';
import { getSortedPostsData } from '../lib/posts';

export default function Home({ allPostsData }) { // props를 불러온다.
  console.log(allPostsData);
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
      </Head>
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              {title}
              <br />
              {id}
              <br />
              {date}
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  );
}

export async function getStaticProps() {
  const allPostsData = getSortedPostsData();
  return {
    props: {
      allPostsData, // 👏 props는 객체형태로 받으며 내부에 전달할 props를 넣어준다.
    },
  };
}

예시2 - 외부 api나 db에 있는 데이터 조회

api를 조회할 수도 있고 query를 사용할 수도 있다.
이런 것이 가능한 이유는 getStaticProps가 오직 server-side에서 동작하기 때문이다. 절대 클라이언트 측에서 실행되지 않는다. 브라우저에 JS번들에서도 확인할 수 없다. 즉, 이런 쿼리문을 브라우저에 보내지 않고 조회할 수 있게 된다.

export async function getSortedPostsData() {
  // Instead of the file system,
  // fetch post data from an external API endpoint
  const res = await fetch('..');
  return res.json();
}
import someDatabaseSDK from 'someDatabaseSDK'

const databaseClient = someDatabaseSDK.createClient(...)

export async function getSortedPostsData() {
  // Instead of the file system,
  // fetch post data from a database
  return databaseClient.query('SELECT posts...')
}

getStaticProps 더 알아보기

  • 개발 모드에서는 getStaticProps가 매 요청마다 동작한다.
  • 배포 모드에서는 getStaticProps가 빌드할 때 동작한다. getStaticPaths로부터 반환된 fallback key를 사용하면 동작을 향상시킬 수 있다.
  • 빌드할 때 getStaticProps가 동작하기 때문에 쿼리 매개변수나 http헤더 같이 요청 시에 사용할 수 있는 데이터는 사용할 수 없다.
  • page내에서만 export할 수 있다. 이유는 렌더링 되기 전 react요소가 있어야 되기 때문이다.

ServerSide Rendering - getServerSideProps()

  • 빌드와 상관없이 매 페이지 요청마다 불러온다.
  • getServerSideProps는 페이지를 렌더링하기전에 반드시 fetch해야할 데이터가 있을 때 사용한다. 매 페이지 요청시마다 호출되므로 getStaticProps보다 느리지만, 빌드 이후에도 페이지 요청마다 실행된다는 특징이 있다.
  • 페이지를 사전 랜더링 할 때, 모든 요청에 대해 사전 랜더링을 하거나 요청 객체(e.g 쿠키)에 접근 해야할 때가 있다.
export async function getServerSideProps(context) {
  return {
    props: {
      // props for your component
    },
  };
}

SWR

데이터를 미리 렌더링 할 필요가 없는 경우 ClientSide Rendering으로 JS를 이용해 데이터를 불러올 수 있다. 빌드되서 페이지가 보이고 JS를 이용해서 필요한 부분을 채운다. 예를 들어 사용자 대시보드 같이 미리 렌더링을 할 필요가 없고 매번 데이터가 업데이트되서 바뀌고 SEO도 필요없을 때 베스트다!
이럴 때 Next.js에서 만든 SWR을 이용할 수 있다. 캐싱, revalidation, refetching on interval 등에 도움을 준다. 아래는 간단한 사용 예시다.

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>;
}

getStaticProps() vs getServerSideProps()

https://beside-lab.tistory.com/entry/Nextjs-getStaticProps-vs-getServerSideProps-%EC%B0%A8%EC%9D%B4%EC%99%80-%ED%99%9C%EC%9A%A9

profile
공유하는 것을 좋아하는 프론트엔드 개발자

0개의 댓글