icon안동민 개발노트

단계별 구현 가이드


 Next.js App Router를 사용한 실전 프로젝트의 단계별 구현 과정을 살펴보겠습니다. 각 단계에서의 주요 고려사항과 효율적인 개발 워크플로우에 중점을 두고 설명하겠습니다.

1. 프로젝트 초기 설정

  • Next.js 프로젝트 생성 : npx create-next-app@latest
  • 필요한 의존성 설치
  • ESLint 및 Prettier 설정

 고려사항

  • App Router 사용 설정 확인
  • 적절한 폴더 구조 설정 (app/, components/, lib/ 등)

 문제점 및 해결방안

  • 버전 호환성 문제 : package.json에서 의존성 버전 명시
// package.json
{
  "dependencies": {
    "next": "^13.4.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

2. 컴포넌트 구현

  • 공통 레이아웃 컴포넌트 구현 (app/layout.js)
  • 페이지별 컴포넌트 구현 (app/page.js, app/products/page.js 등)
  • 재사용 가능한 UI 컴포넌트 구현

 고려사항

  • 서버 컴포넌트와 클라이언트 컴포넌트의 적절한 분리
  • 성능을 고려한 컴포넌트 설계

 문제점 및 해결방안

  • 서버 / 클라이언트 컴포넌트 혼동: 명확한 네이밍 규칙 적용 (예 : ClientComponent.js)
// app/components/ClientComponent.js
'use client';
 
import { useState } from 'react';
 
export default function ClientComponent() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

3. 라우팅 설정

  • 동적 라우트 구현 (app/products/[id]/page.js)
  • 중첩 라우팅 구현
  • 라우트 그룹 사용

 고려사항

  • SEO를 위한 적절한 라우트 구조
  • 로딩 및 에러 상태 처리 (loading.js, error.js)

 문제점 및 해결방안

  • 복잡한 라우팅 로직 : 라우트 그룹과 동적 세그먼트를 효과적으로 활용
// app/products/[id]/page.js
export default function ProductPage({ params }) {
  return <div>Product ID: {params.id}</div>;
}

4. 상태 관리 통합

  • 서버 컴포넌트를 위한 상태 관리 전략
  • 클라이언트 컴포넌트를 위한 상태 관리 솔루션 선택 (예 : React Context, Zustand)

 고려사항

  • 서버 컴포넌트의 제약 사항 고려
  • 성능 최적화를 위한 상태 분리

 문제점 및 해결방안

  • 서버 컴포넌트에서 클라이언트 상태 접근 : 상태 초기화 전략 사용
// app/components/StateWrapper.js
'use client';
 
import { createContext, useContext, useState } from 'react';
 
const StateContext = createContext();
 
export function StateWrapper({ children, initialState }) {
  const [state, setState] = useState(initialState);
  return (
    <StateContext.Provider value={{ state, setState }}>
      {children}
    </StateContext.Provider>
  );
}
 
export function useAppState() {
  return useContext(StateContext);
}

5. API 연동

  • API 라우트 구현 (app/api/[...]/route.js)
  • 서버 컴포넌트에서의 데이터 페칭
  • 클라이언트 컴포넌트에서의 API 호출

 고려사항

  • 효율적인 데이터 페칭 전략 (캐싱, 재검증 등)
  • 에러 처리 및 로딩 상태 관리

 문제점 및 해결방안

  • 과도한 API 호출 : 적절한 캐싱 전략 적용
// app/api/products/route.js
import { NextResponse } from 'next/server';
 
export async function GET() {
  // 데이터베이스 쿼리 로직
  const products = [{ id: 1, name: 'Product 1' }];
  return NextResponse.json(products);
}

6. 인증 구현

  • Next.js의 미들웨어를 활용한 인증 처리
  • 세션 관리 및 보호된 라우트 구현

 고려사항

  • 보안 best practices 적용
  • 서버 사이드와 클라이언트 사이드 인증 처리

 문제점 및 해결방안

  • 세션 관리 복잡성 : NextAuth.js 활용
// middleware.js
import { NextResponse } from 'next/server';
 
export function middleware(request) {
  const token = request.cookies.get('token');
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
}

7. 테스팅 및 최적화

  • 단위 테스트 및 통합 테스트 작성
  • 성능 최적화 (이미지 최적화, 코드 분할 등)
  • 접근성 및 SEO 최적화

 고려사항

  • 서버 컴포넌트와 클라이언트 컴포넌트 각각에 대한 테스트 전략
  • Lighthouse 점수 개선

 문제점 및 해결방안

  • 복잡한 테스트 환경 : 적절한 모킹 및 테스트 유틸리티 사용
// __tests__/Home.test.js
import { render, screen } from '@testing-library/react';
import Home from '../app/page';
 
describe('Home', () => {
  it('renders a heading', () => {
    render(<Home />);
    const heading = screen.getByRole('heading', { name: /welcome/i });
    expect(heading).toBeInTheDocument();
  });
});

 이 구현 과정은 자습서의 다양한 장들과 밀접하게 연결됩니다. 예를 들어, 라우팅 설정은 4장의 내용을, API 연동은 6장과 11장의 내용을, 인증 구현은 10장의 내용을 적용합니다. 상태 관리는 8장의 개념을 확장하여 App Router에 맞게 적용하며, 테스팅 및 최적화는 12장, 13장, 14장의 내용을 종합적으로 활용합니다.

실습 : e-commerce 플랫폼의 상품 목록 및 상세 페이지 구현

 17.1에서 설계한 e-commerce 플랫폼의 상품 목록 및 상세 페이지를 구현해보겠습니다.

  1. 상품 목록 페이지 구현 (app/products/page.js):
import ProductList from '../../components/ProductList';
 
async function getProducts() {
  const res = await fetch('https://api.example.com/products');
  return res.json();
}
 
export default async function ProductsPage() {
  const products = await getProducts();
 
  return (
    <div>
      <h1>Our Products</h1>
      <ProductList products={products} />
    </div>
  );
}
  1. 상품 목록 컴포넌트 구현 (components/ProductList.js):
import Link from 'next/link';
 
export default function ProductList({ products }) {
  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>
          <Link href={`/products/${product.id}`}>
            <h2>{product.name}</h2>
            <p>{product.price}</p>
          </Link>
        </li>
      ))}
    </ul>
  );
}
  1. 상품 상세 페이지 구현 (app/products/[id]/page.js):
