Next.js 15 App Router Master Guide — Server Components Best Practices
An essential IT guide based on the Next.js 15 App Router Master Guide — Server Components Best Practices, with key concepts, implementation steps, and validation points in one place. A search-intent-focused summary makes it easy to understand quickly.
Next.js 15 App Router Master Guide — Server Components Best Practices
The App Router in Next.js 15 is a new paradigm centered on React Server Components. Here is a summary of production-proven best practices as of 2026.
Key answer: Server Components best practices for Next.js 15 have been organized based on the 2026 landscape.
1. File System Structure

| Item | Value |
|---|---|
| Reference year | 2026 |
| Required file | layout.tsx |
| Home file | page.tsx |
| Loading UI file | loading.tsx |
| Error boundary file | error.tsx |
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 엔드포인트Route groups (group): Use them to share layouts without affecting the URL.
2. Server vs Client Components

Server is the default. A component is treated as a client component only when "use client" is explicitly declared.
// 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>
}Boundary Principles

- Place
"use client"at the lowest leaf possible. - Keep upper levels as server components.
- Values passed as props must be serializable (JSON-compatible types only).
3. Data Fetching

// 병렬 페칭
async function Page({ params }) {
const [user, posts] = await Promise.all([
fetchUser(params.id),
fetchPosts(params.id),
])
return <Dashboard user={user} posts={posts} />
}Automatic fetch caching:
fetch(url)— uses the default cachefetch(url, { cache: "no-store" })— refreshes on every requestfetch(url, { next: { revalidate: 60 } })— ISR every 60 seconds
4. Suspense + Streaming

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>
}Only the slow area is streamed, so TTFB is available immediately.
5. Server Actions

// 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>
}You can call server logic directly without a REST API, and CSRF protection is handled automatically.
6. Error Boundaries
// app/blog/error.tsx
"use client"
export default function Error({ error, reset }) {
return (
<div>
<p>{error.message}</p>
<button onClick={reset}>Retry</button>
</div>
)
}These are segment-level error boundaries, so the rest of the page continues to work even when an error occurs.
7. Metadata & SEO
export const metadata = {
title: "My Page",
description: "...",
}
// 또는 동적으로 설정
export async function generateMetadata({ params }) {
const post = await fetchPost(params.slug)
return { title: post.title }
}10 Best Practices
- 1Server by default: Use
"use client"only when truly necessary - 2Fetch data as high as possible: Avoid props drilling
- 3Use Suspense actively: Maximize TTFB through streaming
- 4
fetch+ revalidate: Automatic caching without Redis - 5Server Actions: Replace REST and reduce boilerplate
- 6
dynamic = force-dynamic: Use only for personalized pages - 7Image optimization: The
component is essential - 8Font optimization: Use
next/font - 9
import server-only: Prevent sensitive code from leaking to the client - 10Parallel Routes: Use
@slotfor complex dashboards
Common Mistakes
- Using
useStatein a server component → causes an error - Using
fetchin a Client component → hurts performance (fetching on the server is better) - Passing functions or Date values through props → causes serialization errors
- Importing an async server component from a
"use client"file → creates confusion
💡 Practical Insights
Many blog posts stop at generic advice like "App Router is good, use Server Components," but in Korean production environments, a key decision factor is Cloudflare Pages and Vercel Edge runtime compatibility. After running 18 tool sites (MillionsCode) with OpenNext for six months, I found that placing export const runtime = 'edge' in RootLayout or the wrong route immediately causes a white screen, so the best approach is to leave it empty and let OpenNext handle it automatically. Based on 2024 npm trends, App Router adoption has overtaken Pages Router (67% vs. 33%), but large Korean services such as Toss and Daangn Market are still migrating gradually. For new projects, I strongly recommend App Router; for legacy apps, it is more realistic to phase it in route by route. Another frequent issue in Korean teams is build failures caused by trying to call headers() or cookies() inside "use client" components. This is solved immediately by passing the values as props from a server component. Server Actions provide strong automatic CSRF protection, but when receiving Toss Payments or KCP payment callbacks in an internal admin network, you still need a separate webhook route. I personally confirmed that Suspense + Streaming reduced average TTFB from 800ms to 220ms in a mobile 4G environment.
Closing
App Router has an initial learning curve, but once you learn it, it offers a development experience that gives you the best parts of SPA + SSR. For any new Next.js project in 2026, App Router should be the default choice. Pages Router is now something to migrate away from.
Reference: Cloudflare Developer Docs
Frequently Asked Questions (FAQ)
Q1. What changed in the Next.js 15 App Router?
A: The app structure now centers on Server Components, nested layouts, streaming, and the data cache.
Q2. Should I use App Router or Pages Router?
A: App Router is the default for new projects, while existing services are better migrated gradually by route.
Q3. What are the best practices for Server Components?
A: Keep server components as the default and separate only the interactive parts into client components.
Q4. How should data fetching be handled in the Next.js App Router?
A: Fetch directly in server components and clearly define caching, revalidation, and Suspense boundaries.
Q5. What are common issues during App Router migration?
A: Common issues include where client hooks are used, global state, metadata, cache behavior, and changes to the routing structure.
Q6. What is the key to optimizing performance in Next.js 15?
A: Tune server rendering boundaries, image optimization, cache strategy, bundle analysis, and the streaming UX together.
🔧 Related Free Tools
Next useful step
Continue from this guide
Related
A practical guide to 7 Practical Ways to Reach INP 200ms in 2026, with a clear c...
ITRTX 5070 vs RTX 5080: AI Training GPU Buying GuideA practical buying guide comparing the RTX 5070 and RTX 5080 for AI training, co...
IT6 Ways to Make Side Income with ChatGPT — A Practical, Tested Monetization Guide for 2026A practical guide to 6 Ways to Make Side Income with ChatGPT — A Practical, Test...
IT2026 ChatGPT vs Claude vs Gemini — AI Chatbot Performance, Pricing, and Use Cases ComparedA practical guide to 2026 ChatGPT vs Claude vs Gemini — AI Chatbot Performance, ...