首页 / 文章 / 从零开始学习静态站点生成器:术语、历史及首次构建指南

从零开始学习静态站点生成器:术语、历史及首次构建指南

学习SSG文档假定你已经了解的术语,包括布局、部分文件和前置内容的关联方式,生成器的起源,以及如何轻松选择合适的生成器。

5211 词

静态站点生成器承诺带来简单的解决方案:用 Markdown 编写文章,将页头和页脚放在一处,就能通过普通文件快速搭建出网站。但对许多初学者而言,现实却是满是难以理解的术语、会输出堆栈跟踪信息的终端,以及要求具备多年相关背景知识的文档。本指南将从基础层面填补这一差距。你将了解到开始之前需要掌握哪些技能,生成器文档中出现的各类术语究竟含义何在,典型的 Eleventy 项目各组成部分是如何相互关联的,这些工具的起源是什么,以及当那些流行的工具显得过于复杂时还有哪些更轻量级的选择。

在选择生成器之前:所需的基础技能

静态站点生成器(SSG)是网页之上的一个抽象层。如果你从未手动创建过网页,这种抽象层正好隐藏了出现问题时你需要了解的那些内容。在构建最初的几个网站时,自己编写HTML和CSS会是更好的学习方式。你会体会到将相同导航内容复制到十个文件中的繁琐,而正是这种体验会让你日后更愿意使用生成器。

几乎所有的生成器都默认要求你已熟悉HTML和CSS,通常还要求掌握一些JavaScript知识、变量与循环等基本编程概念、命令行操作,以及Git。其实无需一开始就精通所有这些内容。关键在于,对每一层都有基本的认知模型,就能将那些难以理解的构建错误转化为可解决的问题,而非谜团。

免费资源的学习路径

