這是本系列最後一篇實作文,最後來實作一個微小但重要的功能:舊路徑轉址!
我的個人網站裡也有此系列的好讀版,程式碼更易讀、也支援深色模式和側邊目錄,歡迎前往閱讀!
部落格經營一段時間後,你可能因為各種原因,想要調整文章網址,例如:
把 https://easonchang.com/2016/12/13/grid-note
轉址到 https://easonchang.com/posts/grid-note
在 Next.js 要實作轉址(redirect)有兩種方式:
這裡我們希望轉址規則是每篇文章都能自訂,寫在文章 .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
,瀏覽你設定過的舊路徑,就能看你會直接重導向到正確的文章路徑,依然能順利瀏覽文章!
恭喜你成功在 Next.js Contentlayer blog 實作舊路徑轉址了!
也恭喜你完成這系列文章所有實作了!
下一篇,也就是最後一篇文章,我們會來總結與回顧這 30 篇文章,以及我們一同實作出的炫砲部落格!