import { notFound } from 'next/navigation';
 
async function getProduct(id) {
  const res = await fetch(`https://api.example.com/products/${id}`);
  if (!res.ok) return undefined;
  return res.json();
}
 
export default async function ProductPage({ params }) {
  const product = await getProduct(params.id);
 
  if (!product) {
    notFound();
  }
 
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>Price: {product.price}</p>
    </div>
  );
}
  1. API 라우트 구현 (app/api/products/route.js):
import { NextResponse } from 'next/server';
 
const products = [
  { id: 1, name: 'Product 1', price: 10 },
  { id: 2, name: 'Product 2', price: 20 },
];
 
export async function GET() {
  return NextResponse.json(products);
}

 이 실습을 통해 Next.js App Router를 사용한 e-commerce 플랫폼의 핵심 기능을 구현하는 과정을 경험할 수 있습니다. 서버 컴포넌트와 클라이언트 컴포넌트의 적절한 사용, 데이터 페칭, 동적 라우팅 등 다양한 개념을 실제로 적용해볼 수 있습니다.

 Next.js App Router를 사용한 프로젝트 구현은 기존의 Pages Router와는 다른 접근 방식을 요구합니다. 서버 컴포넌트와 클라이언트 컴포넌트의 적절한 분리, 새로운 라우팅 시스템의 활용, 그리고 데이터 페칭 전략의 변화 등을 고려해야 합니다. 이러한 새로운 패러다임을 이해하고 효과적으로 활용함으로써, 더욱 성능이 뛰어나고 유지보수가 용이한 웹 애플리케이션을 개발할 수 있습니다.