使用Next.js和Supabase为91,000个页面构建动态网站地图
上月我们 Deluxe Astrology 达到了 91,000 个页面。名人出生图表、博客文章、跨六种语言的本地化内容 -- 网站已经远超单一网站地图文件能够处理的范围。Google 的网站地图协议将单个文件限制为 50,000 个 URL 和 50MB 未压缩大小。我们需要一个网站地图索引,包含多个分块的子网站地图,从 Supabase 动态生成,在 Vercel 上用 ISR 缓存,并作为单个索引 URL 提交给 Google Search Console。
这正是我们发布的实现方案。不是理论演练 -- 是能够处理今天 91K URL 并在无需修改的情况下扩展到 500K 的实际生产代码。
目录
- 理解网站地图限制和架构
- Deluxe Astrology 的网站地图结构
- 使用偏移分页设置 Supabase 查询
- 构建网站地图索引路由
- 构建单个分块网站地图
- 静态页面网站地图
- 带有 Hreflang 的本地化网站地图
- ISR 重新验证策略
- 按内容类型划分优先级和更新频率
- Google Search Console 提交
- 调试 Google 不索引页面的问题
- 性能和成本基准
- 常见问题

理解网站地图限制和架构
以下是你需要了解的硬限制:
| 约束 | 限制 | 来源 |
|---|---|---|
| 每个网站地图文件的 URL 数 | 50,000 | sitemaps.org 协议 |
| 每个网站地图的文件大小 | 50MB 未压缩 | sitemaps.org 协议 |
| 每个网站地图索引的网站地图数 | 50,000 | sitemaps.org 协议 |
Supabase .range() 每次查询最大值 |
1,000 行(默认) | Supabase PostgREST 配置 |
| Vercel 无服务器函数超时(Pro) | 60 秒 | Vercel docs 2025 |
| Vercel 响应体大小限制 | 10MB | Vercel 边缘缓存 |
对于 91,000 个 URL,你至少需要两个网站地图文件。但我们不只是将所有内容分成两个 50K-URL 的桶。我们按内容类型划分 -- 名人、博客文章、静态页面、本地化页面 -- 因为每种类型都有不同的 changefreq、priority 和更新模式。这使我们获得更好的控制权,当出问题时在 GSC 中调试也会更容易。
Deluxe Astrology 的网站地图结构
以下是最终的网站地图架构:
/sitemap.xml → 网站地图索引(指向所有子网站地图)
/sitemap-pages.xml → 静态页面(~30 个 URL)
/sitemap-blog-0.xml → 博客文章分块 0(最多 50K)
/sitemap-blog-1.xml → 博客文章分块 1(溢出)
/sitemap-celebrities-0.xml → 名人页面分块 0(最多 50K)
/sitemap-celebrities-1.xml → 名人页面分块 1(溢出)
/sitemap-locale-es.xml → 西班牙语本地化页面
/sitemap-locale-fr.xml → 法语本地化页面
/sitemap-locale-de.xml → 德语本地化页面
/sitemap-locale-pt.xml → 葡萄牙语本地化页面
/sitemap-locale-ja.xml → 日语本地化页面
每个子网站地图是一个 Next.js App Router 路由处理程序,在运行时查询 Supabase,生成 XML,并通过 ISR 使用 revalidate = 3600(每小时)进行缓存。网站地图索引本身也是一个路由处理程序。
使用偏移分页设置 Supabase 查询
这是大多数教程弄错的关键部分:你不能只做 supabase.from('celebrities').select('*') 并期望返回 91,000 行。Supabase 的 PostgREST 层默认返回最多 1,000 行。你需要分页。
我们在 1,000 行的批次中使用基于范围的偏移分页:
// lib/supabase-sitemap.ts
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY! // 使用服务角色用于服务器端
);
const BATCH_SIZE = 1000;
export interface SitemapEntry {
slug: string;
updated_at: string;
}
export async function fetchAllSlugs(
table: string,
selectColumns: string = 'slug, updated_at'
): Promise<SitemapEntry[]> {
const allRows: SitemapEntry[] = [];
let offset = 0;
let hasMore = true;
while (hasMore) {
const { data, error } = await supabase
.from(table)
.select(selectColumns)
.order('updated_at', { ascending: false })
.range(offset, offset + BATCH_SIZE - 1);
if (error) {
console.error(`Sitemap fetch error for ${table}:`, error.message);
break;
}
if (data && data.length > 0) {
allRows.push(...data);
offset += BATCH_SIZE;
hasMore = data.length === BATCH_SIZE;
} else {
hasMore = false;
}
}
return allRows;
}
export async function fetchSlugsChunked(
table: string,
chunkIndex: number,
chunkSize: number = 50000
): Promise<{ entries: SitemapEntry[]; totalCount: number }> {
// 首先获取网站地图索引的总计数
const { count } = await supabase
.from(table)
.select('*', { count: 'exact', head: true });
const totalCount = count || 0;
const startOffset = chunkIndex * chunkSize;
const entries: SitemapEntry[] = [];
let offset = startOffset;
const endOffset = Math.min(startOffset + chunkSize, totalCount);
while (offset < endOffset) {
const batchEnd = Math.min(offset + BATCH_SIZE - 1, endOffset - 1);
const { data, error } = await supabase
.from(table)
.select('slug, updated_at')
.order('updated_at', { ascending: false })
.range(offset, batchEnd);
if (error || !data || data.length === 0) break;
entries.push(...data);
offset += data.length;
}
return { entries, totalCount };
}
export function getChunkCount(totalCount: number, chunkSize: number = 50000): number {
return Math.ceil(totalCount / chunkSize);
}
这里有几点需要注意。我们使用 SUPABASE_SERVICE_ROLE_KEY -- 而不是 anon 密钥 -- 因为这些路由处理程序在服务器端运行,我们不希望 RLS 策略减慢我们的网站地图查询。fetchSlugsChunked 函数仅获取给定网站地图文件所需的特定分块,而不是整个数据集。这对在 Vercel 的 60 秒函数超时上运行很重要。

