avatar
Published on

NextJS 2. Data Fetching

Author
  • avatar
    Name
    yceffort

nextjs์˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๊ณ  ์š”์•ฝํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

Table of Contents

1. getInitialProps

Nextjs 9.3 ์ด์ „์—๋Š” getInitialProps ๋ฐ–์— ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ตœ์‹  ๋ฒ„์ „์ธ 9.3์—์„œ๋Š” ๋ฐ‘์—์„œ ์„ค๋ช…ํ•  getStaticProps๋‚˜ getServerSideProps๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋ฅผ ๊ถŒ์žฅํ•œ๋‹ค. (์™ ์ง€ deprecate ๋  ๊ฒƒ ๊ฐ™์€ ๊ธฐ๋ถ„์ด๋‹ค.)

getInitialProps๋Š” ํŽ˜์ด์ง€์—์„œ ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋ฉฐ, ํŽ˜์ด์ง€๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ์ตœ์ดˆ๋กœ ๋ฐ์ดํ„ฐ ์กฐ์ž‘์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•œ๋‹ค. ์ด ๋ง์˜ ๋œป์€, ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜จ ๋‹ค์Œ์—, ์ด ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ ํŽ˜์ด์ง€๋ฅผ ๋‚ด๋ณด๋‚ธ๋‹ค๋Š” ๋œป์ด๋‹ค. ์ด๋Š” ํŠนํžˆ SEO ๋“ฑ์—์„œ ์œ ์šฉํ•˜๋‹ค.

์ฃผ์˜: getInitialProps๋ฅผ ์“ฐ๋Š” ์ˆœ๊ฐ„ nextjs์˜ automatic static optimization์ด ๋ถˆ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด์ž.

import { NextPageContext } from 'next'
import React from 'react'
import fetch from 'isomorphic-fetch'

interface EmployeeInterface {
  id: number
  employee_name: string
  employee_salary: number
  employee_age: number
  profile_image: string
}

export default function Data({ data }: { data: EmployeeInterface[] }) {
  return (
    <>
      <h1>Employee list</h1>
      {data.map(
        ({ id, employee_age, employee_name, employee_salary }, index) => (
          <div key={index}>
            <span>{id}.</span>
            <span>{employee_name} </span>
            <span>${employee_salary}</span>
            <span> {employee_age} years old</span>
          </div>
        ),
      )}
    </>
  )
}

Data.getInitialProps = async (_: NextPageContext) => {
  const response = await fetch(
    'http://dummy.restapiexample.com/api/v1/employees',
  )
  const { data } = await response.json()

  return { data }
}

getInitialProps ๋‚ด ์—์„œ ๋น„๋™๊ธฐ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ ์˜จ ๋‹ค์Œ์—, props๋ฅผ ๋งŒ๋“ค์–ด ์ปดํฌ๋„ŒํŠธ์— ๋„˜๊ธด๋‹ค. ํ•œ๊ฐ€์ง€ ๋ช…์‹ฌํ•  ๊ฒƒ์€, ์—ฌ๊ธฐ์„œ ์ปดํฌ๋„ŒํŠธ์— ๋„˜๊ฒจ์ฃผ๋Š” ํ–‰์œ„๋Š” JSON.stringify์™€ ๋น„์Šทํ•˜๋‹ค. ๋”ฐ๋ผ์„œ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋Š” ์ˆœ์ˆ˜ Object์—ฌ์•ผ ํ•œ๋‹ค.

