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

Next.js 14动态站点地图生成异常问题求助

Next.js 14 动态站点地图生成问题及解决方案

问题描述

为Next.js 14博客编写的动态站点地图代码在开发环境运行正常,但构建或生产环境中无法生成动态站点地图,构建日志显示站点地图为“预渲染静态”,即使等待revalidate设置的60秒也无效果。已查阅官方文档未解决,尝试基于GetServerSideProps的实现方式,但该方式在开发环境无法显示站点地图。现需实现站点地图的动态生成与定期更新。

第一种实现(MetadataRoute 方式)

开发环境正常,生产/构建环境被静态预渲染:

import { CategoryInterface, PostInterface } from "@/commons/types"
import { getAllCategories, getAllPosts } from "@/lib/get-requests"
import { MetadataRoute } from "next"

export const revalidate = 60

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
    const baseUrl = "https://www.mywebsite.com"

    const { categories } = await getAllCategories()
    const { posts } = await getAllPosts()

    const categoryUrls = categories
        ?.filter((category: CategoryInterface) => category.isPublished)
        ?.map((category: CategoryInterface) => ({
            url: category.liveUrl,
            lastmodified: new Date(),
            priority: 1,
            changeFrequency: "daily",
        }))

    const postUrls = posts?.filter((post: PostInterface) => post.isPublished)?.map((post: PostInterface) => ({
        url: post.liveUrl,
        lastmodified: post.updatedAt,
        priority: 1,
        changeFrequency: "daily",
    }))

    return [
        { 
            url: baseUrl,
            lastmodified: new Date(),
            priority: 1,
            changeFrequency: "daily",
        },
        { 
            url: `${baseUrl}/blogs`,
            lastmodified: new Date(),
            priority: 1,
            changeFrequency: "daily",
        },

        ...postUrls,
        ...categoryUrls,
    ]
}

第二种尝试(GetServerSideProps 方式)

App Router 环境下无效,开发环境无法显示:

import { CategoryInterface, PostInterface } from '@/commons/types';
import { getAllCategories, getAllPosts } from '@/lib/get-requests';
import { GetServerSideProps } from 'next'

export default function Sitemap() {
    return null
}

export const getServerSideProps: GetServerSideProps<{}> = async ({req, res}) => {
    res.setHeader(
        'Content-Type', 'text/xml',
    );
    const xml = await generateSitemap()

    res.write(xml);
    res.end()
    return {
        props: {},
    }
}

async function generateSitemap(): Promise<string> {
    const baseUrl = "https://www.mywebsite.com"
    
    const { categories } = await getAllCategories()
    const { posts } = await getAllPosts()

    return `
    <?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
        <url>
            <loc>${baseUrl}</loc>
            <lastmod>${new Date()}</lastmod>
            <changefreq>daily</changefreq>
            <priority>1</priority>
        </url>
        <url>
            <loc>${baseUrl}/blogs</loc>
            <lastmod>${new Date()}</lastmod>
            <changefreq>daily</changefreq>
            <priority>1</priority>
        </url>
        ${posts?.filter((post: PostInterface) => post.isPublished)?.map((post: PostInterface) => {
            return `
            <url>
                <loc>${post.liveUrl}/blogs</loc>
                <lastmod>${post.updatedAt}</lastmod>
                <changefreq>daily</changefreq>
                <priority>1</priority>
            </url>
            `
        }).join('')}

        ${categories?.filter((category: CategoryInterface) => category.isPublished)?.map((category: CategoryInterface) => {
            return `
            <url>
                <loc>${category.liveUrl}/blogs</loc>
                <lastmod>${category.updatedAt}</lastmod>
                <changefreq>daily</changefreq>
                <priority>1</priority>
            </url>
            `
        }).join('')}
    </urlset>
    `
}

解决方案

方案1:修复 MetadataRoute 方式的静态预渲染问题

在原代码中加入unstable_noStore()强制动态生成,同时处理数组解构的undefined安全问题:

