在 Next.js Contentlayer blog 實作舊路徑轉址 - Modern Next.js Blog 系列 #29

Published on

本文同步發佈於 it 邦幫忙 2022 iThome 鐵人賽

這是本系列最後一篇實作文,最後來實作一個微小但重要的功能:舊路徑轉址!

這篇修改的程式碼如下: https://github.com/Kamigami55/nextjs-tailwind-contentlayer-blog-starter/compare/day28-i18next...day29-old-post-path-redirect


Next.js Contentlayer blog 舊路徑轉址

部落格經營一段時間後,你可能因為各種原因,想要調整文章網址,例如:

https://easonchang.com/2016/12/13/grid-note

轉址到 https://easonchang.com/posts/grid-note

在 Next.js 要實作轉址(redirect)有兩種方式:

  1. 在 next.config.js 羅列出所有轉址規則,參考官方文件「Data Fetching: getStaticProps | Next.js
  2. 在 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 條舊路徑,都會導向最新文章路徑:

成果

這樣就完成了!使用 pnpm dev,瀏覽你設定過的舊路徑,就能看你會直接重導向到正確的文章路徑,依然能順利瀏覽文章!

這篇修改的程式碼如下: https://github.com/Kamigami55/nextjs-tailwind-contentlayer-blog-starter/compare/day28-i18next...day29-old-post-path-redirect

References

小結&下一篇

恭喜你成功在 Next.js Contentlayer blog 實作舊路徑轉址了!

也恭喜你完成這系列文章所有實作了!

下一篇,也就是最後一篇文章,我們會來總結與回顧這 30 篇文章,以及我們一同實作出的炫砲部落格!