์ค‘์š” ํฌ์ธํŠธ

  1. ์ฒ˜์Œ ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋”ฉ ๋œ๋‹ค๋ฉด, getInitialProps๋Š” ์„œ๋ฒ„์—์„œ๋งŒ ๋กœ๋”ฉ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ next/link ๋˜๋Š” next/router๋ฅผ ํ†ตํ•ด์„œ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ ํŽ˜์ด์ง€ ์ด๋™์ด ์ผ์–ด๋‚œ๋‹ค๋ฉด, ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋‹ค.

  2. getInitialProps ๋Š” ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. ์˜ค์ง ๊ฐ ํŽ˜์ด์ง€์—์„œ๋งŒ ์‹คํ–‰ ๊ฐ€๋Šฅํ•˜๋‹ค.

  3. 1๋ฒˆ์˜ ์ด์œ ์— ๋”ฐ๋ผ์„œ, getInitialProps๋‚ด์—์„œ ์„œ๋ฒ„์‚ฌ์ด๋“œ์—์„œ๋งŒ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“ˆ์„ ๋‚ด์žฅํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์ฃผ์˜๋ฅผ ๊ธฐ์šธ์—ฌ์•ผ ํ•œ๋‹ค. ๋งŒ์•ฝ ์„œ๋ฒ„์‚ฌ์ด๋“œ์—์„œ๋งŒ ์ž‘๋™ํ•˜๊ณ  ์‹ถ์€ ๋กœ์ง์ด ์žˆ๋‹ค๋ฉด, ์•„๋ž˜์ฒ˜๋Ÿผ ํ•˜๋ฉด ๋œ๋‹ค.

Data.getInitialProps = async ({req}: NextPageContext) => {
  console.log('fetch some data')
  const response = await fetch(
    'http://dummy.restapiexample.com/api/v1/employees',
  )
  const {data} = await response.json()

  let isServer = false
  if (req) {
    // is server side???????
    isServer = true
  }

  return {data, isServer}
}

2. getStaticProps

์ •์  ํŽ˜์ด์ง€ ์ƒ์„ฑ์„ ์ง€์›ํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฅผ ๋”ฑ ๋นŒ๋“œ ํƒ€์ž„์—๋งŒ! ์‹คํ–‰๋œ๋‹ค.

export async function getStaticProps(_: NextPageContext) {
  const response = await fetch(
    'http://dummy.restapiexample.com/api/v1/employees',
  )
  const {data} = await response.json()

  console.log('fetchData in build time!')

  return {
    props: {data},
  }
}

๋นŒ๋“œ๋ฅผ ํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฉ”์‹œ์ง€๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค.

...
Automatically optimizing pages ..fetchData in build time!
Automatically optimizing pages

Page                                                           Size     First Load
โ”Œ ฮป /                                                          458 B       68.2 kB
โ”œ   /_app                                                      352 B       67.7 kB
โ”œ ฮป /about                                                     301 B         68 kB
โ”œ โ— /data                                                      412 B       68.2 kB
โ”” ฮป /posts/[id]                                                303 B         68 kB
+ shared by all                                                67.7 kB
  โ”œ static/pages/_app.js                                       352 B
  โ”œ chunks/d43014630f87ab6320ffd55320a44642064161b7.111b68.js  9.77 kB
  โ”œ chunks/framework.9daf87.js                                 40.1 kB
  โ”œ runtime/main.d2cfdc.js                                     16.8 kB
  โ”” runtime/webpack.a34f97.js                                  744 B

ฮป  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
โ—‹  (Static)  automatically rendered as static HTML (uses no initial props)
โ—  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
...

data๋ฅผ ๋นŒ๋“œ์‹œ์— ๋ฏธ๋ฆฌ ๋•ก๊ฒจ์™€์„œ staticํ•˜๊ฒŒ ์ œ๊ณตํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  next๋ฅผ ์‹คํ–‰ํ•ด๋ณด๋ฉด ๋ฐ์ดํ„ฐ fetch๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฏธ ๋นŒ๋“œ ์‹œ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋•ก๊ฒจ ์™”๊ธฐ ๋•Œ๋ฌธ์—, ๊ต‰์žฅํžˆ ๋น ๋ฅธ ์†๋„๋กœ ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋”ฉ ๋œ๋‹ค.

