개요

Next.js 15는 App Router를 정식 출시하며 React Server Components를 기반으로 한 새로운 렌더링 패러다임을 제공합니다. 이 가이드에서는 App Router의 핵심 개념부터 실전 활용법까지 상세히 다룹니다.

핵심 개념

App Router는 app 디렉토리를 기반으로 하며, 파일 시스템 기반 라우팅을 제공합니다. 주요 특징은 다음과 같습니다:

  • 서버 컴포넌트 우선: 기본적으로 모든 컴포넌트는 서버에서 렌더링됩니다.
  • 레이아웃 시스템: 중첩된 레이아웃으로 공통 UI를 재사용할 수 있습니다.
  • 스트리밍 SSR: Suspense 경계를 활용한 점진적 렌더링이 가능합니다.
  • 데이터 페칭: 서버 컴포넌트에서 직접 async/await를 사용할 수 있습니다.

파일 구조와 라우팅

App Router는 특별한 파일명 규칙을 따릅니다:

app/
  layout.tsx       # 루트 레이아웃
  page.tsx         # 홈페이지
  loading.tsx      # 로딩 UI
  error.tsx        # 에러 UI
  blog/
    page.tsx       # /blog 페이지
    [slug]/
      page.tsx     # /blog/:slug 동적 라우트
  (auth)/          # 라우트 그룹 (URL에 포함 안 됨)
    login/
      page.tsx     # /login

서버 컴포넌트 활용

서버 컴포넌트에서는 직접 데이터베이스에 접근할 수 있습니다:

// app/blog/page.tsx
import { db } from '@/lib/db';

export default async function BlogPage() {
  const posts = await db.post.findMany({
    orderBy: { createdAt: 'desc' },
    take: 10,
  });

  return (
    <div>
      <h1>블로그</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

클라이언트 컴포넌트와 상호작용

클라이언트 인터랙션이 필요한 경우 'use client' 지시자를 사용합니다:

'use client';

import { useState } from 'react';

export function LikeButton({ postId }: { postId: string }) {
  const [likes, setLikes] = useState(0);

  const handleLike = async () => {
    const res = await fetch(`/api/posts/${postId}/like`, {
      method: 'POST',
    });
    const data = await res.json();
    setLikes(data.likes);
  };

  return (
    <button onClick={handleLike}>
      좋아요 {likes}
    </button>
  );
}

데이터 페칭 패턴

Next.js 15는 향상된 캐싱 전략을 제공합니다:

// 기본: fetch 결과는 자동으로 캐시됨
const data = await fetch('https://api.example.com/posts');

// 재검증 주기 설정
const data = await fetch('https://api.example.com/posts', {
  next: { revalidate: 3600 } // 1시간마다 재검증
});

// 캐시 사용 안 함 (항상 최신 데이터)
const data = await fetch('https://api.example.com/posts', {
  cache: 'no-store'
});

스트리밍과 Suspense

대용량 데이터를 효율적으로 로드하기 위해 스트리밍을 활용합니다:

import { Suspense } from 'react';

async function Posts() {
  const posts = await fetchPosts(); // 느린 쿼리
  return <PostList posts={posts} />;
}

export default function Page() {
  return (
    <div>
      <h1>블로그</h1>
      <Suspense fallback={<PostsSkeleton />}>
        <Posts />
      </Suspense>
    </div>
  );
}

메타데이터 관리

SEO를 위한 메타데이터는 정적 또는 동적으로 생성할 수 있습니다:

// 정적 메타데이터
export const metadata = {
  title: '블로그',
  description: '기술 블로그입니다',
};

// 동적 메타데이터
export async function generateMetadata({ params }) {
  const post = await fetchPost(params.slug);
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      images: [post.coverImage],
    },
  };
}

활용 팁

  • 서버 컴포넌트에서 최대한 많은 로직을 처리하여 클라이언트 번들 크기를 최소화하세요.
  • 레이아웃을 활용하여 공통 UI와 상태를 효율적으로 관리하세요.
  • 병렬 라우트와 인터셉트 라우트를 사용하면 모달과 같은 고급 UI를 구현할 수 있습니다.
  • 서버 액션을 활용하면 API 라우트 없이도 폼 제출을 처리할 수 있습니다.
  • React.cache()를 사용하여 동일한 요청의 중복을 방지하세요.

마무리

Next.js 15 App Router는 React의 최신 기능을 최대한 활용하여 성능과 개발 경험을 모두 향상시켰습니다. 서버 컴포넌트와 스트리밍을 적절히 활용하면 빠르고 효율적인 웹 애플리케이션을 구축할 수 있습니다.