Next.js 13부터 도입된 새로운 라우팅 시스템.
기존 pages/ 디렉토리 방식에서 app/ 디렉토리 방식으로 전환.
React Server Components(RSC)를 기본으로 사용한다.
| 파일명 | 역할 |
|---|---|
page.tsx |
라우트의 실제 UI |
layout.tsx |
공통 레이아웃 (리렌더링 없이 유지) |
loading.tsx |
Suspense 기반 로딩 UI |
error.tsx |
에러 바운더리 |
not-found.tsx |
404 UI |
template.tsx |
layout과 유사하지만 매 이동마다 새 인스턴스 생성 |
route.ts |
API 엔드포인트 (pages/api 대체) |
- Server Component: 기본값. 서버에서만 실행.
useState,useEffect사용 불가. - Client Component: 파일 상단에
"use client"선언 필요.
같은 layout을 공유하는 페이지 간 이동 시 layout은 언마운트되지 않는다.
즉, layout 안에서 useState로 상태를 유지하면 페이지가 바뀌어도 상태가 살아있다.
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
const [sidebarOpen, setSidebarOpen] = useState(true); // 페이지 이동해도 유지됨
return (
<div>
<Sidebar open={sidebarOpen} />
{children}
</div>
);
}반면 template.tsx는 매 이동마다 새 인스턴스가 생성되어 상태가 초기화된다.
애니메이션 트리거가 필요한 경우에 사용한다.
Server Component는 async 함수로 선언할 수 있다. fetch, DB 쿼리를 컴포넌트 안에서 바로 실행 가능하다.
// app/posts/page.tsx
export default async function PostsPage() {
const posts = await fetch("https://api.example.com/posts").then((r) =>
r.json(),
);
return (
<ul>
{posts.map((p) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
);
}Next.js는 fetch를 자동으로 캐싱한다.
같은 URL을 여러 컴포넌트에서 호출해도 실제 요청은 한 번만 발생한다(Request Memoization).
// 기본값: 빌드 시 캐시 (SSG와 동일)
fetch(url);
fetch(url, { cache: "force-cache" });
// 캐시 안 함: 매 요청마다 새로 가져옴 (SSR과 동일)
fetch(url, { cache: "no-store" });
// 일정 시간마다 재검증 (ISR과 동일)
fetch(url, { next: { revalidate: 60 } }); // 60초페이지 단위로도 설정 가능하다:
export const revalidate = 60; // 이 페이지 전체에 적용
export const dynamic = "force-dynamic"; // 항상 SSR@폴더명 컨벤션으로 하나의 layout에서 여러 페이지를 동시에 렌더링할 수 있다.
app/
├── layout.tsx
├── @modal/
│ └── page.tsx ← modal slot
└── @main/
└── page.tsx ← main slot
// app/layout.tsx
export default function Layout({ children, modal, main }) {
return (
<div>
{main}
{modal} {/* 동시에 렌더링 */}
</div>
);
}대시보드에서 동시에 여러 독립적인 섹션을 렌더링하거나, 모달을 URL 기반으로 관리할 때 사용한다.
(.), (..), (...) 컨벤션으로 다른 라우트를 가로채서 현재 레이아웃 위에 렌더링한다.
Instagram 스타일의 피드에서 사진 클릭 시 모달로 보여주는 패턴에 사용한다.