getStaticProps ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฝ์šฐ์— ์œ ์šฉํ•  ๊ฒƒ์ด๋‹ค.

  • ๋งค ์œ ์ €์˜ ์š”์ฒญ๋งˆ๋‹ค fetchํ•  ํ•„์š”๊ฐ€ ์—†๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง„ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋ง ํ• ๋•Œ
  • headless CMS๋กœ ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ฌ๋•Œ
  • ์œ ์ €์— ๊ตฌ์• ๋ฐ›์ง€ ์•Š๊ณ  ํผ๋ธ”๋ฆญํ•˜๊ฒŒ ์บ์‹œํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ
  • SEO ๋“ฑ์˜ ์ด์Šˆ๋กœ ์ธํ•ด ๋น ๋ฅด๊ฒŒ ๋ฏธ๋ฆฌ ๋ Œ๋”๋ง ํ•ด์•ผ๋งŒ ํ•˜๋Š” ํŽ˜์ด์ง€. getStaticProps๋Š” HTML๊ณผ JSONํŒŒ์ผ์„ ๋ชจ๋‘ ์ƒ์„ฑํ•ด ๋‘๊ธฐ ๋•Œ๋ฌธ์—, ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด CDN ์บ์‹œ๋ฅผ ํ•˜๊ธฐ ์‰ฝ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์€ ์‚ฌํ•ญ์„ ์œ ๋…ํ•ด ๋‘์ž.

  • ๋นŒ๋“œ ํƒ€์ž„์—์„œ๋งŒ ์‹คํ–‰๋œ๋‹ค.
  • ์„œ๋ฒ„์‚ฌ์ด๋“œ ์ฝ”๋“œ๋‹ค. ์ ˆ๋Œ€ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค. ์‹ฌ์ง€์–ด ๋ธŒ๋ผ์šฐ์ € JS ๋ฒˆ๋“ค์—๋„ ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋ƒฅ props๊ฒฐ๊ณผ๋ฌผ ์ž์ฒด๋ฅผ JS ๋ฒˆ๋“ค์— ํฌํ•จ์‹œํ‚ค๊ณ  ์žˆ๋‹ค. ํŽ˜์ด์ง€์—์„œ ์†Œ์Šค ๋ณด๊ธฐ๋ฅผ ํ•˜๋ฉด, ์•„๋ž˜ ์ฒ˜๋Ÿผ ๋ฐ์ดํ„ฐ๋ฅผ ์•„์˜ˆ ๋“ค๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
<script id="__NEXT_DATA__" type="application/json">
  {
    "props": {
      "pageProps": {
        "data": [
          {
            "id": "1",
            "employee_name": "Tiger Nixon",
            "employee_salary": "320800",
            "employee_age": "61",
            "profile_image": ""
          }
        ]
      },
      "__N_SSG": true
    },
    "page": "/data",
    "query": {},
    "buildId": "ExAlLKs0H7K3JGmYT162x",
    "nextExport": false,
    "isFallback": false,
    "gsp": true
  }
</script>
  • Page์—์„œ๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ๋Š” ๋งค ๋ฒˆ ์š”์ฒญ์ด ๊ฐ„๋‹ค.

3. getStaticPaths

์œ„์—์„œ ์–ธ๊ธ‰ํ•œ getStaticProps์™€ ๋งค์šฐ ์œ ์‚ฌํ•˜๋‹ค. ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค๋ฉด, getStaticPaths๋Š” ๋‹ค์ด๋‚˜๋ฏน ๋ผ์šฐํŠธ์—์„œ๋งŒ ์“ด๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์„ค๋ช…๋ณด๋‹จ ์˜ˆ์‹œ๋ฅผ ๋ณด๋Š”๊ฒŒ ๋” ๋น ๋ฅด๋‹ค.

/pages/post/[id].tsx

import React from 'react'
import fetch from 'isomorphic-fetch'
import { GetStaticProps } from 'next'

interface PostInterface {
  userId: number
  id: number
  title: string
  body: string
}

