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

Next.js生产环境调用fs.readdirSync出现ENOENT错误求助

Next.js App Router部署Vercel后fs.readdirSync部分页面报错问题

问题情况

使用Next.js App Router,通过fs.readdirSync()读取data/posts/目录下的markdown文章文件,本地运行完全正常,但部署到Vercel后出现异常:将读取逻辑封装为FetchPosts函数并在两个页面调用时,其中一个页面正常工作,另一个页面抛出Error: ENOENT: no such file or directory, scandir 'data/posts/'错误。构建及部署过程无报错,仅访问网站时触发该错误。

相关代码

FetchPosts函数

const fs = require('fs');

export async function FetchPosts() {
    // read all markdown files in this dir
    // get an array containing filenames
    let posts = fs.readdirSync('data/posts/');
    
    try {
        let data = [];
        let promises = [];

        posts.forEach((filename) => {
            let promise = new Promise((resolve, reject) => {  
                // use async so i can import module dynamically
                (async () => {
                    // i used a webpack plugin called frontmatter-markdown-loader
                    // this allows me to import markdown files as a module
                    await import(`data/posts/${filename}`)
                        .then((post) => { 
                            // substring is to remove `.md` in the filename
                            const slug = filename.substring(filename.indexOf('.'), 0)
                            resolve({
                                "slug": slug,
                                "attributes": post.attributes,
                                "html": post.html
                            })
                        }
                    )
                })();  
            })
            promises.push(promise);
        })
        data = await Promise.all(promises);

        // sort posts by date
        data.sort((a, b) => {
            let dateA = new Date(a.attributes.date);
            let dateB = new Date(b.attributes.date);
            
            return dateB - dateA;
        })

        return data

    } catch(err) {
        throw err
    }
}

正常工作的页面(Blog)

import { FetchPosts } from '@/lib/fetchPosts'

// ...

export default async function Blog() {
    const posts = await FetchPosts();
    
    return (
        <>
            <Header />
            <article>
                <Heading />
                <Padding>
                    <BlogCategories />
                    <BlogList posts={posts} />
                </Padding>
            </article>
        </>
    )
}

报错的页面(分类页)

import categories from "@/data/categories.data";
import { FetchPosts } from "@/lib/fetchPosts";

// ...

export default async function Page({ params }) {
    const posts = await FetchPosts();

    let data = [];
    posts?.map((post) => {
        if (post.attributes.category == params.slug)
            data.push(post)
    })

    return (
        <>
            <Header />
            <article>
                <Heading />
                <Padding>
                    <BlogList posts={data} />
                </Padding>
            </article>
        </>
    )
}

原因分析

  1. 相对路径解析差异:本地开发时Node.js工作目录为项目根目录,但Vercel部署后,不同页面组件的执行上下文工作目录可能不同,导致fs.readdirSync('data/posts/')无法正确定位目标目录。
  2. 动态import路径不确定性:未使用Next.js别名的相对路径data/posts/${filename},在Vercel的静态优化或页面模块解析逻辑中,可能出现路径解析错误。
  3. 代码潜在逻辑问题:原slug截取代码filename.substring(filename.indexOf('.'), 0)因参数顺序错误会返回空字符串,虽不是本次报错原因,但会导致后续功能异常。

解决方案

1. 使用绝对路径读取目录

引入path模块,结合process.cwd()生成绝对路径,确保无论执行上下文如何都能正确定位目录:

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

export async function FetchPosts() {
    const postsDir = path.resolve(process.cwd(), 'data/posts');
    let posts = fs.readdirSync(postsDir);
    
    // 后续逻辑保持不变
}

2. 修正动态import路径

使用Next.js别名@确保路径解析一致:

await import(`@/data/posts/${filename}`)

3. 修复slug截取逻辑

调整substring参数顺序,正确截取文件名去除.md后缀:

const slug = filename.substring(0, filename.indexOf('.'));

4. 简化Promise逻辑(可选)

原代码Promise封装冗余,可直接用map生成Promise数组提升可读性:

const promises = posts.map(async (filename) => {
    const post = await import(`@/data/posts/${filename}`);
    const slug = filename.substring(0, filename.indexOf('.'));
    return {
        slug,
        attributes: post.attributes,
        html: post.html
    };
});
data = await Promise.all(promises);

5. 确认Vercel部署包含目标目录

检查.vercelignore文件,确保未配置忽略data/posts目录(默认Vercel会包含项目根目录下所有文件)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 09:36:02