본문으로 건너뛰기

페이지

넥스트에서 페이지는 pages 디렉터리의 .js, .jsx, .ts, .tsx 파일에서 내보낸 리액트 컴포넌트입니다. 각 페이지는 파일 이름을 기반으로 하는 경로와 연결됩니다.

예시로 다음와 같이 리액트 컴포넌트를 내보내는 pages/about.js을 생성하면 /about에서 접근이 가능해집니다.

jsx
function About() {
return <div>About</div>;
}
export default About;
jsx
function About() {
return <div>About</div>;
}
export default About;

동적 경로가 있는 페이지​

넥스트는 동적 경로가 있는 페이지를 지원합니다.

예를 들어 pages/posts/[id].js라는 파일을 생성하면 posts/1, posts/2에서 접근이 가능해집니다.

참고

동적 라우팅에 대해 자세히 알아보려면 동적 라우팅 문서를 확인하세요.

사전 렌더링​

기본적으로 넥스트는 모든 페이지를 미리 렌더링합니다. 넥스트는 클라이언트 측 자바스크립트에서 모든 작업을 수행하는 대신 각 페이지에 대해 미리 HTML을 생성합니다. 사전 렌더링은 더 나은 성능과 SEO를 가집니다.

생성된 각 HTML은 해당 페이지에 필요한 최소한의 자바스크립트 코드와 연결됩니다. 브라우저에서 페이지를 로드하면 해당 자바스크립트 코드가 실행되어 페이지를 완전한 대화형으로 만듭니다. 이 과정을 수화(hydration)라고 부릅니다.

두 가지 방식의 사전 렌더링​

넥스트에는 정적 생성과 SSR이라는 두 가지 방식의 사전 렌더링이 있습니다. 둘의 차이점은 페이지에 대한 HTML을 생성하는 시기입니다.

  • 정적 생성(권장) - 빌드 타임에서 HTML이 생성되며 각 요청에서 재사용됨
  • SSR - 각 요청마다 HTML이 생성됨

넥스트를 사용하면 각 페이지에 사용할 사전 렌더링 방식을 선택할 수 있습니다. 대부분의 페이지에는 정적 생성을 사용하고 일부 페이지에는 SSR을 사용하여 하이브리드 넥스트 앱을 만들 수 있습니다.

성능상의 이유로 SSR보다 정적 생성을 사용하는 것이 좋습니다. 정적으로 생성된 페이지는 성능 향상을 위한 추가 설정 없이 CDN에서 캐시가 가능합니다. 그러나 경우에 따라 SSR이 유일한 옵션일 수 있습니다.

CSR을 정적 생성, SSR과 함께 사용하는 것도 가능합니다. 페이지의 일부는 클라이언트 측 자바스크립트로 완전히 렌더링될 수 있습니다. 자세한 내용은 데이터 가져오기 문서를 참조하세요.

정적 생성​

페이지가 정적 생성을 사용하는 경우에는 페이지의 HTML이 빌드 타임에 생성됩니다. 즉, 프로덕션에서 next build를 실행할 때 페이지 HTML이 생성됩니다. 이 HTML은 각 요청에서 재사용되며 CDN에 의해 캐시될 수 있습니다.

넥스트에서는 데이터가 있는 페이지나 없는 페이지를 정적으로 생성할 수 있습니다. 각각의 경우를 살펴보겠습니다.

데이터가 없는 정적 생성​

기본적으로 넥스트는 데이터를 가져오지 않는 정적 생성을 사용하여 페이지를 미리 렌더링합니다.

예시:

jsx
function About() {
return <div>About</div>;
}
export default About;
jsx
function About() {
return <div>About</div>;
}
export default About;

이 페이지는 미리 렌더링할 외부 데이터를 가져올 필요가 없습니다. 이와 같은 경우, 넥스트는 빌드 타임에 페이지당 하나의 HTML 파일을 생성합니다.

데이터가 있는 정적 생성​

일부 페이지는 사전 렌더링을 위해 외부 데이터를 가져와야 합니다. 두 가지 시나리오가 있으며 하나만 적용하거나 둘 다 적용할 수 있습니다. 각각의 경우에 넥스트가 제공하는 다음 기능을 사용할 수 있습니다.

  1. 페이지 콘텐츠가 외부 데이터에 의존 - getStaticProps을 사용
  2. 페이지 경로가 외부 데이터에 의존 - getStaticPaths을 사용(보통 getStaticProps에 추가로 사용)

