跳到主要内容

个人博客开发记录:从零到Docusaurus

阅读需 30 分钟
wqz

嘿,欢迎来到我的博客开发历程分享!这篇文章将带你了解我如何从零开始,使用Docusaurus构建这个你现在正在浏览的个人博客网站。从最初的技术选型纠结,到一步步实现各种功能,再到最终部署上线,我会分享整个过程中的思考、挑战和解决方案。

为什么我最终选择了Docusaurus

说实话,选择一个合适的博客框架真的让我头疼了好一阵子。市面上有太多选择,每个都有各自的优缺点。在做决定之前,我花了不少时间研究和对比了几个主流的静态站点生成器:

我考虑过的其他选择

  • Next.js:

    • ✅ 极其强大的React框架,几乎能实现任何功能
    • ✅ 支持SSR/SSG/ISR等多种渲染方式
    • ❌ 需要从头开始搭建博客功能,工作量大
    • ❌ 配置相对复杂,学习曲线陡峭
    • ❌ 对于"只是想要一个博客"来说有点大材小用
  • Hugo:

    • ✅ 构建速度惊人(Go语言编写)
    • ✅ 部署简单,不依赖Node.js环境
    • ❌ 使用Go模板语言,不是我熟悉的技术栈
    • ❌ 自定义组件相对困难
    • ❌ 主题定制需要深入了解Hugo的模板系统
  • Hexo:

    • ✅ 专为博客设计,中文社区活跃
    • ✅ 主题和插件生态丰富
    • ❌ 很多主题质量参差不齐,需要大量修改
    • ❌ EJS模板不如React组件灵活
    • ❌ 文档质量不够高,有些功能需要自己摸索
  • VuePress/VitePress:

    • ✅ 基于Vue生态,界面美观
    • ✅ 文档支持良好
    • ❌ 主要针对文档网站,博客功能是次要的
    • ❌ 我更熟悉React而非Vue

为什么Docusaurus最终胜出

经过一番对比和实验,我最终选择了Docusaurus,主要基于以下几点考虑:

  1. React驱动的开发体验:作为一名React开发者,我可以直接利用已有的技能和组件库。不需要学习新的模板语言,直接用JSX就能自定义任何部分。

    // 举个例子,我可以轻松创建自定义React组件
    function Hero() {
    return (
    <div className={styles.hero}>
    <div className={styles.intro}>
    <h1>王起哲的博客</h1>
    <p>在这里分享我的技术探索之旅</p>
    </div>
    </div>
    );
    }
  2. 开箱即用的完整功能:Docusaurus提供了博客所需的几乎所有功能,包括:

    • 文章列表和分类
    • 标签系统
    • 全文搜索(集成Algolia)
    • 代码高亮
    • 暗黑模式
    • 国际化支持
    • SEO优化

    这意味着我可以专注于内容和定制,而不是从头实现这些基础功能。

  3. MDX的强大表现力:能在Markdown中直接使用React组件是一个巨大的优势。这让我可以在保持内容简洁的同时,在需要的地方添加交互式元素。

    # 我的文章标题

    这是普通的Markdown内容。

    <CodeSandbox slug="react-demo" />

    继续写Markdown内容...
  4. 灵活的主题定制机制:Docusaurus的swizzling机制让我可以精确地覆盖和定制任何组件,而不需要fork整个主题。

  5. 文档与博客的完美结合:我不仅需要写博客文章,还想整理一些技术笔记和教程。Docusaurus的文档+博客双模式正好满足这一需求。

  6. 活跃的社区和持续维护:由Meta(Facebook)团队维护,更新频繁,文档详尽,这给了我长期使用的信心。

  7. 性能优化:内置了很多现代Web性能优化,如代码分割、预加载、PWA支持等。

总的来说,Docusaurus提供了一个平衡的解决方案——足够简单,可以快速上手;又足够灵活,能满足各种定制需求。对于像我这样既想要一个漂亮的博客,又希望能充分发挥前端技术的开发者来说,它是一个理想的选择。

开发环境搭建:从零开始的第一步

好啦,既然决定用Docusaurus了,那就赶紧动手搭建环境吧!说实话,刚开始我也是一头雾水,但实际操作起来比想象中简单多了。

前置准备

首先,得确保电脑上装了这些东西(没装的话赶紧补上):

  • Node.js:v18或更高版本就行(我用的v18.16.0,老版本可能会有奇怪的问题)
  • npmyarn:我习惯用npm,主要是懒得再装一个yarn😂
  • Git:这个必须有,不然后面部署和版本控制会很头疼

创建项目

Docusaurus的脚手架工具真是太方便了,敲一行命令就搞定,完全不用手动配置那堆烦人的东西:

# 创建项目(classic是模板名称)
npx create-docusaurus@latest my-blog classic

# 进到项目目录
cd my-blog

# 启动开发服务器看看效果
npm start

敲完这几行命令后,浏览器会自动打开http://localhost:3000,然后...哇!一个模板网站就这么出现了!虽然长得有点丑,但基本功能都有了,这就是我喜欢用框架的原因——先跑起来再说,看到成果心里才有底。

