docs(engineering): add project audit report and improvement plan

This commit introduces a comprehensive engineering audit report for the Tootaio Studio project. The report is structured into documents covering architecture, code quality, performance, security, CI/CD, and
observability. It also includes a phased improvement roadmap and a set of `.patch` files to apply immediate fixes for content schemas, package scripts, and CI configuration.
This commit is contained in:
xiaomai
2025-11-06 10:15:00 +08:00
parent 8cc04b7f59
commit 40b3ee147f
14 changed files with 744 additions and 0 deletions

28
docs/20251106/README.md Normal file
View File

@@ -0,0 +1,28 @@
# Tootaio Studio 工程与架构审计2025-11-06
本报告对当前基于 Nuxt 4.2.0 的网站项目进行一次性全方位工程化审计涵盖架构、代码质量、性能、CI/CD、安全与可观测性并提供可直接落地的改进方案与补丁。
## 摘要
- 架构总体清晰,采用 Nuxt Content + i18n + @nuxt/ui模块职责明确Low
- 发现内容 Schema 与实际 YAML 不一致Critical导致构建期校验风险与类型漂移。
- 缺少统一工程工具链lint/typecheck/CIHigh
- 未配置 Route Rules/SWR 与静态资源缓存Medium
- 运行时配置、监控与安全头未体系化Medium
## 关键输出
- 补丁:
- 修正 `content.config.ts` Schema 与内容对齐0001
- 增加 `package.json` 脚本lint/typecheck/check0002
- 提供 GitHub Actions 工作流lint → typecheck → build0003
- 可选:为首页与静态资源增加 `routeRules`0004
- 提供 8 份专题文档架构、质量、性能、CI/CD、安全、可观测性与路线图。
## 执行指引
1) 审阅 `docs/20251106/` 文档;
2) 依次应用补丁(位于 `docs/20251106/patches/`
- `git apply docs/20251106/patches/0001-content-config-align.patch`
- `git apply docs/20251106/patches/0002-package-scripts.patch`
- `git apply docs/20251106/patches/0003-github-actions-ci.patch`
- (可选)`git apply docs/20251106/patches/0004-nuxt-route-rules.patch`
3) 本地验证:`pnpm i && pnpm check && pnpm preview`

View File

@@ -0,0 +1,52 @@
# 架构结构与改进方案
本文审计 Nuxt 4.2.0 架构与模块使用,指出结构异味并提供可落地改进。
## 现状评估
- 模块与目录Low
- `app/`pages/layouts/composables/utils/assets职责清晰。
- `content/``content.config.ts` 使用 zod 定义 Schema利于类型驱动内容。
- `i18n/` 采用 `locales/<locale>` 结构;`no_prefix` 策略与当前站点定位一致。
- Nuxt 4 特性使用Medium
- 自动导入:已使用(如 `useLocalizedCollection`)。建议将文件命名统一为 `useXxx.ts` 便于识别。
- Runtime Config未使用。建议引入 `runtimeConfig` 管理外部域名、监控 DSN 等。
- Nitro Route Rules/缓存:未配置。建议为首页与静态资源设置 `prerender`+`swr` 与长缓存。
- 设计风险Critical
- `content.config.ts` 的 Index Schema 与 YAML 不一致:缺少 `title/description/seo` 字段;`icon/highlight/spotlight` 约束与内容不符。
## 改进策略
1) 内容 Schema 对齐Critical
- 为 Index 增加 `title/description/seo`;将 `icon` 设为可选,将 `highlight/spotlight` 设为可选并默认 `false`
- 参见补丁:`0001-content-config-align.patch`
2) 运行时配置Medium
-`nuxt.config.ts` 中:
```ts
export default defineNuxtConfig({
runtimeConfig: {
sentryDsn: '',
public: { assetBase: 'https://img.tootaio.com' },
},
})
```
- 在代码中读取 `useRuntimeConfig()`,避免硬编码外链与密钥。
3) 路由规则与缓存Medium
- 为首页预渲染并设置 SWR为构建产物设置 immutable 缓存:
```ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true, swr: 3600 },
'/_nuxt/**': {
headers: { 'cache-control': 'public, max-age=31536000, immutable' },
},
},
})
```
- 参见补丁:`0004-nuxt-route-rules.patch`。
4) 组合式命名与分层Low
- 将 `app/composables/LocalizedCollection.ts` 重命名为 `useLocalizedCollection.ts`。
- 保持 composable 只做“取数+组装”,复杂 UI/状态拆到组件/Pinia若引入
## 与 Nuxt 4.2.0 兼容性
- 现有 `compatibilityDate: '2025-07-15'` 符合要求。
- 上述配置属于稳定 API`routeRules/runtimeConfig/auto-import`,安全可用。

40
docs/20251106/ci-cd.md Normal file
View File

