Next.js 기초 입문 29회차: 미니 프로젝트 – 간단한 블로그 만들기

📚
제29주차 학습 목표

미니 프로젝트: 간단한 블로그 만들기

지금까지 배운 내용을 활용하여 간단한 블로그 애플리케이션을 만들어봅니다.

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()을 활용하여 정확한 경로를 구성해야 합니다. 상대 경로는 개발 환경과 빌드 환경에서 다르게 동작할 수 있습니다.
  • getStaticPathsfallback 옵션 이해 부족: fallback: falsegetStaticPaths에 의해 정의되지 않은 경로로 접근 시 404 페이지를 반환합니다. fallback: true 또는 'blocking'은 새로운 경로가 요청될 때 동적으로 페이지를 생성할 수 있도록 하지만, 초기 로딩 시 '로딩 중...' 상태를 처리해야 합니다. 이 미니 프로젝트에서는 모든 게시물이 미리 알려져 있으므로 false가 적합합니다.
  • 데이터 직렬화 문제: getStaticPropsgetServerSideProps에서 반환되는 props 객체는 직렬화 가능한(serializable) 객체여야 합니다. 즉, 함수, Date 객체 등은 직접 전달할 수 없습니다. JSON으로 변환 가능한 형태여야 합니다.
  • 클라이언트 사이드 데이터 페칭 오용: 정적 생성이나 서버 사이드 렌더링이 가능한 데이터를 클라이언트 사이드에서만 페칭하면 초기 로딩 속도나 SEO에 불리할 수 있습니다. 각 데이터 페칭 전략의 장단점을 이해하고 적절히 활용해야 합니다.

🔗 다음 회차 예고

이번 미니 프로젝트를 통해 Next.js의 핵심 기능들을 실전적으로 적용해보는 귀중한 경험을 하셨을 것입니다. 다음 30회차이자 마지막 회차에서는 'Next.js 배포 및 최적화'에 대해 학습합니다. 개발한 블로그 애플리케이션을 Vercel과 같은 플랫폼에 배포하고, 성능 최적화를 위한 다양한 기법들을 살펴보면서 Next.js 기초 입문 과정을 마무리할 예정입니다. 실제 서비스 가능한 애플리케이션을 만드는 마지막 단계를 함께 학습해봅시다.

✅ 이번 회차 핵심 정리
  • Next.js를 활용하여 간단한 블로그 애플리케이션의 게시물 목록상세 페이지를 구현했습니다.
  • 로컬 JSON 파일을 데이터 소스로 활용하여 블로그 게시물 데이터를 관리하는 방법을 익혔습니다.
  • getStaticPropsgetStaticPaths를 사용하여 정적 생성(SSG) 방식으로 데이터를 페칭하고 페이지를 렌더링하는 과정을 실습했습니다.
  • 동적 라우팅(pages/posts/[id].js)을 통해 개별 게시물 페이지를 효과적으로 생성하는 방법을 이해했습니다.
  • 재사용 가능한 컴포넌트(예: Header)를 생성하고 _app.js를 통해 전역적으로 적용하는 방법을 알아보았습니다.

댓글 남기기

Wordpress Social Share Plugin powered by Ultimatelysocial
Copy link
URL has been copied successfully!
THREADS
RSS
error: 저작권 콘텐츠보호를 부탁드립니다.