本文面向使用 Next.js App Router 的开发者,梳理站点地图、robots.txt 与 canonical 的职责、最小配置方式和生产环境排查顺序。重点是"让搜索引擎拿到一致的站点信号",而不是承诺收录、排名或流量结果。
摘要
项目上线后,常见现象是:页面能正常打开,但 /sitemap.xml 内容不对;/robots.txt 没有指向正确域名;同一页面又因为 www、非 www、参数或尾斜杠出现多个 URL。这类问题通常不在页面 UI,而在站点的可发现性配置、部署环境和边缘层规则。
在 Next.js App Router 中,可以使用 app/sitemap.ts、app/robots.ts 和 Metadata API 管理这些信号。更重要的是,不能只在本地查看代码:部署后还需要直接请求生产地址,核对 HTTP 状态、最终响应内容、canonical 输出以及 CDN/反向代理是否改写或缓存了错误结果。
一、先区分三个文件(或标签)各自解决什么问题
这三个配置经常一起出现,但职责不同:
| 项目 | 主要作用 | 不解决什么 |
|---|---|---|
sitemap.xml |
列出希望被发现的规范 URL,帮助爬虫了解站点 URL 集合 | 不保证一定收录或获得排名 |
robots.txt |
告诉爬虫哪些路径允许或不建议抓取,并可声明 sitemap 地址 | 不等于为页面建立索引 |
| canonical | 对可能重复的页面,声明首选 URL | 不替代 301 重定向、权限控制或内容质量 |
先明确这个边界,排查时才不会把"未收录"全部归因给某一行配置。
二、用 app/sitemap.ts 生成站点地图
对于 URL 数量不大的内容站或企业站,可以在 app/sitemap.ts 中导出一个返回 URL 数组的函数。Next.js 会将它作为特殊路由处理,并输出 /sitemap.xml。
ts
// app/sitemap.ts
import type { MetadataRoute } from 'next'
const SITE_URL = 'https://example.com'
export default function sitemap(): MetadataRoute.Sitemap {
const staticPages = ['', '/services', '/about', '/insights']
return staticPages.map((path) => ({
url: `${SITE_URL}${path}`,
lastModified: new Date(),
changeFrequency: path === '/insights' ? 'weekly' : 'monthly',
priority: path === '' ? 1 : 0.7,
}))
}
动态内容不要漏掉
如果文章详情页来自 CMS、数据库或本地内容集合,站点地图应把它们一并映射进去。重点不是字段写得多,而是每个 url 都能在生产环境返回正常页面。
ts
const articles = [
{ slug: 'nextjs-seo-checklist', updatedAt: '2026-09-10' },
{ slug: 'canonical-debugging', updatedAt: '2026-09-12' },
]
const articleEntries = articles.map((article) => ({
url: `${SITE_URL}/insights/${article.slug}`,
lastModified: article.updatedAt,
changeFrequency: 'monthly' as const,
priority: 0.6,
}))
常见错误有三种:
- 页面已经上线,但内容数据没有被加入 sitemap;
SITE_URL仍是测试域名、旧域名或错误协议;- sitemap 生成正确,但 CDN、Worker、反向代理把请求转到了另一套应用。
因此,发布后应直接打开生产环境的 /sitemap.xml,确认 XML 中的域名、URL 数量和新内容 URL 都正确。
三、用 app/robots.ts 明确抓取规则和 sitemap 地址
一个基础的 robots.ts 可以这样写:
ts
// app/robots.ts
import type { MetadataRoute } from 'next'
const SITE_URL = 'https://example.com'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin/', '/preview/'],
},
sitemap: `${SITE_URL}/sitemap.xml`,
}
}
生成结果应包含类似以下内容:
text
User-Agent: *
Allow: /
Disallow: /api/
Disallow: /admin/
Disallow: /preview/
Sitemap: https://example.com/sitemap.xml
不要把 robots 当作"禁止收录"开关
若一个 URL 已经被其他页面或外部站点发现,单纯在 robots 中禁止抓取,并不能替代需要的索引控制。对于确实不应出现在搜索结果的页面,应结合具体场景使用认证、noindex、删除页面或重定向等方案;robots 的主要任务是定义爬虫访问路径,而不是清理已存在的索引记录。
规则要以真实路径为准
例如后台实际上是 /dashboard,却只写了 /admin/,规则就没有覆盖目标路径。部署前先列出:预览页、内部接口、管理后台、测试路由、搜索筛选页等,逐项判断是否应允许抓取。
四、为可重复访问的页面输出 canonical
在 App Router 中,canonical 可以通过 metadata 或 generateMetadata 的 alternates.canonical 设置。根布局中配置 metadataBase,能让后续的相对路径被组合为完整 URL。
ts
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
}
ts
// app/insights/[slug]/page.tsx
import type { Metadata } from 'next'
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> },
): Promise<Metadata> {
const { slug } = await params
return {
title: '文章标题',
alternates: {
canonical: `/insights/${slug}`,
},
}
}
渲染到页面 <head> 后,应得到指向自身规范地址的 <link rel="canonical">。这在以下情况尤其重要:
- URL 带营销参数,例如
?utm_source=...; - 同一内容被多个分类路径访问;
- 使用了大小写、尾斜杠或域名形式不一致的入口;
- 预览域名和正式域名可能同时可访问。
但 canonical 必须与实际可访问的正式页面一致。不要把所有页面都指向首页,也不要把不同内容错误地声明成同一个 canonical。
五、代码正确不代表生产结果正确:按这个顺序排查
当 Search Console 或其他工具提示"无法抓取",优先从网络链路排查,而不是先修改一堆 SEO 标签。
1. 检查三个公网地址
bash
curl -I https://example.com/sitemap.xml
curl -I https://example.com/robots.txt
curl -I https://example.com/insights/example-article
期待看到的是稳定的 200 或明确的重定向链路;如果出现 403、404、5xx、循环重定向或跳转到意外域名,先处理部署和路由问题。
2. 检查正文,不只看状态码
bash
curl -s https://example.com/sitemap.xml | head -n 30
curl -s https://example.com/robots.txt
curl -s https://example.com/insights/example-article | grep -i canonical
需要确认:
- sitemap 内是正式域名,而不是旧域名或测试域名;
- robots 中
Sitemap:指向同一个正式域名; - 文章 HTML 中出现预期的 canonical;
- sitemap 里的 URL 逐个访问不会落到 404。
3. 区分源站与边缘层
本地构建成功只说明应用代码可以运行,不能证明公网请求到了这套代码。如果请求经过 CDN、Cloudflare Worker、Nginx rewrite 或多域名代理,还要确认规则是否过宽。
一个典型风险是:为根域名配置的通配规则,意外匹配了子域名;结果是子站 /sitemap.xml 返回了主站的旧内容。遇到"本地对、线上错"的情况,可以临时比对响应头、最终内容和不同域名的返回结果,定位是应用层、缓存层还是重写层在接管请求。
4. 清缓存后复测,再提交工具
如果刚修正了路由或代理规则,应先让公网 /sitemap.xml 和 /robots.txt 返回稳定正确的内容,再到站长平台重新提交 sitemap 或使用 URL 检查。先验证、后提交,能避免让工具反复抓到旧响应。
六、一个可复用的发布检查清单
text
[ ] 正式域名已确定,http/https、www/非 www 的跳转策略明确
[ ] app/sitemap.ts 输出的所有 URL 都是正式规范 URL
[ ] 新增文章、服务页等动态内容已进入 sitemap
[ ] app/robots.ts 的 Sitemap 地址与正式域名一致
[ ] 非公开接口、后台、预览路径按实际路由处理
[ ] 根 layout 配置 metadataBase,页面按需输出 canonical
[ ] 公网访问 /sitemap.xml、/robots.txt、核心页面均正常
[ ] 核对 sitemap 内容、robots 内容和 HTML canonical,而不只看 200
[ ] 若存在 CDN/Worker/Nginx,确认规则不会串到其他域名或旧站
[ ] 完成公网验证后,再在站长平台提交或检查
结语
sitemap、robots 和 canonical 并不是孤立的"SEO 配置项"。它们共同描述了:哪个域名是正式站、哪些 URL 值得被发现、爬虫可以访问哪些路径,以及重复入口应该归并到哪里。
对 Next.js 项目来说,使用 App Router 的文件约定可以降低维护成本;但真正决定排查效率的,是把"代码配置---生产响应---边缘规则---站长平台反馈"连成一条完整验证链路。
参考资料
- Next.js 官方文档:Metadata Files --- sitemap.xml
https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap - Next.js 官方文档:Metadata Files --- robots.txt
https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots - Next.js 官方文档:generateMetadata / metadataBase / alternates
https://nextjs.org/docs/app/api-reference/functions/generate-metadata