feat(server): 单域托管 client/dist 与 SPA 回退(E12)

- 新增 SERVE_CLIENT_DIST/CLIENT_DIST_DIR 配置
- 开启后注册 @fastify/static 并对非 /api GET(html) 回退 index.html
- docker-compose.yml 与 README 补充用法
这个提交包含在:
2026-02-09 00:06:59 +08:00
父节点 75d5b3d5de
当前提交 89c0ed2ec1
共修改 5 个文件,包含 41 行新增和 2 行删除
+20 -1
查看文件
@@ -9,6 +9,7 @@ const cookie = require("@fastify/cookie");
const multipart = require("@fastify/multipart");
const helmet = require("@fastify/helmet");
const rateLimit = require("@fastify/rate-limit");
const staticPlugin = require("@fastify/static");
const { getConfig } = require("./config");
const authPlugin = require("./plugins/auth");
@@ -95,11 +96,29 @@ async function buildApp() {
},
});
if (config.serveClientDist) {
await app.register(staticPlugin, {
root: config.clientDistDir,
});
app.log.info({ dir: config.clientDistDir }, "Client dist static serving enabled");
}
app.addHook("onRequest", async (request, reply) => {
reply.header("x-request-id", request.id);
});
app.setNotFoundHandler(async () => {
app.setNotFoundHandler(async (request, reply) => {
if (
config.serveClientDist &&
request.method === "GET" &&
!request.url.startsWith("/api") &&
typeof request.headers.accept === "string" &&
request.headers.accept.includes("text/html") &&
typeof reply.sendFile === "function"
) {
return reply.type("text/html").sendFile("index.html");
}
throw new AppError({
code: "SYS_404",
message: "Not Found",
+3
查看文件
@@ -14,6 +14,7 @@ function getConfig() {
const nodeEnv = process.env.NODE_ENV ?? "development";
const serverRoot = path.resolve(__dirname, "..");
const storageDir = process.env.STORAGE_DIR ?? "generated/storage";
const clientDistDir = process.env.CLIENT_DIST_DIR ?? "../client/dist";
return {
nodeEnv,
@@ -40,6 +41,8 @@ function getConfig() {
appTimezone: process.env.APP_TIMEZONE ?? "Asia/Shanghai",
storageDir: path.resolve(serverRoot, storageDir),
serveClientDist: process.env.SERVE_CLIENT_DIST === "true",
clientDistDir: path.resolve(serverRoot, clientDistDir),
signedUrlTtlSeconds: Number(process.env.SIGNED_URL_TTL_SECONDS ?? 600),
maxUploadSizeBytes: Number(process.env.MAX_UPLOAD_SIZE_BYTES ?? 10 * 1024 * 1024),