import { CategoryInterface, PostInterface } from "@/commons/types"
import { getAllCategories, getAllPosts } from "@/lib/get-requests"
import { MetadataRoute } from "next"
import { unstable_noStore } from "next/cache"

export const revalidate = 60

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
    unstable_noStore() // 禁用静态缓存,强制按revalidate周期重新生成
    const baseUrl = "https://www.mywebsite.com"

    const { categories } = await getAllCategories()
    const { posts } = await getAllPosts()

    const categoryUrls = categories
        ?.filter((category: CategoryInterface) => category.isPublished)
        ?.map((category: CategoryInterface) => ({
            url: category.liveUrl,
            lastmodified: new Date(),
            priority: 1,
            changeFrequency: "daily",
        })) || [] // 兜底空数组避免解构错误

    const postUrls = posts?.filter((post: PostInterface) => post.isPublished)?.map((post: PostInterface) => ({
        url: post.liveUrl,
        lastmodified: post.updatedAt,
        priority: 1,
        changeFrequency: "daily",
    })) || [] // 兜底空数组

    return [
        { 
            url: baseUrl,
            lastmodified: new Date(),
            priority: 1,
            changeFrequency: "daily",
        },
        { 
            url: `${baseUrl}/blogs`,
            lastmodified: new Date(),
            priority: 1,
            changeFrequency: "daily",
        },
        ...postUrls,
        ...categoryUrls,
    ]
}
  • 关键说明:unstable_noStore()会阻止Next.js在构建时静态预渲染sitemap,转而根据revalidate设置的周期重新生成内容。
  • 额外修复:给categoryUrls和postUrls添加空数组兜底,避免解构undefined导致的运行时错误。

方案2:改用 App Router Route Handler 实现(推荐)

在App Router中,GetServerSideProps属于Pages Router API无法生效,正确的动态响应处理方式是创建Route Handler:

创建app/sitemap.xml/route.ts文件,内容如下:

import { CategoryInterface, PostInterface } from "@/commons/types";
import { getAllCategories, getAllPosts } from "@/lib/get-requests";

export async function GET() {
  const baseUrl = "https://www.mywebsite.com";
  const { categories } = await getAllCategories();
  const { posts } = await getAllPosts();

  // 生成符合标准的XML内容
  const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>${baseUrl}</loc>
    <lastmod>${new Date().toISOString()}</lastmod>
    <changefreq>daily</changefreq>
    <priority>1</priority>
  </url>
  <url>
    <loc>${baseUrl}/blogs</loc>
    <lastmod>${new Date().toISOString()}</lastmod>
    <changefreq>daily</changefreq>
    <priority>1</priority>
  </url>
  ${posts
    ?.filter((post: PostInterface) => post.isPublished)
    ?.map((post: PostInterface) => `
  <url>
    <loc>${post.liveUrl}</loc>
    <lastmod>${new Date(post.updatedAt).toISOString()}</lastmod>
    <changefreq>daily</changefreq>
    <priority>1</priority>
  </url>`)
    .join("") || ""}
  ${categories
    ?.filter((category: CategoryInterface) => category.isPublished)
    ?.map((category: CategoryInterface) => `
  <url>
    <loc>${category.liveUrl}</loc>
    <lastmod>${new Date().toISOString()}</lastmod>
    <changefreq>daily</changefreq>
    <priority>1</priority>
  </url>`)
    .join("") || ""}
</urlset>`;

  return new Response(xmlContent, {
    headers: {
      "Content-Type": "text/xml",
    },
  });
}

// 设置每60秒重新验证缓存
export const revalidate = 60;
  • 关键说明:Route Handler是App Router中处理自定义响应的官方方式,支持完全控制XML输出和缓存策略。
  • 额外优化:将日期转换为ISO格式符合站点地图标准,添加空值兜底避免生成无效XML。

通用注意事项

  • 检查getAllCategories和getAllPosts中的数据请求:如果调用外部API,需确保请求未被静态缓存(可在fetch选项中添加cache: 'no-store'或next: { revalidate: 60 })。
  • 验证liveUrl字段:确保生成的URL是完整的绝对路径(包含https://),避免站点地图解析错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 15:52:05