搭建我的博客

2025-05-31

2025-06-02 15:31更新

1. 概述

我写博客经历了以下阶段:

  1. 2016 年,记在一本纸质笔记本上
  2. 2017 年,发表在 QQ 空间/微信公众号
  3. 2018 ~ 2021 年,发表在 csdn,至今还有很多平台盗用我的文章
  4. 2022 年,开始自建博客

由于文档使用 markdown 编写非常高效,且有利于版本管理,所以就直接选用了静态博客。

本篇博客由 gatsby 构建而成,当初选型 gatsby 看重于其构建的灵活性。 时至今日,gatsby 已非主流博客搭建工具,但亦足够满足需求。

2. gatsby

官方文档:https://www.gatsbyjs.cn/docs/

重点说明当前博客用到或者需要用到的特性。

2.1 项目结构

在 Gatsby 项目中,你会看到这些文件和文件夹,可能是一些,可能是全部:

|-- /.cache
|-- /plugins
|-- /public
|-- /src
    |-- /pages
    |-- /templates
    |-- html.js
|-- /static
|-- gatsby-config.js
|-- gatsby-node.js
|-- gatsby-ssr.js
|-- gatsby-browser.js

一些值得注意的文件和它们的定义:

  • gatsby-config.js——配置 Gatsby 站点的选项,比如项目标题和项目描述的元数据,插件等等
  • gatsby-node.js——实现了 Gatsby 的 Node.js API,来自定义和扩展影响构建过程的设置
  • gatsby-browser.js——使用 Gatsby 的浏览器 API 自定义和扩展影响浏览器的的设置
  • gatsby-ssr.js——使用 Gatsby 的服务端渲染 API 自定义影响服务端渲染的默认设置

2.2 静态创建页面

Gatsby 的核心自动把 src/pages 中的 React 组件转变为页面和 URL。比如:在 src/pages/index.jssrc/pages/about.js 中的组件,会为网站的索引页(/)和 关于页(/about)自动创建基于文件名的页面。

操作步骤:

  1. 如果你没有 src/pages 这个目录的话,创建它。
  2. 添加一个组件文件在这个 pages 目录里:
import React from "react"

const AboutPage = () => (
  <main>
    <h1>About the Author</h1>
    <p>Welcome to my Gatsby site.</p>
  </main>
)

export default AboutPage
  1. 运行 yarn dev 以启动开发服务器。
  2. 在浏览器中访问你的新页面:http://localhost:8080/about

2.3 动态创建页面

你可以使用 Gatsby 提供的辅助方法,在 gatsby-node.js 文件中以编程方式创建页面。

操作步骤:

  1. gatsby-node.js 文件中,为 createPages 添加一个输出(export)
exports.createPages = ({ actions }) => {
  // ...
}
  1. 从可用操作中解构 createPage 操作,以便可以单独调用它,并添加或获取数据
exports.createPages = ({ actions }) => {
  const { createPage } = actions
  // pull in or use whatever data
  const dogData = [
    {
      name: "Fido",
      breed: "Sheltie",
    },
    {
      name: "Sparky",
      breed: "Corgi",
    },
  ]
}
  1. 遍历 gatsby-node.js 中的数据,并为每次调用将路径,模板和上下文(在 props 的 pageContext 中传入的数据)提供给 createPage
exports.createPages = ({ actions }) => {
  const { createPage } = actions

  const dogData = [
    {
      name: "Fido",
      breed: "Sheltie",
    },
    {
      name: "Sparky",
      breed: "Corgi",
    },
  ]
  dogData.forEach(dog => {
    createPage({
      path: `/${dog.name}`,
      component: require.resolve(`./src/templates/dog-template.js`),
      context: { dog },
    })
  })
}
  1. 创建一个 React 组件作为你的页面模板,这个组件在 createPage 中已经被使用了

src/templates/dog-template.js

import React from "react"

export default ({ pageContext: { dog } }) => (
  <section>
    {dog.name} - {dog.breed}
  </section>
)
  1. 运行 yarn dev 并导航到一个你创建的页面路径(例如 http://localhost:8080/Fido)来查看传给它的数据是否成功显示在页面上

2.4 页面跳转

gatsby 中不要使用 a 标签跳转,否则不会加上网站根目录。

<Link to="/">Vance’s Blog</Link>

2.5 全局样式

操作步骤:

  1. 添加一个全局 CSS 文件 src/styles/global.css 并输入以下内容:
/* src/styles/styles/global.css */
html {
  background-color: lavenderblush;
}

p {
  color: maroon;
}
  1. gatsby-browser.js 文件中引入这个全局 CSS 文件,像这样:
import "./src/styles/global.css"

2.6 局部样式

局部样式只作用在组件当中,使用 scss 加作用域完成。

Gheader.jsx 组件中:

import "../styles/gheader.scss"

const Gheader = () => {
  return (
    <header id="g-header">
      <div className="header__left"></div>
    </header>
  )
}

gheader.scss 中:

#g-header {
  position: fixed;
  top: 0;
  left: 0;

  .header__left {
    display: flex;
  }
}

2.7 pageQuery

pageQuery 是一个内置组件,用于从 Gatsby 页面的数据层检索信息。每个页面可以有一个页面查询。它可以为查询中的变量接受 GraphQL 参数。

使用场景: 博客文章页、产品详情页,根据 URL 中参数动态查询数据

