You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Next.js 14 App Router生成并提供大型动态站点地图?

Next.js 14 App Router 大型媒体站点地图优化方案

核心优化思路:分块分页 + 流式生成 + 批量处理

1. 实现分页式站点地图索引(Sitemap Index)

将超大媒体条目拆分为多个子站点地图(建议每1万条一个文件),通过主索引文件统一指向这些子文件,避免一次性加载全量数据。

主索引文件 app/sitemap.js

import { getImageCount, getVideoCount } from "@/services/mediaService";

const ITEMS_PER_SITEMAP = 10000; // 每个子站点地图条目上限

export default async function sitemap() {
  const imageCount = await getImageCount();
  const videoCount = await getVideoCount();
  
  // 生成图片类子站点地图列表
  const imageSitemaps = Array.from({ length: Math.ceil(imageCount / ITEMS_PER_SITEMAP) }, (_, i) => ({
    url: `${process.env.NEXT_PUBLIC_DEV_URL}/media/sitemap-images-${i + 1}.xml`,
    lastModified: new Date(),
  }));
  
  // 生成视频类子站点地图列表
  const videoSitemaps = Array.from({ length: Math.ceil(videoCount / ITEMS_PER_SITEMAP) }, (_, i) => ({
    url: `${process.env.NEXT_PUBLIC_DEV_URL}/media/sitemap-videos-${i + 1}.xml`,
    lastModified: new Date(),
  }));
  
  return [
    {
      url: `${process.env.NEXT_PUBLIC_DEV_URL}`,
      lastModified: new Date(),
      changeFrequency: "monthly",
      priority: 1,
    },
    // 其他静态路由...
    ...imageSitemaps,
    ...videoSitemaps,
  ];
}

动态子站点地图路由 app/media/sitemap-images-[page].js

import { fetchImageIdsByPage } from "@/services/imageService";

const ITEMS_PER_SITEMAP = 10000;

export default async function sitemap({ params }) {
  const page = parseInt(params.page);
  const imageIds = await fetchImageIdsByPage(page, ITEMS_PER_SITEMAP);
  
  return imageIds.map(id => ({
    url: `${process.env.NEXT_PUBLIC_DEV_URL}/media/image/${id}`,
    lastModified: new Date(),
    changeFrequency: 'monthly',
    priority: 0.8,
  }));
}

数据库分页查询实现(services/imageService.js)

export async function fetchImageIdsByPage(page, limit) {
  const offset = (page - 1) * limit;
  // 仅查询ID字段,避免加载冗余数据
  const images = await db.image.findMany({
    select: { id: true },
    skip: offset,
    take: limit,
  });
  return images.map(img => img.id);
}

export async function getImageCount() {
  return await db.image.count();
}

2. 流式响应减少内存占用

直接生成XML字符串并流式输出,避免将所有URL对象存储在内存中,彻底解决OOM问题。

修改子站点路由为流式返回:

import { fetchImageIdsByPage, getImageCount } from "@/services/imageService";
import { Readable } from 'stream';

const ITEMS_PER_SITEMAP = 10000;

// 预先生成所有分页参数
export async function generateStaticParams() {
  const count = await getImageCount();
  const pages = Math.ceil(count / ITEMS_PER_SITEMAP);
  return Array.from({ length: pages }, (_, i) => ({ page: (i + 1).toString() }));
}

export async function GET({ params }) {
  const page = parseInt(params.page);
  const imageIds = await fetchImageIdsByPage(page, ITEMS_PER_SITEMAP);
  
  // 分段生成XML内容
  const xmlStart = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`;
  
  const xmlEntries = imageIds.map(id => `
  <url>
    <loc>${process.env.NEXT_PUBLIC_DEV_URL}/media/image/${id}</loc>
    <lastmod>${new Date().toISOString()}</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.8</priority>
  </url>`).join('');
  
  const xmlEnd = `
</urlset>`;
  
  // 流式输出
  const stream = Readable.from([xmlStart, xmlEntries, xmlEnd]);
  
  return new Response(stream, {
    headers: {
      'Content-Type': 'application/xml',
    },
  });
}

// 启用ISR,24小时自动更新
export const revalidate = 86400;

3. 数据库层面深度优化

  • 仅查询必要字段:始终只获取id和lastModified(如果需要),避免加载整个媒体文档
  • 索引优化:确保分页查询用到的字段(如id)有数据库索引
  • 游标分页替代OFFSET:千万级以上数据集用游标分页(基于上一条的id),避免OFFSET导致的性能衰减

4. 服务器资源兜底优化

  • 调整Node.js内存限制:启动命令添加--max-old-space-size=4096(分配4GB内存)
  • 启用边缘运行时:在子站点路由顶部添加export const runtime = 'edge';,利用边缘节点资源,降低冷启动耗时

内容的提问来源于stack exchange,提问作者Junbutaw Tawbutaw

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 03:10:59