Node.js 22.12+ 结合大模型,自动化搭建官网并秒级部署

上周有个朋友问我:能不能把散落在文档和笔记里的 Node.js 学习资料,快速变成一个能对外访问的官网?我最初的直觉仍是老老实实建站:设计栏目、写安装教程、整理 API、再配一个部署流程。做到一半我发现,真正消耗时间的不是“想清楚写什么”,而是重复的页面结构和发布步骤。于是换了一种做法:输入一句话,让模型生成内容,Node.js 负责解析、渲染和构建,最后自动化推到服务器上线。全程跑下来,从“一句话”到官网可访问,核心构建和发布加起来不过几分钟。这篇文章把完整链路拆开讲,包括环境安装、版本选择、内容生成、模板渲染、CentOS 7.9 上的发布细节和排查手册,适合想快速搭建学习站、文档站或个人官网的开发者参考。

1. 这个项目的核心思路:从一句话到完整官网

1.1 项目到底解决了什么问题

我最初接到的需求很简单:给团队内部的转岗同学搭一个 Node.js 学习官网,页面上至少要包含课程大纲、安装教程、核心知识点、实战项目和常见报错汇总。按传统做法,我得先画原型,再手写 HTML、CSS,最后还要维护一堆课程页面。最麻烦的是内容更新:今天加一节异步编程,明天补一个模块机制,每次都要动模板、动目录,很容易改错。

后来我把问题拆开看,发现这类学习网站的页面结构其实非常固定——首页、栏目页、课程详情页、安装指引页,一共没几类。与其每加一页就人工折腾一遍,不如写一套自动化流程:用户给一句话,例如“为 Node.js 初学者生成一个学习官网,包含课程目录和安装指南”,程序调用大模型生成结构化内容,Node.js 脚本把内容套到模板里,最后提交代码触发部署,页面直接可访问。

这个项目想解决的,本质上是“内容型小官网的重复劳动”。一句话输入之所以可行,是因为网站的信息架构足够稳定:标题、简介、课程分类、每节课的正文,这些都是固定字段。只要内容层能生成,渲染层能自动处理,剩下的就是发布。

它并不是要替代人工编辑,而是把“生成内容、拼装页面、发布上线”三个环节自动化,让你把省下来的时间用在真正需要判断的内容质量上。对个人开发者来说,这个思路同样适用于学习笔记站、工具文档站甚至简历主页。

1.2 方案选型与路径设计

整个流程我拆成四个环节,每个环节都做了取舍,下面逐个说。

内容生成:我选择让大模型返回 JSON,而不是直接返回完整 HTML。原因是 HTML 和模板耦合太紧,后续想换一套 UI,模型生成的内容就不能用了。JSON 只描述“站点有哪些课程、每节课标题是什么、正文是什么”,模板层完全独立,换皮肤只需要改 EJS 模板。

网页渲染:技术栈选了 Node.js 脚本 + EJS 模板 + marked。EJS 负责页面骨架,marked 负责把 Markdown 正文转成 HTML。为什么不直接上 VitePress、VuePress 这类文档生成器?因为它们本身是完整的生态,学习成本和工程依赖都不低。当前场景只需要几个固定页面,Node 脚本控制在几十行内,反而更好维护。

页面形态:选纯静态页面,而不是 SSR。学习官网没有登录、动态查询、接口交互的需求,静态页加载快,Nginx 直接托管即可,服务器可以非常便宜。后期如果要做评论、访问统计、在线练习判题,再加 Node 后端服务,也不会推倒重来。

发布方式:本地构建完 dist 目录后,通过 Git 或 rsync 推到服务器,Nginx 指向该目录。这个方案在 CentOS 7.9 上表现很稳,没有常驻进程,也就没有进程崩溃、内存泄漏的额外维护需求。

2. 环境准备与 Node.js 版本细节

2.1 本地开发环境安装与验证

标题里提到的 Node.js 22.12+ 是整个项目的运行基础,所以先说环境。如果你从零开始,第一步是确认当前机器有没有安装 Node.js。打开终端执行:

