본문으로 건너뛰기
Previous
Next
Technical SEO with Next.js App Router | SSR· SSG

Technical SEO with Next.js App Router | SSR· SSG

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 fetch options and revalidate per data source
  • Split strategies for dynamic routes, personalization, and admin UIs

Table of contents

  1. Concepts
  2. Hands-on implementation
  3. Advanced: Route Segment Config
  4. Performance comparison
  5. Real-world cases
  6. Troubleshooting
  7. Conclusion

Concepts

Terminology (vs Pages Router intuition)

TermIntuitive meaningApp Router reality
SSGHTML at build (or regenerate) timePaths that can statically cache server component trees—often fetch(..., { cache: 'force-cache' }) patterns
SSRHTML per requestDynamic rendering—closer to cache: 'no-store' or dynamic = 'force-dynamic'
ISRPeriodic or on-demand static refreshfetch 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

SituationDirectionWhy
Marketing, docs, legalSSG + long revalidate or fully staticMax CDN hit ratio, stable TTFB
Dashboards, cartsSSR (no-store) or split client islandsPer-user data, avoid cache poisoning
Blogs, catalogsISR + revalidateTagBalance traffic vs freshness
Real-time inventory/pricingSSR + short TTL or edge + external cacheNext 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 + products tag; call revalidateTag('products') on price updates.
  • Logged-in header: no-store for 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 fetch cache vs revalidate vs CDN/hosting data caches.

revalidatePath did not update”

  • Verify matching segment tree and cache keystag-based invalidation is often more reliable.

“Almost leaked user data across sessions”

  • Never force-cache per-user fetches—use no-store or 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와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

SEO, Next.js, App Router, SSR, SSG, ISR, Caching, Technical SEO 등으로 검색하시면 이 글이 도움이 됩니다.