Next.js는 서버 사이드 렌더링(SSR), 정적 사이트 생성(SSG), 점진적 정적 재생성(ISR) 기능을 지원하여 웹 애플리케이션의 성능과 SEO를 최적화할 수 있습니다. 이번 포스트에서는 Page Router와 App Router를 사용하여 각 기능을 구현하는 방법을 비교해 보겠습니다.
SSR은 사용자가 페이지를 요청할 때마다 서버에서 HTML을 생성하여 클라이언트에 전달하는 방식입니다.
// pages/posts/[id].js
export async function getServerSideProps(context) {
const { id } = context.params; // URL에서 ID 추출
const res = await fetch(`https://api.example.com/posts/${id}`);
if (!res.ok) {
return { notFound: true }; // 데이터가 없을 경우 404 페이지 반환
}
const post = await res.json();
return {
props: { post }, // 페이지 컴포넌트에 전달할 데이터
};
}
const PostPage = ({ post }) => {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
};
export default PostPage;
App Router를 사용하면 페이지 컴포넌트 내에서 fetch를 사용하여 간단하게 SSR을 구현할 수 있습니다.
// app/posts/[id]/page.js
const PostPage = async ({ params }) => {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
if (!res.ok) {
throw new Error('Failed to fetch data');
}
const post = await res.json();
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
};
export default PostPage;
SSG는 페이지를 빌드 시 미리 생성하여 정적으로 제공하는 방식입니다.
// pages/posts/[id].js
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map((post) => ({
params: { id: post.id.toString() }, // ID를 문자열로 변환
}));
return { paths, fallback: false }; // 미리 생성된 경로 외의 요청은 404 반환
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
if (!res.ok) {
return { notFound: true }; // 데이터가 없을 경우 404 페이지 반환
}
const post = await res.json();
return {
props: { post }, // 페이지 컴포넌트에 전달할 데이터
};
}
const PostPage = ({ post }) => {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
};
export default PostPage;
App Router를 사용하여 SSG를 구현하는 방법은 비슷하지만, generateStaticParams와 fetch를 사용하여 페이지를 설정합니다.
// app/posts/[id]/page.js
export async function generateStaticParams() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return posts.map((post) => ({
id: post.id.toString(), // ID를 문자열로 변환
}));
}
const PostPage = async ({ params }) => {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
if (!res.ok) {
throw new Error('Failed to fetch data');
}
const post = await res.json();
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
};
export default PostPage;
ISR은 SSG의 장점을 활용하면서 설정된 주기에 따라 페이지를 자동으로 재생성합니다.
// pages/posts/[id].js
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map((post) => ({
params: { id: post.id.toString() },
}));
return { paths, fallback: 'blocking' }; // 페이지가 생성되지 않은 경우 서버에서 데이터 생성
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
if (!res.ok) {
return { notFound: true };
}
const post = await res.json();
return {
props: { post },
revalidate: 10, // 10초마다 재생성
};
}
const PostPage = ({ post }) => {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
};
export default PostPage;
App Router에서 ISR을 적용하려면 fetch 요청에 next: { revalidate: seconds }를 추가합니다.
// app/posts/[id]/page.js
export async function generateStaticParams() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return posts.map((post) => ({
id: post.id.toString(),
}));
}
const PostPage = async ({ params }) => {
const res = await fetch(`https://api.example.com/posts/${params.id}`, {
next: { revalidate: 10 }, // 10초마다 재생성
});
if (!res.ok) {
throw new Error('Failed to fetch data');
}
const post = await res.json();
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
};
export default PostPage;
Next.js의 Page Router와 App Router는 각각의 방식으로 SSR, SSG, ISR을 구현할 수 있습니다. 두 라우터의 차이점을 이해하고 적절히 활용하여 성능과 SEO를 최적화할 수 있습니다.