在 Next.js Contentlayer blog 實作舊路徑轉址 - Modern Next.js Blog 系列 #29
- Published on
本文同步發佈於 it 邦幫忙 2022 iThome 鐵人賽
這是本系列最後一篇實作文,最後來實作一個微小但重要的功能:舊路徑轉址!
Next.js Contentlayer blog 舊路徑轉址
部落格經營一段時間後,你可能因為各種原因,想要調整文章網址,例如:
在 Next.js 要實作轉址(redirect)有兩種方式:
- 在 next.config.js 羅列出所有轉址規則,參考官方文件「Data Fetching: getStaticProps | Next.js」
- 在 getStaticProps 或 getServerSideProps 裡根據自訂條件,回傳 redirect,參考官方文件「Data Fetching: getStaticProps | Next.js」
這裡我們希望轉址規則是每篇文章都能自訂,寫在文章 .mdx 檔案裡面,所以要搭配 Contentlayer 分析所有文章設定後,才能知道所有轉址規則。
我在嘗試後發現方法 1 行不通,因為沒辦法在 next.config.js 裡 import Contentlayer 提供的 allPosts。
所以我們這篇文章會採用方法 2,在 getStaticProps 裡決定是否 redirect。
實作舊路徑轉址
首先修改 contentlayer.config.ts
,在 Post 新增 redirectFrom 屬性,這樣就能在每篇文章 .mdx 裡設定各自的轉址規則:
已複製!// ... export const Post = defineDocumentType(() => ({ // ... fields: { // ... // 新增 redirectFrom redirectFrom: { type: "list", of: { type: "string" }, }, }, // ... })); // ...
新增 src/lib/getAllRedirects.ts
來取得所有需轉址的路徑:
已複製!import { allPosts } from "@/lib/contentLayerAdapter"; import { unifyPath } from "@/lib/unifyPath"; export type Redirect = { source: string; destination: string; permanent: boolean; }; export const getAllRedirects = () => { const redirects: Redirect[] = []; allPosts.forEach((post) => { const allRedirectFrom = post.redirectFrom?.map((from) => unifyPath(from)) || []; allRedirectFrom.forEach((from) => { redirects.push({ source: from, destination: post.path, permanent: false, }); }); }); return redirects; }; export const allRedirects = getAllRedirects();
新增 src/lib/unifyPath.ts
統一轉址 URL 格式:
已複製!// Discard leading and trailing slashes export const unifyPath = (path: string): string => { return '/' + path.replace(/\/$/, '').replace(/^\//, ''); };
新增 src/lib/stringifyCatchAllDynamicRoute.ts
將 route 轉成 string 方便後續處理:
已複製!export const stringifyCatchAllDynamicRoute = ( route: string | string[] | undefined ): string => { if (!route) return ""; if (Array.isArray(route)) return route.join("/"); return route; };
最後修改所有用到動態參數 Dynamic Routes 的 pages,目前只有文章內頁 [slug].tsx
需要修改。
修改 src/pages/posts/[slug].tsx
:
已複製!// ... import { allRedirects } from "@/lib/getAllRedirects"; import { unifyPath } from "@/lib/unifyPath"; // ... export const getStaticProps: GetStaticProps<Props, Params> = async ( context ) => { const { slug } = context.params!; const locale = context.locale!; // 新增下面這段重導向規則 // Handle redirect logic const path = unifyPath("/posts/" + slug); const matchedRedirectRule = allRedirects.find((rule) => rule.source === path); if (matchedRedirectRule) { return { redirect: { destination: matchedRedirectRule.destination, permanent: matchedRedirectRule.permanent, }, }; } // ... }; // ...
以及新增 src/pages/[...pathToRedirectFrom].tsx
,捕捉所有其他路徑:
已複製!import { GetStaticPaths, GetStaticProps } from "next"; import { ParsedUrlQuery } from "querystring"; import { allRedirects, Redirect } from "@/lib/getAllRedirects"; import { stringifyCatchAllDynamicRoute } from "@/lib/stringifyCatchAllDynamicRoute"; import { unifyPath } from "@/lib/unifyPath"; interface Params extends ParsedUrlQuery { pathToRedirectFrom: string | string[]; } export const getStaticPaths: GetStaticPaths = () => { return { paths: [], fallback: "blocking", }; }; export const getStaticProps: GetStaticProps<any, Params> = (context) => { const { pathToRedirectFrom } = context.params!; // Handle redirect logic const path = unifyPath(stringifyCatchAllDynamicRoute(pathToRedirectFrom)); const matchedRedirectRule: Redirect | undefined = allRedirects.find( (rule) => rule.source === path ); if (matchedRedirectRule) { return { redirect: { destination: matchedRedirectRule.destination, permanent: matchedRedirectRule.permanent, }, }; } return { notFound: true, }; }; const NullComponent = () => null; export default NullComponent;
設定各文章轉址規則
現在就能使用前面在 Contentlayer Post 新增的 redirectFrom 屬性來指定轉址規則。
例如我把 content/posts/20220904-custom-link-demo.mdx
設定如下:
已複製!--- // ... type: Post slug: custom-link-demo redirectFrom: - /old-custom-link/ - /2022/08/01/custom-link/ - /posts/old-custom-link/ --- Content...
slug
定義了文章現在的路徑,會是 http://localhost:3000/posts/custom-link-demo
而 redirectFrom
指定了下列 3 條舊路徑,都會導向最新文章路徑:
- http://localhost:3000/old-custom-link/
- http://localhost:3000/2022/08/01/custom-link/
- http://localhost:3000/posts/old-custom-link/
成果
這樣就完成了!使用 pnpm dev
,瀏覽你設定過的舊路徑,就能看你會直接重導向到正確的文章路徑,依然能順利瀏覽文章!
References
小結&下一篇
恭喜你成功在 Next.js Contentlayer blog 實作舊路徑轉址了!
也恭喜你完成這系列文章所有實作了!
下一篇,也就是最後一篇文章,我們會來總結與回顧這 30 篇文章,以及我們一同實作出的炫砲部落格!