@@ -0,0 +1,40 @@
# CI/CD 设计与部署方案
## 目标
提供从代码质量到产物构建的自动化流程lint → typecheck → build。
## GitHub Actions推荐
- 工作流文件参见补丁 `0003-github-actions-ci.patch`,关键步骤:
- 使用 `actions/setup-node@v4` + `corepack enable`
- 安装依赖:`pnpm i --frozen-lockfile`
- 执行 `pnpm lint && pnpm typecheck && pnpm build`
## 部署对比
- Vercel推荐
- 优点:零配置、内置 CDN、适配 Nuxt/NitroPreview 环境完善。
- 缺点:免费额度受限;对长时间 SSR 任务需商用套餐。
- Docker自管
- 优点:环境一致性高;更灵活接入内网服务。
- 缺点:需自建 CI/CD 与监控;维护成本更高。
- 自建 Node + Nginx
- 优点:成本可控;传统可见性强。
- 缺点:手工配置多;需要额外缓存/CDN 配合。
## Dockerfile示例
```dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY . .
RUN corepack enable && pnpm i --frozen-lockfile && pnpm build
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/.output ./.output
EXPOSE 3000
CMD ["node","./.output/server/index.mjs"]
```
## 环境变量与密钥
- 在 CI 中设置 `NITRO_PRESET``SENTRY_DSN`Nuxt 通过 `runtimeConfig` 注入,避免硬编码。

View File

@@ -0,0 +1,41 @@
# 代码规范与质量分析
## 主要发现
- 类型定义High
- `content.config.ts` 缺少 Index 页的 `title/description/seo`,导致 `page?.title` 等缺少类型提示;特定字段(`icon/highlight/spotlight`)与 YAML 不一致。
- 组合式命名Medium
- `LocalizedCollection.ts` 建议改为 `useLocalizedCollection.ts`,与 Nuxt 习惯保持一致,便于检索与自动导入心智统一。
## 统一约定
- 文件与目录
- composables`useXxx.ts`(示例:`useLocalizedCollection.ts`)。
- 页面:`pages/webDev.vue`(帕斯卡命名不用于路由页)。
- 工具:`utils/some-helper.ts`,避免与 composables 混用。
- 代码风格
- TypeScript、ESM、Vue SFC `<script setup lang="ts">`
- 缩进 2 空格;组件/组合式函数采用明确的返回类型。
- i18n 使用嵌套点式键:`$t('index.featuredProjects.viewDemo')`
- 提交规范
- 参考历史:`feat(content): ...``refactor(ui): ...``feat(pages): ...`
- 分支:`feat/<desc>``fix/<desc>`
## 重构建议(示例)
1) 内容类型对齐Critical
- 见补丁 `0001-content-config-align.patch`
2) 工程脚本完善High
- 新增脚本:`lint/typecheck/check`,见补丁 `0002-package-scripts.patch`
3) 组合式命名Medium
-`LocalizedCollection.ts` 重命名为 `useLocalizedCollection.ts`(建议)。
## 示例:更严格的返回类型
```ts
export function useLocalizedCollection<B extends string>(baseName: B) {
const { locale } = useI18n()
type LocalizedKey = keyof { [K in keyof Collections as K extends `${B}_${string}` ? K : never]: any }
type Schema = Collections[LocalizedKey]
return useAsyncData<Schema | null>(`${baseName}-${locale.value}`, async () => {
// ...
})
}
```

View File

