rendering · high priority
Next.js App Router
파일 컨벤션 = React 트리. RSC · Streaming · Cache Components 까지 한 시스템으로 묶인 라우팅
학습 개요
탄생 배경
쉬운 설명
복잡한 개념을 실생활 비유로 설명합니다.
“대형 백화점의 매장 배치도”
`app/` 의 폴더 구조는 *백화점 평면도* 와 같습니다. `layout` 은 *층 전체에 깔린 안내 데스크* — 매장(자식 페이지) 을 옮겨다녀도 안내 데스크는 그대로 서 있죠. `loading` 은 *공사 가림막*, `error` 는 *비상구 안내*. `(group)` 은 *사람에겐 안 보이지만 직원이 쓰는 구역 분류표*. `@slot` 은 *대형 디스플레이 두 개를 동시에 켜둔* 모양. PPR 은 *건물 외관은 미리 지어두고, 가격표만 그날 아침에 바꿔다는* 운영 — 손님이 빨리 들어오면서도 가격은 항상 최신.
핵심 개념
page.tsx
해당 세그먼트의 *고유 UI*. 이 파일이 있어야 URL 로 접근 가능. 기본 RSC.
layout.tsx
하위 세그먼트를 감싸는 *영속 UI*. navigation 시 *리렌더 / 언마운트되지 않음* — 상태가 유지된다.
loading.tsx
자동으로 <Suspense fallback> 경계를 만든다. 같은 세그먼트의 page.tsx 가 서버에서 데이터 로딩 중일 때 즉시 표시된다.
error.tsx
자동으로 ErrorBoundary 를 만든다. *항상 Client Component* — reset() 으로 재시도 가능.
not-found.tsx
notFound() 호출 시 렌더되는 UI. 세그먼트별로 둘 수 있다.
template.tsx
layout 과 비슷하지만 navigation 마다 *새 인스턴스로 마운트*. 페이지 전환 애니메이션·useEffect 재실행이 필요할 때.
route.ts
HTTP 메서드 핸들러 (GET/POST/...) 를 export 하는 *API 엔드포인트*. webhook · 외부 통합용.
layout 은 *상태 유지*, template 은 *매번 새로*
layout.tsx 는 자식 세그먼트 navigation 시 리렌더되지 않습니다. 사이드바 스크롤 위치, 폼 입력값이 그대로 남죠. *반대로* 페이지마다 fade-in 애니메이션을 다시 돌리거나 useEffect 를 다시 실행하고 싶으면 template.tsx 를 씁니다.
실무 적용
어떤 상황에서 사용하는가
월간 100만 PV 의 이커머스 상품 상세 페이지. LCP 2.5s 미만 + 재고는 항상 최신 + 마케터가 상품 설명 변경 시 즉시 반영을 모두 만족해야 함.
어떻게 적용하는가
(1) `app/product/[id]/page.tsx` 를 RSC 로 두고 상품 메타(이름·설명·이미지) 는 `"use cache"` + `cacheLife("days")` + `cacheTag(\`product-${id}\`)` 로 캐시. (2) 재고 표시는 `<Suspense>` 로 감싼 별도 RSC 에서 `fetch(..., { cache: "no-store" })`. PPR 이 자동으로 정적 셸 + 동적 구멍으로 쪼갬. (3) 어드민에서 상품 수정 시 Server Action 에서 `revalidateTag(\`product-${id}\`)` 호출 → 다음 요청부터 새 캐시. (4) 인터랙션이 필요한 *장바구니 버튼* 만 `"use client"` 잎 컴포넌트로 분리해 클라이언트 번들 최소화. (5) `loading.tsx` 를 *세그먼트별로* 두어 첫 렌더에서 깜빡임 최소화.
흔한 실수와 안티패턴
- 루트 가까이 `"use client"` 를 두어 페이지 전체가 클라이언트 번들이 됨.
- Server Action 에서 입력 검증·인가를 빼먹어 공개 엔드포인트로 노출.
- `fetch` 를 16 에서도 자동 캐시된다고 가정 — 16 부터 기본 `no-store` 라 의도와 다른 SSR 부담.
- `async` 컴포넌트에서 `await params` 를 빠뜨려 빌드 에러.
- `loading.tsx` 위치를 너무 위에 두어 *세그먼트 전체* 가 깜빡임.
- Parallel Routes 의 default.tsx 를 안 만들어 새로고침 시 404.
흔한 오해
"layout 은 navigation 시 다시 렌더된다."
교정App Router 의 `layout.tsx` 는 자식 세그먼트 navigation 시 *언마운트되지 않고 상태가 유지* 된다. 매번 새로 마운트하고 싶다면 `template.tsx`.
왜 중요"layout 안 사이드바의 스크롤 위치가 유지된다" 는 흔한 관찰이 이 컨벤션의 결과다.
"`"use server"` 함수는 안전한 내부 함수다."
교정"use server" 는 사실상 *공개 RPC 엔드포인트*. 누구나 임의 인자로 호출 가능 — 입력 검증과 인증·인가를 *반드시* 함수 시작부에 둔다.
왜 중요Server Action 은 클라이언트가 직접 호출하므로 보안 표면적이 일반 API 와 같다. Zod + 세션 체크가 표준.
"PPR 은 SSG 와 SSR 사이 어딘가의 *느린 옵션* 이다."
교정PPR 은 *정적 셸로 LCP 를 즉시* + *동적 구멍은 streaming* 으로 둘 다 챙기는 모델. 잘 쓰면 SSG 보다 빠를 수 있다.
왜 중요브라우저는 첫 바이트로 의미 있는 HTML 을 받고, 동적 부분만 추가 청크로 흘러들어온다 — 둘이 직렬이 아니라 병렬.
면접 질문
답변 방향 힌트
"부분 레이아웃", "워터폴", "번들 크기" 가 키워드.
반드시 언급할 키워드
- 부분 레이아웃 / 영속 layout — `_app.tsx` 단일 루트 한계 해소
- RSC 로 클라이언트 번들에서 빠지는 컴포넌트 → LCP / TTI 개선
- 컴포넌트 단위 데이터 페칭 + Suspense → 워터폴 해소, streaming
- Server Actions / 캐시 태그 등 *mutation 모델* 일원화
- `loading.tsx`/`error.tsx` 컨벤션으로 Suspense/ErrorBoundary 자동화
예상 꼬리 질문
- Pages Router 의 `getServerSideProps` 는 App Router 에서 어떻게 매핑하시겠습니까?
- 마이그레이션을 점진적으로 한다면 어떤 순서로 옮기시겠습니까?
자기 점검
`layout.tsx` 와 `template.tsx` 의 *런타임 차이* 한 줄로?
기대 키워드
자주 하는 오해
"layout 도 매번 다시 렌더된다" 는 잘못. layout 은 자식 navigation 에서도 *유지* 된다.
`error.tsx` 가 항상 Client Component 여야 하는 이유는?
기대 키워드
자주 하는 오해
"서버에서 에러를 잡아주는 파일" 이 아니라 *클라이언트의 ErrorBoundary 를 정의* 하는 파일이다.
Server Action 을 안전하게 호출 가능한 *공개 엔드포인트* 처럼 설계하기 위해 함수 시작부에 두어야 하는 두 가지는?
기대 키워드
자주 하는 오해
"내가 작성한 form 만 호출하니 안전하다" — 누구나 임의 payload 로 호출 가능.
학습 자료
- Next.js — Layouts and Pages`page` · `layout` · `template` 등 핵심 파일 컨벤션 공식 가이드.Docnextjs.org
- Next.js — Caching (cacheComponents · cacheLife · cacheTag)Next.js 16 의 명시적 캐시 모델과 PPR 토대.Docnextjs.org
- Next.js 16 Release NotesPPR · Cache Components stable, Turbopack, 비동기 API 등 16 의 변경사항 요약.Docnextjs.org · 2025
- Parallel Routes`@slot` 컨벤션과 `default.tsx` · `useSelectedLayoutSegments` 사용법.Docnextjs.org
- Intercepting Routes`(.)` · `(..)` · `(...)` modifier 와 modal-as-route 패턴.Docnextjs.org
- Next.js — Upgrading: Version 16비동기 `params`/`cookies()`/`headers()` 마이그레이션 가이드.Docnextjs.org