Pages 문서에서 Next.js는 2종류의 pre-rendering이 있다고 설명했다: Static Generation 과 Server-side Rendering. 이제 각 경우의 data 가져오는 전략의 대해 좀 더 깊이 알아보자.
pre-rendering시 data를 가지고 오기 위해 사용하는 Next.js의 3가지 유니크한 함수들에 대해 이야기하자.
getStaticProps
(Static Generation): 빌드시에 data를 가지고 온다.getStaticPaths
(Static Generation): pre-render 페이지의 동적경로를 지정한다.getStaticProps
(Static Generation): 매 요청시에 data를 가지고 온다추가로 client-side에서 어떻게 data를 가지고 오는지도 간략히 이야기 하자.
getStaticProps
(Static Generation)만약 해당 page에 getStaticProps
라 불리는 async
함수를 export한다면 Next.js는 이 페이지는 빌드시에 pre-render할 거고 props 는 getStatciProps
의 의해 리턴된다.
export async function getStaticProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
context
파라미터는 다음의 속성들을 포함한 object 이다.
params
는 dynamic routes를 사용하는 페이지들의 라우트 파라미터를 포함한다. 예를 들어 만약 페이지의 이름이 [id].js
라면 params
는 {id: ...}
같이 될 것이다. 좀 더 상세한 정보는 Dynamic Routing 문서를 살펴봐라. 이 params
속성은 getStaticPaths
와 함께 사용해야 한다.preview
는 true
이다. 해당 페이지가 preview mode인 경우가 아니라면 undefined
이다. 자세항 사항은 Preview Mode문서를 참조해라.previewData
는 setPreviewData
에 의해 세팅된 data를 포함한다. Preview Mode 문서를 참조해라.locale
은 active locale을 포함한다.(enabled 되어있다면)locales
는 지원되는 모든 locales를 포함한다.(enabled 되어있다면)defaultLocale
은 설정한 디폴트 locale을 포함한다.(enabled 되어있다면)getStaticProps
는 아래 사항들을 포함된 objects를 리턴해야한다.
props
- 이건 해당 페이지 컴포넌트에 의해 전달받아야하는 required object이다. serializable한 객채여야한다.revalidate
- 이건 몇초후에 해당 페이지를 재생성 할지의 대한 optional 수치값이다.notFound
- 이건 404상태 및 해당 페이지를 리턴하도록 하는 optional한 boolean값이다. 아래에 예시가 있다.export async function getStaticProps(context) {
const res = await fetch(`https://.../data`)
const data = await res.json()
if (!data) {
return {
notFound: true,
}
}
return {
props: { data }, // will be passed to the page component as props
}
}
fallback: false
모드는notFound
가 필요없다. 왜냐하면getStaticPaths
에서 반환된 경로들만 pre-render되기 때문이다.
redirect
- 이 optional
redirect 값은 내부나 외부 자원으로 redirecting 할 수 있게 해준다. 이 값은 받드시 {destination: string, permanet: boolean}
형태여야 한다. 드문 경우로 이전 HTTP Client가 적절하게 redirect 되도록 커스텀 상태코드를 할당해야할수도 있는데 이경우는 statusCode
속성을 permanent
속성 대신 사용할 수 있지만 둘다 함께는 사용 못한다. 아래는 동작예시이다.export async function getStaticProps(context) {
const res = await fetch(`https://...`)
const data = await res.json()
if (!data) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: { data }, // will be passed to the page component as props
}
}
현재 Redirecting을 빌드시에 하는 것은 허용되지 않는다. 만약 redirects가 빌드시에 알려질 필요가 있다면 반드시
next.config.js
에 그 redirects를 포함해라.
top-level scope에
getStaticProps
를 사용한 모듈을 import할 수 있고 이 imports 모듈은 client-side에 번들되지 않을 것이다.
이 말은getStaticProps
에 server-side code를 직접 작성할 수 있다는 의미이다. 여기에는 파일시스템이나 데이터베이스에서 읽는 것까지 포함한다.
getStaticProps
에서는 API route를 호출하는fetch()
를 사용하면 안된다. 대신 API route 내부에서 사용되는 로직을 직접 import하는 것은 된다. 이런 접근 방식을 위해서는 코드를 약간 리팩토링 할 필요가 있을 수 있다.
외부 API로 부터 Fetching 하는건 괜찮다.
여기 CMS(conent management system)로부터 블로그 포스트리스트를 fetch하기 위해getStaticProps
를 사용한 예가 있다. 이 예는 또한 Pages 문서에도 있다.
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getStaticProps() {
// Call an external API endpoint to get posts.
// You can use any data fetching library
const res = await fetch('https://.../posts')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
export default Blog
getStaticProps
?getStaticProps
는 이 때 사용해라:
getStaticProps
는 HTML과 JSON파일을 생성하고 둘 다 CDN에 의해 캐시되서 성능적으로 매우 좋다.GetStaticProps
타입스크립트의 경우 next
에서 GetStaticProps
유형을 사용할 수 있다.
import { GetStaticProps } from 'next'
export const getStaticProps: GetStaticProps = async (context) => {
// ...
}
만약 도출한 특정 타입을 원한다면 InferGetStaticPropsType<typeof getStaticProps>
를 아래와 같이 사용해라.
import { InferGetStaticPropsType } from 'next'
type Post = {
author: string
content: string
}
export const getStaticProps = async () => {
const res = await fetch('https://.../posts')
const posts: Post[] = await res.json()
return {
props: {
posts,
},
}
}
function Blog({ posts }: InferGetStaticPropsType<typeof getStaticProps>) {
// will resolve posts to type Post[]
}
export default Blog
getStaticProps
을 사용한다면 더이상 dynamic content에 종속될 필요가 없다. static content도 dynamic content가 될 수있다. Incremental Static REgeneration을 사용하면 트래픽이 들어올때 백그라운드에서 다시 렌더랑해서 기존 페이지를 업데이트 할 수 있게 해준다.
stale-while-revalidate 에 의해 영감받은 백그라운드 재생성은 트래픽이 항상 정적 저장소에서 중단없이 제공됨을 보장하고 새로 빌드된 페이지는 생성이 완료됬을 경우에만 푸시된다.
이전 gesStaticProps
예를 생각해보자. 이제 재생성이 활성화 되었다.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every second
revalidate: 1, // In seconds
}
}
export default Blog
이제 블로그 포스트리스트는 1초마다 재생성될 것이다. 만약 새로운 블로그 포스트가 추가되면 그것은 앱을 재빌드하거나 재배포없이 거의 곧바로 활용가능해진다.
그리고 이건 fallback:true
옵션과 완벽히 동작한다. 왜냐하면 이제 항상 새로운 최신 포스트 리스트를 가질 수 있고 얼마나 많은 포스트가 추가되고 수정되더라도 요구에 따라 생성된 블로그 포스트를 가질 수 있다.
전통적인 SSR과는 다르게 Incremental Static Regeneration은 정적특징의 장점을 보장해준다.
process.cwd()
파일들은 getStaticProps
에서 직접 파일시스템을 통해 읽혀 질 수 있다.
그렇게 하기 위해서 반드시 파일의 full path를 가지고 있어야한다.
Next.js는 코드를 몇개의 디렉토리 속으로 나누어서 컴파일한다. 따라서 __dirname
를 사용하면 실제 페이지 디렉토리 경로와 다른 경로를 리턴하기 때문에 사용할 수 없다.
대신 process.cwd()
를 사용할 수 있다. 이것은 Next.js 실행이 일어나는 디렉토리 정보를 준다.
import { promises as fs } from 'fs'
import path from 'path'
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>
<h3>{post.filename}</h3>
<p>{post.content}</p>
</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
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')
// Generally you would parse/transform the contents
// For example you can transform markdown to HTML here
return {
filename,
content: fileContents,
}
})
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts: await Promise.all(posts),
},
}
}
export default Blog
getStaticProps
는 빌드시에만 동작하기 때문에 정적 HTML을 생성하는 동안 쿼리파라미터나 HTTP Header 같은, 요청이 일어나는 동안만 사용가능한 data를 받을 수 없다.
getStaticProps
는 서버사이드에서만 동작한다. 결코 클라이언트 사이드에서 동작하지 않을 것이다. 또한 브라우저에서 확인 가능한 js bundle에 조차 포함되지 않을 것이다. 이것이 의미하는 것은 굳이 브라우저에 정보를 요청할 필요없이 direct database queries를 직접 사용해서 정보를 가져올 수 있다는 것이다. getStaticProps
로부터 API route를 fetch 하면 안된다. 대신 getStaticProps
에서 server-side 코드를 직접 작성할 수는 있다.
ℹ️ getStaticProps
로부터 API route를 fetch 하면 안된다.
이말이 헷갈릴수 있는데 아래 인용문을 보면 이해하는 데 도움이 될것이다.
getStaticProps (Static Generation) fetches data at build time and only processed there. That means there is no mix between frontend and backend. If you need data in getStaticProps, you should not cause an extra network call to /api to get the data, instead you should use a function with that logic or directly in getStaticProps.
If you need data in your frontend, you can fetch data from your rest/graphql/whatever/api or pass it via getServerSideProps.
이 예를 통해서 Next.js가 클라이언트 사이트 번들로 부터 무엇을 제거하는지 확인 할 수 있다.
// This app shows what Next.js bundles for the client-side with its new SSG
// support. This editor supports TypeScript syntax.
import Cookies from 'cookies';
import Mysql from 'mysql';
import Link from 'next/link';
import SQL from 'sql-template-strings';
import Layout from '../components/Layout';
const pool = Mysql.createPool(process.env.DATABASE_URL);
export default function ({ projects }) {
return (
<Layout>
<h1>Projects</h1>
<ul>
{projects.map((project) => (
<li key={project.id}>
<Link href="/projects/[id]" as={`/projects/${project.id}`}>
<a>{project.name}</a>
</Link>
</li>
))}
</ul>
</Layout>
);
}
export async function getServerSideProps({ req, res }) {
const userId = new Cookies(req, res).get('user_id');
const projects = await new Promise((resolve, reject) =>
pool.query(
SQL`SELECT id, name FROM projects WHERE user_id = ${userId};`,
(err, results) => (err ? reject(err) : resolve(results))
)
);
return { props: { projects } };
}
// This is the code that is bundled for the client-side:
import Link from 'next/link';
import Layout from '../components/Layout';
export var __N_SSP = true;
export default function ({ projects }) {
return (
<Layout>
<h1>Projects</h1>
<ul>
{projects.map((project) => (
<li key={project.id}>
<Link href="/projects/[id]" as={`/projects/${project.id}`}>
<a>{project.name}</a>
</Link>
</li>
))}
</ul>
</Layout>
);
}
getStaticProps
를 가지고 있는 페이지가 빌드시 pre-render될 때, Next.js는 HTML파일뿐만아니라 getStaticProps
의 실행결과를 포함하는 JSON파일도 생성한다.
이 JSON파일은 next/link
나 next/router
를 통해 client-side 라우팅에서 사용될 것이다. getStaticProps
로 pre-render된 페이지로 이동할 때 Next.js는 이 JSON파일(빌드시 계산된)을 가지고 오고 그것을 해당 페이지 컴포넌트의 props로 사용한다. 이것은 client-side transition은 getStaticProps
를 호출하는 것이아니라 단지 exported JSON 을 사용한다는 것을 의미한다.
Incremental Static Generation 을 사용할때 getStaticProps
는 client-side 탐색의 필요에 의한 JSON을 생성하기위해 대역외에서 실행될것이다. 이것이 동일페이지에 대한 multiple 요청에 형태로 볼 수 있지만 이것은 의도된 것이고 end사용자의 퍼포먼스에 영향을 주지 않는다.
getStaticProps
는 page에서만 exported 된다. 다른 곳에서는 사용할 수없다.
이러한 제한의 한가지 이유는 리액트는 페이지가 render 되기 전에 필요한 data 모두를 가지고 있어야 하기 때문이다.
또한 반드시 export async function getStaticProps() {}
로 사용해야한다. getStaticProps
를 페이지 컴포넌트의 속성으로 쓴다면 동작하지 않을 것이다.
개발환경(next dev
)에서 getStaticProps
는 매 요청시에 호출된다.
몇가지 경우에서 임시적인 bypass Static Generation을 원할 지도 모른다. 그리고 요청시에 페이지가 render 되길 원할지도 모르다. 한가지 예로써 만약 headless CMS를 사용하고 있고 그것들이 배포되기전에 프리뷰 초안을 보길 원할 수 있다.
이런 경우 Next.js는 Preview Mode라 불리는 특성을 지원해준다. 자세한 사항은 Preview Mode문서를 참조해라.
getStaticPaths
(Static Generation)만약 dynamic route를 가지고 있는 페이지가 있고 그 페이지가 getStaticProps
를 사용한다면 그 페이지는 빌드시에 HTML pre-render될때 어떤 경로의 리스트의 페이지들인지 정의할 필요가 있다.
만약 dynamic routes를 사용하는 어떤 페이지에서 getStaticPaths
라 불리는 async
함수를 export 한다면, Next.js는 getStaticPaths
에 의해 정의된 모든 paths들을 정적으로 pre-render할 것이다.
export async function getStaticPaths() {
return {
paths: [
{ params: { ... } } // See the "paths" section below
],
fallback: true or false // See the "fallback" section below
};
}
paths
key(required)paths
키는 pre-render되야하는 경로들을 정의한 것이다. pages/posts/[id].js
라는 이름의 dynamic routes를 사용하는 페이지가 있다고 가정해보자. getStaticPaths
를 해당 페이지에서 export하고 이것은 paths
를 다음과 같이 리턴한다.
return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } }
],
fallback: ...
}
그럼 Next.js는 posts/1
, posts/2
를 빌드시에 정적으로 생성할 것이다. 그리고 이건 pages/posts/[id].js
의 페이지 컴포넌트로서 사용된다.
각 params
의 값은 반드시 해당 페이지의 이름으로 사용될 파라미터와 매핑되어야 한다.
pages/posts/[postId]/[commentId]
라면 params
는 반드시 postId
와 commentId
를 포함해야한다.pages/[...slug]
라고 한다면 params
는 반드시 slug
를 배열로서 포함해야한다. 만약 이 배열이 ['foo', 'bar]
라면 Next.js는 foo/bar
라는 페이지를 정적으로 생성할 것이다.null
, []
, undefined
, false
)를 사용한다 가정하자. 예를들어 pages/[[...slug]]
를 위해 slug:false
를 제공한다면 Next.js는 /
page를 생성할 것이다.fallback
key (required)getStaticPaths
에 의해 리턴되는 객체는 반드시 boolean 속성의 fallback
키를 포함해야한다.
fallback: false
만약 fallback
이 false
이고 getStaticPaths
에 의해 전달받지 못한 pathes들은 모두 404 페이지로 호출된다. 만약 적은양의 pre-render 경로들을 가지고 있다면 이렇게 설정 할 수 있다. 이 페이지들은 빌드시 모두 정적으로 생성된다. 그리고 만약 새 페이지가 자주 추가되지 않는다면 이방식은 유용하다. 만약 데이터 소스에 항목을 더 추가하고 새 페이지를 렌더링해야하는 경우엔 빌드를 다시 실행해야한다.
만약 pages/posts/[id].js
라 불리는 페이지를 예를 들어보자. getStaticPaths
에 의해 CMS로 부터 블로그 포스트리스트는 리턴된다. 그리고 각 페이지들은 getStaticProps
에 의해 CMS로부터 각 포스트 데이터가 리턴된다. 아래 예는 Pages 문서에도 있다.
// pages/posts/[id].js
function Post({ post }) {
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false }
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return { props: { post } }
}
export default Post
fallback: true
만약 fallback
이 true
이면 getStaticProps
의 행동이 변한다.
getStaticPaths
로 부터 리턴된 경로들은 getStaticProps
의해 빌드시에 HTML로 render된다.getStaticProps
를 실행하는 것을 포함해서 백그라운드로 Next.js는 요청된 경로의 HTML과 JSON을 정적으로 생성할 것이다.
fallback: true
는next next
사용시 지원되지 않는다.
"fallback" 버전의 페이지에서는
router.isFallback
이 true
값이 되는데 이로인해 fallback이 렌더링 되었다는 것을 확인할 수 있다.// pages/posts/[id].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
return {
// Only `/posts/1` and `/posts/2` are generated at build time
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// Enable statically generating additional pages
// For example: `/posts/3`
fallback: true,
}
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return {
props: { post },
// Re-generate the post at most once per second
// if a request comes in
revalidate: 1,
}
}
export default Post
fallback: true
useful?fallback: true
는 app이 매우 많은 양의 정적페이지(매우 큰 e-commerce site)를 가지고 있을때 유용하다. 모든 상품페이지를 다 pre-render 하고 싶지만 그렇게 되면 빌드시간이 엄청엄청 오래걸린다.
대신 특정페이지의 작은 집합들을 정적으로 생성하고 fallback: true
로 나머지를 사용하면 된다. 어떤 사람들은 아직 생성되지 않은 페이지들을 요청할 수 있는데 그때 그 유저는 로딩창을 볼 수 있을 것이다. 짧은 시간 이후 getStaticProps
는 완료되고 요청된 데이터와 함께 페이지는 렌더될것이다. 그 이후 동일한 페이지를 요청한 모든 사람은 정적으로 pre-render 페이지를 얻을 것이다.
이런 방식은 Static Generation의 장점을 그대로 가져감과 동시에 빠른 빌드 그리고 사용자가 항상 빠른 경험을 하도록 보장해준다.
fallback: true
는 생성된 페이지들을 업데이트하지 않을 것이다. 페이지가 업데이트가 되길 원한다면 Incremental Static Regeneration을 참조해라.
fallback: 'blocking'
만약 fallback
이 blocking
이라면 getStaticPaths
에 의해 리턴되지 않은 새로운 페이지들은 HTML이 생성될때 까지 기다릴 것이다. 이것은 SSR과 동일하다. 그리고 이후 동일한 요청에 대해서는 캐시가 된다.
getStaticProps
는 다음과 같이 행동할 것이다.
getStaticPaths
로 부터 리턴된 경로들은 getStaticProps
에 의해 빌드시 HTML로 render된다.fallback
: blocking
은 디폴트로 생성된 페이지를 업데이트 하지 않는다. 생성된 페이지를 업데이트 하기 위해선 Incremental Static Regeneration 과 fallback: 'blocking'
을 같이 사용해야 한다.
fallbakc: 'blocking'
은 next export
사용시 지원되지 않는다.
getStaticPaths
?동적 라우트를 사용해서 페이지를 정적으로 pre-render 한다면 getStaticPaths
는 반드시 사용해야한다.
GetStaticPaths
타입스크립트에서는 next
에서 GetStaticPaths
를 사용할 수 있다.
import { GetStaticPaths } from 'next'
export const getStaticPaths: GetStaticPaths = async () => {
// ...
}
getStaticProps
getStaticProps
를 동적 라우트 파라미터와 함께 사용한다면 getStaticPaths
는 무조건 써야한다. getStaticPaths
는 getServerSideprops
와 절대 사용 같이 할 수 없다.
getStaticPaths
는 서버사이드에서 빌드시에만 실행된다.
getStaticPaths
는 page에서만 expored 될 수 있다. page가 아닌곳에서는 export할 수 없다.
개발환겨에서는 (next dev
), getStaticPaths
는 매 요청시에 호출된다.
getServerSideProps
(Server-side Rendering)만약 getServerSideProps
라 불리는 async
함수를 export한다면 Next.js는 getServerSideProps
에 의해 리턴된 data를 사용하면서 이 페이지를 매 요청시마다 pre-render할 것이다.
export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
context
파라미터는 다음 속성들을 포함하고 있는 객체이다.
params
: 만약 동적 라우트를 사용하는 페이지라면 params
는 라우트 파라미터들을 포함한다. 만약 페이지 명이 [id].js
라면 params
는 {id:...}
와 같은 형태가 될 것이다. 좀 더 자세하게 알기 원한다면 Dynamic Routing 문서를 찾아봐라req
: HTTP IncommingMessage objectres
: HTTP response objectquery
: An object representing the query stringpreview
: preview
가 true
라면 이 페이지는 preview 모드 인것이고 false
라면 아닌것이다. 자세한 사항은 Preview Mode 문서를 찾아봐라previewData
: setPreviewData
에 의해 세팅된 preview data이다. 자세한 사항은 Preview Mode 문서를 찾아봐라.resolvedUrl
: client transition을 위해 _next/data
접두어가 생략된 요청 url의 정규화 버전이다. 원본 쿼리값도 포함되어있다.locale
: active locale을 포함한다.(if enabled)locales
: 모든 지원 locales를 포함한다.(if enabled)defaultLocale
: 디폴트로 설정된 locale을 포함한다.(if enabled)getServerSideProps
가 리턴하는 객체는 아래항목들이 포함한다.
props
- 페이지 컴포넌트에 의해 리턴되는 props는 필수객체이다. 이것은 serializable object여야한다.notFound
- 페이지가 404 상태와 404페이지로 리턴되도록 허용하는 옵셔널한 boolean 값이다. 아래 동작예가 있다.export async function getServerSideProps(context) {
const res = await fetch(`https://...`)
const data = await res.json()
if (!data) {
return {
notFound: true,
}
}
return {
props: {}, // will be passed to the page component as props
}
}
redirect
: 옵셔널한 redirect 값으로 내외부 자원으로 redirecting 을 해주는 값이다. 반드시 {destination: string, permanent: boolean}
형태여야한다. 드문경우로 만약 이전 HTTP Clients에게 적절한 redict를 위해 custom 상태코드를 할당해야하는 경우가 있을 수 있다. 이 때에는 statusCode
를 permanent
대신 사용할 수 있다 단지 둘 모두를 같이 쓸 수는 없다. 아래는 동작 예시이다.export async function getServerSideProps(context) {
const res = await fetch(`https://.../data`)
const data = await res.json()
if (!data) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: {}, // will be passed to the page component as props
}
}
getServerSideProps
는 top-level scope에 import 해서 모듈로 쓸 수 있다. 이것은 client-side에서 번들링 되지 않는다.
이 말은getServerSideProps
안에 서버사이드 코드를 직접작성할 수 있다는 의미이고 파일시스템과 db를 읽는 것도 포함된다.
getServerSideProps
에는 API route를 호출하는fetch()
를 사용해선 안된다. 대신 API route 내부에서 사용되는 로직을 직접 import해라. 이런 방식을 위해서는 코드가 리팩토링 될 여지가 있을 수있다.
외부 API로부터 Fetching하는건 괜찮다.
여기에 사용자 요청시에 data를 가져오고 pre-render 하는 getServerSideProps
사용 예가 있다.
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Page
getServerSideProps
?getServerSideProps
는 pre-render 되는 페이지의 필요한 데이터가 요청시에 가져와야하는 경우에만 사용해라. getStaticProps
보다는 좀 느릴것이다. 왜냐면 서버가 반드시 모든 요청에 대한 결과를 계산해야만 하기 때문이다. 그리고 그 결과는 특정 설정 없이는 CDN에 의해 캐시될수 없다.
만약 데이터가 pre-render 될 필요 없다면, client-side에서 데이터를 가져오는 것을 고려해봐라.
GetServerSideProps
타입스크립트를 사용할 때는 next
에서 GetServerSideProps
를 사용할 수있다.
import { GetServerSideProps } from 'next'
export const getServerSideProps: GetServerSideProps = async (context) => {
// ...
}
만약 props가 도출된 타입이 되길 원한다면 InferGetServerSidePropsType<typeofgetServerSideProps>
를 사용할 수 있다.
import { InferGetServerSidePropsType } from 'next'
type Data = { ... }
export const getServerSideProps = async () => {
const res = await fetch('https://.../data')
const data: Data = await res.json()
return {
props: {
data,
},
}
}
function Page({ data }: InferGetServerSidePropsType<typeof getServerSideProps>) {
// will resolve posts to type Data
}
export default Page
getServerSideProps
는 서버에서만 동작하고 결고 브라우저에서 동작하지 않는다. 만약 페이지가 getServerSideProps
를 사용한다면
getServerSideProps
는 요청시에만 실행된다 그리고 이페이지는 리턴된 props 와 함께 pre-render 된다.next/link
나 next/router
를 통해 client-side transitions 에서 요청된다면 Next.js는 API 요청을 서버에 보낸다. 그리고 getServerSideProps
를 실행한다. 그리고 getServerSideProps
의 실행결과로 만들어진 JSON을 리턴한다. 그리고 그 JSON은 그 페이지에 render에 사용될 것이다. 이 모든 과정은 Next.js 에 의해 자동적으로 핸들될 것이다. 그러니 getServerSideProps
만 잘 정의하면 된다.아래 예를 통해 Next.js가 client-side 번들시에 어떻게 서버사이드부분 코드를 제거하는지 확인할 수 있다.
// This app shows what Next.js bundles for the client-side with its new SSG
// support. This editor supports TypeScript syntax.
import Cookies from 'cookies';
import Mysql from 'mysql';
import Link from 'next/link';
import SQL from 'sql-template-strings';
import Layout from '../components/Layout';
const pool = Mysql.createPool(process.env.DATABASE_URL);
export default function ({ projects }) {
return (
<Layout>
<h1>Projects</h1>
<ul>
{projects.map((project) => (
<li key={project.id}>
<Link href="/projects/[id]" as={`/projects/${project.id}`}>
<a>{project.name}</a>
</Link>
</li>
))}
</ul>
</Layout>
);
}
export async function getServerSideProps({ req, res }) {
const userId = new Cookies(req, res).get('user_id');
const projects = await new Promise((resolve, reject) =>
pool.query(
SQL`SELECT id, name FROM projects WHERE user_id = ${userId};`,
(err, results) => (err ? reject(err) : resolve(results))
)
);
return { props: { projects } };
}
// This is the code that is bundled for the client-side:
import Link from 'next/link';
import Layout from '../components/Layout';
export var __N_SSP = true;
export default function ({ projects }) {
return (
<Layout>
<h1>Projects</h1>
<ul>
{projects.map((project) => (
<li key={project.id}>
<Link href="/projects/[id]" as={`/projects/${project.id}`}>
<a>{project.name}</a>
</Link>
</li>
))}
</ul>
</Layout>
);
}
getServerSideProps
는 page안에서만 export될 수있다. 페이지가 아니면 export할 수 없다.
또한 export async function getServerSideProps() {}
으로 사용해야 하며 페이지 컴포넌트의 속성값으로 getServerSideProps
를 추가한다면 동작하지 않을 것이다.
만약 페이지가 자주 업데이트 되는 data를 포함한다면 그 데이터는 pre-render할 필요가 없다. 클라이언트 사이드에서 데이터를 가져올 수 있다. 이것의 예로 사용자 개인화 데이터를 들 수 있다.
여기 동작예가 있다.
이런 접근은 사용자 대시보드페이지에 적합하다. 왜냐하면 대시보드는 개인적이고 특별한 페이지이기 때문이다. SEO에도 연관없고 해당 페이지는 pre-render 될 필요도 없다. 데이터는 빈번히 업데이트 된다는 것은 요청시의 데이터가 가져올 필요가 있다는 의미이다.
Next.js팀은 SWR이라 불리는 data fetching 리액트 훅을 개발했다. 클라이언트사이드에서 데이터를 가져올때 이것이 굉장히 강추임. 이것은 캐싱, revalidation, focus tracking, 일정시간에 다시 가져오는 기능 등이 있다. 아래 예가 있다.
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>
}
자세한 사항은 SWR문서를 확인해라.