@@ -0,0 +1,40 @@
# 监控、日志与可观测性
## 目标
构建可追踪错误、性能瓶颈与用户关键路径的最小监控闭环。
## Sentry推荐
1) 配置 `runtimeConfig.sentryDsn`
2) 插件(示例,待引入依赖后启用):
```ts
// plugins/sentry.client.ts
export default defineNuxtPlugin(() => {
const { public: pub, sentryDsn } = useRuntimeConfig()
if (!sentryDsn) return
// @ts-ignore: add SDK in deps later
const Sentry = (window as any).Sentry
if (Sentry) {
Sentry.init({ dsn: sentryDsn, tracesSampleRate: 0.1 })
}
})
```
## 日志与错误边界
- 服务器:使用 Nitro 默认日志(生产应接入集中式日志,如 CloudWatch/ELK
- 客户端:全局错误捕获(`app:error` 事件)上报到 Sentry/自建网关。
## Web Vitals可选
收集 LCP/CLS/INP 等指标(示例):
```ts
// plugins/web-vitals.client.ts
import { onCLS, onINP, onLCP } from 'web-vitals'
export default defineNuxtPlugin(() => {
const send = (metric: any) => navigator.sendBeacon('/vitals', JSON.stringify(metric))
onLCP(send); onCLS(send); onINP(send)
})
```
## Dashboard 路线
- 第一步:仅错误计数与关键事务。
- 第二步接口慢查询、资源错误分布、版本追踪commit SHA

View File

@@ -0,0 +1,89 @@
diff --git a/content.config.ts b/content.config.ts
--- a/content.config.ts
+++ b/content.config.ts
@@
const defineIndexSchema = () =>
- z.object({
- capabilities: z.object({
- title: z.string(),
- features: z.array(
- z.object({
- title: z.string(),
- description: z.string(),
- icon: z.string(),
- })
- ),
- }),
- featuredProjects: z.object({
- title: z.string(),
- projects: z.array(
- z.object({
- title: z.string(),
- description: z.string(),
- image: z.string(),
- demoLink: z.string(),
- highlight: z.boolean(),
- spotlight: z.boolean(),
- })
- ),
- }),
- techStack: z.object({
- title: z.string(),
- }),
- whyChooseUs: z.object({
- title: z.string(),
- features: z.array(
- z.object({
- title: z.string(),
- description: z.string(),
- icon: z.string(),
- })
- ),
- }),
- });
+ z.object({
+ title: z.string(),
+ description: z.string(),
+ seo: z
+ .object({
+ title: z.string().optional(),
+ description: z.string().optional(),
+ })
+ .optional(),
+ capabilities: z.object({
+ title: z.string(),
+ features: z.array(
+ z.object({
+ title: z.string(),
+ description: z.string(),
+ icon: z.string().optional(),
+ })
+ ),
+ }),
+ featuredProjects: z.object({
+ title: z.string(),
+ projects: z.array(
+ z.object({
+ title: z.string(),
+ description: z.string(),
+ image: z.string(),
+ demoLink: z.string().url().optional(),
+ highlight: z.boolean().optional().default(false),
+ spotlight: z.boolean().optional().default(false),
+ })
+ ),
+ }),
+ techStack: z.object({
+ title: z.string(),
+ }),
+ whyChooseUs: z.object({
+ title: z.string(),
+ features: z.array(
+ z.object({
+ title: z.string(),
+ description: z.string(),
+ icon: z.string().optional(),
+ })
+ ),
+ }),
+ });

View File

@@ -0,0 +1,39 @@
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@
"scripts": {
- "build": "nuxt build",
- "dev": "nuxt dev",
- "generate": "nuxt generate",
- "preview": "nuxt preview",
- "postinstall": "nuxt prepare"
+ "build": "nuxt build",
+ "dev": "nuxt dev",
+ "generate": "nuxt generate",
+ "preview": "nuxt preview",
+ "postinstall": "nuxt prepare",
+ "typecheck": "nuxt typecheck",
+ "lint": "eslint --ext .ts,.js,.mjs,.vue --ignore-path .gitignore .",
+ "lint:fix": "pnpm lint -- --fix",
+ "check": "pnpm lint && pnpm typecheck && pnpm build"
},
@@
"dependencies": {
"@iconify-json/mdi": "^1.2.3",
"@nuxt/content": "3.8.0",
"@nuxt/eslint": "1.10.0",
"@nuxt/ui": "4.1.0",
"@nuxtjs/i18n": "10.2.0",
"@nuxtjs/seo": "3.2.2",
"better-sqlite3": "^12.4.1",
"eslint": "^9.39.0",
"nuxt": "^4.2.0",
"typescript": "^5.9.3",
"vue": "^3.5.22",
"vue-router": "^4.6.3"
- }
+ },
+ "engines": {
+ "node": ">=18.20.0"
+ }

View File

@@ -0,0 +1,34 @@
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/ci.yml
@@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Enable Corepack
run: corepack enable
- name: Install dependencies
run: pnpm i --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Typecheck
run: pnpm typecheck
- name: Build
run: pnpm build

View File

@@ -0,0 +1,50 @@
diff --git a/nuxt.config.ts b/nuxt.config.ts
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@
export default defineNuxtConfig({
compatibilityDate: "2025-07-15",
devtools: { enabled: true },
modules: [
"@nuxt/content",
"@nuxt/ui",
"@nuxt/eslint",
"@nuxtjs/i18n",
"@nuxtjs/seo",
],
css: ["@/assets/css/main.css"],
app: {
head: {
titleTemplate: "%s - Tootaio Studio",
meta: [
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ charset: "utf-8" },
],
},
},
@@
seo: {
meta: {
title: DEFAULT_SEO.title,
description: DEFAULT_SEO.description,
keywords: DEFAULT_SEO.keywords,
ogTitle: DEFAULT_SEO.title,
ogDescription: DEFAULT_SEO.description,
ogImage: DEFAULT_SEO.image,
ogUrl: DEFAULT_SEO.url,
twitterCard: "summary_large_image",
twitterTitle: DEFAULT_SEO.title,
twitterDescription: DEFAULT_SEO.description,
twitterImage: DEFAULT_SEO.image,
},
},
site: {
url: "https://tootaio.com",
},
+ routeRules: {
+ "/": { prerender: true, swr: 3600 },
+ "/_nuxt/**": {
+ headers: { "cache-control": "public, max-age=31536000, immutable" },
+ },
+ },
});