不过说实话,这个默认模板离我心目中的博客差得还挺远的,后面得下不少功夫改造它。但至少有个基础,总比从零开始写HTML/CSS强多了,对吧?

项目结构一览

创建好的项目结构大概长这样:

my-blog/
├── blog/ # 博客文章目录(我的文章都扔这里)
├── docs/ # 文档内容目录(不怎么用,但先留着)
├── src/ # 源代码(重头戏在这里)
│ ├── components/ # 自定义React组件(后面会加很多东西)
│ ├── css/ # 样式文件(调整样式的地方)
│ └── pages/ # 自定义页面(首页、关于我之类的)
├── static/ # 静态资源(图片、字体什么的)
├── docusaurus.config.js # 主配置文件(超级重要!)
├── sidebars.js # 侧边栏配置(文档用的,不太管它)
└── package.json # 项目依赖(装插件的时候会动它)

看起来挺多文件的,但别被吓到!实际上大部分时间只会修改其中几个。我刚开始也是一脸懵,但摸索了一会儿就大概知道该去哪里改东西了。

网站配置:开始搭建我的小窝

Docusaurus最让我惊喜的一点就是配置超级简单!所有重要的设置都集中在一个文件里:docusaurus.config.ts。这对我这种经常忘记"我到底在哪个文件里改过这个设置"的人来说简直是救星。

基础配置

首先得把网站的基本信息改一改,默认的那些placeholder看着就别扭:

const config = {
// 网站标题(浏览器标签上显示的那个)
title: '王起哲的博客',

// 网站URL(部署后的地址,先随便填一个,反正后面会改)
url: 'https://20030727.xyz',

// 基础路径(一般不用动)
baseUrl: '/',

// 网站图标(必须换掉默认的,太丑了)
favicon: 'img/favicon.ico',

// GitHub用户名(用于部署和评论系统)
organizationName: 'wwwqqqzzz',

// 仓库名(就叫blog好了,简单明了)
projectName: 'blog',

// 自定义字段(这个超好用,可以在任何组件里拿到这些数据)
customFields: {
bio: '技术探索之路', // 个人简介
description: '这是王起哲的个人博客,主要分享编程、游戏开发和Web3技术等领域的知识和项目,该网站基于 React 驱动的静态网站生成器 Docusaurus 构建。',
},

// ...还有一堆配置,先不管它们
}

老实说,刚开始我根本不知道这些配置项都是干嘛用的,就是照着文档和别人的博客抄了一遍,然后慢慢调整。有些字段看起来无关紧要,但后面才发现原来它们会影响SEO和社交媒体分享效果!

导航栏与页脚

接下来,我定制了网站的导航栏和页脚,这是用户与网站交互的重要界面元素:

themeConfig: {
// 导航栏配置
navbar: {
logo: {
alt: '王起哲',
src: 'img/logo.webp',
srcDark: 'img/logo.webp',
},
hideOnScroll: true, // 滚动时隐藏导航栏
items: [
{ label: '博客', position: 'right', to: 'blog' },
{ label: '项目', position: 'right', to: 'project' },
{ label: '友链', position: 'right', to: 'friends' },
{ label: '关于', position: 'right', to: 'about' },
{
label: '更多',
position: 'right',
items: [
{ label: '归档', to: 'blog/archive' },
{ label: '技术笔记', to: 'docs/docusaurus-guides' },
{ label: '私密博客', to: 'private' },
],
},
],
},

// 页脚配置
footer: {
style: 'dark',
links: [
{
title: '技术',
items: [
{ label: '博客', to: 'blog' },
{ label: '归档', to: 'blog/archive' },
{ label: '项目展示', to: 'project' },
],
},
{
title: '社交媒体',
items: [
{ label: '关于我', to: '/about' },
{ label: 'GitHub', href: 'https://github.com/wwwqqqzzz' },
],
},
// ...更多链接
],
copyright: `Copyright © 2024 - ${new Date().getFullYear()} 王起哲 | Built with Docusaurus.`,
},
}

这样配置后,我的网站有了清晰的导航结构,用户可以轻松找到各个部分的内容。

博客模式配置

Docusaurus默认是文档优先的,但我想要一个以博客为主的网站。为此,我做了一些特殊配置:

// 在plugins配置中添加自定义博客插件
plugins: [
[
'./src/plugin/plugin-content-blog', // 自定义博客插件路径
{
path: 'blog',
editUrl: ({ locale, blogDirPath, blogPath, permalink }) =>
`https://github.com/wwwqqqzzz/blog/edit/main/${blogDirPath}/${blogPath}`,
editLocalizedFiles: false,
blogDescription: '代码人生:编织技术与生活的博客之旅',
blogSidebarCount: 10,
blogSidebarTitle: '博文',
postsPerPage: 12, // 每页显示的文章数
showReadingTime: true, // 显示阅读时间
readingTime: ({ content, frontMatter, defaultReadingTime }) =>
defaultReadingTime({ content, options: { wordsPerMinute: 300 } }),
feedOptions: { // RSS订阅配置
type: 'all',
title: '王起哲',
description: '个人博客RSS订阅',
copyright: `Copyright © ${new Date().getFullYear()} 王起哲 Built with Docusaurus.`,
},
},
],
// ...其他插件
]

这里我使用了自定义的博客插件,而不是默认的@docusaurus/plugin-content-blog,这是因为我需要对博客功能进行一些特殊定制,比如将博客数据设置为全局可访问,以便在其他组件中使用。

预设与插件

Docusaurus的强大之处在于它的插件系统。我配置了以下预设和插件:

// 预设配置
presets: [
[
'classic',
{
docs: {
path: 'docs',
sidebarPath: 'sidebars.ts',
},
blog: false, // 禁用默认博客插件,使用自定义插件
theme: {
customCss: ['./src/css/custom.css', './src/css/tweet-theme.css'],
},
sitemap: {
priority: 0.5,
},
gtag: { // Google Analytics配置
trackingID: 'G-S4SD5NXWXF',
anonymizeIP: true,
},
},
],
],

// 插件配置
plugins: [
'docusaurus-plugin-image-zoom', // 图片缩放
'@docusaurus/plugin-ideal-image', // 图片优化
[
'@docusaurus/plugin-pwa', // PWA支持
{
debug: process.env.NODE_ENV === 'development',
offlineModeActivationStrategies: ['appInstalled', 'standalone', 'queryString'],
pwaHead: [
{ tagName: 'link', rel: 'icon', href: '/img/logo.png' },
{ tagName: 'link', rel: 'manifest', href: '/manifest.json' },
{ tagName: 'meta', name: 'theme-color', content: '#12affa' },
],
},
],
[
'vercel-analytics', // Vercel分析
{
debug: process.env.NODE_ENV === 'development',
mode: 'auto',
},
],
// ...其他插件
]

主题定制:让我的博客看起来不那么"模板化"

配置完基本设置后,接下来就是最有趣的部分了——让这个网站看起来像是"我的",而不是千篇一律的Docusaurus默认样式。说实话,默认主题虽然干净,但实在是太...普通了,看起来就像是官方文档。

颜色与样式系统:选择让我心动的蓝色

首先,我得把那个默认的紫色换掉(不是说它不好看,只是不太符合我的风格)。在src/css/custom.css里,我定义了一套完整的颜色系统:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
:root {
/* 设计系统主色调 - 我超喜欢这种蓝色! */
--color-primary-50: #E6F7FF;
--color-primary-100: #BAE7FF;
--color-primary-200: #91D5FF;
--color-primary-300: #69C0FF;
--color-primary-400: #40A9FF;
--color-primary-500: #12AFFA; /* 主色,选了好久才定下来 */
--color-primary-600: #0E9FE5;
--color-primary-700: #0A81BC;
--color-primary-800: #086293;
--color-primary-900: #0B5A82;

/* Docusaurus变量 - 这些必须设置,不然主题会乱套 */
--ifm-color-primary: #12affa;
--ifm-color-primary-dark: #0799e0;
--ifm-color-primary-darker: #0790d4;
--ifm-color-primary-darkest: #0676ae;
--ifm-color-primary-light: #2cb8fa;
--ifm-color-primary-lighter: #39bcfb;
--ifm-color-primary-lightest: #61c9fc;

/* 文本颜色 - 不要用纯黑色,太刺眼了 */
--ifm-text-color: #1F2937;
--ifm-secondary-text-color: #4B5563;

/* 其他全局变量 */
--content-background: #ffffff;
--ifm-navbar-background-color: var(--content-background);
}

/* 暗色模式变量 - 熬夜写代码必备 */
html[data-theme='dark'] {
--content-background: #18181b;
--ifm-color-primary: hsl(214deg 100% 60%); /* 暗模式下蓝色要亮一点 */
--ifm-color-primary-light: hsl(214deg 100% 75%);

/* 文本颜色 - 暗色模式下不要用纯白,太晃眼 */
--ifm-text-color: #F9FAFB;
--ifm-secondary-text-color: #9CA3AF;

/* 背景颜色 - 我喜欢深一点的背景 */
--ifm-background-color: #121212;
--ifm-background-surface-color: #1E1E1E;

/* 博客项样式 - 加点渐变和阴影,看起来更立体 */
--blog-item-background-color: linear-gradient(180deg, #171717, #18181b);
--blog-item-shadow: 0 10px 18px #25374833, 0 0 8px #25374866;
}
}

/* 文章内容样式优化 - 不要太宽,不然读起来累 */
.theme-doc-markdown,
.blog-post-content {
max-width: 780px; /* 经过测试,这个宽度读起来最舒服 */
margin: 0 auto;
}

/* 引用块样式 - 暗色模式下默认的太丑了 */
html[data-theme='dark'] .theme-doc-markdown blockquote,
html[data-theme='dark'] .blog-post-content blockquote {
background-color: rgba(255, 255, 255, 0.05);
}

/* 移动端优化 - 手机上看也要舒服 */
@media (max-width: 768px) {
.theme-doc-markdown,
.blog-post-content {
padding: 0 1rem;
}

.theme-doc-markdown h1, .blog-post-content h1 {
font-size: 1.75rem; /* 手机屏幕小,标题也要小一点 */
}
}

