稍微複雜一些的機制。
簡述
再解釋之前先說一下什麼時候會用到 Fallback?舉例來說,假設我們有一個這樣的網頁:
首頁是一個食譜列表,接著點進去以後可以看到詳細內容,非常的簡單,而這些資料都是串接 CMS 來產生出的靜態資料(SSG)。
那麼現在思考一個問題:
假設我在 CMS 上加了一個新食譜,那我該怎麼同步到網頁上?
這邊會碰到的問題是「詳細頁面」的內容是透過網址上的 slug
來抓取的,所以會需要:
- 產生新的頁面(
recipes/peanu-butter-cookie.html
)
- 把對應的資料抓取下來
所以這部分的 code 會大概長這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| export async function getStaticPaths() { const response = await client.getEntries({ content_type: 'recipe' }) const paths = response.items.map((recipes) => ({ params: { slug: recipes.fields.slug } })) return { paths, fallback: false } }
export async function getStaticProps({ params }) { const response = await client.getEntries({ content_type: 'recipe', 'fields.slug': params.slug })
return { props: { recipe: response.items[0] }, revalidate: 1 } }
|
這邊雖然有利用「Incremental Static Regeneration」來做自動更新內容,但之前有說過他只能幫你更新現有的頁面,沒辦法幫你產生新的 page。
所以假設我現在新增了一個叫「abc」的食譜,接著造訪的話只會得到 404 的結果,因為這是一個全新的 page(recipes/abc.html
),而 Incremental Static Regeneration 不會幫我產生新的 page。
所以要更新的話就是把整個專案重新 build 以後在重新 deploy 一次。因為是重新產生所有資料,因此內容就會更新。
這樣確實是一種方式。但有沒有更好的作法?像是自動幫我抓取新資料,接著產生對應的頁面?
有,就是本篇的標題 fallback,他就是用來做這件事的。
使用 Fallback
眼尖的話應該能注意到剛剛的範例中其實就有出現 fallback
,不過值是 false
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export async function getStaticPaths() { const response = await client.getEntries({ content_type: 'recipe' }) const paths = response.items.map((recipes) => ({ params: { slug: recipes.fields.slug } })) return { paths, fallback: false } }
|
設為 false
的行為就是跳轉到 404 頁面,意思是當我們造訪不存在的頁面(例如:recipe/abc
)時,因為找不到對應頁面所以就直接導向 404 頁面。
重點來了,現在我們希望他能「自動抓取新資料 & 產生頁面」的功能,因此要把他設為 true
。
設為 true
的話當我們在造訪 recipe/abc
時,他會做下面幾件事:
- 進入 Fallback Page(通常會做一個 Loading 效果)
- 重新執行
getStaticProps
,抓取 abc
的食譜資料
- 把回傳結果傳入 props,把
recipes/abc
的頁面渲染出來給使用者
- 自動在 Server 產生新的
recipes/abc.html
,下次就不需要在跑這個流程
附註:如果在第三步抓資料這一段出問題的話,可以做重新導向的動作。
所以我們只要這樣子改寫就好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| export async function getStaticPaths() {
return { paths, fallback: true } }
export async function getStaticProps({ params }) { const response = await getData()
if (!response.items.length) { return { redirect: { destination: '/', permanent: false } } }
return { props: { recipe: response.items[0] }, revalidate: 1 } }
export default function RecipeDetails({ recipe }) { if (!recipe) { return <Skeleton /> } const { title, cookingTime, featureImage, ingredients, method } = recipe.fields
return <div>{/*...*/}</div> }
|
這樣子就完成啦,我覺得還蠻划算的,只需要簡單加個幾行就能實現。