[Next.js] Pre-rendering and Data Fetching

Pre-rendering and Data Fetching


Pre-rendering

  • Next.js는 브라우저가 로드 되면 모든 페이지를 pre-render한다.
  • 생성된 HTML은 최소한의 필요한 자바스크립트 코드가 포함되어 있어 화면을 빠르게 띄운다.
  • 그 후에 자바스크립트가 작동하면서 fully interactive하게 한다.
  • 이를 hydration이라고 한다.
  • Next를 사용하지 않은 React 앱이라면 pre-render를 지원하지 않음
  • 각 페이지마다 Static Generation 또는 Server-side Rendering를 선택 가능

Static Generation

  • build time에 HTML를 생성하고 각각의 request마다 재사용 되는 방식
  • 미리 페이지를 생성해 두기 때문에 사용자마다 페이지를 generate할 필요가 없어진다.
  • 상품 페이지, 블로그 게시물, 도움말 등의 정적인 페이지에 사용하면 좋다.
getStaticProps
  • async 함수로 외부 파일 시스템에서 데이터를 가져옴
  • pre-render시에 데이터도 함께 render하게 함
  • 이는 build time시 가장 먼저 실행됨
  • 작동 시기
  • In development (npm run dev or yarn dev), getStaticProps runs on every request.
  • In production, getStaticProps runs at build time.
  • only be exported from a page. You can’t export it from non-page files.
gray-matter
  • 파일의 메타데이터(ex. title, date 등)를 YAML Front Matter라고 부른다.
  • 이를 parsing 하기 위한 라이브러리가 gray-matter
npm install gray-matter
path
  • nextjs-blog
  • lib
    • posts.js
  • pages
    • index.js
  • posts
    • pre-rendering.md
    • ssg-ssr.md
posts.js
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'

 process.cwd(): node명령을 호출한 작업디렉터리의 절대경로
 파라미터로 받은 경로들을 하나로 합쳐서 문자열 형태로 path를 리턴
const postsDirectory = path.join(process.cwd(), 'posts')

export function getSortedPostsData() {
   /posts 폴더 아래의 파일 이름을 읽어들임
  const fileNames = fs.readdirSync(postsDirectory)

   파일들의 데이터를 읽어들여 특정 데이터를 배열로 저장함(map 함수 참고)
  const allPostsData = fileNames.map(fileName => {
     확장자 .md를 없앤 이름을 id로 
    const id = fileName.replace(/\.md$/, '')

     파일이름을 포함한 전체경로를 변수에 저장
    const fullPath = path.join(postsDirectory, fileName)

     파일의 데이터를 읽어 들여 저장
    const fileContents = fs.readFileSync(fullPath, 'utf8')

     gray-matter를 사용해서 metadata section 부분을 파싱
    const matterResult = matter(fileContents)

     id 값과 gray-matter를 사용해 얻어낸 메타데이터를 합쳐서 반환
     id, title, date
    return {
      id,
      ...matterResult.data
    }
  })

   위에서 얻은 데이터 배열을 최신 순으로 정렬하여 리턴
  return allPostsData.sort((a, b) => {
    if (a.date < b.date) {
      return 1
    } else {
      return -1
    }
  })
}
index.js
import Head from 'next/head'
import Layout, { siteTitle } from '../components/layout'
import utilStyles from '../styles/utils.module.css'

 posts.js로부터 getSortedPostsData() 함수를 불러들임
import { getSortedPostsData } from '../lib/posts'

export async function getStaticProps() {

   getSortedPostsData() 함수를 통해 {id, title, date} 데이터를 리턴받음
  const allPostsData = getSortedPostsData()
  return {
    props: {
      allPostsData
    }
  }
}

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
      </Head>
      <section className={utilStyles.headingMd}>
        <p>[Your Self Introduction]</p>
        <p>
          (This is a sample website - youll be building a site like this on{' '}
          <a href="https://nextjs.org/learn">our Next.js tutorial</a>.)
        </p>
      </section>

      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
           allPostsData의 id, date, title 값을 뽑아 리스트 출력
           utilStyles는 CSS
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              {title}
              <br />
              {id}
              <br />
              {date}
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  )
}
Fetch External API or Query Database
  • 외부 API나 DB를 통한 데이터 fetch 가능
  • 이게 가능한 이유는 getStaticProps는 오직 server-side에서만 작동하기 때문
  • broweser를 위한 JS bundle에도 들어가지 않는다.
  • 이는 브라우저를 통할 것 없이 직접 데이터베이스에 쿼리를 작성해서 전달할 수 있다는 뜻
코드 설명
  • 외부 API
export async function getSortedPostsData() {
   파일 시스템 대신에 외부 API
   fetch post data from an external API endpoint
  const res = await fetch('..')
  return res.json()
}
  • DB 쿼리이용
import someDatabaseSDK from 'someDatabaseSDK'

const databaseClient = someDatabaseSDK.createClient(...)

export async function getSortedPostsData() {
   파일 시스템 대신에 DB
  // fetch post data from a database
  return databaseClient.query('SELECT posts...')
}

Server-side Rendering

  • 각각의 request마다 generate되는 방식
  • getServerSideProps 사용( <-> getStaticProps)
getServerSideProps
  • getServerSideProps는 request time마다 실행됨
  • 파라미터 context는 request의 상세인자를 포함하고 있음
  • request time에 데이터가 fetch되어야 하는 경우에만 사용해야 함
export async function getServerSideProps(context) {
  return {
    props: {
      // props for your component
    }
  }
}

Client-side-Rendering

  • 데이터를 pre-render할 필요가 없을 경우 사용
순서
  1. 외부 데이터를 필요로 하지 않는 페이지의 부분들을 먼저 정적으로 generate함
  2. 페이지가 로드되면 클라이언트가 자바스크립트를 사용해 외부데이터를 fetch, 데이터를 덧붙힘
사용처
  • 유저페이지 같이 private한 정보를 담는 페이지에서 사용
  • 빈번히 데이터가 업데이트 되어 request할 때마다 가장 최신 데이터를 필요로 할 경우 사용
SWR
  • Client-side-Rendering 할 때 사용
  • caching, revalidation, focus tracking, 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>
}

links

social