使用next打造自己的博客
本文将介绍,如何使用next来打造自己的私人博客。
创建next项目
使用create-next-app来创建项目
pnpm create next-app
得到一个大概这样的目录结构
把首页的内容清一清,开始完成我们的博客系统
创建markdown文件夹
我们做开发,非常喜欢使用markdown来编写技术文档,在根目录下创建文件夹docs。然后随便加入几篇markdown文章。
我这边也准备好了
读取静态的markdown文件
这一步主要使用 nodejs 的文件模块来读取src/docs下的markdown文件,然后转换成html渲染到页面中
| 库名 | 功能 |
|---|---|
globby | 递归读取文件系统中的md文件 |
markdown-it | 将markdown内容转换为html |
prismjs | 代码高亮 |
dayjs | 日期函数 |
front-matter | 读取文件头的配置 |
pnpm i globby front-matter prismjs front-matter dayjs -D
- 创建文件
src/tool/index.ts来处理文章内容
import { globby } from "globby";
import { promises as fsp } from "fs";
import fm from "front-matter";
import p from "path";
import MarkdownIt from "markdown-it";
import Prism from "prismjs";
// 加载需要的语言
const loadLanguages = require("prismjs/components/");
loadLanguages(["go", "typescript", "ts", "go-module", "go-mod"]);
import dayjs from "dayjs";
// 初始化 MarkdownIt 实例
const md = new MarkdownIt({
html: true,
// 代码高亮
highlight: (code, lang) => {
// 获取语法解析器
const grammar = Prism.languages[lang] || Prism.languages.markup;
// 使用 Prism 对代码进行高亮
return Prism.highlight(code, grammar, lang);
},
});
/**
* 获取绝对路径
* @param {string} dir - 相对路径
* @returns {string} 绝对路径
*/
function absPath(dir: string) {
return p.isAbsolute(dir) ? dir : p.resolve(process.cwd(), dir);
}
/**
* 获取所有文章路径
* @returns {Promise<string[]>} 所有文章的相对路径
*/
export const readPostListPath = async () => {
const files2 = await globby(["docs/**/*.md"], {});
return files2.map((s) => s.replace(".md", ""));
};
/**
* 读取指定路径的 Markdown 文章内容,并解析 Front Matter 和正文,返回解析结果
* @param {string} path - Markdown 文章的相对路径
* @returns {Promise<postItem>} Markdown 文章的解析结果
*/
export const readMdContent = async (path: string) => {
const _path = p.join(absPath(""), path + ".md");
const data = await fsp.readFile(
// 处理一些读取文件出现的 bug
_path.replace(`\\docs\\docs`, "\\docs").replace(`/docs/docs/`, "/docs/"),
"utf8"
);
const matter = fm(data) as any;
const html = md.render(matter.body);
// 将日期格式化为时间戳
matter.attributes.date = dayjs(matter.attributes.date).valueOf();
// 将 Front Matter 和正文解析结果整合成一个对象返回
return {
html,
...matter.attributes,
path,
} as postItem;
};
/**
* 读取所有 Markdown 文章的内容,并解析 Front Matter 和正文,返回解析结果数组
* @returns {Promise<postItem[]>} Markdown 文章的解析结果数组
*/
export const readAllMdContent = async () =>
Promise.all((await readPostListPath()).map((path) => readMdContent(path)));
读取文章列表
在src\pages\index.tsx我们读取所有的文章内容
// 导入必要的实用函数和组件
import { readAllMdContent, readPostListPath } from '@/utils';
import { GetStaticProps, NextPage } from 'next';
import Link from 'next/link';
import { ReactElement } from 'react';
// 定义 getStaticProps 函数,用于为 Next.js 页面生成静态 props
export const getStaticProps: GetStaticProps = async (ctx) => {
// 读取所有 Markdown 文件的内容
const list = await readAllMdContent();
return {
props: {
list: list
}
};
};
// 定义 ListProps 类型,表示列表页面的 props
type ListProps = {
list: postItem[]
};
// 定义 List 组件,表示列表页面
const List: NextPage<ListProps> = ({ list }): ReactElement => {
// 遍历文章列表,对于每篇文章,渲染一个链接到该文章的列表项
return <div>{
list.map(({ title, path }) => <div key={path}>
<Link href={path}>{title}</Link>
</div>)
}</div>;
};
// 导出 List 组件
export default List;
getStaticProps 会在客户端页面渲染前调用,通过nodejs的能力,来获取到所有文章,生成一个列表
文章详情页面配置
创建文章组件,创建文件src/pages/docs/[...path].tsx,填写内容
[...path].tsx属于next中的动态路由,可以匹配docs/开头的所有路径
同时动态路由需要 导出getStaticPaths来告诉next需要生成多少页面。
// 导入必要的实用函数和组件
import { readMdContent, readPostListPath } from '@/utils';
import { GetStaticProps, NextPage } from 'next';
import { ReactElement, useMemo } from 'react';
// 定义 getStaticProps 函数,用于为 Next.js 页面生成静态 props
export const getStaticProps: GetStaticProps = async (ctx) => {
// 从上下文对象中提取路径参数
const { path } = ctx.params as any;
// 读取位于指定路径下的 Markdown 文件的内容
const data = await readMdContent(`/docs/${path.join('/')}`);
return {
props: {
data
}
};
};
// 定义 getStaticPaths 函数,用于为动态路由生成静态路径
export const getStaticPaths = async () => {
// 读取所有文章的路径列表,并将每个路径包装在 params 对象中
const paths = (await readPostListPath()).map(path => ({ params: { path: [path] } }));
return {
paths,
fallback: true
};
};
// 定义 PostPageProps 类型,表示文章页面的 props
type PostPageProps = {
data: postItem
};
// 定义 PostPage 组件,表示文章页面
const PostPage: NextPage<PostPageProps> = ({ data = {} as postItem }): ReactElement => {
return <div>
<div className='container-box vp-doc'>
<h1>{data.title}</h1>
<div dangerouslySetInnerHTML={{
__html: data.html
}}></div>
</div>
</div>;
};
// 导出 PostPage 组件
export default PostPage;
这样我们已经渲染了文章内容
博客的主要功能都已经完成了
参考文档