IT기술· 7분 읽기
📘

Next.js 15 App Router 마스터 가이드 — Server Components 베스트 프랙티스

Next.js 15 App Router 완전 정복 가이드. 서버/클라이언트 경계, 데이터 페칭, 라우팅, 에러 핸들링, 최적화까지 베스트 프랙티스.

Next.js 15 App Router 마스터 가이드 — Server Components 베스트 프랙티스

Next.js 15의 App Router는 React Server Components를 중심으로 하는 새 패러다임입니다. 2026년 기준 프로덕션에서 검증된 베스트 프랙티스 정리.

1. 파일 시스템 구조

app/
  layout.tsx           # 루트 레이아웃 (필수)
  page.tsx             # 홈 /
  loading.tsx          # 로딩 UI
  error.tsx            # 에러 경계
  not-found.tsx        # 404
  (marketing)/         # 라우트 그룹 (URL 영향 X)
    page.tsx
  blog/
    [slug]/
      page.tsx         # /blog/xxx
  api/
    route.ts           # REST 엔드포인트

라우트 그룹 (group): URL에 영향 없이 레이아웃 공유용.

2. Server vs Client Components

기본은 서버. "use client" 명시해야 클라이언트.

tsx
// Server Component (default)
async function Page() {
  const user = await fetchUser()  // 서버에서 직접
  return <ProfileCard user={user} />
}

// Client Component
"use client"
function InteractiveButton() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

경계 원칙

  • "use client"는 최하위 리프에 놓기
  • 그 위는 서버 컴포넌트 유지
  • props로 전달되는 값은 직렬화 가능해야 (JSON 가능한 타입만)

3. 데이터 페칭

tsx
// 병렬 페칭
async function Page({ params }) {
  const [user, posts] = await Promise.all([
    fetchUser(params.id),
    fetchPosts(params.id),
  ])
  return <Dashboard user={user} posts={posts} />
}

fetch 자동 캐싱:

  • fetch(url) — 기본 캐시
  • fetch(url, { cache: "no-store" }) — 매 요청
  • fetch(url, { next: { revalidate: 60 } }) — 60초 ISR

4. Suspense + Streaming

tsx
import { Suspense } from "react"

export default function Page() {
  return (
    <>
      <FastSection />
      <Suspense fallback={<Skeleton />}>
        <SlowSection />
      </Suspense>
    </>
  )
}

async function SlowSection() {
  await new Promise(r => setTimeout(r, 2000))
  return <div>Done</div>
}

느린 영역만 스트리밍. TTFB 즉시.

5. Server Actions

tsx
// app/actions.ts
"use server"
export async function createPost(formData: FormData) {
  const title = formData.get("title") as string
  await db.insert(posts).values({ title })
  revalidatePath("/blog")
}

// app/blog/new/page.tsx
import { createPost } from "../actions"
export default function NewPost() {
  return <form action={createPost}>...</form>
}

REST API 별도 없이 서버 로직 직접 호출. CSRF 자동 보호.

6. Error Boundaries

tsx
// app/blog/error.tsx
"use client"
export default function Error({ error, reset }) {
  return (
    <div>
      <p>{error.message}</p>
      <button onClick={reset}>Retry</button>
    </div>
  )
}

세그먼트 단위 에러 경계. 에러 나도 나머지 페이지는 정상.

7. Metadata & SEO

tsx
export const metadata = {
  title: "My Page",
  description: "...",
}

// 또는 동적
export async function generateMetadata({ params }) {
  const post = await fetchPost(params.slug)
  return { title: post.title }
}

베스트 프랙티스 10가지

  1. 1서버 기본: "use client"는 꼭 필요할 때만
  2. 2데이터는 가능한 한 상위에서 페칭: props drilling 대신
  3. 3Suspense 적극 활용: 스트리밍으로 TTFB 극대화
  4. 4fetch + revalidate: Redis 없이 자동 캐시
  5. 5Server Actions: REST 대체, 보일러플레이트 감소
  6. 6dynamic = force-dynamic: 개인화 페이지 전용
  7. 7이미지 최적화: 필수
  8. 8폰트 최적화: next/font 사용
  9. 9import server-only: 민감 코드 클라이언트 누수 방지
  10. 10Parallel Routes: 복잡 대시보드는 @slot 활용

자주하는 실수

  • 서버 컴포넌트에서 useState → 에러
  • Client 컴포넌트에서 fetch → 성능 저하 (서버에서 페칭 권장)
  • Props로 함수/Date 전달 → 직렬화 에러
  • "use client" 파일에서 async server component import → 혼란

마무리

App Router는 초기 학습 곡선이 있지만 한 번 익히면 SPA + SSR의 장점만 챙기는 개발 경험을 제공합니다. 2026년 새 Next.js 프로젝트는 무조건 App Router. Pages Router는 마이그레이션 대상.

🔧 이 글과 관련된 무료 도구

이 글과 관련된 상품 (NextJS15)[광고/제휴]

이 포스팅은 쿠팡 파트너스, 아마존 어소시에이트, 알리익스프레스 제휴 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다. 이는 상품 가격에 영향을 주지 않습니다.
As an Amazon Associate, Coupang Partner, and AliExpress affiliate, I earn from qualifying purchases at no extra cost to you.

관련 글