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

Next.js部署Vercel生成Sitemap时出现ENOENT文件不存在错误

Next.js + Vercel 站点地图生成问题

我正在构建部署于Vercel的Next.js应用,已在/src/app/sitemap.ts中实现动态站点地图生成器,通过扫描项目根目录下_posts文件夹中的Markdown文件生成站点地图,代码如下:

import fs from 'fs';
import { join } from 'path';
import { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const blogPosts = fs.readdirSync(join(process.cwd(), '_posts'));
  const blogPostPages: MetadataRoute.Sitemap = blogPosts.map((post) => ({
    url: `${url}${post}`,
    lastModified: getLastModified(`_posts/${post}.md`),
    changeFrequency: 'daily',
    priority: 1,
  }));
  return blogPostPages;
}

该代码在本地环境运行正常,但部署到Vercel后出现以下错误:

Error: ENOENT: no such file or directory, scandir '/var/task/_posts'

看起来_posts目录在Vercel部署环境中不可访问或不存在。

附加信息

  • _posts文件夹包含Markdown文件(post1.md、post2.md等)。
  • 应用使用Vercel默认构建流水线部署。

问题

  1. 为何该错误仅在Vercel出现而本地正常?
  2. 如何确保_posts目录在部署环境中可用?
  3. 有没有更适配Vercel的Next.js动态生成站点地图的方案?

问题1:本地正常但Vercel报错的原因

本地环境中process.cwd()指向项目根目录,自然能找到_posts文件夹。但Vercel部署时,构建产物会被打包到/var/task目录,默认情况下,项目根目录下的_posts不会被自动包含到部署包——Vercel的Next.js构建流水线只打包框架识别的源码目录(如src/、app/、pages/等)和package.json指定的资源,未明确声明的目录会被忽略。

问题2:确保_posts目录在部署环境可用的方法

有两种可靠方案:

  1. 修改next.config.js配置资源包含规则
    在项目根目录的next.config.js中添加配置,告诉Next.js将_posts目录纳入构建产物:
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
  },
  webpack: (config) => {
    // 将_posts目录添加到webpack资源处理链
    config.module.rules.push({
      test: /\.md$/,
      include: path.join(__dirname, '_posts'),
      use: 'raw-loader',
    });
    return config;
  },
};

module.exports = nextConfig;

同时安装依赖:

npm install raw-loader --save-dev
  1. 移动_posts到框架自动打包的目录
    Vercel会自动打包public/或src/下的所有内容,可将_posts移到src/app/_posts或public/_posts,再修改代码中的路径:
// 示例:移到src/app/_posts后的路径
const blogPosts = fs.readdirSync(join(process.cwd(), 'src/app/_posts'));

问题3:适配Vercel的站点地图生成方案

推荐构建时生成站点地图,避免运行时文件系统访问的限制,效率更高也更稳定:

方案1:用postbuild脚本生成静态站点地图

在package.json中添加构建后执行的脚本:

{
  "scripts": {
    "build": "next build",
    "postbuild": "node scripts/generate-sitemap.js"
  }
}

创建scripts/generate-sitemap.js脚本:

const fs = require('fs');
const path = require('path');

const baseUrl = 'https://your-domain.com';
const postsDir = path.join(process.cwd(), '_posts');
const posts = fs.readdirSync(postsDir).filter(file => file.endsWith('.md'));

const sitemapContent = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  ${posts.map(post => {
    const filePath = path.join(postsDir, post);
    const stats = fs.statSync(filePath);
    return `
      <url>
        <loc>${baseUrl}/posts/${post.replace('.md', '')}</loc>
        <lastmod>${stats.mtime.toISOString()}</lastmod>
        <changefreq>daily</changefreq>
        <priority>1.0</priority>
      </url>
    `;
  }).join('')}
</urlset>`;

fs.writeFileSync(path.join(process.cwd(), 'public/sitemap.xml'), sitemapContent);

构建完成后,静态sitemap.xml会生成在public/目录,Vercel会直接托管该文件,无需运行时处理。

方案2:App Router下构建时生成动态站点地图

在sitemap.ts中提前在构建阶段获取文章数据(需确保_posts已被纳入构建产物,参考问题2的配置):

import { MetadataRoute } from 'next';
import fs from 'fs';
import path from 'path';

// 构建时预加载所有文章
const getPosts = () => {
  const postsDir = path.join(process.cwd(), '_posts');
  return fs.readdirSync(postsDir).filter(file => file.endsWith('.md'));
};

export default function sitemap(): MetadataRoute.Sitemap {
  const posts = getPosts();
  return posts.map(post => {
    const filePath = path.join(process.cwd(), '_posts', post);
    const stats = fs.statSync(filePath);
    return {
      url: `https://your-domain.com/posts/${post.replace('.md', '')}`,
      lastModified: stats.mtime,
      changeFrequency: 'daily' as const,
      priority: 1,
    };
  });
}

Next.js会在构建时执行该文件生成站点地图,而非运行时执行,适配Vercel的部署环境。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 18:57:06