Docs / SEO Extras 插件 (Yoast-like)

SEO Extras 插件 (Yoast-like)

GoPress 内置的 Yoast 风格 per-content SEO 覆盖插件。激活后,每条内容编辑页底部会多出一个折叠的「SEO 设置(可选)」面板,提供 4 个独立字段让编辑给单条内容定制 SEO 输出。

它解决什么问题

默认情况下,单内容页的 SEO 字段是从内容自身字段推断的:

SEOMeta 字段 默认数据源
<meta description> / og:description Content.Excerpt(自动 truncate 到 160)
og:image Content.ImageURL
og:title Content.Title
<meta robots> index, follow

但有些场景需要"独立覆盖"——譬如:

  • 内容标题想短一点好阅读,但 <title> 想塞更多 SEO 关键词
  • description 不想自动用 Excerpt(可能被截断或不利于点击率)
  • 社交分享卡用一张特制的 og:image,而不是页面主图
  • 临时下架的产品想 noindex,但页面还要保留给老链接访问者

安装

激活方式:后台「插件管理」 → 找到「seo-extras」 → 点击「启用」。

任意支持内容编辑的内容类型页面滚到底,会出现:

▶ SEO 设置(可选)

  SEO Title         [_______________] (推荐 50–60 字符)
  SEO Description   [_______________] (推荐 50–160 字符)
  Open Graph 分享图 [_______________] [选择图片]
  Robots 指令       [默认 (index, follow) ▾]

4 个字段全是可选的,留空走默认。

数据存储

字段以 _seo_ 前缀存到 gp_content_meta

字段 meta key 覆盖什么
SEO Title _seo_title seo.Title + og:title
SEO Description _seo_description <meta description> + og:description
Open Graph 分享图 _seo_image og:image
Robots 指令 _seo_robots <meta robots>

下划线前缀避开 typeDef.MetaFields 注册的常规 meta 字段,清晰归属本插件。

字段为空时执行 DeleteMeta 而非保存空字符串——保证读路径"不存在 = 用默认"语义清晰,不会因为残留 _seo_title="" 把默认行为屏蔽掉。

实现架构

它是一个纯插件:core 完全不动、零数据库表(直接复用 gp_content_meta)、零路由。激活时仅注册 3 个 hook:

admin.content_form.fields  filter → 渲染 meta box HTML
admin.content.saved         action → 把 form 值持久化到 gp_content_meta
seo.content.meta            filter → 在 SEOBuilder 输出后 patch SEOMeta

完整数据流(包含插件介入):

SEOBuilder.ForContent(item, typeDef)
                ▼
ApplySiteOptionOverrides         (site_name / site_description 兜底)
                ▼
ApplyContentMetaSEO              (触发 seo.content.meta filter 链)
   ├── seo-extras 插件插入        (读 _seo_* meta,覆盖 Title/Description/OGImage/Robots)
   └── 你的其它 SEO 插件...
                ▼
data.SEO / data["SEO"]          (注入模板)
                ▼
{{seoHeadFor .}}                (渲染 HTML)

行为契约

场景 行为
插件未激活 meta box 不显示;SEOContentMeta 过滤器无订阅者;SEO 输出回退到默认(Excerpt + ImageURL)
插件激活但字段全空 meta box 显示但都没填;过滤器读到空 meta,原样返回 SEOMeta;输出仍是默认
插件激活 + 部分字段填值 填了的字段覆盖,没填的走默认(混合模式)
插件激活后又停用 hook 全部 Remove,meta box 消失;已存的 _seo_* meta 留在库里但不再被读

自定义 struct 主题需要做的事

如果你的主题用自定义 PageService(参考 modern-company / financial-news),要让 seo-extras 这类插件生效,buildContentSEO 末尾必须调一次 ApplyContentMetaSEO

import (
    "go-press/core/hook"
    coreTheme "go-press/core/theme"
)

type PageService struct {
    seoBuilder  *rewrite.SEOBuilder
    registry    *content.Registry
    hookBus     *hook.Bus            // 新增:从 engine.Hooks 获取
    contentRepo *content.Repository
}

func (s *PageService) buildContentSEO(item *content.Content, typeName string) rewrite.SEOMeta {
    seo := s.seoBuilder.ForContent(item, s.registry.GetType(typeName))
    s.applySEOOverrides(&seo)
    coreTheme.ApplyContentMetaSEO(s.hookBus, s.contentRepo, &seo, item)  // ← 让插件能 patch
    return seo
}

BaseTheme + gin.H 主题完全不用关心,core 的 renderSingle 已经替你调好了。

详见 主题 SEO 接入规范

自己写"扩展 SEO"插件

如果你想加自己的 SEO 字段(比如 schema.org 产品规格、自定义 og:type),完全可以再写一个插件,跟 seo-extras 并存。每个插件订阅同一组 hook,按 priority 顺序累加修改 SEOMeta,互不干扰。

这才是这套架构的真正价值:SEO 不是 core 写死的特性,而是可叠加的插件能力