构建网站地图索引路由
网站地图索引是你提交给 Google 的单个 URL。它引用所有子网站地图。
// app/sitemap.xml/route.ts
import { NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';
export const revalidate = 3600; // ISR:每小时重新生成一次
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const CHUNK_SIZE = 50000;
const SITE_URL = 'https://deluxeastrology.com';
const LOCALES = ['es', 'fr', 'de', 'pt', 'ja'];
async function getTableCount(table: string): Promise<number> {
const { count } = await supabase
.from(table)
.select('*', { count: 'exact', head: true });
return count || 0;
}
export async function GET() {
const blogCount = await getTableCount('blog_posts');
const celebrityCount = await getTableCount('celebrities');
const blogChunks = Math.ceil(blogCount / CHUNK_SIZE);
const celebrityChunks = Math.ceil(celebrityCount / CHUNK_SIZE);
const now = new Date().toISOString();
let sitemaps = '';
// 静态页面网站地图
sitemaps += `
<sitemap>
<loc>${SITE_URL}/sitemap-pages.xml</loc>
<lastmod>${now}</lastmod>
</sitemap>`;
// 博客网站地图
for (let i = 0; i < blogChunks; i++) {
sitemaps += `
<sitemap>
<loc>${SITE_URL}/sitemap-blog-${i}.xml</loc>
<lastmod>${now}</lastmod>
</sitemap>`;
}
// 名人网站地图
for (let i = 0; i < celebrityChunks; i++) {
sitemaps += `
<sitemap>
<loc>${SITE_URL}/sitemap-celebrities-${i}.xml</loc>
<lastmod>${now}</lastmod>
</sitemap>`;
}
// 区域设置网站地图
for (const locale of LOCALES) {
sitemaps += `
<sitemap>
<loc>${SITE_URL}/sitemap-locale-${locale}.xml</loc>
<lastmod>${now}</lastmod>
</sitemap>`;
}
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${sitemaps}
</sitemapindex>`;
return new NextResponse(xml, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=600',
},
});
}
注意我们这里只做 count 查询 -- head: true 意味着 Supabase 仅返回计数,无需任何行数据。这使网站地图索引生成几乎瞬间完成。
构建单个分块网站地图
以下是具有完整分页的名人网站地图处理程序:
// app/sitemap-celebrities-[chunk].xml/route.ts
import { NextResponse } from 'next/server';
import { fetchSlugsChunked } from '@/lib/supabase-sitemap';
export const revalidate = 3600;
const SITE_URL = 'https://deluxeastrology.com';
export async function GET(
request: Request,
{ params }: { params: Promise<{ chunk: string }> }
) {
const { chunk } = await params;
const chunkIndex = parseInt(chunk, 10);
if (isNaN(chunkIndex) || chunkIndex < 0) {
return new NextResponse('Invalid chunk index', { status: 400 });
}
const { entries } = await fetchSlugsChunked('celebrities', chunkIndex);
const urls = entries
.map(
(entry) => `
<url>
<loc>${SITE_URL}/celebrities/${entry.slug}</loc>
<lastmod>${new Date(entry.updated_at).toISOString()}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>`
)
.join('');
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls}
</urlset>`;
return new NextResponse(xml, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=600',
},
});
}
博客网站地图遵循相同的模式,但优先级和更新频率不同:
// app/sitemap-blog-[chunk].xml/route.ts
import { NextResponse } from 'next/server';
import { fetchSlugsChunked } from '@/lib/supabase-sitemap';
export const revalidate = 3600;
const SITE_URL = 'https://deluxeastrology.com';
export async function GET(
request: Request,
{ params }: { params: Promise<{ chunk: string }> }
) {
const { chunk } = await params;
const chunkIndex = parseInt(chunk, 10);
const { entries } = await fetchSlugsChunked('blog_posts', chunkIndex);
const urls = entries
.map(
(entry) => `
<url>
<loc>${SITE_URL}/blog/${entry.slug}</loc>
<lastmod>${new Date(entry.updated_at).toISOString()}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>`
)
.join('');
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls}
</urlset>`;
return new NextResponse(xml, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=600',
},
});
}
你需要配置 Next.js 路由来处理动态段。在 App Router 中,文件夹名称使用方括号:
app/
sitemap.xml/
route.ts
sitemap-pages.xml/
route.ts
sitemap-blog-[chunk].xml/
route.ts
sitemap-celebrities-[chunk].xml/
route.ts
sitemap-locale-[lang].xml/
route.ts
如果文件夹名称中的方括号方法给你带来问题(有时确实会),改用 next.config.ts 中的路由重写:
// next.config.ts
const nextConfig = {
async rewrites() {
return [
{
source: '/sitemap-blog-:chunk(\\d+).xml',
destination: '/api/sitemap-blog/:chunk',
},
{
source: '/sitemap-celebrities-:chunk(\\d+).xml',
destination: '/api/sitemap-celebrities/:chunk',
},
{
source: '/sitemap-locale-:lang.xml',
destination: '/api/sitemap-locale/:lang',
},
];
},
};
export default nextConfig;
静态页面网站地图
对于静态页面网站地图,我们硬编码 URL,因为它们很少改变:
// app/sitemap-pages.xml/route.ts
import { NextResponse } from 'next/server';
export const revalidate = 86400; // 每天一次对静态页面来说很好
const SITE_URL = 'https://deluxeastrology.com';
const staticPages = [
{ path: '/', priority: '1.0', changefreq: 'daily' },
{ path: '/about', priority: '0.7', changefreq: 'monthly' },
{ path: '/solutions/birth-chart', priority: '0.9', changefreq: 'weekly' },
{ path: '/solutions/compatibility', priority: '0.9', changefreq: 'weekly' },
{ path: '/solutions/transit-report', priority: '0.9', changefreq: 'weekly' },
{ path: '/blog', priority: '0.8', changefreq: 'daily' },
{ path: '/celebrities', priority: '0.8', changefreq: 'daily' },
{ path: '/contact', priority: '0.5', changefreq: 'yearly' },
{ path: '/pricing', priority: '0.7', changefreq: 'monthly' },
];
export async function GET() {
const now = new Date().toISOString();
const urls = staticPages
.map(
(page) => `
<url>
<loc>${SITE_URL}${page.path}</loc>
<lastmod>${now}</lastmod>
<changefreq>${page.changefreq}</changefreq>
<priority>${page.priority}</priority>
</url>`
)
.join('');
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls}
</urlset>`;
return new NextResponse(xml, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, s-maxage=86400, stale-while-revalidate=3600',
},
});
}
带有 Hreflang 的本地化网站地图
这就变得有趣了。对于多语言内容,你需要带有 hreflang 属性的 xhtml:link 元素。每个本地化网站地图引用每个页面的所有备用语言版本:
// app/sitemap-locale-[lang].xml/route.ts
import { NextResponse } from 'next/server';
import { fetchAllSlugs } from '@/lib/supabase-sitemap';
export const revalidate = 3600;
const SITE_URL = 'https://deluxeastrology.com';
const ALL_LOCALES = ['en', 'es', 'fr', 'de', 'pt', 'ja'];
export async function GET(
request: Request,
{ params }: { params: Promise<{ lang: string }> }
) {
const { lang } = await params;
if (!ALL_LOCALES.includes(lang)) {
return new NextResponse('Invalid locale', { status: 404 });
}
const entries = await fetchAllSlugs('localized_pages');
// 筛选具有此区域设置的页面
const localeEntries = entries.filter((e: any) => e.locale === lang);
const urls = localeEntries
.map((entry: any) => {
const alternates = ALL_LOCALES.map(
(loc) =>
` <xhtml:link rel="alternate" hreflang="${loc}" href="${SITE_URL}/${loc}/${entry.slug}" />`
).join('\n');
return `
<url>
<loc>${SITE_URL}/${lang}/${entry.slug}</loc>
<lastmod>${new Date(entry.updated_at).toISOString()}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
${alternates}
</url>`;
})
.join('');
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">${urls}
</urlset>`;
return new NextResponse(xml, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=600',
},
});
}
ISR 重新验证策略
我们在所有网站地图路由上设置 revalidate = 3600。这意味着 Vercel 在最多一小时内服务缓存的 XML,然后在下一个请求时在后台重新生成它。对于 91K 页面,这是甜蜜点 -- 频繁到足以让新内容当天显示,但不要激进到我们不断敲 Supabase。
对于内容发布时的按需重新验证,添加一个重新验证端点:
// app/api/revalidate-sitemap/route.ts
import { revalidatePath } from 'next/cache';
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const { secret, paths } = await request.json();
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// 重新验证特定的网站地图路径
const targetPaths = paths || ['/sitemap.xml'];
for (const path of targetPaths) {
revalidatePath(path);
}
return NextResponse.json({ revalidated: true, paths: targetPaths });
}
然后设置 Supabase 数据库 Webhook(或通过 pg_net 的 Postgres 触发器)在 celebrities 或 blog_posts 表更新时调用此端点。
按内容类型划分优先级和更新频率
以下是我们使用的优先级矩阵。Google 表示他们大多忽略 priority 和 changefreq,但其他爬虫(Bing、Yandex)仍然使用它们,而且没有害处:
| 内容类型 | 优先级 | 更新频率 | 理由 |
|---|---|---|---|
| 首页 | 1.0 | daily | 最高重要性,经常更新 |
| 解决方案/功能 | 0.9 | weekly | 核心产品页面 |
| 博客列表 | 0.8 | daily | 定期发布新文章 |
| 博客文章 | 0.8 | weekly | 内容偶尔更新 |
| 名人页面 | 0.6 | monthly | 创建后很少改变 |
| 本地化页面 | 0.6 | monthly | 翻译更新不频繁 |
| 联系/法律 | 0.5 | yearly | 几乎从不改变 |
lastmod 值至关重要,应该始终来自你的数据库的 updated_at 列 -- 永远不要将其硬编码到 new Date()。Google 使用 lastmod 来优先重新抓取,如果每个页面都说它被修改了,Google 最终会完全忽略你的 lastmod。
Google Search Console 提交
这是直截了当的部分。在 GSC 中:
- 转到左侧边栏中的网站地图
- 输入
https://yourdomain.com/sitemap.xml(仅索引 URL) - 点击提交
就这样。不要提交单个子网站地图。Google 读取索引并自动发现所有子级。你应该在几个小时内看到状态"成功",索引的 URL 计数将在接下来的 2-4 周内增加。
对于 91K URL,预期 Google 在第一个月内索引 70-90%。剩余页面通常具有较薄的内容、重复内容问题,或简单地在 Google 的爬虫预算分配中优先级较低。
还要将你的网站地图添加到 robots.txt:
# robots.txt
User-agent: *
Allow: /
Sitemap: https://deluxeastrology.com/sitemap.xml
调试 Google 不索引页面的问题
这是大多数人卡住的地方。你已提交 91K URL,但 GSC 仅显示 40K 索引。以下是我们遵循的系统调试清单:
检查意外的 Noindex 标签
这是 #1 原因。运行快速检查:
curl -s https://deluxeastrology.com/celebrities/some-slug | grep -i 'noindex'
还要检查你的 Next.js 布局或页面元数据。常见的错误是在应用于数千个页面的布局中设置 noindex:
// 不好:这 noindex 所有使用此布局的页面
export const metadata = {
robots: { index: false, follow: true },
};
验证 robots.txt 不阻止爬虫
在浏览器中检查 https://yourdomain.com/robots.txt。确保你没有意外阻止动态路由。在 Vercel 上,还要检查任何可能对 Googlebot 返回 403 的中间件。
检查 GSC 中的爬虫错误
转到页面 → 页面不被索引的原因。常见问题:
- "已爬虫 - 目前未索引":Google 看到了该页面但决定不索引它。通常内容较薄。
- "发现 - 目前未索引":Google 知道 URL 存在但还没有爬取它。爬虫预算问题。
- "被 noindex 标签排除":不言自明。修复标签。
- "无规范的重复":添加正确的规范标签。
使用内部链接修复孤立页面
这对大型网站非常重要。如果你的名人页面只能通过网站地图发现,没有零个内部链接指向它们,Google 会降低爬取它们的优先级。添加:
- 链接到名人页面组的类别/列表页面
- 每个名人页面上的相关名人链接
- 高流量页面上的"趋势"或"最近更新"部分
- 具有结构化数据的面包屑导航
验证单个 URL
在 GSC 中的特定未索引页面上使用 URL 检查工具。它向你显示 Google 看到的确切内容 -- 渲染的 HTML、任何错误、移动易用性问题和索引状态。
检查网站地图响应标头
确保你的网站地图路由返回适当的标头:
curl -I https://deluxeastrology.com/sitemap.xml
你应该看到 Content-Type: application/xml 和 200 状态。如果你从陈旧缓存获得 304 Not Modified 响应,这可能导致 Google 跳过重新读取你的网站地图。
性能和成本基准
以下是我们截至 2025 年初的生产部署中的实际数字:
| 指标 | 值 |
|---|---|
| 网站地图中的总 URL | 91,247 |
| 网站地图索引生成时间 | ~120ms(仅计数查询) |
| 单个网站地图生成(50K URL) | ~4.2 秒 |
| 每次网站地图重新生成的 Supabase 查询成本 | ~$0.01 |
| 所有文件合并的总网站地图 XML 大小 | ~8.4MB 未压缩 |
| 每月网站地图的 Vercel 带宽 | ~2.1GB(大多数是 Googlebot) |
| Vercel Pro 计划成本 | 每用户每月 $20 |
| Supabase Pro 计划成本 | 每月 $25 |
| 30 天后的 GSC 索引率 | 提交 URL 的 84% |
| 从内容发布到网站地图更新的时间 | ≤1 小时(ISR)或 ~5 秒(按需) |
大收获:这整个设置基本上免费运行。网站地图生成是 Vercel 和 Supabase 账单上的舍入误差。
如果你正在构建类似的大规模项目并需要架构方面的帮助,我们已经在多个客户网站上完成了此操作。查看我们的 Next.js 开发功能 或我们的 无头 CMS 开发工作。对于具有类似规模要求的基于 Astro 的网站,我们已使用 Astro 的端点方法 构建了可比较的解决方案。
完整的工作代码可作为 GitHub gist 获得:所有路由处理程序、Supabase 查询库和 next.config.ts 重写。如果你的项目需要更自定义的东西 -- 多租户网站地图、实时重新验证或 1M+ 页面的网站地图 -- 联系我们,我们会给你一个范围。
常见问题
单个网站地图文件最多可以包含多少个 URL? 网站地图协议允许每个文件最多 50,000 个 URL 和 50MB 未压缩文件大小。对于超过 50K 页面的网站,你需要一个引用多个分块网站地图文件的网站地图索引。实际上,大多数网站地图生成器在 45,000-50,000 URL 处分块,以保留安全边际。
我应该使用 next-sitemap 还是构建自定义路由处理程序? next-sitemap (v4+) 对于更简单的设置非常好并处理自动分块。但对于 91K+ 具有内容类型特定优先级的动态页面、带有 hreflang 的本地化网站地图和细粒度 ISR 控制,自定义路由处理程序给你更多控制。我们选择自定义是因为我们需要每种内容类型的不同重新验证间隔,并希望网站地图结构与我们的 GSC 调试工作流相匹配。
我应该向 Google Search Console 提交每个单个网站地图文件吗?
不。仅提交网站地图索引 URL(例如 https://yourdomain.com/sitemap.xml)。Google 读取索引并自动发现并处理所有引用的子网站地图。提交单个文件是不必要的,会使你的 GSC 仪表板混乱。
对于大型动态网站,网站地图应该多久重新生成一次?
对于大多数内容丰富的网站,通过 ISR (revalidate = 3600) 进行每小时重新生成是一个很好的默认设置。如果你非常频繁地发布内容,将其与由数据库 webhook 触发的按需重新验证配对。不要每次请求都重新生成 -- 这会破坏缓存并不必要地增加 Supabase 负载。
为什么 Google 不索引我所有的网站地图 URL? 最常见的原因是:意外的 noindex 元标签、robots.txt 阻止、较薄/重复的内容、没有内部链接的孤立页面和爬虫预算限制。在 GSC 的"页面"报告中的"页面不被索引的原因"下检查具体原因。对于大型网站,改进孤立页面的内部链接通常是单一最大的杠杆。
网站地图中的 priority 值是否实际影响 Google 排名?
Google 公开声称他们在很大程度上忽略 priority 和 changefreq 值。但是,Bing 和其他搜索引擎确实使用它们。lastmod 字段是最重要的网站地图信号 -- 确保它反映来自你的数据库的实际内容更改,而不是当前时间戳。
我如何处理 Supabase 对网站地图查询的 1,000 行限制?
使用 Supabase 的 .range(offset, offset + batchSize - 1) 方法在 1,000 行的批次中分页。循环直到你为当前网站地图分块获取了所有行。对于仅计数查询(在网站地图索引中使用),使用 .select('*', { count: 'exact', head: true }),它仅返回计数而不传输任何行数据。
这种方法可以处理 500K 或 100 万个页面吗? 可以,进行细微调整。分块架构线性扩展 -- 100 万个页面会产生约 20 个子网站地图。主要关注变成生成单个 50K-URL 网站地图的 Vercel 的 60 秒函数超时。如果你达到了该限制,将分块大小减少到 25,000 或 10,000 URL 每文件。网站地图协议允许单个索引中最多 50,000 个网站地图,所以你不会很快遇到索引级限制。