Skip to content

feat(blog-seo): add SEO quality checker for post#20

Open
yumingHe0705 wants to merge 2 commits into
floatboatai:mainfrom
yumingHe0705:feat/blog-seo-checker
Open

feat(blog-seo): add SEO quality checker for post#20
yumingHe0705 wants to merge 2 commits into
floatboatai:mainfrom
yumingHe0705:feat/blog-seo-checker

Conversation

@yumingHe0705

Copy link
Copy Markdown

博客 SEO 质检器 —— PR 说明

选题方向:Blog SEO 质量检查器。在博客 CMS 之上加一道「SEO 质量闸门」:对文章内容自动检查并评分 + 给修复建议

本 PR 基于「博客 CMS(Post/Author/Tag)」那次改动叠加(stacked)——SEO 质检需要 Post 数据与编辑器,因此构建在 CMS 分支之上。

概述

新增一个零框架依赖的 SEO 规则引擎,对文章的标题、meta、正文结构、图片、链接、canonical 等做体检,输出 0–100 分和逐项结果(含可执行的修复建议)。同一套纯逻辑在两处复用:

  • 编辑器实时体检:在文章编辑页边写边算,即时反馈(客户端);
  • 服务端质检:analyzePostSeo(管理员)对已保存文章做审计(可供程序化/流水线调用)。

改动内容

  • src/blog-cms/seoChecker.ts:纯规则引擎(analyzeSeo + 解析辅助函数)。
  • src/blog-cms/SeoReportPanel.tsx:展示评分与逐项结果的纯展示组件。
  • src/blog-cms/AdminPostEditorPage.tsx:接入实时体检面板(useMemo 对当前表单计算)。
  • src/blog-cms/operations.ts + blog-cms.wasp.ts:新增服务端 analyzePostSeo 查询(管理员鉴权)。
  • src/blog-cms/seoChecker.test.ts:规则与解析函数的单元测试。

检查规则(阈值均为具名常量,便于调优与引用)

规则 判定
标题长度 空→error;<30 或 >60 字符→warning
meta 标题 有效值(metaTitle 回退 title)>60→warning
meta 描述 有效值(metaDescription 回退 excerpt)空→error;50–160 之外→warning
slug 合法性 提供了但非 URL-safe→error
H1 冲突 正文里出现 H1→warning(页面标题已是 H1,正文应从 H2 起)
H2 结构 长文(>300 词)无 H2→warning
图片 alt 有图片缺 alt→warning
链接 无任何链接→warning
canonical 提供了但非合法绝对 URL→error
正文字数 <100 词→warning(薄内容)

评分:满分 100,每个 error −20、每个 warning −8,截断到 [0,100]。

关键设计取舍

  • 一套纯逻辑、双端复用(DRY)。 规则引擎不依赖 Prisma/Wasp,因此客户端(实时体检)与服务端(analyzePostSeo)跑的是同一份代码,规则永远一致,且每条规则都能纯函数级单测。
  • 不是"通过/不通过",而是分级 + 加权评分 + 修复建议。error/warning/ok 三级 + 加权分数,给出的是"如何改进"的可执行反馈,而非二元结论。
  • 阈值集中为常量(SEO_LIMITS),依据常见 SEO 实践(标题 ≤60、meta 描述 50–160、单一 H1、图片 alt、canonical 等),便于团队按需调整。
  • Markdown + 基础 HTML 双解析:标题/图片/链接/字数都同时兼容 Markdown 与 HTML 写法(用正则,零额外依赖)。
  • 返回类型用 type 而非 interface Wasp operation 的返回值必须是 SuperJSON 可序列化的 Payload;TS 中对象 type 满足该索引签名约束、interface 不满足,因此 SeoReport/SeoChecktype 定义。
  • 客户端/服务端边界:引擎只依赖同模块内的纯函数(如 isValidSlug,类型导入会被打包器擦除),可安全进客户端包。

边界处理

  • meta 描述缺失时回退到 excerpt;都没有才判 error。
  • meta 标题缺失时回退到标题参与长度判断。
  • 图片区分 Markdown ![alt](src) 与 HTML <img alt>,alt 为空才算缺失。
  • 链接统计排除图片(![...] 不计为链接)。
  • canonical 只在"提供了但非法"时报错;留空视为自指 canonical,不报错。
  • analyzePostSeo 对不存在的文章返回 404;非管理员 401/403。

测试方式

  • 单元测试(wasp test client run),本 PR 新增 11 个(全仓库 27 个全过):
    • 集成:优质文章高分、空标题 error、meta 描述缺失/回退、非法 slug 与 canonical、H1 冲突、图片缺 alt。
    • 解析辅助函数:countHeadings(Markdown+HTML 分级)、analyzeImages(alt 检测)、countLinks(排除图片)、wordCount(去语法计词)。
  • 手动:在编辑器修改标题/正文/meta,观察实时评分与逐项标红/告警变化。

本地评审步骤

cd template/app
wasp start                 # 打开 http://localhost:3000/admin/posts,编辑任一文章 → 底部“SEO check”面板
wasp test client run       # 运行单元测试

局限与后续

  • 目前只有后台使用;若做公开渲染页,HTML 内容需在渲染端做 sanitize(XSS)。
  • 可扩展:关键词密度、可读性评分、内链 vs 外链区分、图片尺寸/懒加载、与「SEO 发布流水线」联动(发布前强制分数门槛 + 发布后更新 sitemap/canonical)。
  • 阈值目前是全局常量,后续可做成每站点可配置。

“hym” and others added 2 commits July 15, 2026 11:27
Adds an admin-only blog CMS to the Wasp app:

- Data model: Post, Author, Tag (+ PostStatus enum) with a many-to-many
  Post<->Tag relation and an optional Author<->User link.
- Operations: admin CRUD, public read (published only), tag/author creation,
  with zod input validation and admin-only authorization.
- Slug handling: auto-slugify from title, app-level uniqueness with a numeric
  suffix, and a DB unique constraint as the final guard (P2002 -> 409).
- Publish lifecycle modeled as an explicit state machine (DRAFT/PUBLISHED/
  ARCHIVED); publishedAt is stamped on first publish; archived posts must be
  restored to draft before going live again.
- Admin UI: posts list (filter/search/paginate, inline status transitions,
  delete) and a create/edit editor (inline author/tag creation, SEO fields),
  plus a "Blog Posts" sidebar entry.
- Dev seed (seedBlogContent) and unit tests for slug + status-machine logic.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds an SEO quality gate on top of the blog CMS:

- A framework-free rule engine (`seoChecker.ts`) that scores a post 0-100 and
  reports per-rule results with actionable fixes. Rules cover title/meta-title
  length, meta description (with excerpt fallback), slug validity, a competing
  H1, missing H2 structure, image alt text, links, canonical URL validity, and
  thin content. Thresholds are named constants based on common SEO guidance.
- The same pure engine runs in two places (DRY): live in the post editor for
  instant feedback while typing (`SeoReportPanel`), and server-side via the
  admin-only `analyzePostSeo` query for auditing a saved post.
- Unit tests (`seoChecker.test.ts`) for the rules and the markdown/HTML parsing
  helpers (heading counts, image alt detection, link counting, word count).

Stacked on the blog CMS (Post/Author/Tag) change.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant