# SEO / GEO 优化诊断报告 > 项目:website-template(Nuxt 多租户 CMS 官网模板) > 检查日期:2026-08-04 > 检查范围:Nuxt 配置、页面/模板 SEO、结构化数据、站点地图、robots、图片与性能、GEO 友好度 --- ## 一、执行摘要 当前项目已具备 **基础的 SEO 骨架**:SSR 渲染、统一的 `usePageSeo` 工具、动态 `robots.txt` / `sitemap.xml`、Organization 结构化数据、旧链接 301 重定向等。 但 **详情页 SEO 几乎空白**,这是目前最大的流量损失点:`/article/:id`、`/product/:id`、`/case/:id` 的标题、描述、OG 信息都没有使用实际内容,导致搜索引擎看到的所有详情页都是统一的栏目名(如“新闻资讯 - 广西恒瑞天诚项目管理有限公司”)。 下方按 **P0(立即修复)/ P1(强烈建议)/ P2(持续优化)** 分级列出问题与落地建议。 --- ## 二、已做 SEO 事项(可保留) | 事项 | 位置 | 状态 | |------|------|------| | 全局 SSR,搜索引擎可抓取完整 HTML | `nuxt.config.ts` `routeRules` | ✅ | | 统一页面 SEO 工具(TDK + OG + Twitter + Canonical) | `app/composables/usePageSeo.ts` | ✅ | | Organization 结构化数据 | `useOrganizationSeo` | ✅ | | 首页 SEO + 组织结构化数据 | `app/pages/index.vue` | ✅ | | CMS 单页 SEO(title/keywords/description/photo) | `app/pages/page/[id].vue` | ✅ | | 关于我们页 SEO | `template-*/pages/About.vue` | ✅ | | 动态 `robots.txt`(开发环境屏蔽) | `server/api/robots.txt.ts` | ✅ | | 动态 `sitemap.xml` | `server/api/sitemap.xml.ts` | ✅(但范围不足,见 P0) | | 旧链接 301 重定向(/news→/article 等) | `server/middleware/z-news-detail-redirect.ts` | ✅ | | 部分图片使用 `loading="lazy"` | 模板 Home | ✅ | | 富文本图片响应式处理 | `app/components/RichText.vue` | ✅ | --- ## 三、问题清单与优化建议 ### 🔴 P0 — 立即修复(影响搜索收录与排名) #### 1. 文章/产品/案例详情页没有使用内容标题做 SEO **问题**: - `useModuleRoute` 只给详情页设置了栏目兜底标题,如“新闻资讯 / 产品中心 / 案例展示”。 - 所有模板中的 `NewsDetail.vue`、`ProductDetail.vue`、`CaseDetail.vue` **均未调用 `usePageSeo`**。 - 截图中的 `/article/396`(招标代理)实际标题应该是“招标代理 - 广西恒瑞天诚项目管理有限公司”,但搜索引擎看到的是“新闻资讯 - 广西恒瑞天诚项目管理有限公司”。 **影响**:大量详情页在搜索结果中标题重复、CTR 低;长尾关键词无法被有效索引。 **修复方案**:在每个详情组件中,拿到数据后立即调用 `usePageSeo`,并注入 `Article` / `Product` 结构化数据。 **示例(`app/templates/template-08/pages/NewsDetail.vue`)**: ```ts const route = useRoute() const id = route.params.id as string const { siteInfo, fetchSiteInfo } = useSite() await fetchSiteInfo() const { data: article } = await useFetch
(`/api/article/detail?id=${id}`, { key: `article-${id}` }) // 用文章内容覆盖 SEO usePageSeo({ title: article.value?.title || '新闻资讯', description: stripHtml(article.value?.summary || '').slice(0, 160) || undefined, keywords: article.value?.tags?.join(',') || undefined, path: route.path, // 注意用 route.path 而非 route.fullPath,避免 ?navId= 进入 canonical image: article.value?.image || article.value?.cover || undefined, type: 'article', publishedTime: article.value?.publishTime || article.value?.createTime, modifiedTime: article.value?.updateTime }, siteInfo.value) // 注入文章结构化数据 useJsonLd({ '@context': 'https://schema.org', '@type': 'Article', headline: article.value?.title, description: stripHtml(article.value?.summary || ''), image: article.value?.image || article.value?.cover || undefined, datePublished: article.value?.publishTime || article.value?.createTime, dateModified: article.value?.updateTime, author: { '@type': 'Organization', name: siteInfo.value?.websiteName } }) ``` **需同步修改的模板文件**: - `app/templates/template-0*/pages/NewsDetail.vue`(9 套) - `app/templates/template-0*/pages/ProductDetail.vue`(9 套) - `app/templates/template-0*/pages/CaseDetail.vue`(9 套) > 如果 9 套模板结构差异大,可先在 `useModuleRoute` 中统一注入一份“最小可用 SEO”(标题用详情接口数据),再逐步让各模板补充更精细的 OG 图和结构化数据。 --- #### 2. Sitemap 只包含单页,未覆盖文章/产品/案例详情和分页 **问题**:`server/api/sitemap.xml.ts` 只拉取 `cms-website/pageAll`(即 CMS 单页 slug),未包含: - 文章详情页 `/article/{id}` - 产品详情页 `/product/{id}` - 案例详情页 `/case/{id}` - 栏目列表页 `/article`、`/article/{navId}`、`/product`、`/case` **影响**:搜索引擎依赖 sitemap 发现新内容,缺失会导致收录速度慢、遗漏详情页。 **修复方案**:扩展 `sitemap.xml.ts`,增加 article/product/case 列表接口调用,并拆分 `sitemapindex`(如果 URL 数量 > 50,000 或体积 > 50MB)。 **最小可用实现(伪代码)**: ```ts // server/api/sitemap.xml.ts const [pages, articles, products, cases] = await Promise.all([ fetchPages(), fetchList('/cms/cms-article/page', { limit: 1000 }), fetchList('/cms/cms-product/page', { limit: 1000 }), fetchList('/cms/cms-case/page', { limit: 1000 }) ]) const staticUrls = [ { loc: '/', priority: '1.0', changefreq: 'daily' }, { loc: '/article', priority: '0.9', changefreq: 'daily' }, { loc: '/product', priority: '0.9', changefreq: 'daily' }, { loc: '/case', priority: '0.9', changefreq: 'daily' } ] const articleUrls = articles.map(a => ({ loc: `/article/${a.id || a.articleId}`, priority: '0.7', changefreq: 'weekly', lastmod: a.updateTime || a.publishTime })) // ... product/case 同理 ``` **注意事项**: - 只输出 `status === 0`(已发布)的内容。 - 列表接口若支持 `limit`,需分页拉取全部;若总量大,建议生成 `sitemapindex.xml` + 多个子 sitemap。 - 列表页分页(`/article?page=2`)可不必写入 sitemap,让搜索引擎通过页面内链发现即可。 --- #### 3. Canonical URL 可能包含查询参数,导致重复内容 **问题**:多处使用 `route.fullPath` 作为 `path` 传给 `usePageSeo`: - `useModuleRoute`:`path: route.fullPath` - `app/pages/page/[id].vue`:`path: route.fullPath` - `app/pages/[slug].vue`:`path: /${slug}`(这个 OK) 当 URL 带 `?navId=xxx`、`?keywords=xxx`、`?page=2` 时,canonical 会带上这些参数,造成同一内容多个 canonical。 **修复方案**:统一使用**干净路径**(`route.path`)作为 canonical,必要时把分页参数也排除。 ```ts // 建议增加一个干净 canonical 工具 function getCanonicalPath(route: RouteLocationNormalizedLoaded) { // 只保留 path,去掉 navId/keywords/page 等查询参数 return route.path } ``` 对于列表分页,如果希望保留 `?page=2` 的 canonical,可单独处理;否则建议所有列表页的 canonical 都不带查询参数。 --- #### 4. 详情页没有面包屑结构化数据 **问题**:`useBreadcrumbSeo` 已定义但未被任何页面使用。 **影响**:Google 搜索结果可能无法展示面包屑导航,降低 SERP 丰富度。 **修复方案**:在详情页和列表页注入面包屑: ```ts useBreadcrumbSeo([ { name: '首页', url: '/' }, { name: article.value?.categoryName || '新闻资讯', url: '/article' }, { name: article.value?.title || '详情', url: route.path } ]) ``` --- ### 🟡 P1 — 强烈建议(提升 CTR 与收录质量) #### 5. 缺少 `` **问题**:`nuxt.config.ts` 的 `app.head` 未设置 `htmlAttrs.lang`。 **影响**:搜索引擎无法准确判断页面语言;屏幕阅读器体验受损。 **修复方案**: ```ts app: { head: { htmlAttrs: { lang: 'zh-CN' }, // ... } } ``` --- #### 6. 404 页面没有 `noindex` **问题**:`app/pages/404.vue` 没有设置 `robots: noindex`。 **修复方案**: ```ts useSeoMeta({ robots: 'noindex, follow', title: '页面未找到' }) ``` --- #### 7. 标题模板未统一站点名后缀 **问题**:`nuxt.config.ts` 中 `titleTemplate: '%s'` 只是原样输出,各页面需自己拼 `- 站点名`。目前 `usePageSeo` 会自动拼接,但兜底标题(如 `app/pages/[slug].vue` 中的 `slug`)可能出现没有站点名的情况。 **建议**:把全局 `titleTemplate` 改为 `' %s - {{siteName}}'`,或在 `usePageSeo` 中统一处理。当前 `usePageSeo` 已经拼接,可作为单一事实源。 --- #### 8. 图片懒加载与 CLS 优化不足 **问题**: - 多数模板图片没有 `loading="lazy"`。 - 所有图片缺少 `width` / `height` 或 `aspect-ratio`,导致 Cumulative Layout Shift(CLS)。 - 没有 `decoding="async"`、没有响应式 `srcset/sizes`。 - 装饰性/无意义图片没有 `alt=""`。 **修复方案**: ```html ``` 对于 CMS 富文本中的图片,可在 `RichText.vue` 中统一注入 `loading="lazy" decoding="async"`。 --- #### 9. Open Graph 图片缺失或 fallback 到站点 Logo **问题**:`usePageSeo` 在没有 `input.image` 时会 fallback 到 `site.websiteLogo`。站点 Logo 通常是 PNG/SVG,尺寸可能不符合 OG 推荐(1200×630)。 **建议**: - 列表页/首页配置一张专门的 OG 封面图(可在 CMS `siteInfo` 中增加 `ogImage` 字段)。 - 详情页优先使用内容封面图,并确保图片可被公开访问、尺寸合适。 --- #### 10. 分页缺少 `rel="prev/next"` 或规范处理 **问题**:文章/产品列表页使用按钮分页,没有 `` 标签链接,也没有 `rel="prev/next"`。 **影响**:搜索引擎可能无法顺畅抓取深层列表页。 **建议**: - 分页按钮使用 `` 而不是 `