November 27, 2022
μ μ : λΉλνλ λμ νμ΄μ§λ₯Ό μ¬μ μμ±νλ κ²
(pre-generate a page with data prepared on the server-side during build time.)
μ¬μ μμ±μ΄λ λͺ¨λ νμ΄μ§μ html ꡬ쑰μ λͺ¨λ λ°μ΄ν°λ₯Ό μ¬μ μ μ€λΉμμΌ λλλ€λ λ»μ΄λ©° λ³΄ν΅ μλ² μ¬μ΄λμμλ§ μ€νλλ μ½λλ₯Ό λΉλ νλ‘μΈμ€λμ μ€νλλλ‘ νμ©νλ κ²μ μλ―Ένλ€.
NextJS μ μ΄λ€ νμ΄μ§λ₯Ό μ¬μ μ μμ±ν΄μΌ νλμ§, μ¬μ μμ ν νμ΄μ§μ μ΄λ€ λ°μ΄ν°κ° ν¬ν¨λμ΄μΌ νλμ§ μ§μ νκΈ° μν΄ getStaticProps
λΌλ μ΄λ¦μ λΉλκΈ° ν¨μλ₯Ό μ¬μ©νλ€.
pages/ κ²½λ‘ λ΄μ μλ μ»΄ν¬λνΈ λ΄λΆμμλ§ μ¬μ© κ°λ₯νλ©°, jsx λ₯Ό 리ν΄νλ μ»΄ν¬λνΈ λ΄λΆμμκ° μλ λ³λ ν¨μλ₯Ό μ μΈ λ° export νλ€.
getStaticProps λ₯Ό μ μΈνλ©΄ nextjs λ‘ νμ¬κΈ μ΄ νμ΄μ§λ μ¬μ μμ±μ΄ λμ΄μΌ νλ€λ κ²μ μλ €μ€λ€.
revalidate : μ΄λ€ νμ΄μ§μ λ°μ΄ν°κ° μμ£Ό λ°λλ κ²½μ° λ§€λ² λΉλλ₯Ό ν΄μ£ΌκΈ°μλ 리μμ€κ° λλΉλλ€. 첫 λ°°ν¬ μ΄ν μ¬λ°°ν¬ μμ΄ νμ΄μ§λ₯Ό μ¬μμ±νλλ‘ νλλ°, revalidate: 10 μ ν΄λΉ νμ΄μ§κ° 10μ΄ λ§λ€ μ¬μμ± λμ΄μΌ νλ€λ κ²μ μλ €μ€λ€.
export async function getStaticProps() {
// μ¬κΈ° μμμ fs λͺ¨λμ μΈ μ μλ€.
console.log('(Re-)Generating...')
const filePath = path.join(process.cwd(), 'data', 'dummy-backend.json')
const jsonData = await fs.readFile(filePath)
const data = JSON.parse(jsonData)
if (!data) {
return {
redirect: {
destination: '/no-data',
},
}
}
if (data.products.length === 0) {
return {
notFound: true,
}
}
return {
props: {
products: data.products,
},
revalidate: 10,
}
}
npm run build μ΄ν, npm start (λΉλ νλ‘λμ ready μΉ μ¬μ΄νΈλ₯Ό μ€ννλ κ²μ μλ―Έ) λ‘ μλ‘κ³ μΉ¨μ ν΄λ³΄λ©΄ μ ν¨μ± μ¬κ²μ¬κ° μ΄λ£¨μ΄μ§κ³ μ΄λ 10μ΄ λ§λ€ μ¬κ΅¬μΆ λλ κ²μ νμΈν μ μλ€. (ISR : Incremental Static Generation, μ¦λΆ μ μ μμ±)
Root μΈλ±μ€ νμ΄μ§μ μ getStaticProps λ₯Ό ν΅ν΄ λ€μκ³Ό κ°μ λλ―Έ λ°μ΄ν°λ₯Ό props λ‘ λΆλ¬μ¬ μ μμλ€.
{
"products": [
{ "id": "p1", "title": "Product 1", "description": "This is product 1" },
{ "id": "p2", "title": "Product 2", "description": "This is product 2" },
{ "id": "p3", "title": "Product 3", "description": "This is product 3" }
]
}
products 리μ€νΈ λ€μ ul κ³Ό li λ₯Ό ν΅ν΄ λ λλ§ ν΄μ£Όκ³ <Link />
νκ·Έλ₯Ό ν΅ν΄ /productId κ²½λ‘λ‘ λ€μ΄κ°μ μΈλΆ μν μ 보λ₯Ό νμΈνλλ‘ νμλ€.
κ·Έλ¬λ©΄ ν΄λΉ νμ΄μ§μ μ»΄ν¬λνΈ μμλ νμ΄μ§ μ¬μ μμ±ν¨μ getStaticProps λ₯Ό νμλ‘ ν κ²μ΄λ€.
getStaticProps μ context μΈμλ₯Ό ν΅ν΄ λμ λΌμ°ν μ κ²½λ‘ μ΄λ¦μ λΆλ¬ μ¬ μ μλ€.
export async function getStaticProps(context) {
const { params } = context
const productId = params.pid
const data = await getData()
const filteredData = data.products.find(product => product.id === productId)
if (!filteredData) {
return {
notFound: true,
}
}
return {
props: {
productDetail: filteredData,
},
revalidate: 10,
}
}
κ·Όλ° μλμ κ°μ μλ¬κ° λ¬λ€.
pages λ΄λΆ μ»΄ν¬λνΈ μ€, [pid].js
, [pid].tsx
λ±μ λμ νμ΄μ§λ₯Ό μν μ»΄ν¬λνΈλ NextJS μμ κΈ°λ³Έ λμμΌλ‘ νμ΄μ§λ₯Ό μ¬μ μμ±νμ§ μλλ€.
NextJS μμλ μ¬μ μ μ΄ λμ νμ΄μ§λ₯Ό μν΄ μΌλ§λ λ§μ νμ΄μ§λ₯Ό 미리 μμ±ν΄μΌ νλμ§ μμ§ λͺ»νκΈ° λλ¬Έμ΄λ€.
getStaticPaths λΉλκΈ° ν¨μλ λμ νμ΄μ§μμ μ΄λ€ μΈμ€ν΄μ€κ° μ¬μ μμ±λμ΄μΌ ν μ§ NextJS μ μλ €μ€ μ μλ€.
export async function getStaticPaths() {
return {
paths: [
{ params: { pid: 'p1' } },
{ params: { pid: 'p2' } },
{ params: { pid: 'p3' } },
],
fallback: false,
}
}
path λ°°μ΄ λ΄ μμ±ν νμ΄μ§μ μΈμ€ν΄μ€λ₯Ό μμ κ°μ ννλ‘ λ£μ΄μ€λ€.
νμ§λ§, μνμ΄ λ§μ΄ μλ€λ©΄ λͺ¨λ μνμ λ€ μ¬μ μμ±νκΈ°μ μκ°κ³Ό μμ λλΉμ΄λ©° μΌλΆμ μν μμΈ νμ΄μ§λ λ°©λ¬Έκ°μ΄ κ±°μ μλ€κ³ νλ©΄ μ΄ λν 리μμ€ λλΉμ΄λ€.
fallback μ true λ‘ μ€μ μ, paths μ ν¬ν¨λμ§ μμ μΈμ€ν΄μ€λΌλ νμ΄μ§ λ°©λ¬Έ μ λ‘λ©λλ κ°μ΄ μ ν¨ν μ μλλ‘ ν΄μ€λ€.
λ€λ§ μ΄λ μ¬μ μμ±λλ κ²μ΄ μλ μμ²μ΄ μλ²μ λλ¬νλ μκ°+μμ μ μμ±λλ€.
export async function getStaticPaths() {
return {
paths: [
{ params: { pid: 'p1' } },
// { params: { pid: 'p2' } },
// { params: { pid: 'p3' } },
],
fallback: false,
}
}
κ·Έλ κΈ° λλ¬Έμ fallback: true λ‘ μ€μ μ, url μ λ ₯μΌλ‘ μ κ·Όνλ©΄ μλ¬κ° λλ€.
localhost:3000/p3, props μ detail μ λ³΄κ° μλ€λ Error λ°μν¨
μ΄ κ²½μ°λ κ·Έλμ λκ°μ§ λ°©λ²μ΄ μλλ°, νλλ ν΄λΌμ΄μΈνΈ λ¨μμ props λ‘ λ°λ λ°μ΄ν°μ μ 무μ λ°λΌ loading μ²λ¦¬ λ±μΌλ‘ λΆκΈ°μμΌμ νλ©΄μ λ λλ§ν΄μ£Όλ λ°©λ², λλ²μ§Έλ fallback: βblockingβ μΌλ‘ μ€μ νλ λ°©λ² μ΄ μλ€.
νλ©΄ μ»΄ν¬λνΈμμ λΆκΈ°νκΈ°
function ProductDetailPage(props) {
const { productDetail } = props
if (!productDetail) {
return <p>Loading...</p>
}
return (
<>
<h1>{productDetail.title}</h1>
<p>{productDetail.description}</p>
</>
)
}
export default ProductDetailPage
fallback: βblockingβ μΌλ‘ μ€μ
fallback: βblockingβ μΌλ‘ μ€μ μ μμ κ°μ΄ ν΄λΌμ΄μΈνΈ μ»΄ν¬λνΈμμ λΆκΈ°ν νμκ° μλ€. NextJS μμ λ°μμ€λ λ°μ΄ν°λ₯Ό κΈ°λ€λ €μ€λ€. λ¨μ μ νμ΄μ§ λ°©λ¬Έμκ° μλ΅λ°λ μκ°μ΄ κΈΈμ΄μ§ μ μλ€λ μ μ΄λ€.
import React from 'react'
import fs from 'fs/promises'
import path from 'path'
function ProductDetailPage(props) {
const { productDetail } = props
if (!productDetail) {
return <p>Loading...</p>
}
return (
<>
<h1>{productDetail.title}</h1>
<p>{productDetail.description}</p>
</>
)
}
export default ProductDetailPage
async function getData() {
const filePath = path.join(process.cwd(), 'data', 'dummy-backend.json')
const jsonData = await fs.readFile(filePath)
const data = JSON.parse(jsonData)
return data
}
export async function getStaticProps(context) {
const { params } = context
const productId = params.pid
const data = await getData()
const filteredData = data.products.find(product => product.id === productId)
if (!filteredData) {
return {
notFound: true,
}
}
return {
props: {
productDetail: filteredData,
},
revalidate: 10,
}
}
export async function getStaticPaths() {
const data = await getData()
const ids = data.products.map(product => product.id)
const pathsWithParams = ids.map(id => ({
params: {
pid: id,
},
}))
return {
// paths: [
// { params: { pid: "p1" } },
// // { params: { pid: "p2" } },
// // { params: { pid: "p3" } },
// ],
paths: pathsWithParams,
// fallback: false,
fallback: true,
// fallback: "blocking",
}
}
μ¬μ λ λλ§μ μ΅μ΄ λ‘λ©μμλ§ μν₯μ λ―ΈμΉλ€. μ΄ν λΆν°λ SPA λ°©μμΌλ‘ React κ° νλ‘ νΈμλμμ λͺ¨λ μ²λ¦¬λ₯Ό μννλ€.