[이제와서 시작하는 Next.js 마스터하기 #2] App Router와 라우팅 시스템 완전 정복
“URL이 자동으로 만들어진다고?” - 네! Next.js의 파일 기반 라우팅은 폴더 구조가 곧 웹사이트 주소입니다. 복잡한 설정 없이 파일만 만들면 끝!
🎯 이 글에서 배울 내용
- 파일 기반 라우팅의 마법 같은 편리함
- 동적 라우팅 (
[id],[slug],[...all]) - 레이아웃으로 공통 UI 재사용하기
- Loading UI로 부드러운 사용자 경험 만들기
- Error Boundary로 에러 우아하게 처리하기
- Route Groups로 프로젝트 구조 깔끔하게 정리하기
예상 소요 시간: 40분 사전 지식: Next.js #1 - 처음 시작하는 Next.js
🗺️ 라우팅이란? (쉽게 설명)
라우팅은 “주소(URL)에 따라 다른 페이지를 보여주는 것”입니다.
🏢 빌딩으로 이해하는 라우팅
웹사이트를 큰 빌딩이라고 생각해보세요:
1
2
3
4
yoursite.com/ → 1층 로비 (홈페이지)
yoursite.com/about → 2층 회사 소개
yoursite.com/blog → 3층 블로그
yoursite.com/contact → 4층 연락처
전통적인 방식 (React Router 등):
- 엘리베이터 버튼(라우터)을 일일이 프로그래밍해야 함
- 각 층(페이지)과 버튼을 연결하는 코드 작성
Next.js 방식:
- 각 층에 방(폴더)을 만들면 엘리베이터가 자동으로 버튼 생성!
- 설정 파일? 필요 없음!
📁 파일 기반 라우팅 기초
1. 기본 페이지 만들기
1
2
3
4
5
6
7
8
app/
├── page.js → yoursite.com/
├── about/
│ └── page.js → yoursite.com/about
├── blog/
│ └── page.js → yoursite.com/blog
└── contact/
└── page.js → yoursite.com/contact
핵심 규칙:
- 📁 폴더 이름 = URL 경로
- 📄
page.js= 해당 URL에서 보여줄 페이지
2. 중첩 라우팅 (Nested Routes)
1
2
3
4
5
6
7
8
9
app/
└── blog/
├── page.js → /blog
├── authors/
│ └── page.js → /blog/authors
└── categories/
├── page.js → /blog/categories
└── tech/
└── page.js → /blog/categories/tech
폴더를 중첩하면 URL도 중첩됩니다!
🎯 실습: 블로그 구조 만들기
1단계: 폴더 생성
1
mkdir -p app/blog/posts
2단계: 페이지 파일 작성
1
2
3
4
5
6
7
8
9
// app/blog/page.js
export default function BlogPage() {
return (
<div className="p-8">
<h1 className="text-4xl font-bold mb-4">블로그</h1>
<p className="text-gray-600">블로그 글 목록이 여기 표시됩니다.</p>
</div>
);
}
1
2
3
4
5
6
7
8
9
// app/blog/posts/page.js
export default function PostsPage() {
return (
<div className="p-8">
<h1 className="text-4xl font-bold mb-4">모든 포스트</h1>
<p className="text-gray-600">전체 포스트 목록</p>
</div>
);
}
3단계: 브라우저에서 확인
http://localhost:3000/blog→ 블로그 메인http://localhost:3000/blog/posts→ 포스트 목록
🎉 설정 파일 없이 2개의 페이지가 자동으로 생성되었습니다!
🎭 동적 라우팅 (Dynamic Routes)
🤔 왜 필요한가요?
블로그 글이 100개라면 100개의 폴더를 만들 건가요? 당연히 아니죠!
문제 상황:
1
2
3
4
app/blog/post-1/page.js → /blog/post-1
app/blog/post-2/page.js → /blog/post-2
app/blog/post-3/page.js → /blog/post-3
... (100개?! 😱)
해결책: 동적 라우팅
1
app/blog/[id]/page.js → /blog/아무거나
1. 기본 동적 라우팅: [id]
대괄호 []가 핵심입니다!
1
2
3
4
5
6
7
8
9
10
11
12
// app/blog/[id]/page.js
export default async function PostPage({ params }) {
// Next.js 16부터 params는 Promise입니다!
const { id } = await params;
return (
<div className="p-8">
<h1 className="text-4xl font-bold mb-4">블로그 포스트</h1>
<p className="text-xl">포스트 ID: {id}</p>
</div>
);
}
동작 원리:
1
2
3
4
/blog/1 → id = "1"
/blog/2 → id = "2"
/blog/hello → id = "hello"
/blog/안녕 → id = "안녕"
2. 실전 예제: 블로그 포스트 상세 페이지
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// app/blog/[slug]/page.js
const posts = {
'nextjs-intro': {
title: 'Next.js 시작하기',
content: 'Next.js는 React 기반의...',
author: '홍길동',
date: '2025-11-23'
},
'react-basics': {
title: 'React 기초',
content: 'React는 사용자 인터페이스를...',
author: '김철수',
date: '2025-11-22'
}
};
export default async function PostDetailPage({ params }) {
const { slug } = await params;
const post = posts[slug];
// 포스트가 없으면?
if (!post) {
return (
<div className="p-8">
<h1 className="text-2xl font-bold text-red-600">
포스트를 찾을 수 없습니다 😢
</h1>
</div>
);
}
return (
<article className="max-w-2xl mx-auto p-8">
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<div className="flex gap-4 text-gray-600 mb-8">
<span>작성자: {post.author}</span>
<span>날짜: {post.date}</span>
</div>
<div className="prose">
{post.content}
</div>
</article>
);
}
테스트:
/blog/nextjs-intro→ Next.js 시작하기 글/blog/react-basics→ React 기초 글/blog/unknown→ 포스트를 찾을 수 없습니다
3. Catch-all Routes: [...slug]
여러 단계의 경로를 한 번에 처리
1
2
3
4
5
6
7
8
9
10
11
12
// app/docs/[...slug]/page.js
export default async function DocsPage({ params }) {
const { slug } = await params;
// slug는 배열입니다!
return (
<div className="p-8">
<h1 className="text-3xl font-bold mb-4">문서 페이지</h1>
<p>경로: {slug.join(' → ')}</p>
</div>
);
}
동작 예시:
1
2
3
4
5
6
7
8
/docs/getting-started
→ slug = ["getting-started"]
/docs/api/authentication
→ slug = ["api", "authentication"]
/docs/guides/deployment/vercel
→ slug = ["guides", "deployment", "vercel"]
4. Optional Catch-all: [[...slug]]
이중 대괄호 [[]]는 선택적입니다
1
2
3
4
5
6
7
8
9
10
11
12
13
// app/shop/[[...categories]]/page.js
export default async function ShopPage({ params }) {
const { categories } = await params;
if (!categories) {
// /shop → 전체 상품
return <div>전체 상품 목록</div>;
}
// /shop/electronics → 전자제품
// /shop/electronics/phones → 휴대폰
return <div>카테고리: {categories.join(' > ')}</div>;
}
🖼️ 레이아웃 (Layouts) - 공통 UI 재사용
🤔 레이아웃이란?
여러 페이지에서 공통으로 사용하는 “틀”입니다.
비유: 액자로 이해하기
- 레이아웃 = 액자 (테두리, 고정된 부분)
- 페이지 콘텐츠 = 그림 (바뀌는 부분)
1. 루트 레이아웃 (Root Layout)
모든 페이지에 적용되는 최상위 레이아웃
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="ko">
<body>
{/* 모든 페이지에 표시되는 헤더 */}
<header className="bg-blue-600 text-white p-4">
<nav className="max-w-6xl mx-auto flex gap-4">
<a href="/">홈</a>
<a href="/blog">블로그</a>
<a href="/about">소개</a>
</nav>
</header>
{/* 각 페이지의 내용이 여기 들어감 */}
<main className="min-h-screen">
{children}
</main>
{/* 모든 페이지에 표시되는 푸터 */}
<footer className="bg-gray-800 text-white p-8 text-center">
© 2025 My Website
</footer>
</body>
</html>
);
}
{children}이 핵심입니다!
- 홈 페이지를 보면 →
children에 홈 페이지 내용 - 블로그를 보면 →
children에 블로그 내용
2. 중첩 레이아웃 (Nested Layouts)
특정 섹션에만 적용되는 레이아웃
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// app/blog/layout.js
export default function BlogLayout({ children }) {
return (
<div className="max-w-6xl mx-auto p-8">
{/* 블로그 전용 사이드바 */}
<div className="grid grid-cols-12 gap-8">
<aside className="col-span-3">
<h2 className="font-bold mb-4">카테고리</h2>
<ul className="space-y-2">
<li><a href="/blog/tech">기술</a></li>
<li><a href="/blog/life">일상</a></li>
<li><a href="/blog/review">리뷰</a></li>
</ul>
</aside>
{/* 블로그 페이지 내용 */}
<main className="col-span-9">
{children}
</main>
</div>
</div>
);
}
레이아웃 중첩 구조:
1
2
3
4
5
RootLayout (모든 페이지)
├─ Header, Footer
└─ BlogLayout (블로그 섹션만)
├─ 사이드바
└─ 페이지 내용
3. 레이아웃 vs 템플릿
| 특징 | Layout | Template |
|---|---|---|
| 파일명 | layout.js | template.js |
| 페이지 이동 시 | 유지됨 (리렌더 안 됨) | 새로 렌더링됨 |
| 상태(state) | 유지됨 | 초기화됨 |
| 사용 케이스 | 공통 UI (헤더, 사이드바) | 페이지마다 초기화 필요한 경우 |
Template 예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app/blog/template.js
'use client';
import { useEffect } from 'react';
export default function BlogTemplate({ children }) {
useEffect(() => {
// 페이지 이동할 때마다 실행
console.log('새 페이지 로드!');
}, []);
return (
<div className="animate-fadeIn">
{children}
</div>
);
}
⏳ Loading UI - 로딩 상태 처리하기
🤔 왜 필요한가요?
데이터를 가져오는 동안 사용자에게 “로딩 중”을 보여줘야 합니다.
Next.js 이전:
1
2
3
4
5
6
7
8
9
10
11
12
// 복잡한 로딩 상태 관리
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(data => {
setData(data);
setLoading(false);
});
}, []);
if (loading) return <Spinner />;
Next.js 방식:
1
// loading.js 파일만 만들면 끝!
1. Loading UI 만들기
1
2
3
4
5
6
7
8
9
10
11
12
// app/blog/loading.js
export default function LoadingBlog() {
return (
<div className="p-8">
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/4 mb-4"></div>
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-2/3"></div>
</div>
</div>
);
}
자동으로 동작합니다!
- 페이지 로딩 시작 →
loading.js표시 - 페이지 로딩 완료 → 실제 페이지 표시
2. Suspense와 Streaming
부분적인 로딩도 가능합니다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// app/dashboard/page.js
import { Suspense } from 'react';
async function SlowComponent() {
// 느린 데이터 로딩 (3초)
await new Promise(resolve => setTimeout(resolve, 3000));
return <div>느린 컴포넌트 로드 완료!</div>;
}
async function FastComponent() {
// 빠른 데이터 로딩 (0.5초)
await new Promise(resolve => setTimeout(resolve, 500));
return <div>빠른 컴포넌트 로드 완료!</div>;
}
export default function DashboardPage() {
return (
<div className="p-8">
<h1 className="text-3xl font-bold mb-8">대시보드</h1>
{/* 빠른 컴포넌트는 먼저 표시 */}
<Suspense fallback={<div>빠른 데이터 로딩 중...</div>}>
<FastComponent />
</Suspense>
{/* 느린 컴포넌트는 나중에 표시 */}
<Suspense fallback={<div>느린 데이터 로딩 중...</div>}>
<SlowComponent />
</Suspense>
</div>
);
}
결과:
- 페이지 즉시 표시
- 0.5초 후 → 빠른 컴포넌트 나타남
- 3초 후 → 느린 컴포넌트 나타남
❌ Error Handling - 에러 우아하게 처리하기
🤔 에러가 발생하면?
Next.js 이전: 앱 전체가 멈춤 (흰 화면 😱)
Next.js 방식: 에러 부분만 처리 (나머지는 정상 작동 😊)
1. Error Boundary 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// app/blog/error.js
'use client'; // Error 컴포넌트는 Client Component여야 함!
import { useEffect } from 'react';
export default function BlogError({ error, reset }) {
useEffect(() => {
// 에러 로깅 (Sentry 등)
console.error('블로그 에러:', error);
}, [error]);
return (
<div className="p-8 text-center">
<h2 className="text-2xl font-bold text-red-600 mb-4">
앗! 문제가 발생했습니다 😢
</h2>
<p className="text-gray-600 mb-4">
{error.message}
</p>
<button
onClick={reset}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
다시 시도
</button>
</div>
);
}
2. 에러 테스트하기
1
2
3
4
5
6
7
// app/blog/test-error/page.js
export default function ErrorTestPage() {
// 의도적으로 에러 발생
throw new Error('테스트 에러입니다!');
return <div>이 부분은 실행되지 않습니다</div>;
}
/blog/test-error 접속 시:
- ✅ 에러 메시지 표시
- ✅ “다시 시도” 버튼 제공
- ✅ 나머지 사이트는 정상 작동
3. Global Error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// app/global-error.js
'use client';
export default function GlobalError({ error, reset }) {
return (
<html>
<body>
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h2 className="text-3xl font-bold mb-4">
전체 에러 발생
</h2>
<button onClick={reset}>다시 시작</button>
</div>
</div>
</body>
</html>
);
}
🚫 Not Found 페이지
1. 자동 404 페이지
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// app/not-found.js
export default function NotFound() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1 className="text-6xl font-bold text-gray-800 mb-4">404</h1>
<p className="text-xl text-gray-600 mb-8">
페이지를 찾을 수 없습니다 😢
</p>
<a
href="/"
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
홈으로 돌아가기
</a>
</div>
</div>
);
}
2. 프로그래밍 방식으로 404 표시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// app/blog/[slug]/page.js
import { notFound } from 'next/navigation';
export default async function PostPage({ params }) {
const { slug } = await params;
const post = await getPost(slug);
// 포스트가 없으면 404 페이지 표시
if (!post) {
notFound();
}
return <div>{post.title}</div>;
}
📂 Route Groups - 구조 정리하기
🤔 왜 필요한가요?
문제: 폴더 이름이 URL에 나타납니다
1
app/admin/dashboard/page.js → /admin/dashboard
하지만 때로는:
- 폴더로 구조는 정리하고 싶지만
- URL에는 나타나지 않았으면 좋겠을 때!
Route Groups: (folder)
괄호 ()로 감싸면 URL에 나타나지 않습니다!
1
2
3
4
5
6
7
8
9
10
11
12
app/
├── (marketing)/ # URL에 안 나타남!
│ ├── about/
│ │ └── page.js → /about (not /marketing/about)
│ └── contact/
│ └── page.js → /contact
│
└── (shop)/ # URL에 안 나타남!
├── products/
│ └── page.js → /products
└── cart/
└── page.js → /cart
실전 예제: 여러 레이아웃 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
app/
├── (main)/
│ ├── layout.js # 메인 사이트 레이아웃
│ ├── page.js → /
│ └── about/
│ └── page.js → /about
│
└── (auth)/
├── layout.js # 로그인 페이지 전용 레이아웃
├── login/
│ └── page.js → /login
└── signup/
└── page.js → /signup
1
2
3
4
5
6
7
8
9
10
// app/(main)/layout.js
export default function MainLayout({ children }) {
return (
<div>
<header>메인 헤더</header>
{children}
<footer>메인 푸터</footer>
</div>
);
}
1
2
3
4
5
6
7
8
9
10
// app/(auth)/layout.js
export default function AuthLayout({ children }) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<div className="max-w-md w-full bg-white p-8 rounded-lg shadow">
{children}
</div>
</div>
);
}
결과:
/,/about→ 헤더 + 푸터 있음/login,/signup→ 중앙 카드 레이아웃
🎯 실전 프로젝트: 블로그 라우팅 구조
완성된 폴더 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
app/
├── layout.js # 루트 레이아웃
├── page.js # 홈
├── not-found.js # 404
│
├── (marketing)/ # 마케팅 페이지
│ ├── about/
│ │ └── page.js → /about
│ └── contact/
│ └── page.js → /contact
│
└── blog/
├── layout.js # 블로그 레이아웃
├── loading.js # 블로그 로딩
├── error.js # 블로그 에러
├── page.js → /blog
├── [slug]/
│ ├── page.js → /blog/[slug]
│ └── loading.js # 포스트 로딩
└── category/
└── [...slug]/
└── page.js → /blog/category/[...slug]
단계별 구현
1단계: 루트 레이아웃
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// app/layout.js
import Link from 'next/link';
export default function RootLayout({ children }) {
return (
<html lang="ko">
<body>
<header className="bg-white border-b">
<nav className="max-w-6xl mx-auto p-4 flex gap-6">
<Link href="/" className="font-bold">홈</Link>
<Link href="/blog">블로그</Link>
<Link href="/about">소개</Link>
<Link href="/contact">연락처</Link>
</nav>
</header>
<main className="min-h-screen">
{children}
</main>
<footer className="bg-gray-50 border-t p-8 text-center text-gray-600">
© 2025 My Blog
</footer>
</body>
</html>
);
}
2단계: 블로그 레이아웃
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// app/blog/layout.js
export default function BlogLayout({ children }) {
return (
<div className="max-w-6xl mx-auto p-8">
<div className="grid grid-cols-12 gap-8">
<aside className="col-span-3">
<h2 className="font-bold text-lg mb-4">카테고리</h2>
<ul className="space-y-2">
<li>
<a href="/blog/category/tech" className="text-blue-600 hover:underline">
기술
</a>
</li>
<li>
<a href="/blog/category/life" className="text-blue-600 hover:underline">
일상
</a>
</li>
</ul>
</aside>
<div className="col-span-9">
{children}
</div>
</div>
</div>
);
}
3단계: 블로그 목록 페이지
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// app/blog/page.js
import Link from 'next/link';
const posts = [
{ slug: 'nextjs-intro', title: 'Next.js 시작하기', excerpt: 'Next.js를 배워봅시다' },
{ slug: 'react-hooks', title: 'React Hooks', excerpt: 'Hooks 완벽 가이드' },
];
export default function BlogPage() {
return (
<div>
<h1 className="text-4xl font-bold mb-8">블로그</h1>
<div className="space-y-6">
{posts.map(post => (
<article key={post.slug} className="border-b pb-6">
<Link href={`/blog/${post.slug}`}>
<h2 className="text-2xl font-semibold mb-2 hover:text-blue-600">
{post.title}
</h2>
</Link>
<p className="text-gray-600">{post.excerpt}</p>
</article>
))}
</div>
</div>
);
}
4단계: 포스트 상세 페이지
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// app/blog/[slug]/page.js
import { notFound } from 'next/navigation';
const posts = {
'nextjs-intro': {
title: 'Next.js 시작하기',
content: 'Next.js는 React 기반의 강력한 프레임워크입니다...',
date: '2025-11-23'
},
'react-hooks': {
title: 'React Hooks 완벽 가이드',
content: 'React Hooks는 함수형 컴포넌트에서...',
date: '2025-11-22'
}
};
export default async function PostPage({ params }) {
const { slug } = await params;
const post = posts[slug];
if (!post) {
notFound();
}
return (
<article className="prose max-w-none">
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<p className="text-gray-600 mb-8">{post.date}</p>
<div className="leading-relaxed">
{post.content}
</div>
</article>
);
}
🔍 자주 묻는 질문 (FAQ)
Q1: params를 await 해야 하는 이유는?
답: Next.js 16부터 params가 비동기(Promise)가 되었습니다!
이유: 성능 최적화를 위해 params를 나중에 읽을 수 있게 변경
사용법:
1
2
3
4
5
6
7
8
9
10
// ✅ Next.js 16 (올바름)
export default async function Page({ params }) {
const { id } = await params;
return <div>{id}</div>;
}
// ❌ Next.js 15 방식 (16에서 오류)
export default function Page({ params }) {
return <div>{params.id}</div>;
}
Q2: layout.js와 page.js의 차이는?
답:
- layout.js: 여러 페이지에서 공유하는 “틀” (헤더, 사이드바 등)
- page.js: 실제 페이지 내용
비유:
- layout.js = 액자
- page.js = 그림
중요: layout.js는 페이지 이동 시 리렌더링되지 않습니다! (성능 향상)
Q3: [slug]와 [...slug]의 차이는?
답:
[slug] (단일 세그먼트):
1
2
/blog/hello ✅ slug = "hello"
/blog/hello/world ❌ 매치 안 됨
[...slug] (여러 세그먼트):
1
2
3
/blog/hello ✅ slug = ["hello"]
/blog/hello/world ✅ slug = ["hello", "world"]
/blog/a/b/c ✅ slug = ["a", "b", "c"]
[[...slug]] (선택적):
1
2
3
/blog ✅ slug = undefined
/blog/hello ✅ slug = ["hello"]
/blog/hello/world ✅ slug = ["hello", "world"]
Q4: loading.js가 표시되지 않아요
답: 데이터 로딩이 너무 빠르면 loading.js가 보이지 않을 수 있습니다!
해결책:
1
2
3
4
5
6
// 개발 중 테스트를 위해 인위적인 지연 추가
export default async function Page() {
await new Promise(resolve => setTimeout(resolve, 2000)); // 2초 대기
return <div>페이지 내용</div>;
}
프로덕션에서는: 실제 API 호출이 있으면 자연스럽게 표시됩니다.
Q5: Route Groups는 언제 사용하나요?
답: 이런 경우에 유용합니다!
1. 여러 레이아웃 적용
1
2
3
(main) → 일반 페이지 레이아웃
(auth) → 로그인 페이지 레이아웃
(admin) → 관리자 페이지 레이아웃
2. 코드 구조 정리
1
2
3
(marketing) → 마케팅 관련 페이지
(shop) → 쇼핑 관련 페이지
(blog) → 블로그 관련 페이지
3. 팀별 작업 분리
1
2
(team-frontend) → 프론트엔드 팀
(team-backend) → 백엔드 팀
핵심: URL에는 영향 없이 폴더 구조만 정리!
💡 초보자를 위한 팁
1. 폴더 구조는 미리 계획하세요
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
✅ 좋은 구조 (명확함):
app/
├── (marketing)/
│ ├── about/
│ └── contact/
└── blog/
├── [slug]/
└── category/
❌ 나쁜 구조 (혼란스러움):
app/
├── page1/
├── page2/
├── temp/
└── test/
2. loading.js는 작은 단위로
1
2
3
4
5
6
// ✅ 좋음: 각 세그먼트마다 loading
app/blog/loading.js
app/blog/[slug]/loading.js
// ⚠️ 피하기: 루트에만 loading
app/loading.js
3. error.js는 적절한 위치에
1
2
3
4
5
6
7
8
// 전체 앱 에러
app/error.js
// 블로그 섹션 에러
app/blog/error.js
// 특정 페이지 에러
app/blog/[slug]/error.js
4. 개발자 도구 활용
Next.js는 유용한 에러 메시지를 제공합니다!
1
2
3
Error: Page "/blog/[slug]/page.js" is missing generateStaticParams.
→ 이 메시지를 읽고 문서를 찾아보세요!
🎯 오늘 배운 내용 정리
✅ 핵심 개념
- 파일 기반 라우팅
- 폴더 = URL 경로
page.js= 페이지 표시
- 동적 라우팅
[id]= 단일 세그먼트[...slug]= 여러 세그먼트[[...slug]]= 선택적
- 레이아웃
- 공통 UI 재사용
- 중첩 가능
- 페이지 이동 시 유지
- 특수 파일
loading.js= 로딩 UIerror.js= 에러 처리not-found.js= 404 페이지
- Route Groups
(folder)= URL에 안 나타남- 구조 정리용
🚀 다음 단계
다음 포스트에서는:
- Server Components 깊이 이해하기
- 데이터 페칭 전략
- 캐싱 최적화
를 배워보겠습니다!
📚 시리즈 네비게이션
이전 글
다음 글
- #3 Server Components와 데이터 페칭 전략 (다음 포스트에서 계속!)
🔗 참고 자료
“복잡해 보이지만 하나씩 따라하면 금방 익숙해집니다!” - 라우팅은 Next.js의 가장 강력한 기능 중 하나입니다! 🎯