Next.js应用本地可读取MDX文件,部署至Vercel/Netlify时遇ENOENT错误
解决Next.js部署Vercel/Netlify时MDX文件ENOENT找不到的问题
问题场景
本地环境可正常读取src/app/bounties/content/bounties目录下的MDX文件,但部署到Vercel或Netlify时,出现ENOENT错误,提示目标文件不存在。例如访问路由/bounties/dynamic-sidebar时,报错找不到src/app/bounties/content/bounties/dynamic-sidebar.mdx。
原代码示例
import { type MDXRemoteSerializeResult } from "next-mdx-remote"; import { serialize } from "next-mdx-remote/serialize"; import { promises as fs } from "fs"; import { MdxContent } from "../../mdx-content"; import BountiesBreadcrumb from "@/app/components/bountiesBreadcrumb"; import path from "path"; type Frontmatter = { title: string; date: string; }; type Post<TFrontmatter> = { serialized: MDXRemoteSerializeResult; frontmatter: TFrontmatter; }; interface IBountyProps { params: { slug: string; }; } async function getPost(filepath: string): Promise<Post<Frontmatter>> { const raw = await fs.readFile(filepath, "utf-8"); const serialized = await serialize(raw, { parseFrontmatter: true, }); const frontmatter = serialized.frontmatter as Frontmatter; return { frontmatter, serialized, }; } export default async function Bounty({ params }: IBountyProps) { const filePath = path.join( "src", "app", "bounties", "content", "bounties", `${params.slug}.mdx` ); const { serialized, frontmatter } = await getPost(filePath); return ( <div className="text-zinc-400 p-16 border-r border-zinc-800"> <BountiesBreadcrumb /> <div className="py-16 max-w-4xl"> <h1 className="text-zinc-300 font-bold text-4xl"> {frontmatter.title} </h1> <span className="text-zinc-400 text-md mt-8"> Published at {frontmatter.date} </span> <div className="mt-8"> <MdxContent source={serialized} /> </div> </div> </div> ); }
错误原因
部署时Next.js生产构建会将src目录代码打包到输出目录,此时直接使用相对路径src/app/...不再有效——部署环境的工作目录并非项目根目录,导致路径解析错误。
解决方案
使用process.cwd()获取项目根目录作为路径起始点,确保本地和部署环境都能正确定位文件,同时增加文件存在检查提升调试体验。
修改后的代码
import { type MDXRemoteSerializeResult } from "next-mdx-remote"; import { serialize } from "next-mdx-remote/serialize"; import { promises as fs } from "fs"; import { MdxContent } from "../../mdx-content"; import BountiesBreadcrumb from "@/app/components/bountiesBreadcrumb"; import path from "path"; type Frontmatter = { title: string; date: string; }; type Post<TFrontmatter> = { serialized: MDXRemoteSerializeResult; frontmatter: TFrontmatter; }; interface IBountyProps { params: { slug: string; }; } async function getPost(filepath: string): Promise<Post<Frontmatter>> { // 提前检查文件是否存在 try { await fs.access(filepath); } catch (err) { throw new Error(`目标文件不存在: ${filepath}`); } const raw = await fs.readFile(filepath, "utf-8"); const serialized = await serialize(raw, { parseFrontmatter: true, }); const frontmatter = serialized.frontmatter as Frontmatter; return { frontmatter, serialized, }; } export default async function Bounty({ params }: IBountyProps) { // 基于项目根目录拼接文件路径 const filePath = path.join( process.cwd(), "src", "app", "bounties", "content", "bounties", `${params.slug}.mdx` ); const { serialized, frontmatter } = await getPost(filePath); return ( <div className="text-zinc-400 p-16 border-r border-zinc-800"> <BountiesBreadcrumb /> <div className="py-16 max-w-4xl"> <h1 className="text-zinc-300 font-bold text-4xl"> {frontmatter.title} </h1> <span className="text-zinc-400 text-md mt-8"> Published at {frontmatter.date} </span> <div className="mt-8"> <MdxContent source={serialized} /> </div> </div> </div> ); }
关键说明
process.cwd()返回当前Node.js进程的工作目录,本地开发和部署环境中均指向项目根目录,确保路径解析一致。fs.access()提前检查文件存在性,避免模糊的ENOENT错误,抛出更明确的调试信息。
内容的提问来源于stack exchange,提问作者Duca
相关产品推荐
相关产品推荐

