上月我们 Deluxe Astrology 达到了 91,000 个页面。名人出生图表、博客文章、跨六种语言的本地化内容 -- 网站已经远超单一网站地图文件能够处理的范围。Google 的网站地图协议将单个文件限制为 50,000 个 URL 和 50MB 未压缩大小。我们需要一个网站地图索引,包含多个分块的子网站地图,从 Supabase 动态生成,在 Vercel 上用 ISR 缓存,并作为单个索引 URL 提交给 Google Search Console。

这正是我们发布的实现方案。不是理论演练 -- 是能够处理今天 91K URL 并在无需修改的情况下扩展到 500K 的实际生产代码。

目录

使用 Next.js 和 Supabase 为 91,000 个页面构建动态网站地图

理解网站地图限制和架构

以下是你需要了解的硬限制:

约束 限制 来源
每个网站地图文件的 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 的桶。我们按内容类型划分 -- 名人、博客文章、静态页面、本地化页面 -- 因为每种类型都有不同的 changefreqpriority 和更新模式。这使我们获得更好的控制权,当出问题时在 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 秒函数超时上运行很重要。

使用 Next.js 和 Supabase 为 91,000 个页面构建动态网站地图 - 架构

构建网站地图索引路由

网站地图索引是你提交给 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 触发器)在 celebritiesblog_posts 表更新时调用此端点。

按内容类型划分优先级和更新频率

以下是我们使用的优先级矩阵。Google 表示他们大多忽略 prioritychangefreq,但其他爬虫(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 中:

  1. 转到左侧边栏中的网站地图
  2. 输入 https://yourdomain.com/sitemap.xml(仅索引 URL)
  3. 点击提交

就这样。不要提交单个子网站地图。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 公开声称他们在很大程度上忽略 prioritychangefreq 值。但是,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 个网站地图,所以你不会很快遇到索引级限制。