export default function Employee({ todo }: { todo: PostInterface }) {
  const { userId, id, title, body } = todo
  return (
    <>
      <h1>Todo</h1>
      <div>userId: {userId}</div>
      <div>id: {id}</div>
      <div>title: {title}</div>
      <div>body: {body}</div>
    </>
  )
}

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

  const paths = data.map(({ id }: PostInterface) => ({
    params: { id: String(id) },
  }))

  return { paths, fallback: false }
}

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${params?.id}`,
  )
  const data = await response.json()

  return {
    props: { todo: data },
  }
}

getStaticPaths ์—์„œ /pages/post/[id]๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋ชฉ๋ก์„ ๋•ก๊ฒจ์˜จ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ€๋Šฅํ•œ ์ ‘๊ทผ ๋ชฉ๋ก์„

[{"params": {"id": 1}}, {"params": {"id": 2}}]

์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ๋งŒ๋“ค์–ด ๋‘”๋‹ค. ๋ฌธ์„œ์™€ ๋‹ค๋ฅด๊ฒŒ ๊ผญ ์ฃผ์˜ ํ•ด์•ผ ํ•  ๊ฒƒ์€ value๋Š” ๋ฌด์กฐ๊ฑด string ์ด์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด์ œ ๋นŒ๋“œ ํƒ€์ž„์— ๊ฐ€๋Šฅํ•œ ๋ชจ๋‘ ๊ฒฝ์šฐ์˜ ์ˆ˜๋ฅผ ๋•ก๊ฒจ์™€์„œ - ๋นŒ๋“œ ํ•˜๊ฒŒ ๋œ๋‹ค.

๋ช‡ ๊ฐ€์ง€ ๋” ์ƒ˜ํ”Œ์„ ๋ณด๋„๋ก ํ•˜์ž.

pages/todo/[userId]/[id].tsx

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

  const paths = data.map(({id, userId}: TodoInterface) => ({
    params: {userId: String(userId), id: String(id)},
  }))

  return {paths, fallback: false}
}

pages/todo/[...slug].tsx

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

  const paths = data.reduce(
    (acc: Array<{params: {slug: string[]}}>, {userId, id}: PostInterface) => {
      return acc.concat([
        {params: {slug: [String(userId), String(id)]}},
        {params: {slug: [String(id)]}},
      ])
    },
    [],
  )

  return {paths, fallback: false}
}

์ด๋ ‡๊ฒŒ array ํ˜•ํƒœ๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด ๋œ๋‹ค.

{"slug":["10","95"]}},{"params":{"slug":["95"]}}

getStaticProps์—์„œ๋Š” params๋กœ ์ ‘๊ทผํ•˜๋ฉด

{"slug": ["1", "3"]}

์—ฌ๊ธฐ์„œ ๊บผ๋‚ด ์“ฐ๋ฉด ๋œ๋‹ค.

getStaticPaths๋Š” ๋ฆฌํ„ด ๊ฐ’์œผ๋กœ ์•ž์„œ ๋งŒ๋“ค์—ˆ๋˜ paths์™€ fallback์„ ๋„˜๊ฒจ์ค€๋‹ค. fallback์„ true๋‚˜ false๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. false๋ผ๋ฉด nextjs์˜ 404๊ฐ€ ๋œฌ๋‹ค. ์ด๋Š” ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด ๋‘์–ด์•ผ ํ•  ํŽ˜์ด์ง€์˜ ์ˆ˜๊ฐ€ ์ ์„ ๋•Œ, ๋นŒ๋“œ ํƒ€์ž„์„ ์งง๊ฒŒ ๊ฐ€์ ธ๊ฐ์œผ๋กœ์„œ ์ด์ต์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋งŒ์•ฝ fallback์˜ ๊ฐ’์ด true๋ผ๋ฉด getStaticProps๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋‹ฌ๋ผ์ง„๋‹ค.

  • getStaticPaths์—์„œ ๋ฆฌํ„ด๋˜๋Š” paths๋Š” ๋นŒ๋“œํƒ€์ž„์— HTML์ด ๋ Œ๋”๋ง ๋œ๋‹ค.

  • ์—ฌ๊ธฐ์„œ ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š” ์˜ˆ์™ธ Path๋“ค์€ 404 ํŽ˜์ด์ง€๋ฅผ ๋ฆฌํ„ดํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋Œ€์‹ , NextJs๋Š” fallback page๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ค. ์•„๋ž˜ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด์ž.

export default function Employee({ todo }: { todo: PostInterface }) {
  const { isFallback } = useRouter()

  if (isFallback) {
    return <>Fail!</>
  }

  const { userId, id, title, body } = todo
  return (
    <>
      <h1>Todo</h1>
      <div>userId: {userId}</div>
      <div>id: {id}</div>
      <div>title: {title}</div>
      <div>body: {body}</div>
    </>
  )
}

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

  const paths = data.map(({ id }: PostInterface) => ({
    params: { id: String(id) },
  }))

  return { paths, fallback: true }
}

Fallback ํŽ˜์ด์ง€์˜ props๋Š” ์•„๋ฌด๊ฒƒ๋„ ์—†๋‹ค. ๋”ฐ๋ผ์„œ props๋ฅผ ๊ฐ€๊ณตํ•˜๋Š” ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค.

  • ํ•ด๋‹น path๊ฐ€ ์—†๋Š” ํŽ˜์ด์ง€์— ๋Œ€ํ•ด์„œ Nextjs๋Š” ์„œ๋ฒ„๋‹จ์—์„œ ์ •์ ์ธ HTML๊ณผ JSON์„ ๋งŒ๋“ค์–ด ๋‘”๋‹ค. ์—ฌ๊ธฐ์—๋Š” getStaticProps์„ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ๋„ ํฌํ•จ๋œ๋‹ค.

  • ์œ„ ์ž‘์—…์ด ๋๋‚ฌ๋‹ค๋ฉด, ๋ธŒ๋ผ์šฐ์ €๋Š” ํ•ด๋‹น path์— ๋”ฐ๋ผ์„œ ๋งŒ๋“  JSON์„ ๋ฐ›๊ฒŒ๋œ๋‹ค. ์ด JSON์€ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง์— ํ•„์š”ํ•œ Props๋ฅผ ์ œ๊ณตํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ์œ ์ € ์ž…์žฅ์—์„œ๋Š”, fallback ํŽ˜์ด์ง€์—์„œ ์ „์ฒด ํŽ˜์ด์ง€๋กœ ์Šค์™‘๋˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ผ ๊ฒƒ์ด๋‹ค. (fallback์ด ์ž ์‹œ ๋ณด์˜€๋‹ค๊ฐ€ ๋‹ค์‹œ ๋ฐ›์•„์˜จ props๋กœ ๊ทธ๋ฆฌ๋Š” ํŽ˜์ด์ง€๊ฐ€ ๋‚˜ํƒ€๋‚จ (isFallback์ด true์—์„œ false๋กœ ๋ฐ”๋€œ))

  • ์ด์™€ ๋™์‹œ์—, ํ•ด๋‹น path๋ฅผ ๋ฏธ๋ฆฌ ๋ Œ๋”๋งํ•œ path์— ์ถ”๊ฐ€ํ•ด๋‘”๋‹ค. ๊ฐ™์€ path๋กœ ์˜ค๋Š” ์š”์ฒญ๋“ค์€ ์ด์ œ ๋งˆ์น˜ ๋นŒ๋“œ์‹œ์— ์‚ฌ์ „์— ๋ Œ๋”๋งํ•ด ๋‘” ํŽ˜์ด์ง€ ์ฒ˜๋Ÿผ ์ œ๊ณต๋œ๋‹ค.

๋ณต์žกํ•˜๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด์„œ ์„ค๋ช…ํ•ด๋ณด์ž.

export async function getStaticPaths() {
  const items = Array.from(Array(10).keys())

  const paths = items.map(value => ({
    params: { id: String(value) },
  }))

  return { paths, fallback: true }
}

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const id = params?.id

  if (Number(id) > 10) {
    return {
      props: {
        todo: {
          userId: 1,
          id,
          title: `์ด๊ฑด ์—๋Ÿฌ์•ผ.`,
          body: `์•„ ์ด๊ฑด ์—๋Ÿฌ๋ผ๋‹ˆ๊น.`,
        },
      },
    }
  } else {
    return {
      props: {
        todo: {
          userId: 1,
          id,
          title: `ํ• ์ผ ${id}`,
          body: `์ด๊ฑฐ ํ•˜์ž. ${id}`,
        },
      },
    }
  }

๊ฐœ ๋–ก ๊ฐ™์€ ์ฝ”๋“œ์ง€๋งŒ (...) getStaticPaths๋Š” /todo/0 ๋ถ€ํ„ฐ /todo/9๊นŒ์ง€๋งŒ ๋ฏธ๋ฆฌ ๋นŒ๋“œ ํƒ€์ž„์— ๋งŒ๋“ค์–ด ๋‘”๋‹ค.

 โ— /todo/[id]                                                 378 B       68.1 kB
    โ”œ /todo/0
    โ”œ /todo/1
    โ”œ /todo/2
    โ”” [+7 more paths]

๊ทธ๋ฆฌ๊ณ  ๋งŒ์•ฝ ์–ด๋–ค ์‚ฌ์šฉ์ž๊ฐ€ ์ฒ˜์Œ์œผ๋กœ /todo/1111๋กœ ์ ‘๊ทผํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. ๊ทธ๋Ÿผ ์‚ฌ์šฉ์ž๋Š” ์ž ์‹œ fallback ํŽ˜์ด์ง€๋ฅผ ๋ดค๋‹ค๊ฐ€, ๋‹ค์‹œ getStaticProps๊ฐ€ ๋ Œ๋”๋งํ•ด์ฃผ๋Š” ์—๋Ÿฌ ํŽ˜์ด์ง€๋ฅผ ๋ณด๊ฒŒ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  nextjs๋Š” ํ•ด๋‹น path์— ๋Œ€ํ•ด ๋ Œ๋”๋ง ํ•ด๋‘” ๊ฒƒ์„ ์ €์žฅํ•ด๋‘”๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ดํ›„์— ๋‹ค์‹œ ์ ‘๊ทผํ•˜๋Š” ์‚ฌ์šฉ์ž๋Š” fallback ํŽ˜์ด์ง€๋ฅผ ๋ณด์ง€ ์•Š๊ณ  ๋ฐ”๋กœ ์•ž์„œ ๋งŒ๋“ค์–ด ๋‘์—ˆ๋˜ ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ค.

fallback ํŽ˜์ด์ง€๋Š” ์–ธ์ œ ์œ ์šฉํ• ๊นŒ?

์•„์ฃผ ํฐ ์ปค๋จธ์Šค ์‚ฌ์ดํŠธ์™€ ๊ฐ™์ด, ๋ฐ์ดํ„ฐ์— ๋”ฐ๋ผ ๋งŒ๋“ค์–ด ๋‘์–ด์•ผํ•  ์ •์ ํŽ˜์ด์ง€๊ฐ€ ๋งŽ์€ ์‚ฌ์ดํŠธ์—์„œ ์œ ๋ฆฌํ•  ๊ฒƒ์ด๋‹ค. ๋ชจ๋“  ํŽ˜์ด์ง€๋ฅผ ๋นŒ๋“œ์‹œ์— ๋งŒ๋“ค์–ด ๋‘๊ณ  ์‹ถ์ง€๋งŒ, ๊ทธ๋žฌ๋‹ค๊ฐ€๋Š” ๋นŒ๋“œ๊ฐ€ ์—„์ฒญ๋‚˜๊ฒŒ ์˜ค๋ž˜๊ฑธ๋ฆด ๊ฒƒ์ด๋‹ค. ๋Œ€์‹ , ๋ฏธ๋ฆฌ ๋ช‡๊ฐœ์˜ ์ฃผ์š” ํŽ˜์ด์ง€๋งŒ ๋งŒ๋“ค์–ด๋‘๊ณ , ๋‚˜๋จธ์ง€๋Š” fallback: true๋กœ ์ฒ˜๋ฆฌํ•˜์ž. ๋ˆ„๊ตฐ๊ฐ€ ์•„์ง ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š์€ ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•˜๋ ค ํ•œ๋‹ค๋ฉด, ์œ ์ €์—๊ฒŒ ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ๋ฅผ ๋„์šฐ์ž. ๊ทธ๋Ÿฌ๋ฉด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ๋Š” getStaticProps๋ฅผ ์‹คํ–‰ํ•ด์„œ ๋ Œ๋”๋ง์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์ž‘์—…์ด ๋๋‚œ๋‹ค๋ฉด, ๋‹ค๋ฅธ ์œ ์ €๋“ค์€ ์ด์ œ ๋ฏธ๋ฆฌ ๋ Œ๋”๋ง๋œ ์ •์ ์ธ ํŽ˜์ด์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์€ ์‚ฌํ•ญ์„ ์œ ๋…ํ•ด ๋‘์ž.

  • ํ•ญ์ƒ getStaticProps์™€ ์ง์œผ๋กœ ์“ฐ์ž. ๊ทธ๋ฆฌ๊ณ  getServerSideProps์™€๋Š” ์“ธ์ˆ˜๊ฐ€ ์—†๋‹ค.
  • getStaticPaths๋Š” ์„œ๋ฒ„์‚ฌ์ด๋“œ์—์„œ ๋นŒ๋“œ ํƒ€์ž„์—๋งŒ ์‹คํ–‰๋œ๋‹ค.
  • getStaticPaths๋Š” ํŽ˜์ด์ง€์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ๋Š” ํ•ญ์ƒ ์‹คํ–‰๋œ๋‹ค.

4. getServerSideProps

getServerSideProps๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ๊ฐ ์š”์ฒญ ๋งˆ๋‹ค getServerSideProps์—์„œ ๋ฆฌํ„ดํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„๋‹ค๊ฐ€ ์„œ๋ฒ„์‚ฌ์ด๋“œ์—์„œ ๋ฏธ๋ฆฌ ๋ Œ๋”๋ง์„ ํ•˜๊ฒŒ ๋œ๋‹ค.

export async function getServerSideProps(context) {
  return {
    props: {},
  }
}

๋นŒ๋“œ๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ๋‚˜ํƒ€๋‚œ๋‹ค.

Page                                                           Size     First Load
...
โ”œ ฮป /server                                                    415 B       68.2 kB
...

ฮป  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
โ—‹  (Static)  automatically rendered as static HTML (uses no initial props)
โ—  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)

context์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ๋“ค์ด ํฌํ•จ๋˜์–ด ์žˆ๋‹ค.

  • params: ๋‹ค์ด๋‚˜๋ฏน ๋ผ์šฐํŠธ ํŽ˜์ด์ง€๋ผ๋ฉด, params๋ฅผ ๋ผ์šฐํŠธ ํŒŒ๋ผ๋ฏธํ„ฐ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
  • req: HTTP request object
  • res: HTTP response object
  • query: ์ฟผ๋ฆฌ์ŠคํŠธ๋ง
  • preview: preview ๋ชจ๋“œ ์—ฌ๋ถ€ preview mode
  • previewData: setPreviewData๋กœ ์„ค์ •๋œ ๋ฐ์ดํ„ฐ

์–ธ์ œ ์จ์•ผ ํ• ๊นŒ?

getServerSideProps๋Š” ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜๊ธฐ์ „์— ๋ฐ˜๋“œ์‹œ fetchํ•ด์•ผํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ๋งค ํŽ˜์ด์ง€ ์š”์ฒญ์‹œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋ฏ€๋กœ ๋‹น์—ฐํžˆ, TTFB๊ฐ€ getStaticProps๋ณด๋‹ค ๋Š๋ฆฌ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์€ ์‚ฌํ•ญ์„ ์œ ๋…ํ•ด ๋‘์ž.

  • getServerSideProps๋Š” ์„œ๋ฒ„์‚ฌ์ด๋“œ์—์„œ๋งŒ ์‹คํ–‰๋˜๊ณ , ์ ˆ๋Œ€๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.
  • getServerSideProps๋Š” ๋งค ์š”์ฒญ์‹œ ๋งˆ๋‹ค ์‹คํ–‰๋˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ๊ฐ’์„ props๋กœ ๋„˜๊ฒจ์ค€ ๋’ค ๋ Œ๋”๋ง์„ ํ•œ๋‹ค.
  • next/link๋ฅผ ์ด์šฉํ•ด์„œ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ํŽ˜์ด์ง€ ํŠธ๋ Œ์ง€์…˜์„ ํ•˜๋”๋ผ๋„, getInitialProps์™€๋Š” ๋‹ค๋ฅด๊ฒŒ ๋ฌด์กฐ๊ฑด ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋œ๋‹ค.
  • ๋‹น์—ฐํžˆ page ์—์„œ๋งŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.