跳到主要内容

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

阅读需 36 分钟
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>

     );

   }

  1. 开箱即用的完整功能:Docusaurus提供了博客所需的几乎所有功能,包括:

   - 文章列表和分类

   - 标签系统

   - 全文搜索(集成Algolia)

   - 代码高亮

   - 暗黑模式

   - 国际化支持

   - SEO优化

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

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

   # 我的文章标题



   这是普通的Markdown内容。



   <CodeSandbox slug="react-demo" />



   继续写Markdown内容...

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

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

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

  4. 性能优化:内置了很多现代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

  1. 部署步骤

   - 在Vercel控制台中点击"New Project"

   - 选择博客所在的GitHub仓库

   - 配置构建设置:

     - 构建命令:npm run build

     - 输出目录:build

     - 环境变量:根据需要添加

  1. 域名设置

   - 在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. 体验优化

   - 给暗黑模式切换加个平滑过渡动画(现在切换时闪一下,有点突兀)

   - 优化移动端的触摸体验(现在某些交互在手机上不太友好)

   - 给文章目录加个自动高亮当前阅读位置的功能(这个我已经研究了一半)

  1. 内容功能

   - 加个文章阅读量统计(想知道哪些文章最受欢迎)

   - 做个文章系列功能(比如"React深入理解系列"这种)

   - 添加相关文章推荐(现在是随机推荐,感觉没什么用)

  1. 互动功能

   - 加个点赞按钮(虽然不知道会不会有人点😂)

   - 改进评论区体验(现在的Giscus还是有些局限)

   - 加个一键分享到社交媒体的功能(尤其是微信)

  1. 技术升级

   - 升级到Docusaurus最新版(每次升级都有点担心会不会破坏现有功能)

   - 尝试用React Server Components重构部分组件(听说性能提升很明显)

   - 研究更先进的图片处理方案(现在的方案在某些浏览器上还有兼容性问题)

  1. 内容计划

   - 建立固定的写作习惯(目标是每周至少一篇,但老实说有点难坚持)

   - 写更多深度技术文章(不只是教程,还有原理分析和最佳实践)

   - 尝试更多技术领域的内容(比如AI和Web3,这些我正在学习中)

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

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

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

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

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

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

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

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

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

分享这篇文章
Loading Comments...