Web 开发 约 7 分钟阅读

用 Node.js 搭一个够用的静态站点

内容型站点不一定需要前端框架。一个小的构建脚本、一套模板和几条部署校验,已经足够支撑长期更新。

这个站点本身就是一个静态站点。没有数据库,没有服务端渲染,也没有前端路由。 每次写完文章,运行一个脚本生成 HTML,再同步到服务器。整套流程很小,但足够稳定。

一、目录结构

site/
├── src/
│   ├── layout.html
│   ├── index.html
│   ├── articles/
│   │   └── first-post.html
│   └── assets/
│       └── style.css
├── build.mjs
└── dist/

src 放源文件和模板,dist 是构建产物,部署时只同步 dist。 产物目录不手工修改,避免源文件和线上文件不一致。

二、用模板拼装公共部分

页头、页脚和导航在每个页面重复,改动一次就要改很多文件。我用一个非常简单的占位符替换来处理: 模板里写 {{CONTENT}},构建时把页面正文填进去。

import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { dirname, join } from 'node:path';

const layout = await readFile('src/layout.html', 'utf8');

async function render(src, dest) {
  const body = await readFile(src, 'utf8');
  const html = layout.replace('{{CONTENT}}', body);
  await mkdir(dirname(dest), { recursive: true });
  await writeFile(dest, html);
}

await render('src/index.html', 'dist/index.html');
await render('src/articles/first-post.html', 'dist/articles/first-post.html');

这里没有模板引擎,也没有转义逻辑。因为输入全部来自自己维护的文件,不需要处理不可信内容。 如果要支持用户提交的内容,就必须换成会正确转义的模板方案。

三、给静态资源加版本

浏览器会缓存 CSS 和图片,更新后用户可能还看到旧样式。最简单的方式是在文件名或查询串里带上内容哈希。

import { createHash } from 'node:crypto';

const css = await readFile('src/assets/style.css');
const hash = createHash('sha256').update(css).digest('hex').slice(0, 8);

await writeFile(`dist/assets/style.${hash}.css`, css);

页面里引用带哈希的文件名,就可以给静态资源设置很长的缓存时间。 HTML 本身保持短缓存或协商缓存,这样新版本能及时生效。

四、本地预览

预览不需要复杂工具,Node 自带的静态服务器就够了。重点是要按真实路径访问,确认相对路径和绝对路径都没有问题。

npx serve dist
# 或者
python3 -m http.server 8080 --directory dist

我通常会在预览时检查三件事:导航链接是否可达、移动端宽度下是否横向溢出、控制台是否有 404。 这三项能覆盖大部分低级问题。

五、部署时做摘要校验

静态站点可以用 rsync 同步,再用摘要确认线上文件与本地一致。保留服务器上的验证文件, 不要把整个目录删掉重传。

rsync -av --delete \
  --exclude 'MP_verify_*.txt' \
  dist/ deploy@example.com:/srv/site/

find dist -type f -print0 | sort -z | xargs -0 sha256sum > dist.sha256

--delete 会删除目标目录中多余的文件,所以排除规则要提前确认。 第一次部署前,先做一次备份,确认可以回滚。

六、缓存与压缩交给 Nginx

静态文件不需要应用层处理。让 Nginx 负责 gzip、缓存头和 404 页面,构建脚本只关心内容。

location /assets/ {
  expires 30d;
  add_header Cache-Control "public, immutable";
}

location / {
  try_files $uri $uri.html $uri/ =404;
}

try_files 里加上 $uri.html,可以让 /about 这样的地址 对应到 about.html,链接写起来更干净。

七、什么时候需要更重的方案

当站点需要评论、搜索、多作者协作或频繁的结构化内容时,手工维护 HTML 会变得吃力。 那时候再引入生成器或内容管理系统也不迟。工具应该跟着需求走,而不是一开始就堆上去。

← 返回文章列表 个人软件技术分享