Technical SEO with Next.js App Router | SSR· SSG
이 글의 핵심
Choose App Router rendering per route: SSG, SSR, and ISR with fetch cache, revalidate, tags, Route Segment Config, and SEO-safe patterns for metadata and personalization.
Introduction
In Next.js App Router (13+), how pages are built on the server and how aggressively they cache shapes performance, SEO, and operating cost at once. Knowing SSR vs SSG vs ISR only by name is not enough—one fetch option can change caching behavior in surprising ways.
This article maps static generation, server rendering, and incremental revalidation onto server components, fetch cache semantics, and Route Segment Config. It assumes 2026-era App Router + fetch cache behavior.
After reading this post
- Distinguish what SSG / SSR / ISR mean in App Router
- Design
fetchoptions and revalidate per data source - Split strategies for dynamic routes, personalization, and admin UIs
Table of contents
- Concepts
- Hands-on implementation
- Advanced: Route Segment Config
- Performance comparison
- Real-world cases
- Troubleshooting
- Conclusion
Concepts
Terminology (vs Pages Router intuition)
| Term | Intuitive meaning | App Router reality |
|---|---|---|
| SSG | HTML at build (or regenerate) time | Paths that can statically cache server component trees—often fetch(..., { cache: 'force-cache' }) patterns |
| SSR | HTML per request | Dynamic rendering—closer to cache: 'no-store' or dynamic = 'force-dynamic' |
| ISR | Periodic or on-demand static refresh | fetch revalidate seconds or revalidatePath / revalidateTag |
App Router creates cache boundaries from fetch + segment config, not a single page-level toggle. |
React Server Components (RSC)
Server components run on the server by default; the client bundle receives serialized output. “SSR vs SSG” becomes when and how that output is cached.
Hands-on implementation
SSG-like: build-time cached data
// app/posts/page.tsx — cache at build (similar to default force-cache)
export default async function PostsPage() {
const res = await fetch('https://api.example.com/posts', {
cache: 'force-cache',
});
const posts = await res.json();
return (
<ul>
{posts.map((p: { id: string; title: string }) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
);
}
SSR: fresh data every request
// app/dashboard/page.tsx
export default async function DashboardPage() {
const res = await fetch('https://api.example.com/me', {
cache: 'no-store',
});
const user = await res.json();
return <div>{user.name}</div>;
}
ISR: time-based revalidation
// app/blog/[slug]/page.tsx
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 3600 }, // background revalidate every hour
});
if (!res.ok) notFound();
const post = await res.json();
return <article>{post.body}</article>;
}
Tag-based invalidation (great for operations)
// On fetch
await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] },
});
// In a Server Action or Route Handler
import { revalidateTag } from 'next/cache';
export async function POST() {
revalidateTag('posts');
return Response.json({ ok: true });
}
Summary: Mixed strategies inside one route are normal—define a per-source cache policy table for the team.
Advanced: Route Segment Config
Force dynamic segments
// app/admin/layout.tsx
export const dynamic = 'force-dynamic';
export const fetchCache = 'force-no-store';
- dynamic:
'auto' | 'force-dynamic' | 'error' | 'force-static'— default behavior for the route tree - revalidate: segment-level default revalidation window (use alongside fetch-level settings)
generateStaticParams for SSG scope
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const slugs = await getAllSlugs(); // list available at build time
return slugs.map((slug) => ({ slug }));
}
If a CMS exports tens of thousands of slugs, prebuild only top pages and rely on on-demand ISR for the tail.
Performance comparison
| Situation | Direction | Why |
|---|---|---|
| Marketing, docs, legal | SSG + long revalidate or fully static | Max CDN hit ratio, stable TTFB |
| Dashboards, carts | SSR (no-store) or split client islands | Per-user data, avoid cache poisoning |
| Blogs, catalogs | ISR + revalidateTag | Balance traffic vs freshness |
| Real-time inventory/pricing | SSR + short TTL or edge + external cache | Next cache alone may be insufficient |
| Key idea: Prefer agreeing “how stale can this fetch be?” over labeling a page “SSG” in isolation. |
Real-world cases
- E-commerce listing:
revalidate: 300+productstag; callrevalidateTag('products')on price updates. - Logged-in header:
no-storefor user info; keep shared nav fragments static to minimize personalized surface area. - Docs site: Mostly SSG; isolate search to client or a separate API—separate rendering from search indexing strategy.
Troubleshooting
“Build is fresh but production shows stale data”
- Check
fetchcache vs revalidate vs CDN/hosting data caches.
“revalidatePath did not update”
- Verify matching segment tree and cache keys—tag-based invalidation is often more reliable.
“Almost leaked user data across sessions”
- Never
force-cacheper-user fetches—useno-storeor route boundaries that separate auth contexts.
Conclusion
SSR vs SSG vs ISR in App Router is really about fetch + segment configuration telling a caching story. Document per-layer TTLs, tags, and invalidation triggers so performance and freshness debates shrink. For async load and server cost, pair with the Node.js performance guide.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Choose App Router rendering per route: SSG, SSR, and ISR with fetch cache, revalidate, tags, Route Segment Config, and S… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- JavaScript 비동기 디버깅 실전 사례 | Promise 체인 에러 추적하기
- Node.js 성능 최적화 | 클러스터링, 캐싱, 프로파일링
- TypeScript 고급 패턴 | 조건부 타입, 템플릿 리터럴 타입
- 기술 블로그 방문자 늘리기 | 주제 클러스터·내부 링크·검색 친화 글쓰기
- Astro로 기술 블로그 만들기 | 콘텐츠 컬렉션·MDX·SEO·배포까지
이 글에서 다루는 키워드 (관련 검색어)
SEO, Next.js, App Router, SSR, SSG, ISR, Caching, Technical SEO 등으로 검색하시면 이 글이 도움이 됩니다.