node -v npm -v

有输出就是已安装,例如v22.12.0。如果提示node: command not found,说明没装或者 PATH 没配置。

安装方式我有两种推荐。一是有图形界面就直接去 Node 官网下载 LTS 安装包,一路 Next,适合不想折腾的人。二是用 nvm 管理版本,适合需要在多个版本间切换的开发者:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash source ~/.bashrc nvm install 22.12.0 nvm use 22.12.0

nvm use之后立刻执行node -v,看到版本号就说明配置成功。Windows 用户如果遇到“node 不是内部或外部命令”,通常是安装完成后没有把安装目录加进 PATH,重新打开终端或者手动配置环境变量即可。

还有一个小检查:npm 默认源有时候会慢,尤其是刚装完环境拉依赖的时候。执行npm config get registry,如果结果是https://registry.npmjs.org/但不稳定,可以临时切换:

npm config set registry https://registry.npmmirror.com

这个操作不影响项目逻辑,只是让 npm install 更快。

2.2 CentOS 7.9 服务器部署环境

本地环境跑通以后,要把网站发布上线,我用的是一台 CentOS 7.9 服务器。CentOS 7.9 的系统仓库默认带的 Node.js 版本很旧,直接yum install nodejs装出来可能只有 v6 或 v10,跑不了现代脚本。所以要么用 nvm,要么用二进制包手动安装。

二进制方式适合生产环境,不依赖用户目录:

wget https://nodejs.org/dist/v22.12.0/node-v22.12.0-linux-x64.tar.xz mkdir -p /usr/local/node tar -xf node-v22.12.0-linux-x64.tar.xz -C /usr/local/node --strip-components=1 ln -s /usr/local/node/bin/node /usr/local/bin/node ln -s /usr/local/node/bin/npm /usr/local/bin/npm

装完执行node -v验证。这里有个容易踩的坑:老系统直接跑新版官方二进制,可能会报GLIBC_2.28 not found一类的错误。官方预编译包对 glibc 版本有要求,CentOS 7.9 自带版本不一定匹配。遇到这种情况,优先改用 NodeSource 仓库安装,或者用 nvm 从源码编译。源码编译前需要确认 gcc、make、python 已安装,编译时间会长一些,但版本自由度最高。

验证相关依赖可以用:

ldd --version gcc --version

这些检查看着不起眼,但能避免部署到一半才发现环境跑不起来的尴尬。

2.3 为什么锁 Node.js 22.12+

有人会问,用 18 或 20 不是更稳妥吗?为什么偏要 22.12+?答案很简单:项目用到了这个版本之后新增的特性,尤其是两个对脚本开发很实用的能力。

第一个是--env-file。以前在 Node 脚本里读环境变量,要额外引入 dotenv,或者手动用fs读.env文件。Node 22 直接支持:

node --env-file=.env scripts/generate.mjs "一句话生成Node.js学习官网"

.env里的LLM_API_KEY=xxx会自动写入process.env,这点对我这种想少装依赖的人非常友好。

第二个是node --run,可以直接执行 package.json 里的脚本,效果类似 npm run,但更轻量。配合 22.12+ 的 require(esm) 能力,写脚本时不用纠结模块格式,整体开发效率提升不少。

另外 22 系列本身是 LTS 线,比 24 这类激进版本稳定,比 18 更现代,对普通服务器部署和长期维护都比较合适。如果你在项目里看到“node.js 22.12+”这个标记,基本可以理解为作者在要求一个既稳定又不过时的运行环境。

3. 核心实现:内容生成、页面渲染与自动发布

3.1 CLI 脚本与大模型接口:一句话变成结构化数据

项目的入口是一个generate.mjs脚本,它接收命令行参数,也就是那句话,然后调用大模型接口,把结果写到content/site.json。脚本骨架如下:

import { writeFile, mkdir } from 'node:fs/promises'; import { chat } from './llm.mjs'; import { parseJsonFromResponse } from './utils.mjs'; const userPrompt = process.argv[2] || '为 Node.js 初学者生成一个学习官网'; const raw = await chat('content-builder', [ { role: 'user', content: userPrompt } ]); const site = parseJsonFromResponse(raw); await mkdir('content', { recursive: true }); await writeFile('content/site.json', JSON.stringify(site, null, 2), 'utf8'); console.log('Content generated: content/site.json');

核心在于让模型输出什么结构。我使用的系统提示词大致是:

你是一个技术教育网站内容架构师。请根据用户需求,生成符合以下 JSON Schema 的站点内容: { "title": "官网标题", "description": "一句话简介", "categories": [ { "name": "课程分类名", "lessons": [ { "title": "课程标题", "slug": "url路径", "level": "beginner|intermediate", "summary": "课程简介", "content": "课程正文,Markdown格式" } ] } ] } 只输出 JSON,不要用代码块包裹。

这里有个关键细节:让模型直接生成 HTML 会踩很多坑,比如标签嵌套错误、样式类名随意、模板数据缺失。让模型生成 JSON,再交给 Node.js 渲染层处理,数据与表现分离,整个流程更可控。模型返回的 JSON 偶尔会被 Markdown 代码块包裹,所以parseJsonFromResponse里要做一个清洗,把开头的```json和结尾的```去掉,再交给JSON.parse。

3.2 模板渲染:把 JSON 和 Markdown 变成官网页面

拿到content/site.json之后,下一步是渲染。我用 EJS 做页面骨架,marked 把课程正文从 Markdown 转为 HTML。

src/layout.ejs大致长这样:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><%= title %></title> <link rel="stylesheet" href="/assets/style.css"> </head> <body> <%- content %> </body> </html>

渲染脚本render.mjs遍历课程,生成每个详情页:

import ejs from 'ejs'; import { marked } from 'marked'; import { readFile, writeFile, mkdir } from 'node:fs/promises'; const site = JSON.parse(await readFile('content/site.json', 'utf8')); const layout = await readFile('src/layout.ejs', 'utf8'); await mkdir('dist', { recursive: true }); for (const category of site.categories) { for (const lesson of category.lessons) { const content = marked.parse(lesson.content); const html = ejs.render(layout, { title: lesson.title, content, site }); await writeFile(`dist/${lesson.slug}.html`, html, 'utf8'); } }

注意 EJS 模板里content变量必须用<%- content %>,也就是不带 HTML 转义,才能让 marked 生成的标签正常输出。如果手滑用了<%= content %>,页面会出现一大段转义后的 HTML 文本,这个错后续排查时容易忽略。

除了详情页,我还会生成一个首页和一个课程列表页。实现方式一样,只是往模板里塞的数据不同。整个渲染逻辑控制在 100 行以内,比引入一套文档生成框架轻得多。

3.3 部署闭环:Git + 构建 + Nginx 秒发布上线

网站真正“上线”的环节,我拆成了三步:本地构建、同步文件、Nginx 指向静态目录。

package.json 里的脚本:

{ "scripts": { "build": "node scripts/generate.mjs && node scripts/render.mjs", "preview": "npx serve dist", "deploy": "bash scripts/deploy.sh" } }

deploy.sh 的核心内容:

#!/bin/bash set -e npm run build rsync -av --delete dist/ root@your-server:/var/www/nodejs-site/ ssh root@your-server "nginx -s reload"

如果不想用 rsync,也可以把 dist 目录推送到 Git 仓库,在服务器的站点目录里执行git pull。两种方式我都试过,rsync 胜在可以直接同步文件并删除远端多余文件,适合个人站;Git 方式胜在有版本记录,适合多人协作。实际部署我用的是前者,因为敏感信息不会进入 Git 历史。

服务器上的 Nginx 配置:

server { listen 80; server_name example.com; root /var/www/nodejs-site; index index.html; charset utf-8; location / { try_files $uri $uri/ =404; } }