View File

@@ -0,0 +1,33 @@
# 性能诊断与优化建议
## 现状与影响
- SSR/CSRMedium默认 SSR未针对首页静态化或增量缓存配置变化不频繁的内容可降本提速。
- 资源缓存Medium未设置构建产物`/_nuxt/**`)的长期缓存与 immutable重复访问浪费带宽。
- 图片与外链LowOG 图与背景图使用外部域名,建议统一通过 CDN 域名与缓存规则管理。
## Nuxt 4 定向优化
1) Route Rules 与 SWRMedium
- 预渲染首页并启用 SWR降低首屏 TTFB。
- 长缓存构建产物:
```ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true, swr: 3600 },
'/_nuxt/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' } },
},
})
```
- 参见补丁:`0004-nuxt-route-rules.patch`。
2) 代码分割与懒加载Low
- 仅在首屏需要时挂载大型组件(如轮播);通过 `v-if` 配合 `onMounted` 或 `client-only` 降低 SSR 负载。
3) 构建与依赖Low
- 保持 `pnpm-lock.yaml` 锁定CI 使用 `pnpm i --frozen-lockfile` 保证可重复构建。
4) 图片与静态资源Medium
- 将站点图片统一迁到自有 CDN如 Cloudflare/Vercel Assets配置缓存与压缩。
- 如未来引入 `@nuxt/image`,优先使用自动格式与尺寸。
## 部署建议
- Node/Nitro通用适合 SSR + SWR 与渐进更新。
- Edge部份路由静态资源 + 首页可下放至边缘,注意依赖兼容性。
- Static/Generate若大部分页面静态可考虑 `pnpm generate` 并配合 CDN。

34
docs/20251106/roadmap.md Normal file
View File

@@ -0,0 +1,34 @@
# 分阶段工程化改进路线图
## 阶段一基础规范化12 天)
- 目标:消除 Critical/High 风险,建立最小工程基线。
- 动作:
- 应用补丁 0001/0002
- 启用 `pnpm check` 在本地与 CI
- 修复内容与 Schema 校验。
- 验证:`pnpm check` 全绿;手测多语言与内容渲染。
## 阶段二模块解耦23 天)
- 目标:清晰分层,降低耦合与心智成本。
- 动作:
- 统一 composables 命名为 `useXxx.ts`
- 将硬编码外链迁到 `runtimeConfig.public.assetBase`
- 视需要引入 Pinia 拆分跨页状态。
- 验证:路由与数据流稳定;影响面与回归点有测试用例(增量引入 Vitest
## 阶段三自动化部署12 天)
- 目标PR 即可验证、主干可随时发布。
- 动作:
- 应用补丁 0003GitHub Actions
- 配置 Vercel/自建 Docker 部署;
- 产出构建产物与环境变量清单。
- 验证PR 触发 CI产物可预览。
## 阶段四性能与监控24 天)
- 目标:稳定响应、端到端可观测。
- 动作:
- 应用补丁 0004Route Rules/SWR
- 引入 SentryDSN + 插件);
- 图片与静态资源走 CDN 与长缓存。
- 验证:关键页面首屏数据、错误率与 Web Vitals。

33
docs/20251106/security.md Normal file
View File

@@ -0,0 +1,33 @@
# 安全审计与防护建议
## 发现与等级
- 外链与配置Medium
- 外部图片/链接硬编码在源码中;建议迁移到 `runtimeConfig.public.assetBase` 并集中管理。
- 内容与校验High
- Content Schema 与数据不一致会在构建期暴露为错误或导致运行时空值处理不当。
- 依赖与脚本Low
- 缺少 `typecheck`/`lint` 脚本,易让问题晚发现。
## 防护清单
1) 运行时配置Medium
```ts
export default defineNuxtConfig({
runtimeConfig: {
// 仅服务器可见
sentryDsn: '',
// 客户端可见
public: {
assetBase: 'https://img.tootaio.com',
},
},
})
```
2) 安全头(建议由网关/Nginx 配置Low
- `Content-Security-Policy`(允许必要的域名);
- `X-Content-Type-Options: nosniff`、`Referrer-Policy: strict-origin-when-cross-origin`
- 静态资源 `Cache-Control: immutable`。
3) 敏感信息检查Medium
- 使用 `.gitignore` 与 CI 检查(如 `trufflehog`/`gitleaks`,后续可引入)。
4) 依赖与锁定Low
- 全量使用 `pnpm-lock.yaml`CI 中启用 `--frozen-lockfile` 保证可复现构建。