我用了Tailwind CSS来辅助开发,这个工具真是太香了!以前写CSS要想一堆类名,现在直接className="flex items-center justify-between p-4 bg-blue-500"就搞定了。不过要记住一大堆类名也挺烧脑的,好在有VSCode插件提示。

自定义React组件:打造炫酷的首页

默认的Docusaurus首页实在是太无聊了,我决定完全重写一个。这花了我不少时间,但绝对值得!我创建了一个超酷的Hero组件,带有动画效果和渐变色:

// src/components/landing/Hero/index.tsx
export default function Hero() {
const gridRef = useRef<HTMLDivElement>(null)
const [isMobile, setIsMobile] = useState(false)

// 检测设备类型,手机上要简化一些效果
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth < 768)
}

handleResize() // 先执行一次
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize) // 清理事件
}, [])

return (
<motion.div className={styles.hero}>
{/* 网格背景 - 这个效果我超喜欢 */}
<div ref={gridRef} className={styles.grid_background} />

{/* 手绘装饰 - 增加一点活泼感 */}
<DoodleDecoration />

<div className={styles.intro}>
{/* 标题区域 - 带渐入动画 */}
<motion.div
className={styles.hero_text}
custom={1}
initial="hidden"
animate="visible"
variants={variants}
>
<motion.div className="mb-4 text-xl text-blue-400">
王起哲.dev
</motion.div>
<motion.div className="flex items-center justify-center gap-3">
<span className={styles.name}>全栈开发者</span>
{/* 这个摇晃的手势emoji是我的最爱 */}
<motion.span
className="inline-block text-4xl"
animate={{
rotate: [0, 15, 0], // 左右摇摆
scale: [1, 1.2, 1], // 放大缩小
}}
transition={{
repeat: Infinity, // 无限循环
duration: 1.5,
repeatType: "reverse",
ease: "easeInOut",
}}
>
👋
</motion.span>
</motion.div>
</motion.div>

{/* 简介文字 - 也带动画 */}
<motion.p
custom={2} // 延迟出现
initial="hidden"
animate="visible"
variants={variants}
>
在这里我会分享各类技术栈所遇到问题与解决方案,带你了解最新的技术栈以及实际开发中如何应用,并希望我的开发经历对你有所启发。
</motion.p>

{/* 社交链接 - GitHub、Twitter等 */}
<motion.div
custom={3}
initial="hidden"
animate="visible"
variants={variants}
>
<SocialLinks />
</motion.div>

{/* 按钮 - 带发光效果 */}
<motion.div
custom={4}
initial="hidden"
animate="visible"
variants={variants}
>
<MovingButton
borderRadius={isMobile ? '1rem' : '1.25rem'} // 手机上按钮要小一点
className="relative z-10 flex items-center rounded-2xl border border-solid border-blue-500 bg-blue-500/10 px-4 py-2 text-center text-sm font-semibold text-blue-400 transition-all hover:bg-blue-500/20 hover:shadow-lg md:px-6 md:py-3 md:text-base"
>
<a href="/about" className="font-semibold">
了解更多
</a>
</MovingButton>
</motion.div>
</div>

{/* 底部波浪过渡 - 这个效果做了好久 */}
<motion.div
className={styles.smooth_transition}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: isMobile ? 0.5 : 1, duration: 0.8 }}
>
<div className={styles.transition_wave}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1440 200"
preserveAspectRatio="none"
className={styles.wave_svg}
>
{/* 波浪路径 - 调了好多次才满意 */}
<path
d="M0,160 C240,100 480,180 720,150 C960,120 1200,160 1440,140 L1440,200 L0,200 Z"
fill="var(--ifm-background-color)"
fillOpacity="0.95"
/>
<path
d="M0,180 C320,150 720,190 1200,160 C1280,150 1360,180 1440,170 L1440,200 L0,200 Z"
fill="var(--ifm-background-color)"
fillOpacity="1"
/>
</svg>
</div>
</motion.div>
</motion.div>
)
}

说实话,这个组件写得我头都大了,特别是那个波浪效果,调了好几个小时才满意。Framer Motion这个库真的很强大,但学习曲线也挺陡的,我看了好多教程和例子才搞明白怎么用。

对应的CSS样式文件也花了不少功夫:

/* Hero基础样式 */
.hero {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: calc(100vh - var(--ifm-navbar-height)); /* 占满整个屏幕 */
padding: 4rem 2rem 8rem;
overflow: hidden; /* 隐藏溢出的装饰元素 */
background: linear-gradient(to bottom, rgba(80, 117, 177, 0.1), rgba(80, 117, 177, 0.05)); /* 超淡的渐变背景 */
}

/* 文本和内容样式 */
.hero_text {
text-align: center;
margin-bottom: 2rem;
}

/* 这个渐变文字效果我超喜欢 */
.name {
position: relative;
display: inline-block;
font-size: 3rem;
font-weight: 700;
background: linear-gradient(135deg,
#60A5FA 0%, /* 浅蓝 */
#3B82F6 50%, /* 中蓝 */
#2563EB 100% /* 深蓝 */
);
-webkit-background-clip: text; /* 文字裁剪 */
background-clip: text;
color: transparent; /* 文字本身透明,显示背景 */
line-height: 1.2;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 轻微阴影增加立体感 */
}

/* 手机上的样式调整 - 一定要做好响应式! */
@media (max-width: 570px) {
.hero {
padding: 2rem 1rem 5rem; /* 手机上内边距小一点 */
}

.name {
font-size: 2rem; /* 手机上字体小一点 */
line-height: 1.2;
}

.transition_wave {
height: 100px; /* 波浪高度调整 */
bottom: -5px;
}
}

这个组件是我最满意的部分之一,每次看到它我都有点小骄傲。虽然代码看起来有点复杂,但效果真的很棒!

性能优化组件:让网站飞起来

博客最让人头疼的就是图片加载慢的问题了。我的文章里经常会放不少截图,如果不优化的话,加载体验简直是灾难。所以我专门写了一个图片优化组件:

// src/components/ui/optimized-image.tsx
export function OptimizedImage({
src,
alt = '',
className,
wrapperClassName,
zoomable = false, // 是否可以点击放大
caption,
darkModeOptimized = true, // 暗模式下自动调整亮度
style,
}: OptimizedImageProps): React.ReactElement {
const [isZoomed, setIsZoomed] = useState(false)
const [isLoaded, setIsLoaded] = useState(false) // 跟踪加载状态

// 处理放大/缩小切换
const toggleZoom = () => {
if (zoomable) {
setIsZoomed(!isZoomed) // 点击时切换缩放状态
}
}

return (
<figure className={cn('my-8 overflow-hidden rounded-lg', wrapperClassName)}>
<div
className={cn(
'relative overflow-hidden rounded-lg bg-gray-200 dark:bg-gray-800',
zoomable && 'cursor-zoom-in', // 鼠标变成放大镜
isZoomed && 'cursor-zoom-out', // 已放大时变成缩小镜
)}
onClick={toggleZoom}
>
{/* 预加载背景 - 这个灰色背景超赞,不会出现图片加载时的"闪烁"感 */}
<div
className={cn(
'absolute inset-0 bg-gray-200 dark:bg-gray-800 transition-opacity duration-500',
isLoaded ? 'opacity-0' : 'opacity-100', // 图片加载完成后淡出
)}
/>

{/* 优化图片 - 使用Docusaurus的IdealImage插件 */}
<Image
img={src}
src={src}
alt={alt}
className={cn(
'w-full transition-all duration-500',
// 暗模式下自动调整亮度和对比度,这个功能我超喜欢!
darkModeOptimized && 'dark:brightness-90 dark:contrast-105 dark:hover:brightness-100',
isZoomed ? 'scale-125' : 'scale-100', // 缩放效果
className,
)}
style={{
...style,
transitionProperty: 'opacity, filter, transform',
transitionDuration: '0.5s, 0.5s, 0.5s',
}}
onLoad={() => setIsLoaded(true)} // 图片加载完成时更新状态
/>
</div>

{/* 图片说明 - 可选的 */}
{caption && (
<figcaption className="mt-2 text-center text-sm text-gray-500 dark:text-gray-400">
{caption}
</figcaption>
)}
</figure>
)
}

这个组件看起来简单,但其实解决了好几个问题:

  1. 懒加载:图片只有滚动到视口内才会加载,节省带宽
  2. 渐进式加载:先显示占位符,然后平滑过渡到实际图片
  3. WebP支持:自动使用更高效的WebP格式(文件小30%左右)
  4. 暗模式优化:暗模式下自动调整图片亮度,不会太暗看不清
  5. 点击放大:可以放大查看细节,对于截图特别有用

我在写技术文章时经常用这个组件,比如:

<OptimizedImage
src="/img/blog/docusaurus-setup.png"
alt="Docusaurus初始设置界面"
zoomable={true}
caption="Docusaurus初始设置界面(可点击放大)"
/>

这样读者体验好多了,不用忍受图片加载慢或者看不清细节的痛苦。而且代码复用性也很高,一次写好到处用!

内容组织:构建有条理的博客结构

一个好的博客不仅需要漂亮的外观,还需要清晰的内容组织结构。在这方面,我花了不少心思来设计一个既方便我管理,又方便读者浏览的内容结构。

博客文章分类

首先,我将博客文章按主题分类到不同的目录中,这样便于管理和查找:

blog/
├── develop/ # 开发相关文章(编程技巧、框架教程等)
├── lifestyle/ # 生活相关文章(读书笔记、生活感悟等)
├── project/ # 项目记录(个人项目开发过程和心得)
├── reference/ # 年度总结和参考资料
└── authors.yml # 作者信息配置文件

这种目录结构不仅让我在写作时能够更好地组织思路,也方便读者按照自己的兴趣找到相关内容。

文章元数据设计

每篇文章都需要一个元数据区域(frontmatter),用于定义文章的各种属性。我设计了以下格式:

---
title: 个人博客开发记录:从零到Docusaurus
description: 本文记录了我使用Docusaurus构建个人博客的完整过程,包括配置、主题定制、内容组织和部署等环节。
authors: [wqz]
tags: [前端, Docusaurus, 项目]
date: 2024-05-12
image: https://cdn.jsdelivr.net/gh/wwwqqqzzz/Image/img/1746225191887-66abe9fc65a182813698df2d79267bb2.png
slug: blog-development
sticky: 90 # 置顶权重,数值越大越靠前
---

这些元数据不仅用于显示文章的基本信息,还用于实现一些特殊功能:

  • tags:用于文章分类和标签页面的自动生成
  • image:文章封面图,会显示在文章列表和社交媒体分享中
  • slug:自定义URL路径,便于创建更友好的链接
  • sticky:置顶功能,让重要文章显示在列表顶部
  • authors:作者信息,从authors.yml中获取详细资料

自定义静态页面

除了博客文章,一个完整的个人网站还需要一些静态页面。我创建了以下几个重要页面:

src/pages/
├── index.tsx # 首页
├── about.tsx # 关于我
├── project.tsx # 项目展示
├── friends.tsx # 友情链接
└── 404.tsx # 404错误页

这些页面都是使用React组件实现的,而不是简单的Markdown文件,这让我可以实现更复杂的布局和交互效果。例如,项目展示页面使用了卡片网格布局和动画效果:

// src/pages/project.tsx(简化版)
function ProjectPage() {
return (
<Layout title="项目展示">
<div className="container margin-top--lg">
<h1 className="text-center mb-6">我的项目</h1>

<div className="row">
{projects.map((project, i) => (
<motion.div
key={i}
className="col col--4 margin-bottom--lg"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.1 }}
>
<ProjectCard
title={project.title}
description={project.description}
preview={project.preview}
website={project.website}
source={project.source}
tags={project.tags}
/>
</motion.div>
))}
</div>
</div>
</Layout>
)
}

导航与发现机制

为了帮助读者更好地发现内容,我实现了多种导航和发现机制:

  1. 标签系统:每篇文章可以添加多个标签,读者可以通过标签页面浏览相关文章
  2. 归档页面:按时间顺序展示所有文章,方便查找历史内容
  3. 相关文章推荐:在文章底部显示相关文章,基于标签相似度计算
  4. 搜索功能:集成Algolia搜索,支持全文检索

这些功能共同构成了一个完整的内容发现系统,无论读者是有明确目标还是随意浏览,都能找到感兴趣的内容。

插件与功能增强:打造功能丰富的博客体验

一个现代化的博客不仅需要基础的文章展示功能,还需要各种增强功能来提升用户体验。通过Docusaurus强大的插件系统,我为博客添加了多种实用功能。

评论系统:促进读者互动

为了让读者能够方便地留言和讨论,我集成了Giscus评论系统:

// docusaurus.config.ts
themeConfig: {
// ...其他配置
giscus: {
repo: 'wwwqqqzzz/blog',
repoId: 'MDEwOlJlcG9zaXRvcnkzOTc2MjU2MTI=',
category: 'General',
categoryId: 'DIC_kwDOF7NJDM4CPK95',
theme: 'light',
darkTheme: 'dark_dimmed',
},
}

Giscus基于GitHub Discussions,这意味着所有评论都存储在GitHub上,不需要额外的数据库。它还支持Markdown格式、表情反应和主题切换,提供了非常好的用户体验。

图片优化:提升加载速度

图片通常是网站加载最慢的资源之一。为了解决这个问题,我使用了@docusaurus/plugin-ideal-image插件:

// docusaurus.config.ts
plugins: [
'@docusaurus/plugin-ideal-image',
// ...其他插件
]

这个插件提供了以下优化:

  • 懒加载:只有当图片进入视口时才加载
  • 渐进式加载:先显示低质量占位图,然后逐渐加载高质量图片
  • WebP支持:自动使用更高效的WebP格式(如果浏览器支持)
  • 响应式图片:根据设备屏幕大小提供不同尺寸的图片

我还创建了自定义的OptimizedImage组件(前面已经展示过),进一步增强了图片体验。

搜索功能:快速找到内容

为了让读者能够快速找到需要的内容,我集成了Algolia DocSearch:

// docusaurus.config.ts
themeConfig: {
// ...其他配置
algolia: {
appId: 'GV6YN1ODMO',
apiKey: '50303937b0e4630bec4a20a14e3b7872',
indexName: 'wangqizhe',
},
}

Algolia提供了强大的全文搜索功能,支持关键词高亮、搜索建议和实时结果。设置过程也相对简单:

  1. 注册Algolia账号并创建应用
  2. 配置爬虫来索引网站内容
  3. 将API密钥添加到Docusaurus配置中

PWA支持:离线访问能力

为了提供更好的移动端体验,我添加了PWA(渐进式Web应用)支持:

// docusaurus.config.ts
plugins: [
[
'@docusaurus/plugin-pwa',
{
debug: process.env.NODE_ENV === 'development',
offlineModeActivationStrategies: ['appInstalled', 'standalone', 'queryString'],
pwaHead: [
{ tagName: 'link', rel: 'icon', href: '/img/logo.png' },
{ tagName: 'link', rel: 'manifest', href: '/manifest.json' },
{ tagName: 'meta', name: 'theme-color', content: '#12affa' },
],
},
],
// ...其他插件
]

这使得用户可以:

  • 将网站添加到手机主屏幕
  • 在离线状态下访问已浏览过的内容
  • 获得更接近原生应用的体验

代码高亮与交互

作为一个技术博客,代码展示是非常重要的。我配置了增强的代码块功能:

// docusaurus.config.ts
themeConfig: {
// ...其他配置
prism: {
theme: themes.oneLight,
darkTheme: themes.oneDark,
additionalLanguages: ['bash', 'json', 'java', 'python', 'php', 'graphql', 'rust', 'toml', 'protobuf', 'diff'],
defaultLanguage: 'javascript',
magicComments: [
{
className: 'theme-code-block-highlighted-line',
line: 'highlight-next-line',
block: { start: 'highlight-start', end: 'highlight-end' },
},
{
className: 'code-block-error-line',
line: 'This will error',
},
],
},
codeBlockOptions: {
showLineNumbers: true,
wordWrap: false,
},
}

这些配置提供了:

  • 明暗主题自适应的代码高亮
  • 多种编程语言的语法支持
  • 行号显示
  • 代码行高亮功能
  • 复制按钮

部署与优化:让网站飞起来

Vercel部署流程

经过考察各种部署选项,我最终选择了Vercel平台,因为它提供了简单的部署流程和出色的性能:

  1. 准备工作

    • 确保项目代码已推送到GitHub仓库
    • 注册Vercel账号并连接GitHub
  2. 部署步骤

    • 在Vercel控制台中点击"New Project"
    • 选择博客所在的GitHub仓库
    • 配置构建设置:
      • 构建命令:npm run build
      • 输出目录:build
      • 环境变量:根据需要添加
  3. 域名设置

    • 在Vercel项目设置中添加自定义域名:20030727.xyz
    • 按照指引配置DNS记录
    • 等待DNS生效(通常需要几分钟到几小时)

Vercel的一大优势是自动化部署 - 每当我推送新的提交到GitHub,Vercel就会自动构建并部署最新版本,无需手动操作。

性能优化策略

为了确保网站加载速度快、用户体验好,我实施了多项性能优化措施:

1. 图片优化

图片是影响网站性能的主要因素之一,我采取了以下措施:

// 图片URL优化工具函数
export function getOptimizedImageUrl(url, width, height) {
if (!url || typeof url !== 'string') return ''
if (url.includes('?w=') || url.includes('&w=')) return url

const params = new URLSearchParams()
if (width) params.append('w', width.toString())
if (height) params.append('h', height.toString())
params.append('fmt', 'webp')
params.append('q', '80')

const separator = url.includes('?') ? '&' : '?'
return `${url}${params.toString() ? separator + params.toString() : ''}`
}
  • 使用WebP格式替代JPEG/PNG(文件大小减少约30%)
  • 根据显示尺寸提供适当大小的图片,避免加载过大的图片
  • 设置合理的图片质量(80%通常是视觉质量和文件大小的最佳平衡点)
  • 使用CDN(jsdelivr)来分发图片,提供全球加速

2. 资源缓存策略

为了减少重复下载,我配置了合理的缓存策略:

# static/.htaccess
ExpiresActive on
# 设置默认过期时间
ExpiresDefault "access plus 2 days"
# 图片缓存1个月
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/svg+xml "access 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
# 字体缓存1个月
ExpiresByType font/opentype "access plus 1 month"
ExpiresByType font/otf "access plus 1 month"
ExpiresByType font/ttf "access plus 1 month"
# CSS和JS缓存1个月
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"

3. 代码分割与懒加载

为了减少初始加载时间,我实现了代码分割和组件懒加载:

// 懒加载组件包装器
export function lazyLoad(importFn, fallback) {
const LazyComponent = lazy(importFn)

function Component(props) {
return React.createElement(
Suspense,
{ fallback: fallback || React.createElement('div', null) },
React.createElement(LazyComponent, props),
)
}

return Component
}

// 使用示例
const LazyBlogSection = lazyLoad(() => import('../components/landing/BlogSection'))

这样,只有当用户实际需要某个组件时,才会加载相关代码,大大减少了首屏加载时间。

4. 服务工作线程优化

我配置了自定义的Service Worker来优化资源缓存:

// src/sw.js
import { registerRoute } from 'workbox-routing'
import { StaleWhileRevalidate } from 'workbox-strategies'

export default function swCustom(params) {
// 缓存外部资源
registerRoute(
context =>
[/graph\.facebook\.com\/.*\/picture/, /netlify\.com\/img/, /avatars1\.githubusercontent/].some(regex =>
context.url.href.match(regex),
),
new StaleWhileRevalidate(),
)
}

这些优化措施共同作用,使网站在各种网络条件下都能保持良好的性能。根据Lighthouse测试,我的博客在性能、可访问性和最佳实践方面都获得了很高的分数。