操作步骤:

  1. gatsby 中引入 graphql
  2. 导出一个名为 query 的变量,并设置它的值为 graphql 模版加上用两个反引号括起来的查询命令。
  3. data 作为 prop 传入到组件中。
  4. data 变量保存了被查询的数据,并且可以被 JSX 引用来生成 HTML。
import React from "react"
import { graphql } from "gatsby"

import Layout from "../components/layout"

export const query = graphql`
  query HomePageQuery {
    site {
      siteMetadata {
        title
      }
    }
  }
`

const IndexPage = ({ data }) => (
  <Layout>
    <h1>{data.site.siteMetadata.title}</h1>
  </Layout>
)

export default IndexPage

2.8 useStaticQuery

StaticQuery 已经废弃,使用 useStaticQuery 替换。

useStaticQuery hook 接收一个 GraphQL 查询命令并返回所查询的数据。它可以被存储到一个变量里,在之后的 JSX 模版中使用。

使用场景: 不能传递参数,数据是构建时就确定的。显示全站配置、导航栏、footer 等不会变化的内容

操作步骤:

  1. gatsby 中引入 useStaticQuerygraphql 以使用 hook 来查询数据。
  2. 在一个无状态功能性组件的一开始,使用 useStaticQuerygraphql 查询命令作为其参数,给一个变量赋值。
  3. 在组件返回的 JSX 代码中,你可以引用这个变量来处理返回的数据。
import React from "react"
import { useStaticQuery, graphql } from "gatsby"

const NonPageComponent = () => {
  const data = useStaticQuery(graphql`
    query NonPageQuery {
      site {
        siteMetadata {
          title
        }
      }
    }
  `)
  return (
    <h1>
      Querying title from NonPageComponent: {data.site.siteMetadata.title}{" "}
    </h1>
  )
}

export default NonPageComponent

2.9 给节点添加数据

扩展另一个节点。新的节点字段放置在扩展的节点对象下的 fields 键下。

  1. 新增属性 gitModifiedTime
exports.onCreateNode = ({ node, actions }) => {
  const { createNode, createNodeField } = actions
  // Transform the new node here and create a new node or
  // create a new node field.
  const gitDate = execSync(
    `git log -1 --date=format:"%Y-%m-%d %H:%M" --format="%ad" "${filePath}"`
  )
    .toString()
    .trim()

  createNodeField({
    node,
    name: "gitModifiedTime",
    value: gitDate,
  })
}
  1. 查询 gitModifiedTime 属性
export const pageQuery = graphql`
  query BlogPostBySlug($id: String!) {
    markdownRemark(id: { eq: $id }) {
      id
      frontmatter {
        title
        date(formatString: "YYYY-MM-DD")
        description
      }
      fields {
        slug
        gitModifiedTime
      }
    }
  }
`

2.10 文档搜索

使用了 search-data.json 文件保存了博客内容,再通过 fuse.js 进行搜索。

操作步骤:

  1. gatsby-node.js 中增加 search-data.json 生成的逻辑。
exports.onPostBuild = async ({ graphql }) => {
  const result = await graphql(`
    {
      allMarkdownRemark {
        nodes {
          fields {
            slug
          }
          frontmatter {
            title
            date(formatString: "YYYY-MM-DD")
          }
          rawMarkdownBody
        }
      }
    }
  `)

  const posts = result.data.allMarkdownRemark.nodes

  const searchData = posts.map(post => ({
    title: post.frontmatter.title,
    slug: post.fields.slug,
    date: post.frontmatter.date,
    content: markdownToPlainText(post.rawMarkdownBody),
  }))

  const filePath = path.join(__dirname, "public", "search-data.json")
  fs.writeFileSync(filePath, JSON.stringify(searchData))
}
  1. 在页面中加载search-data.json并执行搜索
import Fuse from "fuse.js"

fetch(getPathPrefix() + "search-data.json")
  .then(res => res.json())
  .then(data => {
    const fuseInstance = new Fuse(data, {
      keys: [
        { name: "title", weight: 0.5 },
        { name: "content", weight: 0.5 },
      ],
      includeMatches: true,
      threshold: 0.3,
      ignoreLocation: true,
    })
  })
const search = query => {
  if (fuse && query.length > 0) {
    const searchResults = fuse.search(query)
    setResults(searchResults)
  } else {
    setResults([])
  }
}

3. 构建发布

  1. 使用部署命令后,将会自动构建 gh-pages,并推送到:https://github.com/fanqunxing/vance/tree/gh-pages
yarn deploy
  1. 检查 github 构建流水。https://github.com/fanqunxing/vance/actions

4. 规范

4.1 新建文章

按时间新建文章,并在文章内放置资源文件。

blog/
└── 2025/
    └── 05/
        └── my-first-blog/
            ├── index.md
            ├── cover.jpg
            └── diagram.png

4.2 FrontMatter

---
title: 搭建这个博客
date: "2025-05-31"
description: 讲述本博客的搭建与维护
tags: ["静态博客", "gatsby"]
draft: true
---

draft 字段为草稿,完成后去掉标记。

4.3 正文目录

从二级标题 ## 开始正文结构,用 阿拉伯数字编号 提高逻辑层次清晰度。

## 1. 简介

简要说明文章目的。

## 2. 为什么需要标题规范

讲理由、背景。

## 3. 推荐的写法

### 3.1 正确使用层级

### 3.2 避免过长标题

## 4. 总结

Profile picture

Written by Vance who lives and works in Shenzhen, China, and is working hard to improve.