很多人做 SEO,做着做着就变成了“改几个 meta 标签”。
这事儿短期有安慰剂,长期没复利。因为真正的问题不是某个页面少了一个字段,而是站点没有稳定、可复用、可验证的 SEO 工程层。
这次我把博客的 SEO 从“零散页面优化”升级成“系统化改造”:统一 metadata 生成、按页面类型输出结构化数据、修复 robots/sitemap 与 URL 编码一致性、补上分页 canonical 策略。结论先说:SEO 的上限,不在技巧,在工程纪律。
下面是这次升级的完整复盘。
先看旧状态的典型问题:
public/robots.txt,规则修改要手工维护;这类问题单独看不大,叠起来就是爬虫和用户都不满意:
所以我这次没做“补丁式修复”,而是先做一层公共能力。
createMetadata 统一出口我新增了 lib/seo.ts,把 SEO 的基础规则抽象成一个统一工厂函数:createMetadata()。
核心收益是三件事:
title/description/path/type/images/keywords 一套参数模型;// lib/seo.ts
export function createMetadata(options: MetadataOptions): Metadata {
const path = normalizePath(options.path || '/')
return {
title: options.title,
description: options.description || seoConfig.siteDescription,
alternates: { canonical: path },
openGraph: {
type: options.type || 'website',
url: toAbsoluteUrl(path),
images: normalizeImages(options.images),
},
twitter: {
card: 'summary_large_image',
images: normalizeImages(options.images),
},
}
}现在所有核心页面都接入这层,包括:首页、文章列表、文章详情、标签页、相册页、分享页、关于页。
这一步非常“工程化”,也非常“反人性”:它不炫技,但能从根上减少维护成本。
统一不等于一刀切。不同页面要给搜索引擎不同信号。
/posts 增加了 generateMetadata,当 ?page=n 时输出分页对应的 title/description/canonical,避免分页页面全都指向同一套信息。
// app/posts/page.tsx
export async function generateMetadata({ searchParams }): Promise<Metadata> {
const page = Number((await searchParams).page)
if (!page || page <= 1) return baseMetadata
return createMetadata({
title: `Blog - Page ${page}`,
path: `/posts?page=${page}`,
})
}文章详情页现在明确输出 type: 'article',并挂上文章 tags 与动态 OG 图地址。
return createMetadata({
title: post.title,
description: post.description,
path: `/posts/${slug}`,
type: 'article',
keywords: post.tags,
images: [`/posts/${slug}/opengraph-image`],
})encodeURIComponent标签链接和 sitemap 中的 tag URL 统一编码策略,避免中文或空格 tag 产生路径歧义。
href={`/tags/${encodeURIComponent(tag)}`}这看起来很“小”,但它是 URL 一致性的基本盘。
我保留并强化了 JSON-LD 的分层设计,让不同页面表达不同实体:
Person + WebSite + Blog(明确站点归属与内容类型)ProfilePage(强化作者实体)BlogPosting(含 mainEntityOfPage、image、isPartOf)ImageGallery(相册语义不再伪装成普通文章)示例(文章页):
const jsonLd = {
'@type': 'BlogPosting',
'@id': `${canonicalUrl}#article`,
mainEntityOfPage: { '@id': canonicalUrl },
image: toAbsoluteUrl(`/posts/${slug}/opengraph-image`),
isPartOf: { '@id': `${config.site.url}/#blog` },
}这一步的核心不是“多写几个字段”,而是让搜索引擎更稳定地理解: 谁在写、写了什么、这篇内容属于哪个内容体系。
robots 与 sitemap 回归代码管理robots:从静态文件切到 app/robots.ts我删除了 public/robots.txt,改用 Next.js 路由化的 app/robots.ts 统一输出:
allow: '/'sitemap: https://chiway.blog/sitemap.xmlhost: https://chiway.blog这意味着 robots 策略进入代码版本管理,和路由、页面一起演进,不再靠“手工记忆”。
sitemap:内容驱动而不是拍脑袋时间戳app/sitemap.ts 里新增了更合理的更新时间策略:
encodeURIComponent;share 页 lastModified 取自 shares.json 的最新日期,而不是永远 new Date()。结果是 sitemap 更“诚实”,也更符合爬虫对更新信号的预期。
这次还有一个看似和 SEO 无关、实际强相关的点:
<img> 改为 next/image,减少 LCP 风险;time 增加 dateTime,提升机器可读性。SEO 从来不是“只服务机器人”,而是让页面在可读、可抓、可分享三件事上同时及格。
lib/seo.ts 抽象(有学习成本);robots 和 sitemap 放进代码路由,而不是纯静态文件。WebPage。一句话收尾:
真正有效的 SEO,不是“多写几个 meta 标签”,而是让站点的信息架构、渲染策略和工程流程长期保持一致。