前言
这是我个人博客网站的第四次改版了。相对于之前的版本,这次非常简陋,基本没有接口相关的内容,也没有数据库相关的东西,之所以变成这样,是因为我觉得我把之前的网站写的太过于复杂,没有必要实现完整的登录、在线编辑等功能,而且我发现我用代码编辑器反而有写作的欲望,所以进行了这次改版。
这一次我选择了 Astro 替代 Next.js 作为我个人网站的开发框架,原因在于 Next.js 的开发体验不太好,首先是开发服务启动就非常的慢,再加上 Next.js 的各种缓存的规则让人挺头疼的,总是出现莫名其妙的数据不更新问题。
选择 Astro 的原因在于它非常适合个人博客类型的网站,也不和特定框架绑定,所以想要试一下。
一. 给 Markdown 文件添加自定义字段
Astro 中提供了两种方式渲染 Markdown 文件,一种是直接写在 pages 目录下,另一种是使用内容集合。
直接写在 pages 目录的 Markdown 文件会被直接渲染成页面,而内容集合的方式更加灵活,类似于去通过 API 方式读取文件内容,然后由开发者去决定如何渲染。
我在这个项目中就使用了内容集合的方式去渲染 Markdown 文件,原因就是我需要给每个 Markdown 文件添加一个封面图,这个图片是项目中的图片资源,而不是外部的图片资源。
第一步:创建一个用于存放 Markdown 文件的目录
我的目录结构如下:
|-- src |-- data |-- notes |-- xxx.md |-- yyy.md |-- ...
第二步:定义 Markdown 文件的 Frontmatter 类型
import type { ImageMetadata } from 'astro';
export interface MarkdownFrontmatter { title: string; subtitle: string; author: string; tags: string[]; type: string; cover: ImageMetadata;}
cover 字段就是需要添加的自定义字段,它的类型是 ImageMetadata,这个类型是 Astro 提供的,它的作用是用来表示图片的元数据。
第三步:定义集合
详细的步骤可以查询文档 Content collections
import type { CollectionConfig } from 'astro:content';
import { defineCollection, z } from "astro:content";import { glob } from 'astro/loaders';
/* * 首先定义好集合的分类,比如说笔记按照内容进行分类,我目前有 4 个分类,分别是 'react', 'javascript', 'css', 'fullstack' * as const 的意思是将数组中的元素转换成字面量类型。**/const NOTES_TYPE = [ 'react', 'javascript', 'css', 'fullstack'] as const;
/** * 获取 Markdown 文件的路径 * 我的文件路径是这样 * src/data/notes/react/xxx.md * src/data/notes/javascript/xxx.md * src/data/notes/css/xxx.md * src/data/notes/fullstack/xxx.md * 所以我需要根据 type 参数来拼接路径*/const getNotesCollectionPath = (type?: typeof NOTES_TYPE[number]) => { return type ? `./src/data/notes/${type}` : './src/data/notes'};
/** * 定义集合*/const createNoteCollection = (type?: typeof NOTES_TYPE[number]) => defineCollection({ loader: glob({ pattern: '**/[^_]*.md', base: getNotesCollectionPath(type) }), schema: ({ image }) => z.object({ title: z.string(), subtitle: z.string(), author: z.string(), tags: z.array(z.string()), type: z.string(), // 这里就是图片类型的定义方式 cover: image(), }),});
/** * 最后导出集合 * 这样集合中就包含了不同分类的 Markdown 文件,后续可以通过 getCollection 获取某一种分类的 Markdown 文件,比如: * const notes = await getCollection('react');*/const notes: Partial<Record<typeof NOTES_TYPE[number] | 'all', CollectionConfig<unknown>>> = { all: createNoteCollection()};
NOTES_TYPE.forEach(type => { notes[type] = createNoteCollection(type);});
export const collections = { ...notes};
第四步:生成类型文件
定义完集合之后,需要生成类型文件,这样才能在后续的代码中使用,生成的方式是执行一下这个命令:
pnpm astro sync
如果用不同的包管理器,自行替换即可。
这一步不是必须的,当你执行:astro dev
, astro build
或者 astro check
会自动执行 sync 命令,但是我在开发的时候遇到过类型没有更新的问题,所以这里还是说一下。
第五步:创建 Markdown 文件并使用 cover
创建 Markdown 文件
---title: '实现 UnoCSS 网站的颜色渐变效果'subtitle: 'CSS Animation: When you spend hours on keyframes, but end up making a button bounce.'author: 'Caisr'tags: ["CSS", "Animation"]type: 'css'cover: '~/assets/images/cover/cover-1.webp'---
内容
这样定义完成后就可以直接去用 assets 目录下的图片了。甚至可以用路径别名。使用方法如下:
// 获取单个 Markdown 文件内容const note = await getEntry('react', 'server-components');// cover: {// src: '/@fs/E:/caisr/code/diary-of-madao/src/assets/images/cover/cover-4.jpg?origWidth=3456&origHeight=5184&origFormat=jpg',// width: 3456,// height: 5184,// format: 'jpg'// }const cover = note.data.cover;
cover 可以直接传给 <Image />
组件。
<Image src={cover} alt="notes cover" />
二. 给 Markdown 文件添加更新时间
这个功能也是属于自定义字段,但是是通过插件的方式进行添加的,官方文档有详细介绍:
我也用了这种方式实现,不过我是添加了2个时间字段,一个是更新时间,一个是创建时间。
import { statSync } from "fs";
export function remarkModifiedTime() { return function (tree, file) { const filepath = file.history[0]; const result = statSync(filepath); file.data.astro.frontmatter.lastModified = result.mtime.toISOString(); file.data.astro.frontmatter.birthtime = result.birthtime.toISOString(); };};
添加完成之后记得要更新类型文件:
export interface MarkdownFrontmatter { title: string; subtitle: string; birthtime: string; lastModified: string; author: string; tags: string[]; type: string; cover: ImageMetadata;}
用法需要用一些日期的库进行处理:
import dayjs from "dayjs";import utc from "dayjs/plugin/utc";
dayjs.extend(utc);// frontmatter 就是获取到的 markdown 文件的 frontmatterconst birthtime = dayjs(frontmatter.birthtime).format("YYYY-MM-DD HH:mm:ss")
到这里前端的部分基本没有什么问题了,剩下的问题看文档基本都能解决,接下来就是部署了。
部署至服务器
我的部署方案是采用 docker 的方式,有可能还要部署其他应用到同一台服务器,所以需要 nginx 进行请求的转发,那么就需要一个管理所有应用的地方,首先改写一下目录结构:
|- projects |- diary-of-madao (当前个人博客网站代码目录) |- nginx (nginx相关的配置) |- conf.d |- default.conf // 默认配置 |- shared // 共享的配置 |- ssl // 证书存放的位置 |- docker-compose.yaml (docker-compose的配置)
1. 创建网站的 Dockerfile