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默认构建流水线部署。
问题
- 为何该错误仅在Vercel出现而本地正常?
- 如何确保
_posts目录在部署环境中可用? - 有没有更适配Vercel的Next.js动态生成站点地图的方案?
问题1:本地正常但Vercel报错的原因
本地环境中process.cwd()指向项目根目录,自然能找到_posts文件夹。但Vercel部署时,构建产物会被打包到/var/task目录,默认情况下,项目根目录下的_posts不会被自动包含到部署包——Vercel的Next.js构建流水线只打包框架识别的源码目录(如src/、app/、pages/等)和package.json指定的资源,未明确声明的目录会被忽略。
问题2:确保_posts目录在部署环境可用的方法
有两种可靠方案:
- 修改
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
- 移动
_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
相关产品推荐
相关产品推荐