以下资源按此顺序使用效果最佳。建议边学习边搭建小型测试网站,而非将清单视为必须首先完成的任务。

  • HTML:《HTML for People》专为完全没有编程经验的读者编写。若想深入了解语义化元素与无障碍设计,可参阅MDN关于内容结构的教程。
  • CSS:MDN的CSS基础教程涵盖了盒模型和布局等概念。如果更喜欢动手练习,freeCodeCamp的响应式设计课程会系统讲解HTML、CSS、无障碍设计以及实际应用中的移动端友好设计。
  • JavaScript:MDN的脚本编程教程是在HTML和CSS内容基础上的延伸,而freeCodeCamp的JavaScript课程则提供了互动式的学习方式。
  • 编程基础:哈佛大学提供的免费计算机科学入门课程CS50x教授计算思维、算法、数据结构、函数、条件语句和循环等内容。仅前几周的学习就能打下坚实基础。
  • 终端操作:MIT的“缺失的学期”课程涵盖了在shell环境、编辑器中的操作、命令行环境以及调试技巧。其中的shell相关课程是入门的好起点。
  • Git:免费在线书籍《Pro Git》从命令行和简单提交开始讲解,随后逐步介绍分支、远程仓库及托管服务等内容。
  • Markdown与YAML:对于Markdown,有基本语法参考手册说明各类符号对应的格式效果。YAML 1.2规范虽然比博客作者所需的内容更为详细,但其简介部分已能提供清晰的概览。
  • 验证:W3C Nu HTML Checker能够标记出无效的标记,帮助你尽早发现错误。
  • 如果你更喜欢一个集中的学习平台而非分散的资源,MDN的学习区域提供了涵盖HTML、CSS、JavaScript以及浏览器基础知识的完整课程。web.dev、The Odin Project和w3schools也是不错的选择。

    要保持正确的心态:个人网站通常只是业余项目,非传统的解决方案和错误都是学习过程的一部分,每完成一个小型网站,你就多掌握了一项可以应用到下一个项目中的技能。

    静态站点文档假设你已掌握的词汇

    生成器相关的文档往往使用大量专业术语,仿佛所有人一出生就掌握了这些知识。本节将用通俗的语言解释这些术语,并通过Eleventy(11ty)项目中的实际文件示例进行说明。

    标记、样式与行为:HTML、CSS和JavaScript

    HTML(超文本标记语言)用于描述文档的结构和含义:标题、段落、链接、图片、列表等。它并非编程语言,无法自行做出决策或重复操作,仅能声明页面上存在的内容。最简单的有效页面应包含文档类型声明、带有字符集和标题的<head>部分,以及包含内容的<body>部分。需要注意的是,这里没有任何内容用于控制颜色或字体,因此浏览器会使用默认样式。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <title>My First Page</title>
      </head>
      <body>
        <h1>Hello, world!</h1>
        <p>This page has a <a href="https://brennan.day">link</a> and a list:</p>
        <ul>
          <li>HTML gives a page its structure.</li>
          <li>There's no CSS yet, so this is all default styling.</li>
        </ul>
      </body>
    </html>Copy
    

    将此文件保存为index.html,然后在任意浏览器中打开,即可得到一个可正常使用的网页,无需服务器。(闭合标签后的Copy字样是复制按钮留下的残留内容,并非标记的一部分。)

    CSS(层叠样式表)负责控制该结构的呈现方式:颜色、间距、字体,以及布局如何适应不同的屏幕尺寸。以下规则设置了衬线字体,限制文本宽度以确保行文可读,通过自动边距使内容居中,并为页面赋予温暖的背景色和深色的文字颜色。标题则有单独的配色规则。

    body {
      font-family: Georgia, serif;
      max-width: 35rem;
      margin: 2rem auto;
      padding: 0 1rem;
      background: #fff2ce;
      color: #02005d;
    }
    
    h1 {
      color: rebeccapurple;
    }Copy
    

    将这些规则放在前面页面的<head>中的<style>元素内,无需修改任何HTML代码即可改变页面的外观。这种将内容与呈现分离的做法是所有静态站点生成器都遵循的设计理念。

    JavaScript是浏览器运行的编程语言。它能够实现各种功能:响应点击、更改内容、获取数据。但同时,它也是让简单页面变得臃肿缓慢的最常见原因,因此对于个人网站而言,最佳做法是在确实有必要时才使用它。下面的代码片段会查找id为surprise的元素,监听对该元素的点击事件,并在点击发生时替换第一个<h1>标签中的文本。

    const button = document.querySelector("#surprise");
    
    button.addEventListener("click", () => {
      document.querySelector("h1").textContent = "JavaScript did this!";
    });Copy
    

    要使此代码正常工作,页面上需要有一个对应的<button id="surprise">元素。如果没有这个元素,querySelector会返回null,而调用addEventListener则会引发错误,这是新手常遇到的第一个问题。

    JavaScript 不仅存在于浏览器中,生成器端也有应用。许多 SSG 工具本身就是用 JavaScript 编写的,并利用它来执行构建任务或解析模板。Eleventy 甚至允许整个模板都作为一个 JavaScript 文件:导出的函数返回的字符串内容就会成为页面的内容。

    // hello.11ty.js
    module.exports = function () {
      return "<h1>Hello from JavaScript!</h1>";
    };Copy
    

    此示例使用了 CommonJS 的 module.exports。较新版本的 Eleventy 也支持 ES 模块语法(export default),因此请查看你所安装版本的文档使用的是哪种语法风格。

    “静态”、“构建”和“输出”究竟是什么意思

    • 静态指的是内容的传递方式:服务器直接交付存储时的文件原貌,而不会为每位访问者生成新的响应。静态页面仍然可以包含JavaScript代码,也可以被编辑和重新部署。这并不意味着此类页面一定枯燥无味或永远无法更新。
    • 动态则意味着响应是在接收到请求时才由程序计算生成的。典型的内容管理系统会在每次访问时查询数据库并拼接出页面内容。在线商店就是很好的例子,因为其库存和购物车信息会不断变化。
    • 静态站点生成器是一种程序,它能够读取源文件(如Markdown内容、模板、配置文件、图片及其他资源),并生成一套完整的HTML、CSS、JavaScript和图片文件,这些文件可以被任何静态服务器托管。
  • 构建指的是执行生成器命令的一次运行,其作用是将源文件转换为输出结果。
  • 源文件即你需要编辑的文件:文章、模板、样式表以及配置文件。
  • 输出文件(或构建后的文件)是构建过程产生的结果,通常存放在如 _site/、public/ 或 dist/ 这样的文件夹中。一般不建议手动编辑这些文件,因为下一次构建时会将其覆盖。
  • 本地服务器是指运行在你自己机器上的Web服务器。开发服务器会在 localhost:8000 这样的地址上提供已构建的网站内容,并且通常会在你保存源文件时自动重新构建。
  • 配置文件与站点数据

    • 配置文件用于存储站点级的设置,如站点名称、基础URL、菜单、输出目录或订阅选项等。不同工具的文件名和格式各不相同:config.yml、hugo.toml、eleventy.config.js等等。
    • YAML是一种对人类友好的数据格式,常用于配置文件和前置内容中。它可以表示字符串、数字、列表以及键值对。缩进具有特定含义,因此一个位置错误的空格就可能导致构建失败。
    • 键值对是指包含名称和值的设置,例如title: My post。在YAML中,这类键值对的集合被称为映射。
    • 参数或选项是指传递给命令或写入文件中的设置。稍后会遇到的--serve标志就是一个例子。

    在 Eleventy 中,_data 文件夹中的文件会成为可供所有模板使用的全局数据。下面的 src/_data/site.json 文件存储了访问者可见的详细信息:站点名称、简短描述、作者、公开网址以及语言。

    {
      "name": "My Cool Blog",
      "description": "Where I write about whatever interests me.",
      "author": "Your Name",
      "url": "https://example.com",
      "language": "en"
    }
    

    每个键都会变成一个模板变量。包含 {{ site.name }} 的布局会显示“我的酷博客”这一值,因此只需修改这里的某一行即可更改站点名称,无需在每个页面中逐一查找。Jekyll 将类似的信息存储在 config.yml 中,而 Hugo 则存储在 hugo.toml 中;原理相同,只是文件不同。需要注意的是 JSON 语法非常严格:最后一个条目后若有尾随逗号就会构成语法错误,这一细节在后续指南中会变得很重要。

    布局、部分模板与模板引擎

    • 模板是一种可重复使用的文件,用于定义页面的结构,并为那些会变化的部分预留占位符。
    • 布局则是整个页面的模板,包括语言声明、<head>部分、页头、主要内容区域以及页脚。
    • 片段是指小型且可重复使用的组件,比如导航栏、页脚或文章元数据块。包含则是将某个片段插入到另一个文件中的指令。
    • 模板语言是用于在模板中输出变量、遍历数据以及进行条件判断的语法。Liquid、Nunjucks和Go模板都是常见的例子。
    • 条件判断是模板中的“是/否”规则,例如“仅当文章指定了主图时才显示该图片”。

    下面的基础布局文件 _includes/layouts/base.njk 是用 Nunjucks 编写的。标题由页面自身的标题与整个网站的名称组合而成,两个 include 标签用于插入页头和页脚的片段,而渲染后的页面内容则显示在 <main> 标签内。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <title>{{ title }} | {{ site.name }}</title>
      </head>
      <body>
        {% include "partials/header.njk" %}
        <main>
          {{ content | safe }}
        </main>
        {% include "partials/footer.njk" %}
      </body>
    </html>
    

    | safe 过滤器非常重要。Nunjucks 默认会对输出内容进行转义,这会导致文章中的 HTML 变成可见的标签。将 content 标记为安全内容即可告知引擎该字符串是可信的、已经渲染过的 HTML。仅应在控制范围内的内容上使用此功能。

    这些部分内容本身只是 HTML 片段,可能使用变量并包含其他部分文件。页头会通过站点名称链接回首页并加载导航栏;导航栏只是简单的链接列表;页脚则会根据站点数据打印出包含作者信息的版权声明。

    <!-- partials/header.njk -->
    <header>
      <a href="/">{{ site.name }}</a>
      {% include "partials/nav.njk" %}
    </header>
    
    <!-- partials/nav.njk -->
    <nav>
      <a href="/">Home</a>
      <a href="/archive/">Archive</a>
      <a href="/about/">About</a>
    </nav>
    
    <!-- partials/footer.njk -->
    <footer>
      <p>&copy; 2026 {{ site.author }}</p>
    </footer>
    

    各部分之间的连接方式如下:当一篇文章在开头声明 layout: base.njk 时,Eleventy 会先渲染该文章,然后将结果放置在布局中 {{ content | safe }} 所在的位置,同时每个 include 标签都会被其对应的部分文件替换。只需修改一次导航栏,网站上的所有页面在下次构建时就会自动更新。消除这种复制粘贴式的维护工作正是静态站点生成器存在的核心原因。不过有一个小问题:页脚中的年份是硬编码的,除非用变量替换,否则不会自动更新。

    内容文件、Markdown与前置信息

    • 内容文件是指页面或文章的源文件。Markdown是最常见的格式,但许多生成器也接受HTML、纯文本等其他格式。
    • Markdown是一种轻量级的标记语言,通过标点符号来表示结构:#用于标题,星号用于强调,破折号用于列表。生成器会将其转换为HTML。
    • 前置信息是位于内容文件最顶部的元数据块,通常由两行各三个破折号围成。它可以包含标题、日期、标签、布局名称或草稿标记。
    • 元数据是关于某段内容的描述性信息:标题、作者、发布日期、标签、描述、规范URL或所选布局。
  • 一个具备博客意识的生成器将文章视为一种概念。它能够按日期对文章进行排序,并轻松生成归档页、标签页以及RSS订阅源。
  • 下面的文件posts/my-first-post.md集成了所有这些功能。YAML前置内容设置了标题、日期、两个标签、布局以及草稿状态标志。正文则结合了普通的Markdown语法与Nunjucks风格的模板语法,用于显示标题并根据条件展示某句话。

    ---
    title: My First Post
    date: 2026-09-22
    tags:
      - posts
      - cats
    layout: post.njk
    draft: false
    ---
    Welcome to my blog! This paragraph is **Markdown**.
    
    This post is called "{{ title }}".
    
    {% if draft %}
      This sentence only appears while the post is a draft.
    {% endif %}
    

    Eleventy在渲染任何内容之前会先读取前置信息。layout键用于选择页面布局模板,posts标签会将对应文件添加到名为posts的集合中(归档页面可以遍历该集合),而date则决定博客文章的排序顺序。短横线之后的内容即为页面正文。由于此处draft的值为false,因此条件语句不会出现在输出结果中。需注意,Eleventy本身并不为draft键定义任何默认含义;是否将草稿文章排除在正式发布版本之外需由用户自行配置。

    托管、后端及部署相关术语

    • 托管服务是指用于存储你的输出文件并使其能够在互联网上被访问的服务或服务器。它与网站内容的编写以及版本控制是相互独立的。
  • 后端指的是服务器端的代码与基础设施:身份验证、表单处理、业务规则以及数据库查询。纯静态网站在展示页面时无需这些功能。
  • 数据库是软件可以查询和更新的结构化存储方式,有点类似于可编程的电子表格。传统的动态博客会将文章、评论和设置保存在其中。每当提到SQL时,就意味着涉及数据库。
  • FTP(文件传输协议)用于在计算机之间传输文件,通常是从你的机器传送到网络主机,是发布内容的一种方式。
  • rsync是一种命令行工具,能够同步文件夹并仅传输发生变化的内容,因此非常适合用于上传重建后的网站。
  • 链接验证用于检查那些指向不存在页面的链接。有些生成工具会在构建过程中进行此操作,而另一些则依赖插件或外部工具。
  • 单页版本控制

    • 版本控制能够记录文件随时间发生的变更,以便你查看历史记录、比较不同版本、回退操作以及协同工作。
    • Git是一种特定的版本控制程序。它完全在你的计算机上运行,无需任何在线服务即可追踪历史记录。
    • 仓库(repo)是指由Git管理其历史记录的项目文件夹。
    • 提交是指对变更所做的保存快照,通常会附带描述这些变更的文字信息。
    • 远程仓库是指仓库的另一份副本,通常托管在Codeberg、GitHub、GitLab或你自己的服务器上。
    • Push功能可将本地提交的代码发送到远程仓库;pull功能则从远程仓库获取提交内容并合并到本地副本中。
    • Git托管服务用于存储代码库,通常还提供问题跟踪、代码审查和自动构建功能。虽然这些服务很方便,但它们并非Git本身,没有它们Git依然可以正常工作。

    通过终端使用Git发布新文章只需四条命令。其中第一条命令每个项目只需执行一次,其余三条则是日常操作:将文件暂存、创建快照并将其发送到远程仓库。

    git init                             # turn this folder into a repository (once)
    git add posts/new-post.md            # stage the file for your next commit
    git commit -m "Add new post"         # save a snapshot with a message
    git push                             # copy your commits to the remoteCopy
    

    实际上,只有在配置了远程仓库之后,git push才能正常使用,例如通过 git remote add origin 来配置;而首次推送分支时通常需要使用 git push -u origin main 或类似命令。一旦配置完成,直接使用 git push 即可。

    静态站点生成器的起源

    有了这些术语作为基础,相关发展历史就更容易理解了——因为每一代工具都会新增上述概念之一。

    将内容编写与标记分离的做法早在“静态站点生成器”这一术语出现之前就已存在。HSC 是 “HTML Sucks Completely” 的缩写,是由 Thomas Aglassinger 于1996年发布的一款HTML预处理器。早在该类别被正式命名之前的十年左右,它就已经具备了包含、条件判断以及链接验证等功能。

    在20世纪90年代末至21世纪初,大多数想要建立博客的人会选择Blogger、LiveJournal或Open Diary这类托管的动态服务,或是安装WordPress这样的数据库驱动软件。而由Ben和Mena Trott于2001年创建的Perl平台Movable Type则采取了不同的方式:每次通过其网页界面发布内容时,它都会将博客重新生成为纯静态的HTML文件。用户无需操作终端,就能获得静态页面。这为那些根本不会输入构建命令的人带来了静态输出的优势。

    Nanoc诞生于2007年,由Denis Defreyne开发,起因是他使用的96 MB虚拟服务器上Ruby内容管理系统速度实在过慢。该系统引入了布局功能、每页元数据、Markdown支持以及插件机制。2008年12月,GitHub联合创始人Tom Preston-Werner因对功能繁重的博客引擎不满而发布了Jekyll。Jekyll基于Nanoc的理念,并增加了两项关键特性:每个内容文件顶部的YAML前置数据,以及开箱即用的博客功能,这样只需一个包含Markdown文件的文件夹即可无需额外设置便成为一篇博客。与此同时推出的GitHub Pages提供了免费的静态网站托管服务,这一组合极大地推动了SSG技术的普及。

    此后几乎所有的工具都是用其他语言对同一模式的重构。现已停止开发的 Octopress 与 Middleman 继承了 Ruby 系列的发展脉络。Pelican 基于 Python,而 Hyde 则建立在 Laravel 之上。

    2013 年 7 月,Steve Francia 推出了 Hugo,这是一个以编译后的单一二进制文件形式分发的 Go 语言程序。与 Jekyll 不同,它无需安装 Ruby 环境,也无需处理不同版本的 gem,即便面对数千页的网站,其构建速度也能在几秒内完成,这成为了它的显著特点。

    2017 年末,Zach Leatherman 发布了 Eleventy(简称 11ty),这是一种基于 JavaScript、可通过 npm 安装的灵活的 Jekyll 替代方案。Jekyll 依赖 Liquid 模板语言,而 Eleventy 则支持多种模板格式:

    • 标记语言与内容格式:纯 HTML(.html)、Markdown(.md)以及 MDX(.mdx)
  • 基于 JavaScript 的模板:.11ty.js 文件、TypeScript(.ts)、JSX(.jsx)以及 WebC(.webc)
  • 传统模板语言:Liquid、Nunjucks(.njk)、Handlebars(.hbs)、Mustache、EJS、Haml 和 Pug
  • 用 Sass 编写的样式表(.scss)
  • 您自行注册的任何自定义扩展
  • 其中一些格式需要插件或额外配置才能正常使用,因此在使用前请查阅最新的 Eleventy 文档。如果您已经熟悉某种编程语言,Jamstack.org 上的生成器目录可帮助您筛选出用该语言编写的工具。

    该目录中的许多生成器已多年未更新版本,对于个人网站而言这通常是可以接受的。静态网站不会向访问者暴露任何服务器端代码或数据库,从而消除了动态博客最常见的攻击面。如果新版本的生成器添加了你不喜欢的功能,你仍然可以使用旧版本,它依然能够生成相同的网站。需要注意的是,“不更新”并不等同于“无风险”:构建时使用的依赖项、运行构建的机器以及你嵌入的任何第三方JavaScript都仍需关注,而未经维护的工具最终可能无法在更新的操作系统或语言运行时上正常安装。

    Git解决了两个问题,而你其实并不需要它们

    新手指南几乎总是建议使用 Git。这既是因为开发人员的习惯,也有历史原因:首个被广泛采用的静态站点生成器 Jekyll 最初就是作为一个 GitHub 项目诞生的。将网站托管在 GitHub、Codeberg 或 GitLab 上时,对于“我的文件存放在哪里?”这个问题的回答就是“在仓库中”。而 Neocities 或 Nekoweb 这类服务则采用不同的方式回答这个问题:你需要通过网站本身上传文件。

    在 Codeberg Pages 或 GitLab Pages 这类基于 Git 的托管平台上,即使是通过网页界面进行的编辑,也会在后台转化为提交操作并推送到仓库。无论你是否亲自输入过 Git 命令,实际上都在使用 Git。

    如果你自己托管网站,文件会存储在你的机器上,通常位于 Linux 系统的 /var/www/html 目录中。而在 Tildeverse 服务器这样的共享社区机器上,文件则存放在你账户的公共文件夹中;常见的操作流程是在本地进行构建,然后使用 rsync 将结果复制到共享计算机上,从而实现自动服务。

    在这些架构中,将 Git 所承担的两种任务区分开来会更有帮助:

    • 记录版本历史,这样你就可以撤销错误的编辑,并查看具体更改了什么以及何时更改的
    • 将构建好的文件传输到需要提供服务的位置

    这两项工作其实都不一定非得使用 Git。rsync、FTP 客户端或浏览器上传表单都能很好地发布网站,对于小型个人项目而言,普通的备份也能替代历史版本记录。Git 受欢迎是因为它能够同时处理这两项任务,而且无需成本还能与免费托管服务配合使用,因此它更像是一种最简便的选择而非硬性要求。

    理想架构与实际实现之间的差距

    从理论上讲,博客生成器的架构简直整齐到令人乏味:

    • 文章存储在 posts/ 文件夹中的 Markdown 文件里
    • layout/ 文件夹中的模板负责渲染这些文章
    • 该模板会从 partials/ 文件夹中整合 HTML 片段,比如 header.html 和 footer.html
  • 项目根目录下的 config.yml(或类似文件)用于存储站点全局参数,如名称和颜色等。
  • 每篇文章在两行短横线之间还包含自己的前置内容。
  • 该前置内容存储了文章的标题、日期和标签,这样生成器就可以对内容进行排序和标记,而无需将所有信息都编码到文件名中,例如 "2024-03-14-my-post-title.md"。
  • 图表中未展示的部分是背后的实现机制。要将那些文件夹转换为实际的网站,比如在 _site/ 目录中生成站点,就需要编程语言运行时来执行生成器,而这一流程中的每一个环节都可能存在故障。

    主流工具试图通过一个命令来隐藏这一过程,比如 hugo build 或 npx @11ty/eleventy --serve。Eleventy 命令中的 --serve 选项会启动一个本地开发服务器,每当您保存源文件时就会重新构建,而不会只构建一次后就停止。正是这种快速的反馈机制,使得编辑静态网站时的体验几乎与编辑实时页面一样即时。

    部署平台将这一理念延伸到了云端。Netlify或可自托管的Coolify会在远程机器上运行你的构建任务,识别你所使用的生成工具,执行相应的命令,并将输出文件夹发布到如yoursitename.netlify.app这样的地址。从概念上讲,这与将手写的HTML文件上传到Neocities并获得yoursitename.neocities.org的流程类似,只不过构建是在他们的服务器上完成的。surge.sh、GitHub Pages、Vercel以及Cloudflare的Pages产品也提供了类似的工作流程;选择哪种取决于你更看重便利性还是独立于大型平台。如果你想了解完整的端到端部署流程,我们关于将小型网站部署到Cloudflare的指南就介绍了一个具体的实例。

    为何一个多余的逗号就能毁掉一切

    所有这些便利都建立在某些假设之上:你的文件是正确的,语言运行时安装完好,而且你熟悉终端操作。开发者们常常高估了最后这项技能的普及程度,而这正是这幅XKCD漫画所精准描绘的盲点。

    构建过程也极为脆弱。重要文件中哪怕只有一个微小的语法错误,比如多一个逗号,都可能让安装或构建彻底失败。更糟糕的是,错误信息通常来自底层的运行时或解析器,而非生成工具,因此其表述是以编程语言为视角,而非针对你的网站。一个实际的例子是:如果JSON数据文件的最后一个属性后有多余的逗号,Netlify的构建就会失败,并给出一堆解析器堆栈跟踪信息,而这些信息用的是初学者难以理解的语言,从未提及具体的文件名。

    面对这样的输出结果,许多人会合理地选择手写HTML、转而使用托管型CMS,或者干脆放弃建立网站的想法。最后这种结果才是真正的损失。以下一些习惯有助于降低出现这种情况的概率:

    • 在推送之前先使用开发服务器在本地进行构建,这样错误会首先显示在你的屏幕上
    • 一次只修改一处内容,这样一旦出问题,最新的修改就会成为最明显的嫌疑对象
    • 从下往上阅读错误信息,寻找文件名和行号,这通常是真正的线索
    • 使用编辑器插件或代码检查工具来验证JSON和YAML格式,因为这些格式导致了大量初学者的构建错误
    • 经常提交可运行的版本,这样你随时都可以回到上一个能成功构建的版本

    通过修改示例项目来学习

    学习生成器的有效方法是从现成的启动模板或主题开始,逐步进行修改,直到理解每个文件的功能。最终你就能掌握足够的知识来从零编写自己的生成器。适合此目的的优质启动模板通常具备以下特点:清晰的文档说明、较少的文件数量,以及内容与设置都位于显而易见的位置。典型的示例如下:

    • Hugo启动模板,文章存放在/post目录中,站点自定义设置则在hugo.toml文件中完成,还可选择启用IndieWeb的微格式2及预置的h-card功能
    • Eleventy启动模板,文章存放在/posts目录中,站点相关配置则保存在site.js等数据文件中
    • Jekyll启动模板,文章存放在/_posts目录中,设置则保存在_config.yml文件中

    刻意保持简洁的入门级工具对学习而言是个优势。由于样式极简,结构一目了然,所有的设计工作都由你自行完成。

    几乎无需复杂组件的微型生成器

    Hugo、Eleventy和Jekyll是常见的选择,但对于那些希望能在短时间内了解整个工具的人来说,还有许多更为小巧的生成器可供使用。

    • barf,即“博客真的很有趣”的缩写,是由btxx编写的约170行长的shell脚本,源自Karl Bartel的blog.sh。它没有前置内容也没有模板功能。你需要编写Markdown文件,运行make build命令,然后通过rsync上传生成的build/文件夹。该脚本会自动生成RSS订阅源,可在OpenBSD、macOS和Linux上直接运行,其样式表仅有四行代码。README文件及在线演示展示了最终效果。
  • bashblog实际上是一个名为bb.sh的脚本,共约1,000行代码,除了date、grep、sed和head这类标准Unix工具外没有其他依赖。该工具的第一个版本由Carlos Fenollosa在2011年编写,并在当时的一篇博客文章中解释了其实现方式;截至本文撰写时它仍在持续维护中。将bb.sh放入服务器的公共目录后,执行./bb.sh post即可创建新文章。草稿、标签、Markdown格式以及RSS功能均无需任何安装步骤即可使用。还有一个名为bashblog-ng的社区分支,为该工具增加了更多功能。
  • kiki由vga256开发,自称是一款占用资源极少的微型主页构建工具。它采用PHP而非shell编写,既可以作为动态网站运行,也可作为静态内容生成器使用,还能充当公共维基平台,早期版本甚至可用来创建Gopher洞。该工具包含约1,500行手工编写的代码,不使用JavaScript且没有外部依赖。它属于共享软件,带水印的版本可免费获取,若需要更多功能,则需在撰写本文时支付15加元的一次性费用。适合那些支持PHP但不提供shell访问权限的托管服务。
  • 还有几款值得了解的工具:

    • ssg是Roman Zolotarev开发的符合POSIX标准的shell脚本,为列表中的多个工具提供了灵感;pyssg则是其Python语言重写版本。
    • sw是用C语言编写的、刻意追求极简设计的网页框架,其分支simple-static进一步简化了功能,正如其README中所描述的那样,是维护者能想象到的最简单的静态站点生成器。
    • makesite.py是barf和bashblog的Python版本,代码行数不到130行,由Sunaina Pai基于“代码本身即文档”这一理念开发。它没有配置层,用户可以直接阅读并修改脚本。

    这些工具的功能远不及Hugo或Eleventy,而这正是它们的优势所在。它们在插件和模板格式上做出的妥协,通过极高的透明度得到了弥补:一旦出现故障,整个程序的代码都会显示在屏幕上。

    如果您愿意完全脱离网络环境,那么在 Gemini 协议上发布内容也是一个选择。Gemini 页面采用简单的文本格式直接呈现,因此往往根本无需进行任何生成操作。

    关键要点

    • 首先学会手动编写页面;生成器只是自动化处理那些您本应已熟悉的重复性工作。
    • SSG 令人困惑的地方大多在于术语。一旦理解了源代码、输出结果、构建过程、布局结构、部分组件以及前置内容等概念,各类工具的文档就会显得相似。
    • 布局、部分组件以及全局数据文件的存在,是为了确保一次所做的更改能在下一次构建时同步应用到所有地方。
    • Git 能提供版本历史记录和发布路径,但对于个人网站而言,rsync、FTP 或浏览器上传也是可行的替代方案。
  • 构建失败通常是由于一些细微的语法错误导致的,这些错误会通过不友好的运行时消息表现出来。建议在本地进行构建,一次只修改一处内容,并验证数据文件。
  • 并没有必须使用某种流行的生成器。一个170行的shell脚本或单个PHP文件就能运行一个功能完善的博客,而最好的工具就是那个能帮助你将内容发布到网上的工具。