미니 프로젝트: 간단한 블로그 만들기
지금까지 배운 내용을 활용하여 간단한 블로그 애플리케이션을 만들어봅니다.
Next.js 기초 입문 29회차: 미니 프로젝트 – 간단한 블로그 만들기
이번 29회차에서는 Next.js 기초 입문 과정의 대미를 장식할 미니 프로젝트를 진행합니다. 지금까지 학습한 Next.js의 핵심 개념들을 총동원하여 간단한 블로그 애플리케이션을 직접 구축해볼 것입니다. 이 프로젝트를 통해 이론적 지식을 실질적인 개발 경험으로 전환하고, Next.js의 강력한 기능들을 통합적으로 활용하는 능력을 기를 수 있습니다.
📌 이번 회차 학습 목표
- Next.js의 페이지 라우팅 기능을 활용하여 블로그 게시물 목록 및 상세 페이지를 구현할 수 있습니다.
- 정적 생성(SSG) 또는 서버 사이드 렌더링(SSR)을 활용하여 블로그 게시물 데이터를 효율적으로 페칭하고 렌더링할 수 있습니다.
- 컴포넌트 기반 개발 원칙에 따라 재사용 가능한 UI 컴포넌트(예: 헤더, 푸터, 게시물 카드)를 설계하고 적용할 수 있습니다.
- Next.js 애플리케이션의 전체적인 구조를 이해하고, 실제 프로젝트에 적용하는 경험을 습득할 수 있습니다.
- 지금까지 배운 Next.js의 핵심 개념들을 통합적으로 응용하여 실제 웹 애플리케이션을 개발하는 실전 능력을 향상시킬 수 있습니다.
📝 개념 설명: 블로그 애플리케이션의 기본 구조
간단한 블로그 애플리케이션은 일반적으로 다음과 같은 핵심 요소들로 구성됩니다. Next.js를 사용하여 이러한 요소들을 어떻게 구현할 수 있는지 살펴보겠습니다.
블로그의 핵심 기능
- 게시물 목록 페이지 (Home): 모든 블로그 게시물의 제목, 간략한 설명, 작성일 등을 보여주는 페이지입니다. 사용자는 이 페이지에서 특정 게시물을 선택하여 상세 내용을 확인할 수 있습니다.
- 게시물 상세 페이지 (Post Detail): 특정 게시물의 전체 내용을 보여주는 페이지입니다. 게시물의 제목, 본문, 이미지, 작성자 정보 등이 포함될 수 있습니다.
- 데이터 관리: 블로그 게시물 데이터를 어디에 저장하고 어떻게 가져올 것인지 결정해야 합니다. 이 미니 프로젝트에서는 간단하게 로컬 JSON 파일을 데이터 소스로 활용할 것입니다.
Next.js를 활용한 구조 설계
Next.js는 파일 시스템 기반 라우팅을 제공하므로, 블로그의 각 페이지는 특정 파일 경로에 매핑됩니다.
pages/index.js: 블로그의 메인 페이지, 즉 게시물 목록을 표시하는 역할을 합니다.pages/posts/[id].js: 동적 라우팅을 사용하여 개별 게시물의 상세 페이지를 구현합니다.[id]는 각 게시물의 고유 식별자가 됩니다.components/: 재사용 가능한 UI 요소들을 모아두는 디렉토리입니다. 예를 들어,Header.js,Footer.js,PostCard.js등이 여기에 포함될 수 있습니다.data/: 블로그 게시물 데이터를 저장하는 디렉토리입니다.posts.json과 같은 파일을 생성하여 데이터를 관리합니다.
/)pages/index.js)pages/posts/[id].js)💡 예제 & 실습: 간단한 블로그 만들기
이제 단계별로 간단한 블로그 애플리케이션을 만들어보겠습니다.
1단계: Next.js 프로젝트 초기화
먼저 새로운 Next.js 프로젝트를 생성합니다.
npx create-next-app my-blog
cd my-blog
2단계: 블로그 데이터 준비
프로젝트 루트에 data 디렉토리를 생성하고, 그 안에 posts.json 파일을 만듭니다. 이 파일에 블로그 게시물 데이터를 JSON 형식으로 저장합니다.
// data/posts.json
[
{
"id": "1",
"title": "첫 번째 블로그 게시물",
"content": "이것은 첫 번째 게시물의 내용입니다. Next.js로 블로그를 만드는 것은 정말 재미있어요!",
"date": "2023-01-01"
},
{
"id": "2",
"title": "Next.js의 매력",
"content": "Next.js는 개발 생산성을 높여주는 강력한 프레임워크입니다. 서버 사이드 렌더링과 정적 생성을 쉽게 구현할 수 있죠.",
"date": "2023-01-15"
},
{
"id": "3",
"title": "블로그 프로젝트 시작하기",
"content": "미니 프로젝트를 통해 Next.js의 다양한 기능을 실습해봅시다. 작은 성공들이 모여 큰 성장을 이룹니다.",
"date": "2023-02-01"
}
]
3단계: 게시물 목록 페이지 (pages/index.js) 구현
pages/index.js 파일을 수정하여 모든 게시물의 목록을 표시하도록 합니다. 여기서는 정적 생성 (Static Site Generation, SSG)을 사용하여 빌드 시점에 데이터를 가져오도록 구현합니다.
// pages/index.js
import Link from 'next/link';
import Head from 'next/head';
import fs from 'fs';
import path from 'path';
export async function getStaticProps() {
const postsDirectory = path.join(process.cwd(), 'data');
const filePath = path.join(postsDirectory, 'posts.json');
const fileContents = fs.readFileSync(filePath, 'utf8');
const posts = JSON.parse(fileContents);
return {
props: {
posts,
},
};
}
export default function Home({ posts }) {
return (
<div>
<Head>
<title>나의 Next.js 블로그</title>
<meta name='description' content='Next.js로 만든 간단한 블로그입니다.' />
</Head>
<h1>블로그 게시물 목록</h1&n>
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/posts/${post.id}`}>
<a>
<h2>{post.title}</h2>
<p>{post.date}</p>
</a>
</Link>
</li>
))}
</ul>
</div>
);
}
getStaticProps함수를 사용하여 빌드 시점에data/posts.json파일에서 게시물 데이터를 읽어옵니다.- 읽어온
posts데이터는Home컴포넌트의props로 전달됩니다. posts.map을 사용하여 각 게시물의 제목과 날짜를 렌더링하고,Link컴포넌트를 통해 상세 페이지로 이동할 수 있도록 합니다.
4단계: 게시물 상세 페이지 (pages/posts/[id].js) 구현
pages/posts 디렉토리를 생성하고 그 안에 [id].js 파일을 만듭니다. 이 파일은 동적 라우팅을 처리하여 각 게시물의 상세 내용을 보여줍니다.
// pages/posts/[id].js
import { useRouter } from 'next/router';
import Head from 'next/head';
import fs from 'fs';
import path from 'path';
export async function getStaticPaths() {
const postsDirectory = path.join(process.cwd(), 'data');
const filePath = path.join(postsDirectory, 'posts.json');
const fileContents = fs.readFileSync(filePath, 'utf8');
const posts = JSON.parse(fileContents);
const paths = posts.map((post) => ({
params: { id: post.id },
}));
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
const postsDirectory = path.join(process.cwd(), 'data');
const filePath = path.join(postsDirectory, 'posts.json');
const fileContents = fs.readFileSync(filePath, 'utf8');
const posts = JSON.parse(fileContents);
const post = posts.find((p) => p.id === params.id);
return {
props: {
post,
},
};
}
export default function PostDetail({ post }) {
const router = useRouter();
if (router.isFallback) {
return <div>로딩 중...</div>;
}
if (!post) {
return <div>게시물을 찾을 수 없습니다.</div>;
}
return (
<div>
<Head>
<title>{post.title} - 나의 Next.js 블로그</title>
</Head>
<h1>{post.title}</h1>
<p>작성일: {post.date}</p>
<div>{post.content}</div>
</div>
);
}
```
코드 해설:
getStaticPaths 함수는 Next.js에게 어떤 id 값들이 미리 정적 생성되어야 하는지 알려줍니다. 여기서는 posts.json의 모든 게시물 id를 기반으로 경로를 생성합니다. fallback: false는 정의되지 않은 경로로 접근 시 404 페이지를 보여주도록 합니다.
getStaticProps 함수는 각 경로(params.id)에 해당하는 게시물 데이터를 가져와 PostDetail 컴포넌트의 props로 전달합니다.
PostDetail 컴포넌트는 전달받은 post 데이터를 사용하여 게시물의 상세 내용을 렌더링합니다.
5단계: 컴포넌트 추가 (선택 사항)
블로그에 헤더나 푸터 같은 공통 컴포넌트를 추가하여 재사용성을 높일 수 있습니다. components 디렉토리를 생성하고 Header.js 파일을 만듭니다.
// components/Header.js
import Link from 'next/link';
export default function Header() {
return (
<header style={{ padding: '20px', borderBottom: '1px solid #eee' }}>
<nav>
<Link href='/'>
<a style={{ marginRight: '15px', textDecoration: 'none', color: 'blue' }}>홈</a>
</Link>
<Link href='/about'>
<a style={{ textDecoration: 'none', color: 'blue' }}>소개</a>
</Link>
</nav>
</header>
);
}
그리고 pages/_app.js 파일을 수정하여 모든 페이지에 헤더를 적용할 수 있습니다.
// pages/_app.js
import '../styles/globals.css';
import Header from '../components/Header';
function MyApp({ Component, pageProps }) {
return (
<div>
<Header />
<main style={{ padding: '20px' }}>
<Component {...pageProps} />
</main>
</div>
);
}
export default MyApp;
6단계: 애플리케이션 실행
이제 개발 서버를 실행하여 블로그를 확인해볼 수 있습니다.
npm run dev
브라우저에서 http://localhost:3000으로 접속하여 블로그 게시물 목록을 확인하고, 각 게시물을 클릭하여 상세 페이지로 이동해보세요.
📌 핵심 포인트: 데이터 페칭 전략
getStaticProps빌드 시점에 데이터를 미리 가져와 HTML을 생성합니다. SEO에 유리하고 CDN 캐싱이 가능하여 성능이 우수합니다.
getStaticPaths동적 경로([id].js)를 가진 페이지에서, 어떤 경로들을 미리 정적 생성할지 정의합니다.
getServerSideProps요청 시마다 서버에서 데이터를 가져와 페이지를 렌더링합니다. 실시간 데이터가 필요한 경우에 적합합니다. (이번 프로젝트에서는 사용하지 않음)
⚠️ 자주 틀리는 것 / 주의사항
- 파일 경로 오류:
fs 모듈을 사용하여 파일을 읽을 때는 절대 경로를 사용하는 것이 중요합니다. process.cwd()와 path.join()을 활용하여 정확한 경로를 구성해야 합니다. 상대 경로는 개발 환경과 빌드 환경에서 다르게 동작할 수 있습니다.
getStaticPaths의 fallback 옵션 이해 부족: fallback: false는 getStaticPaths에 의해 정의되지 않은 경로로 접근 시 404 페이지를 반환합니다. fallback: true 또는 'blocking'은 새로운 경로가 요청될 때 동적으로 페이지를 생성할 수 있도록 하지만, 초기 로딩 시 '로딩 중...' 상태를 처리해야 합니다. 이 미니 프로젝트에서는 모든 게시물이 미리 알려져 있으므로 false가 적합합니다.
- 데이터 직렬화 문제:
getStaticProps나 getServerSideProps에서 반환되는 props 객체는 직렬화 가능한(serializable) 객체여야 합니다. 즉, 함수, Date 객체 등은 직접 전달할 수 없습니다. JSON으로 변환 가능한 형태여야 합니다.
- 클라이언트 사이드 데이터 페칭 오용: 정적 생성이나 서버 사이드 렌더링이 가능한 데이터를 클라이언트 사이드에서만 페칭하면 초기 로딩 속도나 SEO에 불리할 수 있습니다. 각 데이터 페칭 전략의 장단점을 이해하고 적절히 활용해야 합니다.
🔗 다음 회차 예고
이번 미니 프로젝트를 통해 Next.js의 핵심 기능들을 실전적으로 적용해보는 귀중한 경험을 하셨을 것입니다. 다음 30회차이자 마지막 회차에서는 'Next.js 배포 및 최적화'에 대해 학습합니다. 개발한 블로그 애플리케이션을 Vercel과 같은 플랫폼에 배포하고, 성능 최적화를 위한 다양한 기법들을 살펴보면서 Next.js 기초 입문 과정을 마무리할 예정입니다. 실제 서비스 가능한 애플리케이션을 만드는 마지막 단계를 함께 학습해봅시다.
✅ 이번 회차 핵심 정리
- Next.js를 활용하여 간단한 블로그 애플리케이션의 게시물 목록과 상세 페이지를 구현했습니다.
- 로컬
JSON 파일을 데이터 소스로 활용하여 블로그 게시물 데이터를 관리하는 방법을 익혔습니다.
getStaticProps와 getStaticPaths를 사용하여 정적 생성(SSG) 방식으로 데이터를 페칭하고 페이지를 렌더링하는 과정을 실습했습니다.
- 동적 라우팅(
pages/posts/[id].js)을 통해 개별 게시물 페이지를 효과적으로 생성하는 방법을 이해했습니다.
- 재사용 가능한 컴포넌트(예:
Header)를 생성하고 _app.js를 통해 전역적으로 적용하는 방법을 알아보았습니다.