시나리오 1: 페이지 콘텐츠가 외부 데이터에 의존​

예시로 블로그 페이지는 CMS(콘텐츠 관리 시스템)에서 블로그 게시물 목록을 가져와야 할 수 있습니다.

jsx
// TODO: 이 페이지를 사전 렌더링하려면 먼저 어떤 API 엔드포인트를 호출해서
// posts을 가져와야 합니다.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
);
}
export default Blog;
jsx
// TODO: 이 페이지를 사전 렌더링하려면 먼저 어떤 API 엔드포인트를 호출해서
// posts을 가져와야 합니다.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
);
}
export default Blog;

사전 렌더링에서 해당 데이터를 가져오기 위해, 넥스트는 동일한 파일에서 getStaticProps라는 async 함수를 export하는 것을 허용합니다. 이 함수는 빌드 타임에 호출되며 가져온 데이터를 사전 렌더링 시 페이지의 props에 전달합니다.

jsx
function Blog({ posts }) {
// posts를 렌더링합니다...
}
// 이 함수는 빌드 타임에 호출됩니다.
export async function getStaticProps() {
// posts를 가져오기 위해 외부 API 엔드포인트를 호출합니다.
const res = await fetch('https://.../posts');
const posts = await res.json();
// { props: { posts } }를 반환하면
// Blog 컴포넌트는 빌드 타임에 posts를 프롭으로 받습니다.
return {
props: {
posts,
},
};
}
export default Blog;
jsx
function Blog({ posts }) {
// posts를 렌더링합니다...
}
// 이 함수는 빌드 타임에 호출됩니다.
export async function getStaticProps() {
// posts를 가져오기 위해 외부 API 엔드포인트를 호출합니다.
const res = await fetch('https://.../posts');
const posts = await res.json();
// { props: { posts } }를 반환하면
// Blog 컴포넌트는 빌드 타임에 posts를 프롭으로 받습니다.
return {
props: {
posts,
},
};
}
export default Blog;

getStaticProps의 작동 방식에 대해 자세히 알아보려면 데이터 가져오기 문서를 확인하세요.

시나리오 2: 페이지 경로가 외부 데이터에 의존​

넥스트를 사용하면 동적 경로를 가진 페이지를 만들 수 있습니다. 예를 들어 id에 기반한 단일 블로그 게시물을 표시하기 위해 pages/posts/[id].js라는 파일을 생성할 수 있습니다. 이렇게 하면 posts/1에 접근할 때 블로그 포스트를 id: 1로 표시할 수 있습니다.

그러나 빌드 타임에 어떤 id를 사전 렌더링할지가 외부 데이터에 의존하는 경우가 있을 수 있습니다.

예시​

데이터베이스에 하나의 블로그 게시물(id: 1)만 추가했다고 가정해 보겠습니다. 이 경우에는 빌드 타임에 posts/1만 사전 렌더링하기를 원할 것입니다.

나중에 두 번째 게시물을 id: 2로 추가할 수 있습니다. 그러면 posts/2도 사전 렌더링하기를 원할 것입니다.

따라서 사전 렌더링되는 페이지 경로는 외부 데이터에 따라 달라집니다. 넥스트에서는 이를 처리하기 위해, 동적 페이지(pages/posts/[id].js)에서 getStaticPaths라는 async 함수를 export할 수 있습니다. 이 함수는 빌드 타임에 호출되며 사전 렌더링할 경로를 지정할 수 있습니다.

jsx
// 이 함수는 빌드 타임에 호출됩니다.
export async function getStaticPaths() {
// posts를 가져오기 위해 외부 API 엔드포인트를 호출합니다.
const res = await fetch('https://.../posts');
const posts = await res.json();
// 게시물에 기반해 사전 렌더링하고 싶은 경로를 구합니다.
const paths = posts.map((post) => ({
params: { id: post.id },
}));
// 빌드 타임에 이 경로를 사전 렌더링합니다.
// { fallback: false }는 paths 이외의 경로가 404가 된다는 것을 의미합니다.
return { paths, fallback: false };
}
jsx
// 이 함수는 빌드 타임에 호출됩니다.
export async function getStaticPaths() {
// posts를 가져오기 위해 외부 API 엔드포인트를 호출합니다.
const res = await fetch('https://.../posts');
const posts = await res.json();
// 게시물에 기반해 사전 렌더링하고 싶은 경로를 구합니다.
const paths = posts.map((post) => ({
params: { id: post.id },
}));
// 빌드 타임에 이 경로를 사전 렌더링합니다.
// { fallback: false }는 paths 이외의 경로가 404가 된다는 것을 의미합니다.
return { paths, fallback: false };
}

