Next.js의 data-feching
Pre-rendering
기본적으로 Next.js는 모든 페이지를 pre-render한다. 각각의 페이지가 클라이언트 사이드에서 렌더링 되기전 미리 데이터 페칭을 한다는 뜻이다. Next.js는 정적 생성을 기본적으로 사용하지만 페이지에 데이터 페칭 방법을 선택할 수 있다.
기본적으로 바닐라 리액트가 제공하는 CSR
과 Next.js가 지원하는 pre-rendering에 사용하는 방식인 SSR
, SSG(+ISR)
들을 알아보자.
CSR
클라이언트 측 데이터 가져오기는 페이지에 SEO 인덱싱이 필요하지 않거나, 데이터를 사전 렌더링할 필요가 없거나, 페이지 콘텐츠를 자주 업데이트해야 하는 경우에 유용하다.
하지만 클라이언트에서 데이터를 가져올시 애플리케이션의 성능과 페이지의 로드 속도에 영향을 미칠 수 있다. 이는 페이지가 마운트될 때 데이터를 가져오고 그 데이터가 캐시되지 않기 때문이다.
useEffect를 사용하여 가져오기
function Profile() {
const [data, setData] = useState(null);
const [isLoading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch('/api/profile-data')
.then((res) => res.json())
.then((data) => {
setData(data);
setLoading(false);
});
}, []);
if (isLoading) return <p>Loading...</p>;
if (!data) return <p>No profile data</p>;
return (
<div>
<h1>{data.name}</h1>
<p>{data.bio}</p>
</div>
);
}
SSR
페이지 파일에서 getServerSideProps
라는 함수를 내보내면 Next.js는 반환된 데이터를 사용하여 각 요청에서 페이지를 미리 렌더링한다.
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의 특징
-
서버 측에서만 실행되고 브라우저에서는 실행되지 않는다.
-
오로지 페이지 파일에서만 내보낼 수 있다.
-
캐시 제어 헤더가 구성된 경우에만 캐시가 가능하다.
export async function getServerSideProps({ req, res }) { res.setHeader('Cache-Control', 'public, s-maxage=10, stale-while-revalidate=59'); return { props: {}, }; }
SSG
정적 생성된 페이지로 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.
export async function getStaticProps() {
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의 특징
- 페이지를 렌더링하는 데 필요한 데이터는 사용자 요청보다 앞서 빌드 시간에 사용할 수 있다.
- 데이터는 공개적으로 캐시된다.(사용자별이 아니다.)
- 항상 서버에서 실행되고 클라이언트에서는 실행되지 않는다.
- 개발 환경에서는 모든 요청에서 호출되므로 build 이후에 제대로 작동하는지 테스트 해본다.
getStaticPath란?
페이지에 동적 경로가 있고 getStaticProps
를 사용하는 경우에는 정적으로 생성할 경로 목록을 정의해야 한다.
// pages/posts/[id].js
// Generates `/posts/1` and `/posts/2`
export async function getStaticPaths() {
return {
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
fallback: false, // can also be true or 'blocking'
};
}
// `getStaticPaths` requires using `getStaticProps`
export async function getStaticProps(context) {
return {
// Passed to the page component as props
props: { post: {} },
};
}
export default function Post({ post }) {
// Render post...
}
- getStaticProps와 함께 사용해야 한다.
ISR
ISR을 사용하면 전체 사이트를 재구축할 필요 없이 페이지별로 정적 생성을 사용할 수 있다. ISR을 사용하면 정적의 이점을 유지하면서 수백만 페이지로 확장할 수 있다.
ISR을 사용하기 위해서는 getStaticProps
의 설정에 revalidate
설정을 추가한다.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{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 10 seconds
revalidate: 10, // In seconds
};
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
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: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: 'blocking' };
}
export default Blog;