티스토리 뷰

Next.js를 활용하면 페이지별로 Pre-rendering 방식을 선택할 수 있다

특정 페이지 별로 이곳은 SSG, 저기는 SSR 이런 식으로 가변적으로 사용할 수 있습니다. 또 아예 Pre-render를 안 하고 싶다고 하고 CSR에서 하도록도 선택할 수 있습니다.

 

  • getStaticProps를 사용하면 SSG
  • getServerSideProps를 사용하면 SSR

 

SSG(Static Site Generation)를 사용하면 좋은 페이지

  • Marketing pages
  • Blog posts
  • E-commerce product listings
  • Help and documentation

위 네 가지 모두 전반적으로 정적일 수 있고 자주 바뀌지 않는 데이터들일 것입니다. 정적으로 만든다면 새로 배포하기 전까지 새로 바뀔 일이 없다면 SSG를 선택하기 좋은 페이지가 됩니다.

 

적용 여부 선택 기준

사용자가 페이지를 요청하기 전에 pre-render 할 수 있는가?

 

Yes → SSG

No → SSR 혹은 ISR 혹은 CSR

 

반대로, SSR은 요청할 때 pre-render를 합니다. 사용자에게 커스터마이징된 페이지를 보여줘야 한다면 로그인이 되어 있어야 하고, 이 로그인으로 사람을 구분한 후에 pre-render를 선택해야 할 수 있습니다. 그럴 때에는 SSG를 선택할 수 없습니다.

 

반면에, 어느 사람이든 볼 컨텐츠를 미리 정할 수 있다면 SSG가 서버의 리소르를 사용하지 않으면서 서비스를 Static하게 제공할 수 있는 솔루션으로 볼 수 있습니다.

 

SSG의 2가지 케이스

  • 외부 데이터 없이 pre-rendering
  • 외부 데이터를 가져와서 pre-rendering

여기서 말하는 외부 데이터는 다른 파일이나, API, DB 등을 말합니다. SSG의 Static Generation은 결국 서버에서 동작합니다.

 

다른 파일을 읽어와보자

md 파일

md 파일에는 metadata를 포함할 수 있는데요!

 

📜 posts/pre-render.md

---
title: 'Two Forms of Pre-rendering'
date: '2020-01-01'
---

Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.

- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.

Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.

---

으로 감싸진 metadata를 읽어보려고 합니다 (YAML Front Matter)

--

어떻게 보면 format이고, 저번 글에서 Head 컴포넌트에 meta 정보들을 담았었죠 ? 마찬가지로, 해당 블로그 글에 대한 meta 정보들을 담고 있고 위와 같은 방식으로 표현했다고 보면 됩니다.

 

파일을 읽어올 때는 추가로 install이 필요합니다.

$ yarn add gray-matter

YAML Front Matter를 해석해줄 라이브러리를 설치해야 합니다.

 

📜 lib/posts.js

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

// 해당 프로젝트의 root 경로 + '/posts' <- md 파일을 저장해둔 경로
const postsDirectory = path.join(process.cwd(), 'posts')

export function getSortedPostsData() {
  // 파일 이름을 읽어온다
  const fileNames = fs.readdirSync(postsDirectory)

  // 파일 이름에서 '.md'라는 텍스트를 지운다
  // ssg-ssr.md -> ssg-ssr : 이를 id로 가져가고 있다
  const allPostsData = fileNames.map((fileName) => {
    // Remove ".md" from file name to get id
    const id = fileName.replace(/\.md$/, '')

    // postsDirectory + '/pre-rendering.md'
    const fullPath = path.join(postsDirectory, fileName)

    // 파일을 직접 읽기
    const fileContents = fs.readFileSync(fullPath, 'utf8')

    // meta 데이터 읽기
    const matterResult = matter(fileContents)

    // Combine the data with the id
    return {
      id,
      ...matterResult.data,
    }
  })

  // date를 기준으로 sort
  return allPostsData.sort((a, b) => {
    if (a.date < b.date) {
      return 1
    } else {
      return -1
    }
  })
}

위 코드는 공식 문서에 포함되어 있는데요. gray-matter를 활용해서 특정 폴더를 긁어와서 그 데이터를 가지고 title을 sorting합니다.

 