또한 pages/posts/[id].js에서는 해당 id로 포스트의 데이터를 가져와 페이지를 사전 렌더링할 수 있도록 getStaticProps를 내보내야 합니다.

jsx
function Post({ post }) {
// post를 렌더링합니다...
}
export async function getStaticPaths() {
// ...
}
// 이 함수는 빌드 타임에 호출됩니다.
export async function getStaticProps({ params }) {
// params에는 포스트의 id가 들어 있습니다.
// 경로가 '/posts/1'과 같다면 arams.id는 1입니다.
const res = await fetch(`https://.../posts/${params.id}`);
const post = await res.json();
// post 데이터를 페이지에 프롭으로 전달합니다.
return { props: { post } };
}
export default Post;
jsx
function Post({ post }) {
// post를 렌더링합니다...
}
export async function getStaticPaths() {
// ...
}
// 이 함수는 빌드 타임에 호출됩니다.
export async function getStaticProps({ params }) {
// params에는 포스트의 id가 들어 있습니다.
// 경로가 '/posts/1'과 같다면 arams.id는 1입니다.
const res = await fetch(`https://.../posts/${params.id}`);
const post = await res.json();
// post 데이터를 페이지에 프롭으로 전달합니다.
return { props: { post } };
}
export default Post;

getStaticPaths의 작동 방식에 대해 자세히 알아보려면 데이터 가져오기 문서를 확인하세요.

참고

정적 생성을 언제 사용해야 할지 궁금하다면 사전 렌더링과 데이터 가져오기를 확인하세요.

SSR​

동적 렌더링으로도 부릅니다.

페이지가 SSR을 사용하는 경우에는 페이지 HTML이 각 요청마다 생성됩니다.

페이지에 SSR을 사용하려면 getServerSideProps라는 async 함수를 export해야 합니다. 이 함수는 매 요청마다 서버에서 호출됩니다.

예를 들어 페이지에서 자주 갱신되는 데이터(외부 API에서 가져옴)를 미리 렌더링해야 한다고 가정해 보겠습니다. 해당 데이터를 가져와 Page에게 전달하는 getServerSideProps을 다음과 같이 작성할 수 있습니다.

jsx
function Page({ data }) {
// data를 렌더링합니다...
}
// 이 함수는 매 요청마다 호출됩니다.
export async function getServerSideProps() {
// 외부 API에서 data를 가져옵니다.
const res = await fetch(`https://.../data`);
const data = await res.json();
// data를 페이지에 프롭으로 전달합니다.
return { props: { data } };
}
export default Page;
jsx
function Page({ data }) {
// data를 렌더링합니다...
}
// 이 함수는 매 요청마다 호출됩니다.
export async function getServerSideProps() {
// 외부 API에서 data를 가져옵니다.
const res = await fetch(`https://.../data`);
const data = await res.json();
// data를 페이지에 프롭으로 전달합니다.
return { props: { data } };
}
export default Page;

보시다시피 getServerSideProps는 getStaticProps와 유사합니다. 둘의 차이점은 getServerSideProps가 빌드 타임이 아니라 모든 요청마다 실행된다는 점입니다.

getServerSideProps의 작동 방식에 대해 자세히 알아보려면 데이터 가져오기 문서를 확인하세요.

요약​

넥스트의 두 가지 사전 렌더링에 대해 알아보았습니다.

  • 정적 생성(권장) - HTML은 빌드 타임에 생성되며 각 요청에서 재사용됩니다. 페이지에서 정적 생성을 사용하려면 페이지 컴포넌트를 내보내거나 getStaticProps(필요한 경우 getStaticPaths도)를 내보내면 됩니다. 이 방식은 사용자의 요청에 앞서 미리 렌더링할 수 있는 페이지에 적합합니다. CSR과 함께 사용하여 추가 데이터를 가져오는 것도 가능합니다.
  • SSR - HTML은 각 요청마다 생성됩니다. 페이지에서 SSR을 사용하려면 getServerSideProps을 내보냅니다. SSR은 정적 생성보다 성능이 떨어지므로 반드시 필요한 경우에만 사용합니다.