纯静态页面没有 Node 常驻进程,也就不需要 pm2,Nginx 直接读文件响应请求,性能很好且配置简单。发布速度为什么能到“秒级”?因为每次改版只重新构建 dist,文件量不大,rsync 增量同步加上 Nginx reload,整个流程通常十几秒内完成。如果只是更新一节课的内容,重建时间更短,体验上确实接近“秒发布上线”。

4. 常见问题与排查实录

4.1 安装与版本类问题

现象可能原因处理办法
node: command not found未安装或 PATH 未配置检查/usr/local/bin/node是否存在,重新配置/etc/profile.d/node.sh
GLIBC_2.28 not found官方二进制与系统 glibc 不匹配用 NodeSource 仓库安装,或 nvm 源码编译
npm install很慢默认源访问不稳定npm config set registry https://registry.npmmirror.com
Node 版本不对系统中存在多个版本nvm ls查看,nvm use 22.12.0切换

检查是否安装 Node.js 的方法很简单:终端执行node -v,有版本号就是已经装好。如果提示找不到,先which node确认节点是否在 PATH 里。

4.2 内容生成与渲染类问题

这类问题多出现在大模型接口返回内容和渲染脚本之间。最常见的一个坑就是 JSON 被 Markdown 代码块包裹。模型有时候会“好心”地把返回内容写成:

```json { ... }
直接 `JSON.parse` 必然报错。我的处理是在 `parseJsonFromResponse` 里先用正则匹配 ```` ```json ```` 和 ```` ``` ````,取出中间内容再解析。 第二个坑是字段缺失。比如生成的课程里没有 `slug`,模板拼接路径时就会变成 `undefined.html`。解决办法是在渲染前做默认值兜底: ```js const slug = lesson.slug || encodeURIComponent(lesson.title);

第三个坑是中文编码。Markdown 内容里如果有特殊字符,写入 HTML 文件时一定要显式指定utf8。Nginx 侧也要配置charset utf-8;,否则页面容易出现乱码。

4.3 部署与线上问题

部署环节我遇到的几个高频问题整理成速查表:

现象可能原因处理办法
页面返回 404Nginx root 路径不对或 try_files 配置缺失检查/var/www/nodejs-site是否存在 dist 文件
页面返回 403目录权限不足chmod -R 755 /var/www/nodejs-site
样式丢失静态资源路径用了绝对路径确认站点部署在根路径,或改用相对路径
页面显示旧内容缓存清理浏览器缓存或配置 Nginx expires

还有一条心得:部署脚本中最好不要把 dist 目录和源码目录混在一起,否则 rsync 的--delete可能误删关键文件。我给 dist 目录建了一个独立输出路径,源文件统一放 src 和 scripts,这样同步时干净利落。

5. 实操心得与下一步扩展

整套流程跑通之后,我发现最有价值的一点是“先让最小闭环跑起来,再谈优化”。最初版本只需要生成首页和一个课程页,部署也是手动 rsync,后面才逐步加入列表页、自动部署、Nginx 缓存配置。如果一开始就追求完整体系,很可能卡在工具链选择上几天都出不了第一版。

我也踩过几次坑,其中最值得说的是:大模型生成内容虽然快,但不能每次都全量生成。每次改动都重新跑整个站点,既浪费 token,也容易让模型把之前写得不错的内容改坏。后来我改成在content/site.json上做增量更新,只让模型针对单节课重新生成,再操作文件,成本低很多。

这个项目后续还可以继续扩展。比如给站点加一个搜索功能,用 Node.js 脚本把课程正文生成索引数据;或者接入访问统计分析,了解哪些课程被看得最多;再往后甚至可以做成一个 Web 界面,让不熟悉命令行的同学也能输入一句话生成自己的学习官网。如果你正在纠结怎么快速搭建一个内容型站点,不妨从这个思路出发:先把内容生成、页面渲染、自动部署这条链路打通,剩下的事都会变得简单。