이제 이를 SSG, SSR, CSR로 구현해보도록 합시다!

 

1) SSG로 구현 : `getStaticProps` 활용

export async function getStaticProps() {
  const allPostsData = getSortedPostsData()

  return {
    props: {
      allPostsData,
    },
  }
}

좀 전에 선언해 두었던 getSortedPostsData 함수를 이용해서 `allPostsData`를 만들고 이를 props로 return 합니다.

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      {/* Keep the existing code here */}

      {/* Add this <section> tag below the existing <section> tag */}
      <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>
  )
}

이를 바로 props로 받아 화면에 렌더링 하게 되면 다음과 같이 화면이 구성되게 됩니다.

결과 화면

 

2) SSR로 구현 : `getServerSideProps` 활용

export async function getServerSideProps() {
  const allPostsData = getSortedPostsData()

  return {
    props: {
      allPostsData,
    },
  }
}

이렇게 되면 요청을 할 때마다 렌더링이 될 것입니다. dev 환경에서는 SSG와 SSR이 동일하게 동작하지만 build 환경에서는 다르게 동작합니다.

 

3) CSR로 구현 :API Routes 활용

CSR로 구현하기 위해서는 API Routes를 활용해야 합니다. 즉, 데이터를 fetch 해오고, 이를 state로 저장하여 화면에 렌더링합니다.

const [allPostData, setAllPostData] = useState([])

useEffect(() => {
    fetch('/api/posts')
      .then((res) => res.json())
      .then((data) => setAllPostData(data.allPostsData))
}, [])

우선 위와 같이 state 하나를 선언해주고, useEffect로 데이터를 fetch해와서 state를 업데이트하는 로직을 작성해봅시다.

 

아직 /api/posts라는 api가 없기 때문에 Next.js의 API Routes를 활용해줍니다.

 

📜 pages/api/posts.js

import { getSortedPostsData } from '../../lib/posts'

export default function handler(req, res) {
  const allPostsData = getSortedPostsData()
  res.status(200).json({ allPostsData })
}

 

번외) SSG를 사용하는데, 직접 fetch를 해서 return을 한다면 (without data)

export async function getStaticProps() {
  const response = await fetch('/api/posts')
  const json = await response.json()

  return {
    props: {
      allPostsData: json.allPostsData,
    },
  }
}

위 처럼 코드를 작성하고 실행했더니 다음과 같은 에러가 발생했습니다.

fetch할 api 주소를 상대경로로 작성했기 때문인데요. 절대 경로로 수정해 봅시다.

export async function getStaticProps() {
  const response = await fetch('http://localhost:3000/api/posts')
  const json = await response.json()

  return {
    props: {
      allPostsData: json.allPostsData,
    },
  }
}

결과 화면

절대 경로로 api 주소를 변경해주었더니 잘 실행되는 것을 확인할 수 있었습니다.

 

⭐ SSG에서 직접 fetch를 하고 싶다면 절대 경로를 사용해야 한다. ⭐

 

하지만 Server Side에서는 API Routes를 사용하지 않아야 합니다. API Routes를 Client Side에서 Server Side로 요청할 때 사용하는 것이기 때문인데요. getStaticProps / getStaticPaths 등은 Client Side 코드에 포함되지 않습니다.

 

그렇기에 서버 사이드에서는 DB에 직접 접근하는 등 훨씬 자유도 높은 작업을 할 수 있습니다.

 

Data를 가져오는 함수 getSortedPostsData의 확장

우리가 앞서 작성했었던 getSortedPostsData 함수를 확장하면 다음과 같이도 사용될 수 있습니다.

  • 다른 File 조회
  • 외부 api 요청
  • DB 조회

이번 글에서는 Pre-rendering과 Data Fetching 연습을 해보았습니다.

 

1. Pre-rendering

SSG 선택 기준

 

2. SSG 2가지 케이스

without data / with data

 

3. YAML Front Matter

Matadata 표기 방식 / gray-matter로 파싱

 

4. API Routes

fs는 server side에서만 가능

728x90
LIST
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함