如何實作增量靜態再生 (ISR)
增量靜態再生 (ISR) 讓您能夠:
- 更新靜態內容而無需重建整個網站
- 透過提供預渲染的靜態頁面來減少伺服器負載
- 確保自動為頁面添加正確的
cache-control
標頭 - 處理大量內容頁面而無需長時間的
next build
以下是一個最小範例:
import type { GetStaticPaths, GetStaticProps } from 'next'
interface Post {
id: string
title: string
content: string
}
interface Props {
post: Post
}
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
const paths = posts.map((post: Post) => ({
params: { id: String(post.id) },
}))
// 我們只會在建置時預渲染這些路徑
// { fallback: 'blocking' } 會在路徑不存在時按需伺服器渲染頁面
return { paths, fallback: false }
}
export const getStaticProps: GetStaticProps<Props> = async ({
params,
}: {
params: { id: string }
}) => {
const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
(res) => res.json()
)
return {
props: { post },
// Next.js 會在請求進來時使快取失效
// 最多每 60 秒一次
revalidate: 60,
}
}
export default function Page({ post }: Props) {
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
export async function getStaticPaths() {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// 我們只會在建置時預渲染這些路徑
// { fallback: false } 表示其他路由應回傳 404
return { paths, fallback: false }
}
export async function getStaticProps({ params }) {
const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
(res) => res.json()
)
return {
props: { post },
// Next.js 會在請求進來時使快取失效
// 最多每 60 秒一次
revalidate: 60,
}
}
export default function Page({ post }) {
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
這個範例的運作方式如下:
- 在
next build
期間,所有已知的部落格文章會被生成(此範例中有 25 篇) - 對這些頁面的所有請求(例如
/blog/1
)會被快取且立即回應 - 60 秒過後,下一個請求仍會顯示快取的(過期)頁面
- 快取失效後,新版本的頁面會在背景開始生成
- 成功生成後,Next.js 會顯示並快取更新後的頁面
- 如果請求
/blog/26
,Next.js 會按需生成並快取此頁面
參考資料
函式
範例
使用 res.revalidate()
進行按需驗證
對於更精確的重新驗證方法,使用 res.revalidate
從 API 路由按需生成新頁面。
例如,可以呼叫 /api/revalidate?secret=<token>
這個 API 路由來重新驗證指定的部落格文章。建立一個只有您的 Next.js 應用程式知道的秘密令牌。這個秘密將用於防止未經授權的存取重新驗證 API 路由。
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// 檢查秘密以確認這是有效的請求
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: '無效的令牌' })
}
try {
// 這應該是實際路徑而非重寫路徑
// 例如對於 "/posts/[id]" 這應該是 "/posts/1"
await res.revalidate('/posts/1')
return res.json({ revalidated: true })
} catch (err) {
// 如果發生錯誤,Next.js 會繼續
// 顯示最後成功生成的頁面
return res.status(500).send('重新驗證錯誤')
}
}
export default async function handler(req, res) {
// 檢查秘密以確認這是有效的請求
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: '無效的令牌' })
}
try {
// 這應該是實際路徑而非重寫路徑
// 例如對於 "/posts/[id]" 這應該是 "/posts/1"
await res.revalidate('/posts/1')
return res.json({ revalidated: true })
} catch (err) {
// 如果發生錯誤,Next.js 會繼續
// 顯示最後成功生成的頁面
return res.status(500).send('重新驗證錯誤')
}
}
如果您使用按需重新驗證,則不需要在 getStaticProps
中指定 revalidate
時間。Next.js 會使用預設值 false
(不重新驗證),並且只在呼叫 res.revalidate()
時按需重新驗證頁面。
處理未捕獲的例外
如果在處理背景重新生成時 getStaticProps
中發生錯誤,或者您手動拋出錯誤,則會繼續顯示最後成功生成的頁面。在下一個後續請求中,Next.js 會重試呼叫 getStaticProps
。
import type { GetStaticProps } from 'next'
interface Post {
id: string
title: string
content: string
}
interface Props {
post: Post
}
export const getStaticProps: GetStaticProps<Props> = async ({
params,
}: {
params: { id: string }
}) => {
// 如果此請求拋出未捕獲的錯誤,Next.js 將
// 不會使目前顯示的頁面失效,並且
// 在下一個請求時重試 getStaticProps
const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
const post: Post = await res.json()
if (!res.ok) {
// 如果有伺服器錯誤,您可能想要
// 拋出錯誤而非回傳,以便快取不會更新
// 直到下一次成功的請求
throw new Error(`取得文章失敗,收到狀態碼 ${res.status}`)
}
return {
props: { post },
// Next.js 會在請求進來時使快取失效
// 最多每 60 秒一次
revalidate: 60,
}
}
export async function getStaticProps({ params }) {
// 如果此請求拋出未捕獲的錯誤,Next.js 將
// 不會使目前顯示的頁面失效,並且
// 在下一個請求時重試 getStaticProps
const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
const post = await res.json()
if (!res.ok) {
// 如果有伺服器錯誤,您可能想要
// 拋出錯誤而非回傳,以便快取不會更新
// 直到下一次成功的請求
throw new Error(`取得文章失敗,收到狀態碼 ${res.status}`)
}
return {
props: { post },
// Next.js 會在請求進來時使快取失效
// 最多每 60 秒一次
revalidate: 60,
}
}
自訂快取位置
如果您想將快取的頁面和資料持久化到持久儲存,或者在 Next.js 應用程式的多個容器或實例之間共享快取,可以配置 Next.js 快取位置。了解更多。
疑難排解
在本地開發中除錯快取資料
如果您使用 fetch
API,可以添加額外的日誌記錄來了解哪些請求被快取或未快取。了解更多關於 logging
選項的資訊。
module.exports = {
logging: {
fetches: {
fullUrl: true,
},
},
}
驗證正確的生產環境行為
若要驗證您的頁面在生產環境中是否正確快取及重新驗證,可以在本地執行 next build
後再執行 next start
來運行 Next.js 的生產環境伺服器。
這將讓您測試 ISR (Incremental Static Regeneration) 行為在生產環境中的運作方式。如需進一步除錯,請在 .env
檔案中加入以下環境變數:
NEXT_PRIVATE_DEBUG_CACHE=1
這會讓 Next.js 伺服器在主控台記錄 ISR 快取命中與未命中的情況。您可以檢查輸出內容,查看哪些頁面是在 next build
時生成的,以及當路徑被按需存取時頁面是如何更新的。
注意事項
- ISR 僅在使用 Node.js 運行環境 (預設) 時支援。
- 建立 靜態匯出 (Static Export) 時不支援 ISR。
- 中介軟體 (Middleware) 不會在按需 ISR 請求時執行,這意味著任何路徑重寫或中介軟體中的邏輯都不會生效。請確保您重新驗證的是確切路徑。例如
/post/1
而非重寫後的/post-1
。
平台支援
部署選項 | 是否支援 |
---|---|
Node.js 伺服器 | 是 |
Docker 容器 | 是 |
靜態匯出 | 否 |
轉接器 (Adapters) | 依平台而定 |
了解如何在使用 Next.js 自架時 設定 ISR。
版本歷史
版本 | 變更 |
---|---|
v14.1.0 | 自訂 cacheHandler 功能穩定。 |
v13.0.0 | 引入 App Router。 |
v12.2.0 | Pages Router: 按需 ISR 功能穩定。 |
v12.0.0 | Pages Router: 新增 Bot-aware ISR 回退機制。 |
v9.5.0 | Pages Router: 引入穩定的 ISR 功能。 |