Featured image of post Next.js 앱 라우터 기본 사항 및 마이그레이션 팁Featured image of post Next.js 앱 라우터 기본 사항 및 마이그레이션 팁

Next.js 앱 라우터 기본 사항 및 마이그레이션 팁

마이그레이션을 계획하는 개발자를 위한 라우팅 설계 개념을 다루는 Next.js 앱 라우터 기본 가이드입니다.

Next.js 13에 도입된 앱 라우터는 기존 페이지 라우터와 비교하여 라우팅 및 렌더링에 근본적으로 다른 접근 방식을 취합니다. 이제 안정적이고 널리 채택되었으므로 마이그레이션을 계획하는 사람들을 위해 기본 사항을 자세히 살펴보겠습니다.

앱 라우터와 페이지 라우터

기능페이지 라우터앱 라우터
라우팅pages/ 아래app/에서
구성 요소기본적으로 클라이언트 구성요소서버 구성요소 기본적으로
레이아웃수동 구현중첩 가능 layout.js
로딩수동 구현loading.js을 통해 자동
데이터 가져오기getServerSideProps구성 요소 내부의 직접 async

1. 파일 기반 라우팅

app/ 내부의 폴더 구조는 URL 경로에 직접 매핑됩니다.

app/
├── page.js          → /
├── layout.js        → Shared layout for all pages
├── about/
│   └── page.js      → /about
└── blog/
    ├── page.js      → /blog
    └── [slug]/
        └── page.js  → /blog/hello-world (dynamic route)
// app/blog/[slug]/page.js
export default async function BlogPost({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);
  return <article>{post.title}</article>;
}

2. 레이아웃 시스템

layout.js은 해당 폴더 아래의 모든 페이지에 자동으로 적용됩니다. 레이아웃은 중첩될 수 있습니다.

// app/layout.js (root layout)
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <header>Site Header</header>
        {children}
        <footer>Site Footer</footer>
      </body>
    </html>
  );
}
// app/blog/layout.js (blog section layout)
export default function BlogLayout({ children }) {
  return (
    <div className="blog-wrapper">
      <aside>Sidebar</aside>
      <main>{children}</main>
    </div>
  );
}

3. 로딩 및 오류 상태

loading.jserror.js을 폴더에 놓으면 로딩 및 오류 UI가 자동으로 표시됩니다.

// app/blog/loading.js
export default function Loading() {
  return <div>Loading...</div>;
}
// app/blog/error.js
'use client';
export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

4. 서버 및 클라이언트 구성 요소

앱 라우터에서는 서버 구성 요소가 기본값입니다. 이것이 핵심 변경 사항입니다.

서버 구성요소클라이언트 구성요소
렌더링 대상서버브라우저
useState / useEffect❌ 이용 불가✅ 이용 가능
직접 DB 접속✅ 가능❌ 불가능
번들 크기작음(JS가 전송되지 않음)대형
상호작용❌ 불가능✅ 가능
// Server Component (default) — can access DB directly
export default async function PostList() {
  const posts = await db.query('SELECT * FROM posts');
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

경험 법칙: 기본적으로 서버 구성 요소를 작성하고 가장 작은 대화형 부분에만 클라이언트 구성 요소를 사용합니다.

5. 데이터 가져오기 패턴

앱 라우터를 사용하면 구성요소 내에서 직접 fetch()을 호출할 수 있습니다. 캐싱은 기본적으로 켜져 있습니다.

// Cached (default)
const data = await fetch('https://api.example.com/posts');

// No cache (fresh every request)
const data = await fetch('https://api.example.com/posts', {
  cache: 'no-store',
});

// Time-based revalidation
const data = await fetch('https://api.example.com/posts', {
  next: { revalidate: 60 },
});

6. 경로 그룹

URL에 영향을 주지 않고 파일을 정리하려면 ( )에 폴더 이름을 래핑하세요.

app/
├── (marketing)/
│   ├── page.js      → /
│   └── pricing/
│       └── page.js  → /pricing
└── (shop)/
    ├── layout.js    → Shop-specific layout
    └── products/
        └── page.js  → /products

URL 접두사가 없는 섹션 전체에서 다양한 레이아웃을 사용하는 데 적합합니다.

요약

App Router는 Server Components를 중심으로 한 새로운 아키텍처로, Pages Router와는 그 철학이 크게 다릅니다. 처음에는 혼란스러울 수도 있지만 “서버 구성 요소를 먼저, 필요한 곳에만 클라이언트 구성 요소"를 고수하면 고성능 응용 프로그램이 탄생할 것입니다. 마이그레이션의 경우 더 작은 페이지부터 시작하여 점진적으로 이동하세요.