getStaticProps
SSG(Static Site Generator)는 페이지를 호출할 때마다 서버에서 pre-render하는 SSR와 달리 빌드시점에 pre-rendering을 한다.
// posts는 빌드 시점에 getStaticProps()에 의해 채워진다.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// 이 함수는 Server Side에서 빌드 타임에 호출된다.
// Client Side에서는 호출되지 않으므로 직접 데이터베이스 쿼리를 수행할 수도 있다.
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
// Blog 컴포넌트는 빌드 시점에 'posts'를 프로퍼티로 받는다.
return {
props: {
posts,
},
}
}
export default Blog
- 페이지 렌더링에 필요한 데이터는 사용자 request 전에 빌드 시점에서 사용할 수 있다.
- headless CMS에서 데이터를 갖고 온다.
- getStaticProps는 HTML과 JSON 파일을 생성하며, 이 두 파일은 성능을 위해 CDN에 캐싱할 수 있다.
- 데이터는 공개적으로 캐시될 수 있다(사용자별이 아님). 특정 상황에서는 미들웨어를 사용하여 경로를 다시 작성함으로써 이 조건을 우회할 수 있다.
- 항상 서버에서 실행되며 클라이언트에서는 실행되지 않는다.
- `next build`가 실행될 때 동작한다.
- 페이지에서만 사용가능하다. 즉 _app, _document, _error 파일에선 사용이 불가능하다. React는 페이지가 렌더링되기 전에 필요한 모든 데이터를 가지고 있어야 하기 때문이다.
- `next dev`로 실행하면, getStaticProps는 매 요청마다 호출된다.
Context Object
context 파라미터의 객체에는 아래의 key값들이 있다.
- params: 페이지가 dynamic route인 경우, (ex: page 이름이 [id].js라면 {id: ...} 객체 return)
- req - HTTP 요청 객체(서버 전용)
- res - HTTP 응답 객체(서버 전용)
- query - 객체로 파싱된 URL의 Query String (ex: /post?type=news 일 경우 {type:'news'} 객체 return)
- preview: true일 경우 Preview Mode 모드
- previewData: preview에서 사용되는 데이터. setPreviewData함수를 이용하여 설정할 수 있다.
- resolvedUrl: 클라이언트 전환을 위해 _next/data 접두사를 제거하고, 원래 쿼리 값을 포함하는 정규화된 요청 URL 버전
(ex /post?type=news 일 경우 '/post?type=news' 문자열 return - locale: 현재 로케일 값
- locales: 사용 가능한 로케일의 목록 값
- defaultLocale: 기본 로케일 값
Server side 코드 직접 작성
getStaticProps는 Server side에서만 실행되고, Client Side에서는 실행되지 않는다.
브라우저용 JS번들에도 포함되지 않으므로 API Route를 통해 데이터를 갖고오는 대신 직접 Server side 코드 (데이터베이스 쿼리등)를 직접 사용할 수 있다.
// lib/load-posts.js
export async function loadPosts() {
const res = await fetch('https://.../posts/')
const data = await res.json()
return data
}
// pages/blog.js
import { loadPosts } from '../lib/load-posts'
export async function getStaticProps(context) {
const posts = await loadPosts()
return { props: { posts } }
}
API 경로는 CMS에서 일부 데이터를 가져오는 데 사용된다. 그 다음 해당 API 경로를 getStaticProps에서 직접 호출한다. 이렇게 하면 추가 호출이 발생하여 성능이 저하된다.
성능이 저하되지 않는 방법으로 lib폴더를 이용하는 방법이있다. CMS에서 데이터를 가져오는 로직은 `lib/` 폴더를 사용하여 공유할 수 있다. 그리고 그 데이터를 getStaticProps와 공유할 수 있다.
lib 폴더
일반적으로 Next.js프로젝트에서 사용되는 JavaScript 모듈을 저장하는 폴더.
lib폴더는 컴포넌트 또는 페이지에 종속되지 않는 util 함수, API 클라이언트, 데이터 로드 함수 등과 같은 코드를 보관하는 데 사용된다.
- util 함수 : getStaticProps에서 데이터를 가공하거나 페이지에서 사용되는 헬퍼 함수를 lib 폴더에 넣어 재사용성을 높일 수 있다.
- API 클라이언트: getStaticProps에서 API를 호출할 때, 일반적으로 fetch나 axios와 같은 라이브러리를 사용한다. 이러한 API 호출 코드를 lib 폴더에 넣어 재사용성을 높일 수 있다.
- 데이터 로드 함수: getStaticProps에서 데이터를 불러올 때, 데이터베이스나 외부 API 등에서 데이터를 가져오는 로직을 lib 폴더에 넣어 재사용성을 높일 수 있다.
Return 값
revalidate
- 정적 페이지를 다시 생성하는 시간을 지정하는 옵션. 해당 시간이 지나면 Nextj.js는 캐시된 페이지를 버리고 새 페이지를 생성한다.
- revalidate()메서드를 이용해서 on-demand 방식으로 시간을 지정할 수 있다.
Incremental Static Regeneration(ISR)을 활용하는 페이지의 캐시 상태는 x-nextjs-cache 응답 헤더의 값을 읽어서 확인할 수 있다. 가능한 값은 다음과 같다.
- MISS - 경로가 캐시에 없다(첫 번째 방문 시 최대 한 번만 발생).
- STALE - 경로가 캐시에 있지만 revalidate 시간을 초과하여 백그라운드에서 업데이트 된다.
- HIT - 경로가 캐시에 있고 revalidate 시간을 초과하지 않았다.
notFound
notFound를 사용하면 페이지에서 404 상태와 404 페이지를 return할 수 있다.
`notFound: true`를 사용하면 이전에 성공적으로 생성된 페이지가 있더라도 404페이지를 return한다. 이는 사용자가 생성한 콘텐츠가 작성자에 의해 삭제되는 등의 경우를 위함이다.
export async function getStaticProps(context) {
const res = await fetch(`https://.../data`)
const data = await res.json()
if (!data) {
return {
notFound: true,
}
}
return {
props: { data },
}
}
getStaticPaths의 fallback: false 모드일 땐 notFound가 필요하지 않다.
getStaticPaths에서 pre-rendering할때 미리 path 목록을 생성하는데, 그 목록에 없는 페이지에 접근하면 404페이지를 return하기 때문이다. 테스트 링크
redirect
redirect는 클라이언트를 다른 페이지로 redirection한다.
export async function getStaticProps(context) {
const res = await fetch(`https://...`)
const data = await res.json()
if (!data) {
return {
redirect: {
destination: '/',
permanent: false,
// statusCode: 301
},
}
}
return {
props: { data },
}
}
위 코드의 경우 data가 없으면 `/`로 redirection 한다.
- `destination` 속성을 사용하여 redirection할 URL을 지정할 수 있다.
- `permanent` 속성을 사용하여 해당 redirection을 영구 redirection로 설정할 수도 있다.
- 일반적으로 `permanent` 속성을 true로 설정하면 redirection을 클라이언트 캐시에 저장하고, 301 HTTP 상태 코드를 return한다.
- false로 설정하면 매번 새로운 redirection을 받고, 302 HTTP 상태 코드를 return한다.
- `redirect`를 사용하여 클라이언트를 다른 페이지로 redirection할 때는, 반드시 올바른 HTTP 상태 코드를 사용하여 redirection해야한다.
- 드물지만 구형 HTTP 클라이언트가 올바르게 redirection되도록 custom statusCode를 할당해야 하는 경우가 있다. 이 경우 statusCode 속성을 사용할 수 있지만 permanent 속성과 같이 사용할 수는 없다.
HTML, JSON 정적 페이지 생성
빌드 시점에 getStaticProps가 포함된 페이지가 pre-rendering되면 페이지의 HTML파일과 JSON이 생성된다.
이 JSON 파일은 `next/link` 또는 `next/router`의 Client Side 라우팅에 사용된다. getStaticProps를 사용하여 pre-rendering된 페이지로 이동하면 Next.js는 이 JSON 파일을 가져와 페이지 구성요소의 프로퍼티로 사용한다.
증분 정적 생성(ISR) 을 사용하는 경우, 백그라운드에서 Client Side 네비게이션에 필요한 JSON을 생성한다.
동일한 페이지에 대해 요청이 여러번 발생하는 것으로 표시될 수 있지만, 이는 사용자 성능에는 영향을 미치지 않는다.
process.cwd() 메서드로 파일 읽기
getStaticProps를 이용해서 file을 읽을 수 있다.
Next.js는 코드를 별도의 디렉터리에 컴파일하므로 return하는 경로가 페이지 디렉터리와 다르다. 그래서 `__dirname`을 사용할 수 없다. 대신에 `process.cwd()` 메서드를 사용해서 파일을 갖고 올 수 있다.
import { promises as fs } from 'fs'
import path from 'path'
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>
<h3>{post.filename}</h3>
<p>{post.content}</p>
</li>
))}
</ul>
)
}
export async function getStaticProps() {
const postsDirectory = path.join(process.cwd(), 'posts')
const filenames = await fs.readdir(postsDirectory)
const posts = filenames.map(async (filename) => {
const filePath = path.join(postsDirectory, filename)
const fileContents = await fs.readFile(filePath, 'utf8')
return {
filename,
content: fileContents,
}
})
return {
props: {
posts: await Promise.all(posts),
},
}
}
export default Blog
다음 포스팅으로 이어집니다.
REFERENCES
Next.js 공식 사이트 - https://nextjs.org/docs/basic-features/data-fetching/get-static-props#runs-on-every-request-in-development