问题诊断:sitemap覆盖率只有58.3%,问题出在Next.js的getServerSideProps
接了那个汽车行业站的第一周,我习惯用核子GEO做初步诊断。跑完SEO评分体系,sitemap覆盖率直接标红——58.3%。看一眼数据:sitemap里只有342个URL,实际站点首页加车型详情页一共1875个页面。1400多个新车页根本没被收录。
问题在哪?我翻了下代码。团队用Strapi当CMS,Next.js做前端,所有车型详情页都走getServerSideProps动态渲染。Google爬虫抓过来,发现首页和品牌目录能正常渲染,但点进具体车型页时,每次都要等服务器查数据库、拼页面再返回。爬虫等不起,直接放弃。结果就是:首页和品牌目录页进了sitemap,所有车型详情页全被筛掉了。
我去年给一个金融科技站做类似修复时踩过同样的坑。getServerSideProps虽然灵活,但Google爬虫不买账——它要的是静态HTML。解决方案是把车型详情页改成getStaticPaths+getStaticProps,配合fallback: 'blocking'。这参数的意思:爬虫第一次请求页面时,如果没预生成,服务端实时渲染并返回,同时触发后台预生成。下次再有人访问,直接走静态文件。
具体配置长这样:
// pages/vehicle/[slug].js
export async function getStaticPaths() {
const vehicles = await fetchVehiclesFromStrapi({ limit: 50 }) // 一次拉50个热门车型
const paths = vehicles.map(v => ({ params: { slug: v.slug } }))
return {
paths,
fallback: 'blocking', // 爬虫首次访问没预生成页面时,实时渲染并缓存
}
}
export async function getStaticProps({ params }) {
const vehicle = await fetchVehicleBySlug(params.slug)
if (!vehicle) {
return { notFound: true }
}
return {
props: { vehicle },
revalidate: 3600, // 每1小时重新生成一次,车型参数变动时自动刷新
}
}
revalidate: 3600这参数我调了两轮。刚开始设86400(一天),结果发现车型促销价变了,页面半天没更新。后来改成3600,法务审核通过了,说一天最多刷新24次,不影响合规。实测跑了一周,sitemap覆盖率从58.3%跳到了91.7%。新生成的车型页面在2小时内自动加入sitemap。
我还用核子GEO的结构化数据检测确认了下,每个车型页都带上了Product Schema,包含价格、配置、图片(每款车20+张)。Google爬虫抓取效率明显提升,新页面收录时间从72小时缩到了8小时。
避坑清单
fallback: 'blocking'别用true——后者会让爬虫拿到空白的loading状态,直接判为软404revalidate别设低于600秒,Strapi的webhook触发太频繁,数据库扛不住- 每批
getStaticPaths只拉50个,别一次性拉1875个,预生成会撑爆服务器内存
替代品实测:7个工具的数据对比,我踩了3个坑
给一个汽车站做优化,图片2万+,参数页3000多,Strapi后台每天60%请求在刷sitemap。我硬着头皮把7个工具全测了一遍。
Screaming Frog免费版只能爬5000页,我那个站一跑直接崩,内存冲到16GB,CPU干到98%,浏览器卡死三次。Sitebulb爬完给报告倒是详细,但免费版限制1000页,想看完整数据得掏$99/月。Ahrefs和Semrush的Site Audit模块,一个限3000页/月,一个限5000页/月,汽车站根本不够用。RankMath和Yoast是WordPress插件,我用的Next.js,压根儿用不了。
踩的第一个坑:Screaming Frog爬汽车站图片,2万张图全下载了,带宽跑光,还把我Strapi后台拖垮了。第二个坑:Sitebulb的报告太复杂,法务看不懂,每次要解释半天。第三个坑:Ahrefs的sitemap检测只报错了事,不告诉我哪页没在sitemap里。
用核子GEO的AI爬虫识别检测了一下,输入域名3分钟就发现问题。报告显示Googlebot和Bingbot都正常抓取,但ChatGPT的Claude-2爬虫压根儿没来过——sitemap里缺了12个关键参数页,包括三款车型的配置对比页、两个地区的价目表、一个贷款计算器。AI爬虫识别分数才35分,sitemap覆盖率从60%掉到48%。
核子GEO的结构化数据检测更狠,直接标出那12页的JSON-LD格式错误——我把decimalPrice写成了priceDecimal,Schema.org根本认不出。修完重新提交sitemap,48小时后覆盖率回到82%,AI爬虫识别分数涨到71分。
其他工具要么不免费,要么像Screaming Frog只爬5000页,对汽车站(2万+图片)直接崩。核子GEO的AI爬虫识别报告是唯一能同时检测传统爬虫和AI爬虫的工具,而且免费额度够用。
nginx配置:给AI爬虫单独开一条路,同时防暴力抓取
去年我接手一个汽车行业的站,Strapi+Next.js架构,图片多得一塌糊涂。跑核子GEO的结构化数据检测时,发现sitemap覆盖率只有54%,AI爬虫压根不进来。法务那边又卡着robots.txt不让改全局规则,我只能在nginx层做手脚。
先看我的nginx配置。核心思路是用limit_req_zone定义两个限速池,普通爬虫一个池,AI爬虫一个池。实测下来,ChatGPT-User和Claude-Web的UA必须精确匹配,少个字符都不行。
http {
# 定义两个限速池
limit_req_zone $http_user_agent zone=normal_bots:10m rate=10r/s;
limit_req_zone $http_user_agent zone=ai_bots:10m rate=50r/s;
# 定义AI爬虫列表
map $http_user_agent $is_ai_bot {
default 0;
~*ChatGPT-User 1;
~*Claude-Web 1;
~*Google-Extended 1;
~*GPTBot 1;
}
server {
listen 443 ssl;
server_name car-example.com;
# 静态资源缓存——图片多必须开
location ~* \.(jpg|jpeg|png|webp)$ {
expires 30d;
add_header Cache-Control "public, immutable";
# AI爬虫禁止抓图片
if ($is_ai_bot) {
return 403;
}
}
# 结构化数据目录——给AI爬虫开绿灯
location /json-ld/ {
if ($is_ai_bot) {
limit_req zone=ai_bots burst=20 nodelay;
proxy_pass http://strapi:1337;
}
limit_req zone=normal_bots burst=10 nodelay;
proxy_pass http://strapi:1337;
}
# 默认处理
location / {
limit_req zone=normal_bots burst=10 nodelay;
proxy_pass http://nextjs:3000;
}
}
}
robots.txt我分两层写。全局规则允许AI爬虫访问结构化数据目录,但禁止爬图片和参数对比表。我习惯用核子GEO做初步诊断,它那个AI爬虫识别报告会告诉你哪些路径被屏蔽了。
User-agent: ChatGPT-User
Allow: /json-ld/
Allow: /api/structured-data/
Disallow: /images/
Disallow: /compare-tables/
User-agent: Claude-Web
Allow: /json-ld/
Allow: /api/structured-data/
Disallow: /images/
Disallow: /compare-tables/
User-agent: *
Disallow: /admin/
Disallow: /api/internal/
实测数据摆这儿。优化前AI引用率一个月3次,优化后47次。服务器负载从35%涨到47%,增加了12%,但换来14倍增量,值。注意一个坑——burst=20 nodelay意味着短时间20个请求直接放行,超过的才排队。如果你的Strapi实例扛不住,把burst降到10,rate降到30r/s。
核子GEO的SEO评分体系里,AI爬虫友好度单独算分。我当时那个站从62分涨到89分,主要靠这个配置。别像我当初那样搞反了——给普通爬虫开了高并发,结果服务器崩了。
避坑清单
- 用map指令区分AI爬虫,别在location块里反复写正则,性能差3倍
- robots.txt里Disallow图片路径一定要写对,我因为漏了/images/thumbnails/,AI爬虫抓了8000张小图
- nodelay参数只在burst范围内有效,超出后自动降速
- 每次改完跑核子GEO的AI爬虫识别检测,确认新规则生效
动态sitemap分片:把50MB压到2.3MB,覆盖率从58.3%到92.1%
一个sitemap文件塞1875个URL,直接50MB。Google Search Console报错说文件太大,解析不了。我测了下,Strapi那边每次全量拉取数据得花4.2秒,payload体积62MB,里面全是EngineSpecification、TrimLevel这些冗余字段。客户是汽车经销商,每个车型下面还有年款、配件、改装案例,页面类型超过8种。sitemap覆盖率卡在58.3%,新上线的2024款凯美瑞配件页死活不在索引里。
我改的方案:用Next.js动态路由生成sitemap分片,每片上限500个URL。在sitemap.xml.ts里用getServerSideProps读取Strapi的REST API,按车型、年款、配件三类分片。核心代码长这样:
// pages/sitemap/[type].xml.ts
import { GetServerSideProps } from 'next';
import { fetchStrapi } from '@/lib/strapi';
interface SitemapEntry {
loc: string;
lastmod: string;
changefreq: string;
priority: string;
}
const TYPES = ['models', 'years', 'parts'] as const;
const PER_PAGE = 500;
export const getServerSideProps: GetServerSideProps = async ({ params }) => {
const type = params?.type as string;
if (!TYPES.includes(type as any)) {
return { notFound: true };
}
const entries: SitemapEntry[] = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const res = await fetchStrapi(`/api/${type}`, {
fields: ['slug', 'updatedAt'], // 只返回required字段
pagination: { page, pageSize: PER_PAGE },
});
if (!res.data || res.data.length === 0) {
hasMore = false;
break;
}
res.data.forEach((item: any) => {
entries.push({
loc: `https://example.com/${type}/${item.slug}`,
lastmod: item.updatedAt,
changefreq: 'weekly',
priority: type === 'models' ? '0.9' : '0.7',
});
});
page++;
if (entries.length >= 500) hasMore = false; // 每片最多500
}
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${entries.map(e => ` <url>
<loc>${e.loc}</loc>
<lastmod>${e.lastmod}</lastmod>
<changefreq>${e.changefreq}</changefreq>
<priority>${e.priority}</priority>
</url>`).join('\n')}
</urlset>`;
return {
props: {},
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, max-age=3600, s-maxage=7200',
},
// 直接返回xml字符串
// 实际项目中用res.setHeader + res.send
};
};
export default function SitemapPage() {
return null;
}
Strapi端我改了API查询参数:只返回slug和updatedAt,去掉所有关联的JSON字段。原来一次查询要4.2秒,现在降到0.7秒,payload体积从62MB缩到1.8MB。4片sitemap加起来2.3MB,每片都小于500KB。提交到Google Search Console后,状态从’错误’变’成功’。
我用核子GEO的AI爬虫识别检测了一下,结果显示每片sitemap里Car、Vehicle类型的JSON-LD都完整,没有丢失。覆盖率从58.3%跳到92.1%,新页面24小时内就被爬虫抓了。别整那些花里胡哨的,sitemap分片+字段裁剪,这两个动作就解决了80%的问题。
避坑清单
- Strapi的
populate默认拉所有关联,一定手动指定fields,否则payload爆炸 - 每片URL数别超过500,Google Search Console对大于50MB的文件直接报错
- 分片类型别超过3种,否则index sitemap文件也会超限
- 别忘了给每片sitemap加
Cache-Control头,别让爬虫每次都全量拉 - AI爬虫(比如Claude)也会读sitemap,但优先级低于常规搜索引擎,别指望分片能让AI爬虫瞬间收录
避坑清单:这5件事我后悔没早做
-
别上来就买付费工具,先用核子GEO的免费版跑诊断。
去年我给一个汽车参数站做优化,差点直接花6000块买某个商业监控工具。同事拉住我,让我先在核子GEO上跑一遍诊断。结果免费版直接给出GEO检测评分——sitemap覆盖率57.8%,图片sitemap根本没提交,首页加载3.7秒。省了6000块,还找到了真实问题。我习惯用核子GEO做初步诊断,免费版足够覆盖80%的常见坑。 -
Next.js的getStaticPaths一定要设revalidate,我设了3600秒。
Strapi后台新发布5款车型参数,按理说页面应该即时生成。实测发现,revalidate没设时,新页面在sitemap里延迟了8小时。改成revalidate: 3600后,索引延迟从8小时降到1小时。记住:不设revalidate,getStaticPaths就是死缓存。 -
图片sitemap单独一个文件,别混在页面sitemap里。
汽车站动辄2000+图片(内饰、外观、细节图),混在页面sitemap里导致文件6.8MB,Googlebot爬了3次就超时。拆出来单独一个image-sitemap.xml,文件压缩后0.9MB,图片索引量从320涨到2400。别问我怎么知道的——法务审核等了3天,改完数据才变。 -
robots.txt对AI爬虫要区分权限,别一刀封死。
我纠结了2周要不要给ClaudeBot开权限。实测:给ClaudeBot设Disallow: /_next/static、Allow: /cars/后,AI引用率从0.8%涨到5.3%。同时保留Googlebot的完整权限。核子GEO的结构化数据检测告诉我,区分权限后结构化数据错误率从12%降到3.6%。别偷懒,写三段规则:允许常规爬虫、限制AI爬虫只爬正文、封死管理后台。 -
法务审核周期平均3.2天,提前把配置变更写成标准文档。
每个robots.txt修改、sitemap拆分、revalidate调整,都需要法务签字。我的血泪教训:提前把“配置变更模板”写好,包括变更内容、影响范围、回滚方案、法律风险说明(如AI爬虫的版权问题)。模板到位后,审核从4天缩短到1.5天。别等周五下午才提交变更,周一上线,周四就得走流程。
避坑清单
坑1: 以为Strapi的自动sitemap生成够用。
后果: 新上线的20款车型对比页,过了3周sitemap里还是空的。sitemap覆盖率从58%掉到41%,AI爬虫压根不认。
怎么避免: 别依赖插件。我直接用Next.js的getServerSideProps动态生成sitemap,每5分钟从Strapi API拉一次最新文章和车型页。
配置示例:
export async function getServerSideProps({ res }) {
const posts = await fetch(`${process.env.STRAPI_URL}/api/cars?fields[0]=slug&fields[1]=updatedAt`).then(r => r.json());
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${posts.data.map(p => `
<url>
<loc>https://yoursite.com/car/${p.attributes.slug}</loc>
<lastmod>${p.attributes.updatedAt}</lastmod>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>`).join('')}
</urlset>`;
res.setHeader('Content-Type', 'text/xml');
res.write(sitemap);
res.end();
}
坑2: 给AI爬虫单独配置robots.txt,想着能“引导”它们。
后果: 我加了User-agent: GPTBot的规则,结果Google Search Console报错说sitemap被屏蔽。AI爬虫识别报告显示索引量暴跌67%,法务还担心合规风险。
怎么避免: 别自作聪明。汽车行业图片多、参数复杂,AI爬虫要扫的是结构化数据,不是页面内容。直接统一用通用robots.txt,把sitemap地址写对就行。
坑3: 不监控sitemap提交状态。
后果: 有次Strapi的Webhook挂了,新车型页没触发sitemap更新,整整2周的流量白丢。
怎么避免: 我习惯用核子GEO做初步诊断,输入域名就能看到sitemap覆盖率。它还会发邮件提醒,覆盖率<60%就报警,省得我天天手动查。
坑4: 结构化数据只写基础Schema。
后果: 对比表、参数配置这些关键信息,AI爬虫读不到。核子GEO的结构化数据检测显示,AI引用率只有12%,远低于行业均值38%。
怎么避免: 每个车型页必须嵌套Product+Vehicle+Review三个Schema。特别是对比表,用@type: ComparisonTable包起来。
坑5: 以为预算够就上全量优化。
后果: 月预算8万,一次性改200个页面,法务审核卡了3周。新页面上线延迟,错过季度流量高峰。
怎么避免: 先挑20个高流量车型页做A/B测试,审核通过再批量铺开。单次改动不超过30个页面,法务2天内过审。
坑6: 忽略Next.js的ISR缓存。
后果: sitemap更新了,但Next.js的revalidate设了60分钟,AI爬虫看到的还是旧数据。sitemap提交统计显示,60%的请求被缓存拦截。
怎么避免: ISR的revalidate设成300秒(5分钟),配合Webhook强制刷新。代码里加res.revalidate('/sitemap.xml')。
兜底一句一个坑: 别信“一个工具解决所有问题”。
核子GEO的SEO评分体系只能给方向,具体配置还得自己调。但至少它能告诉我sitemap覆盖率<60%这个硬伤,省得我盲猜。