总结与展望:这个博客还有很长的路要走

回顾这段折腾之旅

好了,经过几个月的折腾(真的是折腾,有时候一个小功能就能卡我一整天),我的个人博客终于有了个像样的模样。回顾整个过程,真是又痛苦又快乐:

  1. 选对工具真的很重要:选Docusaurus绝对是我做的最明智的决定之一。它给了我足够的自由度去定制,又不至于像Next.js那样什么都要自己写。省了我至少一半的时间!

  2. 小步快跑真的有用:我一开始想一口气把所有功能都做出来,结果发现根本做不完,后来改成先搭个架子,能用了再慢慢加功能,心态好多了。看到进度条一点点前进的感觉真好!

  3. 性能优化不能偷懒:我原本以为"反正是静态网站,性能应该不是问题",结果发现图片一多立马卡成PPT。后来花了好几天专门做优化,才让网站变得流畅起来。

  4. 设计和开发要平衡:有几次我陷入了"调整一个像素"的怪圈,花了几个小时就为了让某个元素看起来完美。后来想想,与其纠结这些细节,不如多写几篇有价值的文章。

  5. 内容才是根本:做了这么久才明白,再酷炫的网站,没有好内容也是白搭。我现在更关注如何写出有深度的技术文章,而不是花哨的UI效果。

踩过的那些坑

开发过程中踩了不少坑,分享几个印象深刻的:

  1. Swizzling地狱:Docusaurus的组件覆盖机制很强大,但有时候改了一个组件,发现它依赖另一个组件,然后又依赖更多组件...最后变成了连环覆盖,代码维护变得超级困难。有一次我花了整整一天就为了修改评论区的样式!

  2. 移动端适配噩梦:我在27寸显示器上做的设计,到了手机上全崩了!特别是那些炫酷的动画效果,在手机上要么卡顿,要么完全错位。后来不得不为移动端单独写了一堆样式和逻辑。

  3. 暗黑模式的坑:实现暗黑模式看起来简单,但细节超多。比如图片在暗模式下会变得很暗看不清,代码高亮配色方案也需要单独设置,甚至连第三方组件都要考虑。我现在每次添加新功能都会问自己:"它在暗模式下长什么样?"

  4. 插件冲突:有一次我同时安装了两个图片处理插件,结果它们互相打架,导致图片要么不显示,要么显示两次。调试这个问题花了我一整晚,最后发现是因为两个插件都在监听同一个事件。

未来的计划(有生之年系列)

虽然博客已经上线了,但我的愿望清单还很长:

  1. 体验优化

    • 给暗黑模式切换加个平滑过渡动画(现在切换时闪一下,有点突兀)
    • 优化移动端的触摸体验(现在某些交互在手机上不太友好)
    • 给文章目录加个自动高亮当前阅读位置的功能(这个我已经研究了一半)
  2. 内容功能

    • 加个文章阅读量统计(想知道哪些文章最受欢迎)
    • 做个文章系列功能(比如"React深入理解系列"这种)
    • 添加相关文章推荐(现在是随机推荐,感觉没什么用)
  3. 互动功能

    • 加个点赞按钮(虽然不知道会不会有人点😂)
    • 改进评论区体验(现在的Giscus还是有些局限)
    • 加个一键分享到社交媒体的功能(尤其是微信)
  4. 技术升级

    • 升级到Docusaurus最新版(每次升级都有点担心会不会破坏现有功能)
    • 尝试用React Server Components重构部分组件(听说性能提升很明显)
    • 研究更先进的图片处理方案(现在的方案在某些浏览器上还有兼容性问题)
  5. 内容计划

    • 建立固定的写作习惯(目标是每周至少一篇,但老实说有点难坚持)
    • 写更多深度技术文章(不只是教程,还有原理分析和最佳实践)
    • 尝试更多技术领域的内容(比如AI和Web3,这些我正在学习中)

给想搭博客的朋友们的建议

如果你也想搭建自己的技术博客,这里有几条血泪教训:

  1. 别纠结技术选型:选一个你熟悉的技术栈就行,与其花一周时间对比各种框架,不如直接开始做。我看到太多人在"Jekyll vs Hugo vs Hexo vs Gatsby vs Next.js"的选择中迷失了自我...

  2. 先求有,再求好:先搭一个能用的版本上线,然后再慢慢美化和完善。完美主义是博客建设的天敌!

  3. 性能从一开始就要考虑:特别是图片优化,一定要从第一篇文章就做好,否则后期返工会很痛苦。

  4. 做好内容规划:想清楚你的博客主题和分类,建立一个合理的目录结构。我一开始没规划好,导致后来不得不大改特改。

  5. 坚持更新:博客最大的敌人是"三分钟热度"。很多人搭好博客后写了两三篇文章就再也不更新了。定一个合理的更新频率,哪怕是每月一篇也好。

最后,希望这篇流水账一样的开发记录能给你一些启发。如果你有什么问题或建议,欢迎在评论区交流!我也会继续完善这个博客,分享更多有趣的技术探索。

谢谢你看到这里!你真是太有耐心了!👋

分享这篇文章
Loading Comments...