做緊 Next.js blog 嘅時候成日有人問:用咗
force-dynamic會唔會影響 SEO?答案係:唔會! 呢篇文章會詳細解釋點解。
TL;DR
好多人以為 server-side rendering (SSR) 同 client-side rendering (CSR) 一樣會影響 SEO,但其實完全唔同。Next.js 嘅 force-dynamic 係喺 server 度生成完整 HTML,搜尋引擎 crawlers 收到嘅同 static site 一模一樣——只不過生成 HTML 嘅時間唔同而已。
核心重點:
- ✅ SSR (force-dynamic) 對 SEO 好:Full HTML + metadata + structured data
- ❌ CSR 對 SEO 差:Empty HTML,要等 JS load 先有內容
- 🎯 關鍵分別:HTML 喺邊度生成(server vs client)
三種 Rendering 策略比較
Next.js 提供三種主要嘅 rendering 方式,每種對 SEO 嘅影響都唔同:
| Rendering Type | SEO | 點樣運作 | HTML 生成時間 |
|---|---|---|---|
| Static (SSG) | ✅ 最好 | Build time 預先生成 HTML | Build time |
| Dynamic (SSR) ← 我哋用緊 | ✅ 好 | 每次 request 喺 server 生成 HTML | Request time (server) |
| Client-side (CSR) | ❌ 差 | HTML 空白,靠 JS load 完先 fetch data | After page load (client) |
詳細流程對比
Static Site Generation (SSG)
Build Time:
└─ Next.js builds all pages
└─ Generates complete HTML files
└─ Stores in CDN
Request Time:
└─ CDN serves pre-built HTML
└─ Browser/crawler receives full content immediately
優點: 最快、SEO 最好
缺點: 內容靜態,唔適合 dynamic data(例如從 Notion API fetch)
Server-Side Rendering (SSR) — force-dynamic
Build Time:
└─ No HTML pre-generated
Request Time:
└─ Server receives request
└─ Server fetches data (e.g., Notion API)
└─ Server renders React components to HTML
└─ Server sends complete HTML to client
└─ Browser/crawler receives full content
優點: Dynamic content + 保持 SEO
缺點: 每次 request 都要 server render(慢少少)
Client-Side Rendering (CSR)
Build Time:
└─ Minimal HTML shell
Request Time:
└─ Server sends empty HTML + JS bundle
└─ Browser loads JS
└─ JS executes, fetches data
└─ React renders content on client
└─ Content finally appears
優點: Interactive、減少 server load
缺點: SEO 災難 — Crawlers 見到空白頁面
force-dynamic 點樣運作?
Next.js App Router 入面,force-dynamic 係一個 route segment config,用嚟 force 個 route 做 dynamic rendering。
基本用法
// app/blog/[slug]/page.tsx
export const dynamic = 'force-dynamic'
export default async function BlogPost({ params }) {
// Fetch from Notion API (or any dynamic source)
const post = await fetchNotionPage(params.slug)
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
)
}
完整 Request Lifecycle
當 Googlebot 或者用戶訪問你嘅 blog page:
1. Request 到達 Next.js server
GET /blog/nextjs-ssr-seo-force-dynamic
2. Server 執行 page component
- 因為有
force-dynamic,唔會 cache - 執行
fetchNotionPage(slug) - 等 API response(Notion data)
3. Server 將 React component render 成 HTML
<!DOCTYPE html>
<html>
<head>
<title>Next.js Server-Side Rendering 同 SEO</title>
<meta name="description" content="深入探討...">
<meta property="og:title" content="...">
<script type="application/ld+json">
{"@type": "BlogPosting", "headline": "..."}
</script>
</head>
<body>
<article>
<h1>Next.js Server-Side Rendering 同 SEO</h1>
<p>做緊 Next.js blog 嘅時候...</p>
<!-- Full content here! -->
</article>
</body>
</html>
4. Server 返回完整 HTML
- Status: 200 OK
- Content-Type: text/html
- Body: 上面嗰個完整 HTML
5. Crawler/Browser 收到
- 所有內容已經喺 HTML 入面
- 唔需要等 JS
- 唔需要 client-side fetch
點解 SSR 對 SEO 冇影響?
搜尋引擎睇到啲咩?
無論你用 SSG 定 SSR,Google/Bing crawlers 收到嘅都係完整 HTML:
| Element | SSG | SSR (force-dynamic) | CSR |
|---|---|---|---|
<title> tag | ✅ Present | ✅ Present | ❌ Generic/Empty |
<meta> description | ✅ Full text | ✅ Full text | ❌ Missing |
| OG tags (social) | ✅ Complete | ✅ Complete | ❌ Missing |
| JSON-LD structured data | ✅ Embedded | ✅ Embedded | ❌ Missing |
Content <h1>, <p>, etc. | ✅ Full content | ✅ Full content | ❌ Empty or loading spinner |
實際例子:Crawler 收到嘅 HTML
SSR with force-dynamic (✅ Good for SEO)
<!-- Googlebot fetch 嘅結果 -->
<!DOCTYPE html>
<html lang="zh-HK">
<head>
<title>Next.js Server-Side Rendering 同 SEO:force-dynamic 點樣保持搜尋引擎優化 | Billy Tse</title>
<meta name="description" content="深入探討 Next.js 三種 rendering 策略...">
<meta property="og:title" content="Next.js Server-Side Rendering 同 SEO">
<meta property="og:description" content="深入探討...">
<meta property="og:image" content="https://example.com/og-image.jpg">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Next.js Server-Side Rendering 同 SEO",
"author": {"@type": "Person", "name": "Billy Tse"},
"datePublished": "2026-02-01"
}
</script>
</head>
<body>
<article>
<h1>Next.js Server-Side Rendering 同 SEO:force-dynamic 點樣保持搜尋引擎優化</h1>
<p>做緊 Next.js blog 嘅時候成日有人問:用咗 force-dynamic 會唔會影響 SEO?</p>
<!-- 所有內容都喺呢度! -->
</article>
</body>
</html>
CSR (❌ Bad for SEO)
<!-- Googlebot fetch 嘅結果 -->
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
<!-- Content 要等 JS load 同 execute 先會出現! -->
</body>
</html>
搜尋引擎見到空白 → SEO 災難
關鍵誤解:動態唔等於 Client-Side
好多人混淆咗呢兩個概念:
| 誤解 | 事實 |
|---|---|
| "Dynamic data 一定要 client-side fetch" | ❌ 錯!可以 server-side fetch (SSR) |
| "force-dynamic 會令 SEO 變差" | ❌ 錯!HTML 一樣係 server 生成 |
| "只有 SSG 先對 SEO 好" | ❌ 錯!SSR 都一樣好 |
| "SSR 同 CSR 一樣" | ❌ 完全唔同!SSR = server render, CSR = client render |
真正影響 SEO 嘅係:HTML 喺邊度生成?
🎯 核心概念
SEO 睇嘅係:當 crawler request 你個 page,server 返嘅 HTML 入面有冇內容?
✅ Server 生成 HTML (SSG/SSR):有內容 → SEO 好
❌ Client 生成 HTML (CSR):冇內容 → SEO 差
force-dynamic係 server-side,所以 SEO 完全冇問題!
Next.js Metadata API + force-dynamic
Next.js App Router 提供 Metadata API 嚟生成 SEO tags。即使用咗 force-dynamic,呢啲 metadata 都係 server-side 生成。
動態 Metadata 例子
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
export const dynamic = 'force-dynamic'
// 👇 Server-side function,每次 request 都會執行
export async function generateMetadata({ params }): Promise<Metadata> {
// Fetch dynamic data from Notion
const post = await fetchNotionPage(params.slug)
return {
title: post.title,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
images: [post.coverImage],
type: 'article',
publishedTime: post.publishedDate,
authors: [post.author],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.description,
images: [post.coverImage],
},
}
}
export default async function BlogPost({ params }) {
const post = await fetchNotionPage(params.slug)
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML= __html: post.content />
</article>
)
}
生成嘅 HTML
<head>
<!-- Next.js 自動將 metadata 轉成 HTML tags -->
<title>Next.js Server-Side Rendering 同 SEO | Billy Tse Blog</title>
<meta name="description" content="深入探討 Next.js 三種 rendering 策略...">
<!-- Open Graph (Facebook, LinkedIn) -->
<meta property="og:title" content="Next.js Server-Side Rendering 同 SEO">
<meta property="og:description" content="深入探討...">
<meta property="og:image" content="https://example.com/cover.jpg">
<meta property="og:type" content="article">
<meta property="article:published_time" content="2026-02-01">
<meta property="article:author" content="Billy Tse">
<!-- Twitter Cards -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Next.js Server-Side Rendering 同 SEO">
<meta name="twitter:description" content="深入探討...">
<meta name="twitter:image" content="https://example.com/cover.jpg">
</head>
所有呢啲 tags 都係 server-side 生成,crawler 直接見到!
Structured Data (JSON-LD)
除咗 meta tags,你仲可以加 structured data 俾 Google Rich Results。
實現方法
// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
const post = await fetchNotionPage(params.slug)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.description,
image: post.coverImage,
datePublished: post.publishedDate,
dateModified: post.modifiedDate,
author: {
'@type': 'Person',
name: post.author,
url: 'https://billytse.dev',
},
publisher: {
'@type': 'Organization',
name: 'Billy Tse Blog',
logo: {
'@type': 'ImageObject',
url: 'https://billytse.dev/logo.png',
},
},
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML= __html: JSON.stringify(jsonLd)
/>
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
</>
)
}
效果
Google 會用呢啲 structured data 顯示 rich snippets:
- ⭐ 文章評分
- 📅 發佈日期
- 👤 作者資訊
- 🖼️ 預覽圖片
- ⏱️ 閱讀時間
所有呢啲都係 server-side 生成,SEO 完全保留!
實際效能考量
雖然 SSR 對 SEO 冇影響,但對 performance 有啲考量:
SSG vs SSR 效能
| Metric | SSG | SSR |
|---|---|---|
| TTFB (Time to First Byte) | ~10-50ms (CDN) | ~200-1000ms (depends on API) |
| Server Load | 無(serve static files) | 每次 request 都要 render |
| Scalability | 極好(CDN edge caching) | 需要 server capacity |
| Data Freshness | Build time snapshot | Real-time |
點樣優化 SSR?
- 用 React Cache
import { cache } from 'react'
// Cache function results during request
const getNotionPage = cache(async (slug: string) => {
return await notionAPI.getPage(slug)
})
- 用 Next.js unstable_cache(跨 request cache)
import { unstable_cache } from 'next/cache'
const getCachedPage = unstable_cache(
async (slug) => await fetchNotionPage(slug),
['notion-page'],
{ revalidate: 60 } // Cache 60 seconds
)
- Partial Prerendering (Next.js 14+)
// app/blog/[slug]/page.tsx
export const experimental_ppr = true
export default function BlogPost({ params }) {
return (
<>
{/* Static shell renders immediately */}
<Header />
{/* Dynamic content streams in */}
<Suspense fallback={<Skeleton />}>
<BlogContent slug={params.slug} />
</Suspense>
</>
)
}
你嘅 Blog 係點樣嘅?
基於你嘅 setup(Next.js + Notion API + force-dynamic),以下係實際情況:
✅ 你有齊所有 SEO 要素
1. Server Components
- 你嘅 blog page 係 async server component
- React 喺 server 度 render,唔係 client
2. Complete HTML
// 每次 Googlebot request:
Server fetches from Notion API
↓
Server renders React to HTML
↓
Sends complete HTML (title, meta, content, everything!)
↓
Googlebot sees full page ✅
3. Metadata Generated Server-Side
<title>tag: ✅<meta name="description">: ✅- Open Graph tags: ✅
- 所有 metadata 都係 server 生成
4. JSON-LD Structured Data
- 如果你有加 BlogPosting schema
- 都係 server-side 生成 ✅
🎯 結論
用咗 force-dynamic 對 SEO 完全冇負面影響!
唯一嘅分別:
- SSG:HTML 喺 build time 生成一次
- SSR (force-dynamic):HTML 喺 request time 生成
但對搜尋引擎嚟講,兩者收到嘅都係完整 HTML,效果一樣。
驗證方法
想確認你嘅 SSR 對 SEO 有效?試下呢啲工具:
1. View Page Source(最簡單)
喺瀏覽器:
- Right-click → "View Page Source"
- 檢查 HTML 入面有冇你嘅內容
如果你見到:
<h1>Next.js Server-Side Rendering 同 SEO</h1>
<p>做緊 Next.js blog 嘅時候成日有人問...</p>
→ ✅ SSR 運作正常,SEO 冇問題
如果你淨係見到:
<div id="root"></div>
→ ❌ 呢個係 CSR,SEO 有問題
2. Google Search Console
- 用 URL Inspection Tool
- Request indexing
- 睇 "View crawled page"
- 確認 Google 見到你嘅內容
3. Rich Results Test
- 去 Google Rich Results Test
- 輸入你嘅 blog URL
- 檢查 structured data 有冇 detect 到
4. curl 測試
curl https://your-blog.com/blog/your-post | grep "<title>"
如果 return 到你嘅 title → ✅ Server 有返 HTML
5. Chrome DevTools Network Tab
- Open DevTools → Network
- Hard reload (Cmd+Shift+R / Ctrl+Shift+R)
- Click on the initial document request
- 睇 Response tab
- 確認 HTML response 已經有晒內容
常見問題 (FAQ)
Q1: force-dynamic 會唔會令 Google ranking 跌?
A: 唔會。Google 主要睇:
- Content quality(內容質素)
- User experience(用戶體驗)
- Page speed(載入速度)
- Mobile-friendliness(手機兼容)
SSR 可能會慢少少(TTFB),但係:
- HTML 一樣係 complete
- Content 一樣係 indexable
- 對 ranking 影響極小
如果你擔心速度,可以用 caching 優化。
Q2: 使唔使改用 SSG?
A: 睇情況:
用 SSG 如果:
- 內容唔常改(例如每日一次 rebuild 都 OK)
- 想極致速度(CDN edge caching)
- 有固定數量嘅 pages(唔係無限)
用 SSR 如果:
- 需要 real-time data(例如 Notion API 即時更新)
- Pages 數量太多(build time 會好長)
- 需要 per-request customization
對你嚟講,如果 Notion 係你嘅 CMS,SSR 可能更合適。
Q3: 可唔可以混合 SSG 同 SSR?
A: 可以!Next.js 支持 per-route configuration:
// app/blog/page.tsx (List page - SSG)
export const dynamic = 'force-static'
// app/blog/[slug]/page.tsx (Post page - SSR)
export const dynamic = 'force-dynamic'
或者用 ISR (Incremental Static Regeneration):
export const revalidate = 3600 // Rebuild every hour
Q4: Server Components 同 SSR 有咩分別?
A: 呢兩個係唔同層面嘅概念:
Server Components:React component 喺 server 執行
SSR:整個 page 喺 server render
喺 Next.js App Router:
- 所有 components 預設係 Server Components
force-dynamic決定幾時 render (every request vs build time)
你可以有:
- Server Components + SSG(build time render)
- Server Components + SSR(request time render)← 你呢個
Q5: 用咗 use client 會唔會影響 SEO?
A: 睇你用喺邊度:
❌ 如果成個 page 都係 client component:
'use client'
export default function BlogPost() {
const [post, setPost] = useState(null)
useEffect(() => {
fetch('/api/post').then(r => setPost(r))
}, [])
return <div>{post?.content}</div>
}
→ SEO 差,因為 content 係 client-side fetch
✅ 如果只係部分 interactive components:
// page.tsx (Server Component)
export default async function BlogPost() {
const post = await fetchNotionPage() // Server-side
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
<LikeButton /> {/* Client Component */}
</article>
)
}
// LikeButton.tsx
'use client'
export default function LikeButton() {
const [likes, setLikes] = useState(0)
return <button onClick={() => setLikes(l => l + 1)}>Like ({likes})</button>
}
→ SEO 好,因為 main content 係 server-side
💡 最佳實踐:
Main content = Server Components
Interactive UI (buttons, forms) = Client Components
呢樣嘢叫 "partial hydration",兼顧 SEO 同 interactivity
總結
核心重點
- force-dynamic 唔會影響 SEO
- HTML 依然係 server-side 生成
- Crawlers 收到完整 content
- Metadata 同 structured data 保留
- 真正影響 SEO 嘅係 rendering 位置
- ✅ Server rendering (SSG/SSR) → 好
- ❌ Client rendering (CSR) → 差
- 你嘅 blog setup 完全 OK
- Server Components ✅
- Full HTML ✅
- Metadata ✅
- JSON-LD ✅
何時擔心 SEO?
❌ 唔使擔心:
- 用
force-dynamic - 用 Server Components
- 用 Next.js Metadata API
- 從 Notion API fetch data (server-side)
⚠️ 要擔心:
- 用
use client+useEffectfetch data - Empty HTML shell
- JavaScript-only rendering
- Missing meta tags
下一步建議
如果你想進一步優化:
- 加 caching 減少 Notion API calls
- 用 Partial Prerendering 平衡速度同 freshness
- 加 JSON-LD 爭取 rich snippets
- 用 Google Search Console 監察 indexing
- 測試 mobile performance(Core Web Vitals)
相關資源
- 📘 Next.js Rendering Documentation
- 🔍 Google Search Central - JavaScript SEO
- 📊 Google Rich Results Test
- 🎯 Next.js Metadata API
- ⚡ Web.dev: Rendering on the Web
希望呢篇文章解答咗你對 Next.js SSR 同 SEO 嘅疑問。記住:只要 HTML 係 server 生成,SEO 就冇問題!