Hotdry.
application-security

用 Nixtml 和 Nix Flakes 实现声明式静态站点构建:内容作为推导值、主题覆盖与可重复部署

利用 Nixtml 通过 Nix flakes 声明式生成静态站点:内容目录转为 HTML 推导、集合分页与 RSS、模块化主题覆盖,无需 JS bundler 即可实现可重复部署。

Nixtml 是一个纯 Nix 实现的静态网站生成器,利用 Nix flakes 的声明式特性,将内容、静态资源和主题组织成可重复构建的推导值(derivations)。这避免了传统 SSG 如 Hugo 或 Jekyll 中的 JS bundler 依赖,直接在 Nix 环境中生成纯静态 HTML、CSS 和 RSS,实现跨机一致的部署。核心优势在于一切均为 Nix 表达式:内容 Markdown 文件自动转换为 HTML 页面,集合支持分页和分类,主题通过模块导入覆盖,整个站点作为一个 flake 输出包,便于 nix build 和缓存。

配置 flake.nix:声明式站点定义

Nixtml 的入口是 nixtml.lib.mkWebsite,嵌入 flake outputs 中。以下是完整配置清单,按参数分类,便于落地:

基础参数(必需)

  • name: 站点名称,如 "my-blog",用于输出目录。
  • baseURL: 部署 URL,如 "https://my-blog.com",确保相对链接正确。
  • pkgs: 从 nixpkgs 导入的包集。

元数据(metadata)

metadata = {
  lang = "en";
  title = "My Blog";
  description = "This is my blog";
};

这些在模板中可用,支持多语言和 SEO。

内容与静态(content.dir/static.dir)

  • content.dir = ./content;:遍历 Markdown 文件,content/blog/post.mdblog/post/index.html
  • static.dir = ./static;:符号链接整个目录到输出,无需额外处理图片 / JS/CSS。

集合(collections):用于博客分页、RSS 和分类。

collections.blog = {
  path = "posts";  # content/posts/ 下文件
  pagination.perPage = 5;  # 每页 5 篇,推荐阈值避免长页
  rss.enable = true;  # 生成 /posts/index.xml
  taxonomies = [ "tags" "series" ];  # 支持 tags/series 等,自动生成 /posts/tags/emacs/index.html
};

分页页码:posts/index.htmlposts/page/2/index.html。Taxonomies 前置 YAML:

---
title: "My Emacs Setup"
date: 2024-07-15
tags: [ "emacs", "productivity" ]
series: [ "dotfiles" ]
---

主题导入(imports)

  • imports = [ ./theme.nix ];:模块化覆盖布局,如自定义 website.layouts.base

Serve 应用

apps.serve = {
  type = "app";
  program = (pkgs.writeShellScript "serve" ''
    ${pkgs.python3}/bin/python -m http.server -d ${self.packages.${system}.blog} 8080
  '').outPath;
};

运行 nix run .#serve 本地预览。

完整 flake 示例见官方仓库,确保 inputs.nixtml.url = "github:arnarg/nixtml";

模板系统:Nix 函数式 HTML

模板定义在模块的 website.layouts 下,每个为函数返回字符串或列表。Nixtml 提供 lib.tagshtmlheadbody,及 attrs 辅助。

函数式示例(推荐,类型安全)

{lib, ...}: let
  inherit (lib.tags) html head body div;
  inherit (lib) attrs;
in {
  website.layouts.base = { path, content, ... }@context:
    "<!DOCTYPE html>\n" + html [ (attrs.lang metadata.lang) ] [
      head [] [ (partials.head context) ]
      body [ (attrs.classes [ "font-sans" "bg-white" ]) ] [
        div [ (attrs.classes [ "container" ]) ] [ content ]
      ]
    ];
}

字符串模板备选

<!DOCTYPE html>
<html lang="${metadata.lang}">
  <head>${partials.head context}</head>
  <body class="font-sans bg-white">
    <div class="container">${content}</div>
  </body>
</html>

标准模板

  • base: 所有页骨架。
  • home: index.md。
  • page: 其他 Markdown。
  • collection / taxonomy: 分页页,接收 { pageNumber, items, hasNext, ... }
  • partials: 共享片段。

参数化监控:模板上下文始终包含 metadatacontent,集合页有 totalPages > 1 时启用分页导航。

构建、部署与可重复性

构建:nix build .#blog,输出 /result 目录纯静态文件。部署到 Netlify/Cloudflare Pages/Vercel 等,支持 GitHub Actions:

- nix build .#blog
- zip -r site.zip result/
- curl -T site.zip $DEPLOY_URL

无 JS bundler:Nix 隔离构建,确保 reproducible,无隐式依赖。

工程化参数与清单

参数 推荐值 作用
pagination.perPage 5-10 加载速度阈值,SEO 友好
rss.enable true 订阅源,perPage 同步
taxonomies ["tags"] 最多 3 个,避免碎片
baseURL 精确域名 绝对链接,避免重定向
metadata.description <160 字 OpenGraph/SEO

回滚:Nix 版本固定 flake.lock,git checkout old-commit && nix build 瞬间回退。缓存:Cachix 或自建 binary cache,首次构建后秒级部署。

风险控制:Nix 学习曲线,起步用官方 examples.simple/blog 测试。规模大时,拆分 modules 避免单文件过长。

Nixtml 将静态站点构建提升到 Nix 声明式水平,内容即推导、主题即模块,完美契合 DevOps 流程。

资料来源

查看归档