docs: 部署手册拆分为多页,左侧形成三层目录
原单页塞了 13 个二级标题、右侧目录过长。拆成 7 页: - 概览(含选型指引与环境对照表) - 路径前缀必读(独立成页,最高频故障) - 推荐环境:Docker + nginx - 静态托管部署(nginx 子路径/根路径、Apache、Caddy、对象存储、pnpm) - 内网与离线部署(4 种方案 + 对比表) - 本地开发与预览(含新增页面步骤) - 常见问题(部署/构建/运行三类 + 自检清单) 侧边栏分为三组:部署手册 / 部署方式 / 开发与排错
This commit is contained in:
+19
-1
@@ -81,7 +81,25 @@ export default defineConfig({
|
|||||||
text: '部署手册',
|
text: '部署手册',
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
items: [
|
items: [
|
||||||
{ text: '部署手册', link: '/deploy/' },
|
{ text: '部署概览', link: '/deploy/' },
|
||||||
|
{ text: '路径前缀必读', link: '/deploy/path-prefix' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '部署方式',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: '推荐:Docker + nginx', link: '/deploy/docker-nginx' },
|
||||||
|
{ text: '静态托管部署', link: '/deploy/static-hosting' },
|
||||||
|
{ text: '内网与离线部署', link: '/deploy/offline' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '开发与排错',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: '本地开发与预览', link: '/deploy/local-dev' },
|
||||||
|
{ text: '常见问题', link: '/deploy/faq' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
---
|
||||||
|
title: Docker + nginx 部署
|
||||||
|
description: 推荐环境部署方式:多阶段构建镜像 + nginx 托管
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# 推荐环境部署:Docker + nginx
|
||||||
|
|
||||||
|
适用:生产环境、需要长期稳定运行、机器上不想装 Node。
|
||||||
|
|
||||||
|
仓库自带多阶段 `Dockerfile`:Node 阶段执行 `vitepress build`,最终镜像只含 nginx 与静态产物,**不含 Node 和 `node_modules`**。
|
||||||
|
|
||||||
|
## 构建镜像
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SHA=$(git rev-parse --short HEAD)
|
||||||
|
docker build -t iotlcm-docs:$SHA .
|
||||||
|
```
|
||||||
|
|
||||||
|
用 commit sha 做标签,便于追溯与回滚;不建议只用 `latest`。
|
||||||
|
|
||||||
|
## 启动容器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name iotlcm-docs \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-p 8019:80 \
|
||||||
|
iotlcm-docs:$SHA
|
||||||
|
```
|
||||||
|
|
||||||
|
## nginx 配置(关键)
|
||||||
|
|
||||||
|
`docker/nginx.conf` 用 `alias` 把 `/docs/` 前缀剥掉:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# 产物按 base=/docs/ 生成,资源引用形如 /docs/assets/xxx
|
||||||
|
location /docs/ {
|
||||||
|
alias /usr/share/nginx/html/;
|
||||||
|
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 根路径跳到文档入口,避免访问 / 时白屏
|
||||||
|
location = / {
|
||||||
|
return 302 /docs/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri.html $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`try_files` 里的 `$uri.html` 与 `$uri/index.html` 是必需的:配置启用了 `cleanUrls: true`,页面 URL 不带 `.html` 后缀,需要服务器做内部映射。
|
||||||
|
|
||||||
|
原理说明见 [路径前缀必读](./path-prefix)。
|
||||||
|
|
||||||
|
## 部署后自检
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 入口可访问(应返回 200)
|
||||||
|
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/docs/
|
||||||
|
|
||||||
|
# 2. 根路径应 302 到 /docs/
|
||||||
|
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/
|
||||||
|
|
||||||
|
# 3. 静态资源不能 404(最容易出问题的一步)
|
||||||
|
ASSET=$(curl -s http://127.0.0.1:8019/docs/ | grep -o '/docs/assets/style[^"]*\.css' | head -1)
|
||||||
|
curl -o /dev/null -s -w '%{http_code}\n' "http://127.0.0.1:8019$ASSET"
|
||||||
|
|
||||||
|
# 4. 抽查正文页面
|
||||||
|
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/docs/manual/web/
|
||||||
|
```
|
||||||
|
|
||||||
|
四项都返回 `200`(第 2 项返回 `302`)才算部署成功。
|
||||||
|
|
||||||
|
## 通过外层反向代理对外
|
||||||
|
|
||||||
|
生产常见做法:容器只监听 `127.0.0.1`,由外层 nginx 转发。
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
upstream iotlcm_docs {
|
||||||
|
server 127.0.0.1:8019;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /docs {
|
||||||
|
return 301 /docs/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /docs/ {
|
||||||
|
proxy_pass http://iotlcm_docs; # 末尾不要加斜杠
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
::: danger proxy_pass 末尾斜杠
|
||||||
|
写成 `proxy_pass http://iotlcm_docs/;`(带末尾斜杠)会把 `/docs/` 前缀剥掉,转发到容器变成 `/`,导致资源全部 404 白屏。**必须不带末尾斜杠**。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## docker compose
|
||||||
|
|
||||||
|
`docker-compose.yml` 中镜像名是占位值,需替换为自己的仓库地址:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
docs:
|
||||||
|
image: your-registry/iotlcm/iotlcm-docs:${TAG:-latest}
|
||||||
|
container_name: iotlcm-docs
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8019:80"
|
||||||
|
networks:
|
||||||
|
- iotlcm-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
iotlcm-network:
|
||||||
|
external: true
|
||||||
|
name: iotlcm-network
|
||||||
|
```
|
||||||
|
|
||||||
|
compose 文件声明 `iotlcm-network` 为外部网络,若不存在需先创建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker network create iotlcm-network
|
||||||
|
```
|
||||||
|
|
||||||
|
启动:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TAG=$(git rev-parse --short HEAD) docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 版本固定与升级
|
||||||
|
|
||||||
|
开源仓按**固定版本**发布(无 git 历史,单次初始提交)。部署时请记录并锁定 commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SHA=$(git rev-parse HEAD)
|
||||||
|
echo "部署基线: $SHA" >> deploy.log
|
||||||
|
docker build -t iotlcm-docs:$SHA .
|
||||||
|
```
|
||||||
|
|
||||||
|
升级文档时,从上游选定基线重新发布后,按新 sha 重建镜像并滚动替换容器:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker rm -f iotlcm-docs
|
||||||
|
docker run -d --name iotlcm-docs --restart unless-stopped -p 8019:80 iotlcm-docs:$NEW_SHA
|
||||||
|
```
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
---
|
||||||
|
title: 常见问题
|
||||||
|
description: 部署与构建过程中的故障排查
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# 常见问题
|
||||||
|
|
||||||
|
## 部署类
|
||||||
|
|
||||||
|
### 页面能打开但白屏、无样式
|
||||||
|
|
||||||
|
**原因**:`/docs/` 路径前缀未剥离,资源全部 404。
|
||||||
|
|
||||||
|
这是最常见的部署故障。`base: '/docs/'` 使 HTML 引用 `/docs/assets/*`,但产物是平铺的,服务器从根路径找 `/assets/*` 就 404。
|
||||||
|
|
||||||
|
**危险之处**:`curl /` 返回 200,看起来部署成功,实际浏览器是空白。
|
||||||
|
|
||||||
|
**处理**:见 [路径前缀必读](./path-prefix),三种做法任选其一。
|
||||||
|
|
||||||
|
### 访问 `/` 返回 404
|
||||||
|
|
||||||
|
**原因**:`base: '/docs/'`,站点根路径不是入口。
|
||||||
|
|
||||||
|
**处理**:访问 `/docs/`。若希望 `/` 自动跳转,加:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location = / { return 302 /docs/; }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 访问 `/docs`(无末尾斜杠)404
|
||||||
|
|
||||||
|
**原因**:未配置重定向,而 VitePress 生成的目录结构需要末尾斜杠。
|
||||||
|
|
||||||
|
**处理**:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location = /docs { return 301 /docs/; }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 首页正常,点进任何内页都 404
|
||||||
|
|
||||||
|
**原因**:`cleanUrls: true` 使页面 URL 不带 `.html`,服务器缺少内部映射。
|
||||||
|
|
||||||
|
**处理**:`try_files` 补上两种映射:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 反向代理后白屏
|
||||||
|
|
||||||
|
**原因**:`proxy_pass` 带了末尾斜杠,把 `/docs/` 前缀剥掉后转发成 `/`。
|
||||||
|
|
||||||
|
**处理**:去掉末尾斜杠:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# 错误
|
||||||
|
proxy_pass http://iotlcm_docs/;
|
||||||
|
# 正确
|
||||||
|
proxy_pass http://iotlcm_docs;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 图片全部 404
|
||||||
|
|
||||||
|
**原因**:图片放在 `docs/public/images/`,构建后映射到产物根的 `images/`,引用时需要用站点绝对路径(含 base)。
|
||||||
|
|
||||||
|
**处理**:正文写 `/images/xxx.png`(VitePress 会自动补 `base`)。若写相对路径 `./images/xxx.png` 会因页面层级不同而失效。
|
||||||
|
|
||||||
|
## 构建类
|
||||||
|
|
||||||
|
### `npm ci` 报 lock 文件不一致
|
||||||
|
|
||||||
|
**原因**:Node 版本过低,或 lock 与 `package.json` 不同步。
|
||||||
|
|
||||||
|
**处理**:
|
||||||
|
|
||||||
|
1. 升级到 Node 22:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node -v # 确认 >= 18,推荐 22
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 若仍失败,改用 `npm install`(会更新 lock,注意提交变更):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 构建报内存不足
|
||||||
|
|
||||||
|
**原因**:构建峰值约 500 MB,容器内存限制过小时 OOM。
|
||||||
|
|
||||||
|
**处理**:给到 ≥ 1 GB:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -m 1g ...
|
||||||
|
```
|
||||||
|
|
||||||
|
或临时提高 Node 堆上限:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NODE_OPTIONS=--max-old-space-size=2048 npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 构建成功但页面内容缺失
|
||||||
|
|
||||||
|
**原因**:`.md` 文件未保存、或文件不在 `docs/`(`srcDir`)目录下。
|
||||||
|
|
||||||
|
**处理**:确认文件路径在 `docs/` 下,且已在 `.vitepress/config.mts` 的 sidebar 注册。
|
||||||
|
|
||||||
|
### 新增页面左侧目录不显示
|
||||||
|
|
||||||
|
**原因**:只在 `docs/` 下建了文件,没在 `sidebar` 配置里注册。
|
||||||
|
|
||||||
|
**处理**:见 [本地开发与预览](./local-dev) 的「新增页面的步骤」。
|
||||||
|
|
||||||
|
### 中文搜索失效或搜不到新页面
|
||||||
|
|
||||||
|
**原因**:本地搜索索引在构建时生成,dev 模式下可能有延迟;产物未重新构建。
|
||||||
|
|
||||||
|
**处理**:重新 `npm run build`。
|
||||||
|
|
||||||
|
## 运行类
|
||||||
|
|
||||||
|
### 端口 5173 被占用
|
||||||
|
|
||||||
|
**处理**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx vitepress dev . --port 5174
|
||||||
|
```
|
||||||
|
|
||||||
|
或结束旧进程:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lsof -ti:5173 | xargs kill
|
||||||
|
```
|
||||||
|
|
||||||
|
### 局域网其他机器访问不了 dev 服务
|
||||||
|
|
||||||
|
**原因**:默认只监听 `127.0.0.1`。
|
||||||
|
|
||||||
|
**处理**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx vitepress dev . --host 0.0.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
并确认防火墙放行对应端口。
|
||||||
|
|
||||||
|
### 修改 `.md` 后页面没更新
|
||||||
|
|
||||||
|
**原因**:浏览器缓存,或 dev 进程已退出。
|
||||||
|
|
||||||
|
**处理**:硬刷新(`Ctrl+Shift+R`),确认终端里 dev 进程仍在运行。
|
||||||
|
|
||||||
|
## 快速自检清单
|
||||||
|
|
||||||
|
部署完成后依次确认:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 入口 200
|
||||||
|
curl -o /dev/null -s -w '%{http_code}\n' http://<host>/docs/
|
||||||
|
|
||||||
|
# 2. 根路径 302(若配置了重定向)
|
||||||
|
curl -o /dev/null -s -w '%{http_code}\n' http://<host>/
|
||||||
|
|
||||||
|
# 3. 静态资源 200(最关键)
|
||||||
|
ASSET=$(curl -s http://<host>/docs/ | grep -o '/docs/assets/style[^"]*\.css' | head -1)
|
||||||
|
curl -o /dev/null -s -w '%{http_code}\n' "http://<host>$ASSET"
|
||||||
|
|
||||||
|
# 4. 内页 200
|
||||||
|
curl -o /dev/null -s -w '%{http_code}\n' http://<host>/docs/manual/web/
|
||||||
|
```
|
||||||
|
|
||||||
|
四项都正常才算部署成功。浏览器再打开一次确认没有白屏。
|
||||||
+27
-388
@@ -1,17 +1,27 @@
|
|||||||
---
|
---
|
||||||
title: 部署手册
|
title: 部署概览
|
||||||
description: iotLCM 文档站的推荐环境、编译方法与部署方式
|
description: iotLCM 文档站的部署方式总览与环境要求
|
||||||
outline: deep
|
outline: deep
|
||||||
---
|
---
|
||||||
|
|
||||||
# 部署手册
|
# 部署概览
|
||||||
|
|
||||||
本页说明 iotLCM 文档站(`iotlcm-docs`)的运行环境要求、编译方法与部署方式。文档站是**纯静态站点**,由 VitePress 1.6 构建,产物不含任何服务端运行时。
|
本手册说明 iotLCM 文档站(`iotlcm-docs`)的环境要求、编译方法与部署方式。文档站是**纯静态站点**,由 VitePress 1.6 构建,产物不含任何服务端运行时。
|
||||||
|
|
||||||
::: tip 一句话结论
|
::: tip 一句话结论
|
||||||
生产推荐用 **Docker + nginx**(Node 只在构建阶段需要);只有一台机器、不想装 Docker 时,用 **Node 构建 + 任意 Web 服务器托管静态产物**。
|
生产推荐 **Docker + nginx**(Node 只在构建阶段需要);只有一台机器、不想装 Docker 时,用 **Node 构建 + 任意 Web 服务器托管静态产物**。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## 怎么选部署方式
|
||||||
|
|
||||||
|
| 你的情况 | 看哪页 |
|
||||||
|
| --- | --- |
|
||||||
|
| 生产环境,机器上不想装 Node | [推荐环境部署:Docker + nginx](./docker-nginx) |
|
||||||
|
| 有现成 nginx,只需托管静态文件 | [静态托管部署](./static-hosting) |
|
||||||
|
| 内网 / 无法访问公网 npm | [内网与离线部署](./offline) |
|
||||||
|
| 只是改文档、本地看效果 | [本地开发与预览](./local-dev) |
|
||||||
|
| 遇到报错或白屏 | [常见问题](./faq) |
|
||||||
|
|
||||||
## 环境要求
|
## 环境要求
|
||||||
|
|
||||||
### 构建环境(必需)
|
### 构建环境(必需)
|
||||||
@@ -35,7 +45,9 @@ outline: deep
|
|||||||
| 容器运行时 | Docker 20+ / Docker Compose v2 | 仅容器方式需要 |
|
| 容器运行时 | Docker 20+ / Docker Compose v2 | 仅容器方式需要 |
|
||||||
| 资源占用 | 内存 32 MB、磁盘 30 MB | 纯静态,压力极低 |
|
| 资源占用 | 内存 32 MB、磁盘 30 MB | 纯静态,压力极低 |
|
||||||
|
|
||||||
### 实测参考(Debian/Ubuntu + Node 22 + Docker 29)
|
### 实测参考
|
||||||
|
|
||||||
|
在 Debian/Ubuntu + Node 22 + Docker 29 上实测:
|
||||||
|
|
||||||
| 操作 | 耗时 | 产物 |
|
| 操作 | 耗时 | 产物 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
@@ -52,392 +64,19 @@ git clone https://git.5iot.com/iotlcm-public/iotlcm-docs.git
|
|||||||
cd iotlcm-docs
|
cd iotlcm-docs
|
||||||
```
|
```
|
||||||
|
|
||||||
开源仓**不含 git 历史**,只有一个初始提交,这是预期行为:
|
开源仓**不含 git 历史**,从初始提交开始累积:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git log --oneline
|
git log --oneline
|
||||||
# 92f239f chore: 初始开源发布
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## ⚠️ 部署前必读:路径前缀
|
|
||||||
|
|
||||||
这是本项目最容易出错的地方,**部署失败九成是这个原因**。
|
|
||||||
|
|
||||||
文档站配置里 `base: '/docs/'`(见 `.vitepress/config.mts`),因此构建产物中所有资源引用都是**绝对路径**:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<link rel="stylesheet" href="/docs/assets/style.xxx.css">
|
|
||||||
<script src="/docs/assets/app.xxx.js"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
而产物目录 `.vitepress/dist` 是**平铺**的,`index.html`、`assets/` 直接在根,**没有 `docs/` 子目录**。
|
|
||||||
|
|
||||||
由此推出两条铁律:
|
|
||||||
|
|
||||||
1. **对外访问路径必须是 `/docs/`**,且服务器必须把 `/docs/` 前缀剥掉后再落到产物目录
|
|
||||||
2. 若想挂到别的前缀(如 `/manual/`),必须**先改 `base` 再重新构建**,不能只改服务器配置
|
|
||||||
|
|
||||||
::: warning 典型故障:白屏
|
|
||||||
如果配置成「从根路径直接服务产物」(`root` 指向 dist,访问 `/`),页面 HTML 会返回 200,但 `/docs/assets/*.js` 全部 404 → **浏览器白屏,没有任何报错提示**。
|
|
||||||
:::
|
|
||||||
|
|
||||||
三种正确做法,任选其一:
|
|
||||||
|
|
||||||
| 做法 | 适用 | 关键配置 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| nginx `alias` 剥前缀 | 容器部署(推荐) | `location /docs/ { alias /usr/share/nginx/html/; }` |
|
|
||||||
| 把产物放进 `docs/` 子目录 | 静态托管 / 对象存储 | `dist/` → `www/docs/` |
|
|
||||||
| 改 `base: '/'` 重新构建 | 独占域名根路径 | 改配置后必须重新 `npm run build` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 推荐环境部署:Docker + nginx
|
|
||||||
|
|
||||||
适用:生产环境、需要长期稳定运行、机器上不想装 Node。
|
|
||||||
|
|
||||||
仓库自带多阶段 `Dockerfile`:Node 阶段执行 `vitepress build`,最终镜像只含 nginx 与静态产物,**不含 Node 和 `node_modules`**。
|
|
||||||
|
|
||||||
### 构建镜像
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SHA=$(git rev-parse --short HEAD)
|
|
||||||
docker build -t iotlcm-docs:$SHA .
|
|
||||||
```
|
|
||||||
|
|
||||||
用 commit sha 做标签,便于追溯与回滚;不建议只用 `latest`。
|
|
||||||
|
|
||||||
### 启动容器
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d \
|
|
||||||
--name iotlcm-docs \
|
|
||||||
--restart unless-stopped \
|
|
||||||
-p 8019:80 \
|
|
||||||
iotlcm-docs:$SHA
|
|
||||||
```
|
|
||||||
|
|
||||||
### nginx 配置(关键)
|
|
||||||
|
|
||||||
`docker/nginx.conf` 用 `alias` 把 `/docs/` 前缀剥掉:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# 产物按 base=/docs/ 生成,资源引用形如 /docs/assets/xxx
|
|
||||||
location /docs/ {
|
|
||||||
alias /usr/share/nginx/html/;
|
|
||||||
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 根路径跳到文档入口,避免访问 / 时白屏
|
|
||||||
location = / {
|
|
||||||
return 302 /docs/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri.html $uri/ =404;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`try_files` 里的 `$uri.html` 与 `$uri/index.html` 是必需的:配置启用了 `cleanUrls: true`,页面 URL 不带 `.html` 后缀,需要服务器做内部映射。
|
|
||||||
|
|
||||||
### 部署后自检
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 入口可访问(应返回 200)
|
|
||||||
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/docs/
|
|
||||||
|
|
||||||
# 2. 根路径应 302 到 /docs/
|
|
||||||
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/
|
|
||||||
|
|
||||||
# 3. 静态资源不能 404(最容易出问题的一步)
|
|
||||||
ASSET=$(curl -s http://127.0.0.1:8019/docs/ | grep -o '/docs/assets/style[^"]*\.css' | head -1)
|
|
||||||
curl -o /dev/null -s -w '%{http_code}\n' "http://127.0.0.1:8019$ASSET"
|
|
||||||
|
|
||||||
# 4. 抽查正文页面
|
|
||||||
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/docs/manual/web/
|
|
||||||
```
|
|
||||||
|
|
||||||
四项都返回 `200`(第 2 项返回 `302`)才算部署成功。
|
|
||||||
|
|
||||||
### 通过外层反向代理对外
|
|
||||||
|
|
||||||
生产常见做法:容器只监听 `127.0.0.1`,由外层 nginx 转发。
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
upstream iotlcm_docs {
|
|
||||||
server 127.0.0.1:8019;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /docs {
|
|
||||||
return 301 /docs/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /docs/ {
|
|
||||||
proxy_pass http://iotlcm_docs; # 末尾不要加斜杠
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
::: danger proxy_pass 末尾斜杠
|
|
||||||
写成 `proxy_pass http://iotlcm_docs/;`(带末尾斜杠)会把 `/docs/` 前缀剥掉,转发到容器变成 `/`,导致资源全部 404 白屏。**必须不带末尾斜杠**。
|
|
||||||
:::
|
|
||||||
|
|
||||||
### docker compose
|
|
||||||
|
|
||||||
`docker-compose.yml` 中镜像名是占位值,需替换为自己的仓库地址:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
docs:
|
|
||||||
image: your-registry/iotlcm/iotlcm-docs:${TAG:-latest}
|
|
||||||
container_name: iotlcm-docs
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:8019:80"
|
|
||||||
networks:
|
|
||||||
- iotlcm-network
|
|
||||||
|
|
||||||
networks:
|
|
||||||
iotlcm-network:
|
|
||||||
external: true
|
|
||||||
name: iotlcm-network
|
|
||||||
```
|
|
||||||
|
|
||||||
compose 文件声明 `iotlcm-network` 为外部网络,若不存在需先创建:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker network create iotlcm-network
|
|
||||||
```
|
|
||||||
|
|
||||||
启动:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
TAG=$(git rev-parse --short HEAD) docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 其他环境
|
|
||||||
|
|
||||||
### 方式一:本地开发与预览(改文档时用)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm ci
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
访问 **`http://127.0.0.1:5173/docs/`**。
|
|
||||||
|
|
||||||
::: warning 地址必须带 /docs/
|
|
||||||
`base: '/docs/'` 决定了 dev 服务器入口也在子路径下,直接访问 `http://127.0.0.1:5173/` 会 404。
|
|
||||||
:::
|
|
||||||
|
|
||||||
改动 `docs/` 下任意 `.md` 文件会热更新,无需重启。
|
|
||||||
|
|
||||||
需要让局域网内其他机器访问时,把监听地址改为 `0.0.0.0`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx vitepress dev . --host 0.0.0.0
|
|
||||||
# 然后访问 http://<本机IP>:5173/docs/
|
|
||||||
```
|
|
||||||
|
|
||||||
端口被占用时换端口:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx vitepress dev . --port 5174
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方式二:构建静态产物 + 任意 Web 服务器
|
|
||||||
|
|
||||||
这是**不使用 Docker** 时最通用的做法。
|
|
||||||
|
|
||||||
#### 编译
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
# 产物在 .vitepress/dist
|
|
||||||
```
|
|
||||||
|
|
||||||
本地验证构建结果(不要用于生产):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run preview
|
|
||||||
# http://127.0.0.1:4173/docs/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 部署到 nginx(子路径挂载,推荐)
|
|
||||||
|
|
||||||
把产物放到一个 `docs` 子目录,即可与 `base` 天然对齐,**无需 alias**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo mkdir -p /var/www/iotlcm/www
|
|
||||||
sudo cp -r .vitepress/dist /var/www/iotlcm/www/docs
|
|
||||||
```
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name _;
|
|
||||||
root /var/www/iotlcm/www;
|
|
||||||
|
|
||||||
location /docs/ {
|
|
||||||
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /docs {
|
|
||||||
return 301 /docs/;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
此处 `root` 指向父目录、产物在 `docs/` 子目录下,`/docs/assets/app.js` 会自然映射到 `/var/www/iotlcm/www/docs/assets/app.js`,不需要 `alias`。
|
|
||||||
|
|
||||||
#### 部署到 nginx(根路径独占域名)
|
|
||||||
|
|
||||||
若文档站独占一个域名、需要挂在 `/`,必须**改 `base` 后重新构建**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sed -i "s#base: '/docs/'#base: '/'#" .vitepress/config.mts
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name docs.example.com;
|
|
||||||
root /var/www/iotlcm-docs;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri.html $uri/ =404;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
::: danger 不要只改服务器配置
|
|
||||||
`base` 与访问前缀必须一致。改了 nginx 却不改 `base`(或反之),一定会白屏。
|
|
||||||
:::
|
|
||||||
|
|
||||||
#### 部署到对象存储 / CDN
|
|
||||||
|
|
||||||
产物是纯静态文件,可直接上传到 S3 / OSS / MinIO 等:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
# 上传 .vitepress/dist 全部内容到桶内 docs/ 前缀下
|
|
||||||
```
|
|
||||||
|
|
||||||
要点:
|
|
||||||
|
|
||||||
- 上传时保持 `/docs/` 前缀(或改 `base` 后重新构建)
|
|
||||||
- 配置**默认首页**为 `docs/index.html`
|
|
||||||
- 配置 **404 页面**为 `docs/404.html`(产物已自带)
|
|
||||||
- `cleanUrls: true` 意味着页面 URL 不带 `.html`,需对象存储支持「无后缀映射到 .html」,否则需关闭 `cleanUrls` 重新构建
|
|
||||||
|
|
||||||
### 方式三:内网 / 离线环境
|
|
||||||
|
|
||||||
机器无法访问公网 npm 源时:
|
|
||||||
|
|
||||||
**方案 A:使用内网镜像源**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm ci --registry=https://your-internal-registry/
|
|
||||||
```
|
|
||||||
|
|
||||||
**方案 B:在有网机器构建后只拷产物**
|
|
||||||
|
|
||||||
产物是纯静态文件,**不需要在目标机器上装 Node**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 有网机器
|
|
||||||
npm ci && npm run build
|
|
||||||
tar czf iotlcm-docs-dist.tar.gz -C .vitepress/dist .
|
|
||||||
|
|
||||||
# 目标机器
|
|
||||||
tar xzf iotlcm-docs-dist.tar.gz -C /var/www/iotlcm/www/docs
|
|
||||||
```
|
|
||||||
|
|
||||||
**方案 C:内网构建镜像**
|
|
||||||
|
|
||||||
`Dockerfile` 里基础镜像来自 Docker Hub(`node:22-alpine`、`nginx:1.27-alpine`),内网环境需先同步到内部镜像仓库,然后修改 `Dockerfile` 的 `FROM`:
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
FROM your-registry/library/node:22-alpine AS builder
|
|
||||||
# ...
|
|
||||||
FROM your-registry/library/nginx:1.27-alpine
|
|
||||||
```
|
|
||||||
|
|
||||||
::: tip 已内置国内 apk 源
|
|
||||||
`Dockerfile` 的 builder 阶段已把 Alpine 源换成 `mirrors.aliyun.com`,无需额外处理。
|
|
||||||
:::
|
|
||||||
|
|
||||||
### 方式四:pnpm / yarn
|
|
||||||
|
|
||||||
仓库同时提供 `package-lock.json` 与 `pnpm-lock.yaml`。使用 pnpm:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm install --frozen-lockfile
|
|
||||||
pnpm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
npm 与 pnpm 二选一即可,**不要混用**(会产生不一致的 `node_modules`)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 环境对照表
|
## 环境对照表
|
||||||
|
|
||||||
| 场景 | Node | Docker | 访问路径 | 关键动作 |
|
| 场景 | Node | Docker | 访问路径 | 关键动作 | 详细 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- |
|
||||||
| 本地改文档 | 必需 | 不需要 | `/docs/` | `npm run dev` |
|
| 本地改文档 | 必需 | 不需要 | `/docs/` | `npm run dev` | [本地开发](./local-dev) |
|
||||||
| 生产(推荐) | 仅构建阶段 | 必需 | `/docs/` | `docker build` + nginx alias |
|
| 生产(推荐) | 仅构建阶段 | 必需 | `/docs/` | `docker build` + nginx alias | [Docker 部署](./docker-nginx) |
|
||||||
| 静态托管(子路径) | 仅构建阶段 | 不需要 | `/docs/` | 产物放 `docs/` 子目录 |
|
| 静态托管(子路径) | 仅构建阶段 | 不需要 | `/docs/` | 产物放 `docs/` 子目录 | [静态托管](./static-hosting) |
|
||||||
| 静态托管(根路径) | 仅构建阶段 | 不需要 | `/` | 改 `base: '/'` 重新构建 |
|
| 静态托管(根路径) | 仅构建阶段 | 不需要 | `/` | 改 `base: '/'` 重新构建 | [静态托管](./static-hosting) |
|
||||||
| 对象存储 / CDN | 仅构建阶段 | 不需要 | `/docs/` | 上传 + 配 404 与默认首页 |
|
| 对象存储 / CDN | 仅构建阶段 | 不需要 | `/docs/` | 上传 + 配 404 与默认首页 | [静态托管](./static-hosting) |
|
||||||
| 内网离线 | 可在别处构建 | 可选 | `/docs/` | 只拷产物或改 FROM |
|
| 内网离线 | 可在别处构建 | 可选 | `/docs/` | 只拷产物或改 FROM | [内网与离线](./offline) |
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
| 现象 | 原因 | 处理 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 页面白屏、无样式 | `/docs/` 前缀未剥离,资源 404 | 用 `alias`,或产物放 `docs/` 子目录 |
|
|
||||||
| 访问 `/` 404 | `base: '/docs/'` | 访问 `/docs/`;容器版已自动 302 |
|
|
||||||
| 访问 `/docs`(无末尾斜杠)404 | 未配置重定向 | 加 `location = /docs { return 301 /docs/; }` |
|
|
||||||
| 正文页 404、首页正常 | 缺 `$uri.html` 映射 | `try_files` 补 `$uri.html $uri/index.html` |
|
|
||||||
| `npm ci` 报 lock 不一致 | Node 版本过低 | 升级到 Node 22 |
|
|
||||||
| 构建报内存不足 | 容器内存限制过小 | 给到 ≥ 1 GB |
|
|
||||||
| 端口 5173 被占用 | 已有 dev 进程在跑 | `--port 5174` 或结束旧进程 |
|
|
||||||
| 反向代理后白屏 | `proxy_pass` 带了末尾斜杠 | 去掉末尾斜杠 |
|
|
||||||
|
|
||||||
## 版本固定
|
|
||||||
|
|
||||||
开源仓按**固定版本**发布(无 git 历史,单次初始提交)。部署时请记录并锁定 commit:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SHA=$(git rev-parse HEAD)
|
|
||||||
echo "部署基线: $SHA" >> deploy.log
|
|
||||||
docker build -t iotlcm-docs:$SHA .
|
|
||||||
```
|
|
||||||
|
|
||||||
升级文档时,从上游选定基线重新发布后,按新 sha 重建镜像并滚动替换容器:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker rm -f iotlcm-docs
|
|
||||||
docker run -d --name iotlcm-docs --restart unless-stopped -p 8019:80 iotlcm-docs:$NEW_SHA
|
|
||||||
```
|
|
||||||
|
|
||||||
## 相关文档
|
|
||||||
|
|
||||||
- [使用手册](/manual/) —— Web 管理端与小程序端操作说明
|
|
||||||
- [OpenAPI 接口文档](/openapi/) —— V3 接口鉴权与调用说明
|
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
---
|
||||||
|
title: 本地开发与预览
|
||||||
|
description: 改文档时用 dev 模式,热更新实时看效果
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# 本地开发与预览
|
||||||
|
|
||||||
|
适用:修改文档内容、调整配置、本地看效果。
|
||||||
|
|
||||||
|
## 启动开发服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
访问 **`http://127.0.0.1:5173/docs/`**
|
||||||
|
|
||||||
|
::: warning 地址必须带 /docs/
|
||||||
|
`base: '/docs/'` 决定了 dev 服务器入口也在子路径下,直接访问 `http://127.0.0.1:5173/` 会 404。
|
||||||
|
:::
|
||||||
|
|
||||||
|
改动 `docs/` 下任意 `.md` 文件会**热更新**,无需重启。按 `Ctrl+C` 退出。
|
||||||
|
|
||||||
|
## 局域网访问
|
||||||
|
|
||||||
|
需要让同网段其他机器访问时,把监听地址改为 `0.0.0.0`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx vitepress dev . --host 0.0.0.0
|
||||||
|
# 然后访问 http://<本机IP>:5173/docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 端口冲突
|
||||||
|
|
||||||
|
默认 5173,被占用时换端口:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx vitepress dev . --port 5174
|
||||||
|
```
|
||||||
|
|
||||||
|
## 预览构建产物
|
||||||
|
|
||||||
|
`dev` 模式是源码实时编译,与最终产物有细微差异(如资源路径、代码分割)。发布前建议跑一次真实构建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build # 产物在 .vitepress/dist
|
||||||
|
npm run preview # http://127.0.0.1:4173/docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
`preview` 只是本地起静态服务验证产物,**不要用于生产**。
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
新增页面时按此结构放置:
|
||||||
|
|
||||||
|
```text
|
||||||
|
iotlcm-docs/
|
||||||
|
├── docs/ # 文档源(VitePress srcDir)
|
||||||
|
│ ├── index.md # 站点首页
|
||||||
|
│ ├── manual/ # 使用手册
|
||||||
|
│ │ ├── web/ # Web 管理端
|
||||||
|
│ │ └── mini-program/ # 小程序端
|
||||||
|
│ ├── openapi/ # OpenAPI 合同
|
||||||
|
│ ├── deploy/ # 部署手册(本手册)
|
||||||
|
│ └── public/ # 静态资源,映射到站点根路径
|
||||||
|
│ └── images/ # 文档配图
|
||||||
|
└── .vitepress/
|
||||||
|
├── config.mts # 站点配置(导航、侧边栏)
|
||||||
|
└── theme/ # 主题定制
|
||||||
|
```
|
||||||
|
|
||||||
|
## 新增页面的步骤
|
||||||
|
|
||||||
|
1. 在 `docs/` 下对应目录新建 `xxx.md`,写 frontmatter:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: 页面标题
|
||||||
|
description: 页面描述
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# 页面标题
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 在 `.vitepress/config.mts` 的 `sidebar` 中注册,否则左侧目录不会显示:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
sidebar: {
|
||||||
|
'/deploy/': [
|
||||||
|
{
|
||||||
|
text: '部署手册',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: '概览', link: '/deploy/' },
|
||||||
|
{ text: '新页面', link: '/deploy/xxx' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 图片放 `docs/public/images/`,正文用绝对路径引用(public 目录映射到站点根):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|
## 构建产物结构
|
||||||
|
|
||||||
|
```text
|
||||||
|
.vitepress/dist/
|
||||||
|
├── index.html # 首页
|
||||||
|
├── 404.html # 404 页
|
||||||
|
├── favicon.png
|
||||||
|
├── assets/ # JS / CSS / 字体(带 hash)
|
||||||
|
├── images/ # 来自 docs/public/images
|
||||||
|
├── manual/ # 各章节
|
||||||
|
├── openapi/
|
||||||
|
└── deploy/
|
||||||
|
```
|
||||||
|
|
||||||
|
注意产物是**平铺**的,没有 `docs/` 子目录——这正是部署时需要处理路径前缀的原因,见 [路径前缀必读](./path-prefix)。
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
---
|
||||||
|
title: 内网与离线部署
|
||||||
|
description: 无法访问公网 npm 时的构建与部署方案
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# 内网与离线部署
|
||||||
|
|
||||||
|
适用:目标机器无法访问公网 npm 源或 Docker Hub。
|
||||||
|
|
||||||
|
## 方案 A:使用内网镜像源
|
||||||
|
|
||||||
|
机器能访问内网 npm 仓库时,直接指定 registry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm ci --registry=https://your-internal-registry/
|
||||||
|
```
|
||||||
|
|
||||||
|
长期使用可写入 `~/.npmrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm config set registry https://your-internal-registry/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 方案 B:有网机器构建,只拷产物(最简单)
|
||||||
|
|
||||||
|
产物是纯静态文件,**目标机器不需要装 Node**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 有网机器
|
||||||
|
npm ci && npm run build
|
||||||
|
tar czf iotlcm-docs-dist.tar.gz -C .vitepress/dist .
|
||||||
|
|
||||||
|
# 目标机器
|
||||||
|
tar xzf iotlcm-docs-dist.tar.gz -C /var/www/iotlcm/www/docs
|
||||||
|
```
|
||||||
|
|
||||||
|
注意 `docs` 子目录与 `base: '/docs/'` 的对应关系,详见 [静态托管部署](./static-hosting)。
|
||||||
|
|
||||||
|
## 方案 C:内网构建镜像
|
||||||
|
|
||||||
|
`Dockerfile` 里基础镜像来自 Docker Hub(`node:22-alpine`、`nginx:1.27-alpine`),内网环境需先同步到内部镜像仓库,然后修改 `Dockerfile` 的 `FROM`:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM your-registry/library/node:22-alpine AS builder
|
||||||
|
# ... 中间不变
|
||||||
|
FROM your-registry/library/nginx:1.27-alpine
|
||||||
|
```
|
||||||
|
|
||||||
|
同步镜像:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在有外网的机器上
|
||||||
|
docker pull node:22-alpine
|
||||||
|
docker tag node:22-alpine your-registry/library/node:22-alpine
|
||||||
|
docker push your-registry/library/node:22-alpine
|
||||||
|
|
||||||
|
docker pull nginx:1.27-alpine
|
||||||
|
docker tag nginx:1.27-alpine your-registry/library/nginx:1.27-alpine
|
||||||
|
docker push your-registry/library/nginx:1.27-alpine
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip 已内置国内 apk 源
|
||||||
|
`Dockerfile` 的 builder 阶段已把 Alpine 源换成 `mirrors.aliyun.com`,无需额外处理。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 方案 D:离线安装依赖
|
||||||
|
|
||||||
|
既不能访问公网 npm、也没有内网 registry 时,可在有网机器打包 `node_modules`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 有网机器(与目标机器同架构、同 Node 大版本)
|
||||||
|
npm ci
|
||||||
|
tar czf iotlcm-docs-full.tar.gz \
|
||||||
|
--exclude=.git --exclude=.vitepress/cache --exclude=.vitepress/dist .
|
||||||
|
|
||||||
|
# 目标机器
|
||||||
|
tar xzf iotlcm-docs-full.tar.gz -C /opt/iotlcm-docs
|
||||||
|
cd /opt/iotlcm-docs
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning 架构与版本必须一致
|
||||||
|
`node_modules` 里可能含平台相关的二进制依赖,跨架构(如 x86 → ARM)或跨 Node 大版本会失败。跨架构场景请用方案 B 直接拷产物。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 方案对比
|
||||||
|
|
||||||
|
| 方案 | 需要外网 | 目标机器需 Node | 适用场景 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| A 内网 registry | 否(有内网源) | 是 | 有内网 npm 仓库 |
|
||||||
|
| B 只拷产物 | 仅在构建机 | **否** | 最通用,推荐 |
|
||||||
|
| C 内网镜像 | 仅在同步镜像时 | 否(用 Docker) | 容器化生产环境 |
|
||||||
|
| D 打包 node_modules | 仅在构建机 | 是 | 需在目标机器改文档后构建 |
|
||||||
|
|
||||||
|
::: tip 推荐优先级
|
||||||
|
优先选 **B(只拷产物)**——最简单,目标机器零依赖。需要容器化则选 **C**。
|
||||||
|
:::
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
title: 路径前缀必读
|
||||||
|
description: base=/docs/ 导致的部署白屏问题与三种正确做法
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# 路径前缀必读
|
||||||
|
|
||||||
|
这是本项目最容易出错的地方,**部署失败九成是这个原因**。部署前请务必读完本页。
|
||||||
|
|
||||||
|
## 问题成因
|
||||||
|
|
||||||
|
文档站配置里 `base: '/docs/'`(见 `.vitepress/config.mts`),因此构建产物中所有资源引用都是**绝对路径**:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<link rel="stylesheet" href="/docs/assets/style.xxx.css">
|
||||||
|
<script src="/docs/assets/app.xxx.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
而产物目录 `.vitepress/dist` 是**平铺**的,`index.html`、`assets/` 直接在根,**没有 `docs/` 子目录**。
|
||||||
|
|
||||||
|
由此推出两条铁律:
|
||||||
|
|
||||||
|
1. **对外访问路径必须是 `/docs/`**,且服务器必须把 `/docs/` 前缀剥掉后再落到产物目录
|
||||||
|
2. 若想挂到别的前缀(如 `/manual/`),必须**先改 `base` 再重新构建**,不能只改服务器配置
|
||||||
|
|
||||||
|
::: warning 典型故障:白屏
|
||||||
|
如果配置成「从根路径直接服务产物」(`root` 指向 dist,访问 `/`),页面 HTML 会返回 200,但 `/docs/assets/*.js` 全部 404 → **浏览器白屏,没有任何报错提示**。
|
||||||
|
|
||||||
|
这种情况最坑的地方是:`curl /` 返回 200,看起来部署成功了,实际打开浏览器是空白页。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 三种正确做法
|
||||||
|
|
||||||
|
| 做法 | 适用 | 关键配置 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| nginx `alias` 剥前缀 | 容器部署(推荐) | `location /docs/ { alias /usr/share/nginx/html/; }` |
|
||||||
|
| 把产物放进 `docs/` 子目录 | 静态托管 / 对象存储 | `dist/` → `www/docs/` |
|
||||||
|
| 改 `base: '/'` 重新构建 | 独占域名根路径 | 改配置后必须重新 `npm run build` |
|
||||||
|
|
||||||
|
### 做法一:nginx alias 剥前缀
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /docs/ {
|
||||||
|
alias /usr/share/nginx/html/;
|
||||||
|
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`alias` 会剥掉 `location` 的前缀,`/docs/assets/app.js` 实际读到 `/usr/share/nginx/html/assets/app.js`,正好对上平铺的产物目录。
|
||||||
|
|
||||||
|
详见 [推荐环境部署:Docker + nginx](./docker-nginx)。
|
||||||
|
|
||||||
|
### 做法二:产物放进 docs 子目录
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp -r .vitepress/dist /var/www/iotlcm/www/docs
|
||||||
|
```
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
root /var/www/iotlcm/www;
|
||||||
|
location /docs/ {
|
||||||
|
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`/docs/assets/app.js` 自然映射到 `/var/www/iotlcm/www/docs/assets/app.js`,不需要 `alias`。
|
||||||
|
|
||||||
|
详见 [静态托管部署](./static-hosting)。
|
||||||
|
|
||||||
|
### 做法三:改 base 重新构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sed -i "s#base: '/docs/'#base: '/'#" .vitepress/config.mts
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
仅适用于文档站独占域名根路径的场景。
|
||||||
|
|
||||||
|
::: danger 不要只改服务器配置
|
||||||
|
`base` 与访问前缀必须一致。改了 nginx 却不改 `base`(或反之),一定会白屏。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## cleanUrls 的额外要求
|
||||||
|
|
||||||
|
配置启用了 `cleanUrls: true`,页面 URL 不带 `.html` 后缀(如 `/manual/web/` 而非 `/manual/web.html`)。
|
||||||
|
|
||||||
|
因此服务器的 `try_files` **必须**包含 `$uri.html` 与 `$uri/index.html` 映射:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||||
|
```
|
||||||
|
|
||||||
|
缺了这两项的表现是:首页正常,但点进任何内页都是 404。
|
||||||
|
|
||||||
|
对象存储场景下需确认支持「无后缀映射到 `.html`」,否则要关闭 `cleanUrls` 重新构建。
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
---
|
||||||
|
title: 静态托管部署
|
||||||
|
description: 不使用 Docker 时,用 nginx / 对象存储 / CDN 托管静态产物
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# 静态托管部署
|
||||||
|
|
||||||
|
适用:已有 nginx / Apache / Caddy,或使用对象存储 / CDN,不想引入 Docker。
|
||||||
|
|
||||||
|
核心流程只有两步:**构建产物** → **托管静态文件**。
|
||||||
|
|
||||||
|
## 编译产物
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
# 产物在 .vitepress/dist
|
||||||
|
```
|
||||||
|
|
||||||
|
本地验证构建结果(不要用于生产):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run preview
|
||||||
|
# http://127.0.0.1:4173/docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
## nginx:子路径挂载(推荐)
|
||||||
|
|
||||||
|
把产物放到一个 `docs` 子目录,即可与 `base` 天然对齐,**无需 alias**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /var/www/iotlcm/www
|
||||||
|
sudo cp -r .vitepress/dist /var/www/iotlcm/www/docs
|
||||||
|
```
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /var/www/iotlcm/www;
|
||||||
|
|
||||||
|
location /docs/ {
|
||||||
|
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /docs {
|
||||||
|
return 301 /docs/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
此处 `root` 指向父目录、产物在 `docs/` 子目录下,`/docs/assets/app.js` 会自然映射到 `/var/www/iotlcm/www/docs/assets/app.js`,不需要 `alias`。
|
||||||
|
|
||||||
|
## nginx:根路径独占域名
|
||||||
|
|
||||||
|
若文档站独占一个域名、需要挂在 `/`,必须**改 `base` 后重新构建**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sed -i "s#base: '/docs/'#base: '/'#" .vitepress/config.mts
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name docs.example.com;
|
||||||
|
root /var/www/iotlcm-docs;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri.html $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
::: danger 不要只改服务器配置
|
||||||
|
`base` 与访问前缀必须一致。改了 nginx 却不改 `base`(或反之),一定会白屏。详见 [路径前缀必读](./path-prefix)。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Apache
|
||||||
|
|
||||||
|
需要启用 `mod_rewrite`,用 `.htaccess` 做 cleanUrls 映射:
|
||||||
|
|
||||||
|
```apache
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteBase /docs/
|
||||||
|
|
||||||
|
# 已存在的文件/目录直接返回
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -d
|
||||||
|
RewriteRule ^ - [L]
|
||||||
|
|
||||||
|
# 无后缀路径映射到 .html 或 index.html
|
||||||
|
RewriteCond %{REQUEST_FILENAME}.html -f
|
||||||
|
RewriteRule ^(.+)$ $1.html [L]
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_FILENAME}/index.html -f
|
||||||
|
RewriteRule ^(.+)$ $1/index.html [L]
|
||||||
|
</IfModule>
|
||||||
|
```
|
||||||
|
|
||||||
|
产物目录放置方式与 nginx 子路径方案一致:`/var/www/html/docs/`。
|
||||||
|
|
||||||
|
## Caddy
|
||||||
|
|
||||||
|
Caddy 配置更简洁,`try_files` 语义与 nginx 类似:
|
||||||
|
|
||||||
|
```caddyfile
|
||||||
|
example.com {
|
||||||
|
handle_path /docs/* {
|
||||||
|
root * /var/www/iotlcm-docs
|
||||||
|
try_files {path} {path}.html {path}/index.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
或产物放在子目录、不做前缀处理:
|
||||||
|
|
||||||
|
```caddyfile
|
||||||
|
example.com {
|
||||||
|
root * /var/www/iotlcm/www
|
||||||
|
try_files {path} {path}.html {path}/index.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 对象存储 / CDN
|
||||||
|
|
||||||
|
产物是纯静态文件,可直接上传到 S3 / OSS / MinIO 等:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
# 上传 .vitepress/dist 全部内容到桶内 docs/ 前缀下
|
||||||
|
```
|
||||||
|
|
||||||
|
要点:
|
||||||
|
|
||||||
|
- 上传时保持 `/docs/` 前缀(或改 `base` 后重新构建)
|
||||||
|
- 配置**默认首页**为 `docs/index.html`
|
||||||
|
- 配置 **404 页面**为 `docs/404.html`(产物已自带)
|
||||||
|
- `cleanUrls: true` 意味着页面 URL 不带 `.html`,需对象存储支持「无后缀映射到 .html」,否则需关闭 `cleanUrls` 重新构建
|
||||||
|
|
||||||
|
::: tip 关闭 cleanUrls
|
||||||
|
若对象存储不支持无后缀映射,编辑 `.vitepress/config.mts` 把 `cleanUrls: true` 改为 `false`,重新构建后页面 URL 会带 `.html` 后缀。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## pnpm / yarn
|
||||||
|
|
||||||
|
仓库同时提供 `package-lock.json` 与 `pnpm-lock.yaml`。使用 pnpm:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
npm 与 pnpm 二选一即可,**不要混用**(会产生不一致的 `node_modules`)。
|
||||||
Reference in New Issue
Block a user