05|SEO 与元信息
第八章 / 05。目标:让搜索引擎、社交抓取器和真实读者看到同一页面身份。
SEO 不是只改 title
一个详情页至少有四组互相约束的信息:响应状态与可爬取正文、title/description、canonical URL、Open Graph/结构化数据。它们应由同一已校验的页面模型派生;否则页面正文是商品 A,分享卡片可能仍是商品 B。
下面页面保存为 Nuxt 实验项目 app/pages/articles/[slug].vue。先补 server/api/articles/[slug].get.ts:
export default defineEventHandler(event => {
const slug = getRouterParam(event, 'slug')
if (slug !== 'start') throw createError({ statusCode: 404, statusMessage: 'Article not found' })
return { slug, title: '水合从哪一步开始', summary: '从同一份首屏输入理解两次执行。' }
})2
3
4
5
页面:
<script setup lang="ts">
const route = useRoute()
const { data: article, error } = await useAsyncData(
() => `article:${route.params.slug}`,
() => $fetch(`/api/articles/${route.params.slug}`)
)
if (error.value) {
throw createError({ statusCode: error.value.statusCode === 404 ? 404 : 500, statusMessage: 'Article unavailable' })
}
if (!article.value) {
throw createError({ statusCode: 404, statusMessage: 'Article not found' })
}
const canonical = computed(() => article.value ? new URL(
`/articles/${encodeURIComponent(article.value.slug)}`,
'https://example.test'
).href : undefined)
useSeoMeta({
title: () => article.value?.title ?? '文章',
description: () => article.value?.summary ?? '',
ogTitle: () => article.value?.title ?? '文章',
ogDescription: () => article.value?.summary ?? '',
ogUrl: canonical
})
useHead(() => ({ link: canonical.value ? [{ rel: 'canonical', href: canonical.value }] : [] }))
</script>
<template>
<article v-if="article"><h1>{{ article.title }}</h1><p>{{ article.summary }}</p></article>
<p v-else-if="error" role="alert">文章加载失败</p>
<p v-else role="status">正在加载文章</p>
</template>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
生产域名应来自受控运行配置,不能信任任意 Host header 拼 canonical。用户内容进入 head 或 JSON-LD 前同样需要安全边界;优先使用类型化 API 和安全序列化,不手拼 <script>。
客户端复用详情页并切换 slug 时,数据可能暂时为空,所以元信息 getter 也必须处理空值,不能仅靠 setup 首次执行的非空判断。上例等待时用通用标题、移除尚不确定的 canonical;初始服务端请求则仍根据错误返回404或500。
可重放检查
对生产构建的预览地址同时执行:
- 请求正常详情页,保存原始 HTML,检查唯一 title、description、canonical、正文 H1。
- 请求不存在 slug,确认 HTTP 状态是 404,而非“200 + 客户端空状态”。
- 关闭 JS 或用只读 HTTP 客户端检查正文是否仍在响应中。
- 分享图使用绝对 URL,且 URL 对匿名抓取器可访问。
- canonical 去掉无内容差异的追踪参数,但保留真正区分语言/版本的路径规则。
SSR/SSG 提高内容在响应中的可见性,却不保证排名。内容质量、抓取规则、站点速度、链接关系和搜索引擎策略都在框架之外。
练习:从页面模型派生元信息
实现纯函数 toSeo(article, baseUrl)。输入 { slug, title, summary, imagePath };先 trim,title 或 slug 为空时抛 TypeError;输出 title、description(最多 160 个 Unicode code point)、canonical 和绝对 ogImage。断言恶意 baseUrl 不能来自请求头,imagePath 必须解析到受信任 origin。
参考答案:第 8 章第 05 题。