In Next.js 15, when using generateStaticParams to generate static paths, next dev runs normally, but next build fails with an error stating that the file cannot be found (ENOENT: no such file or directory).
Specifically, the URL parameters generated by generateStaticParams are incorrectly decoded during the next build process, causing a path mismatch. For example:
generateStaticParamsgenerates the path/detail/%E6%96%87%E7%AB%A0(URL-encoded "文章").- However,
fs.readFileSynctries to readpublic/%E6%96%87%E7%AB%A0.md, while the actual file path should bepublic/文章.md.
This causes next build to fail, even though next dev runs successfully.
Error message:
> url-encode-error-next@0.1.0 build> next build
▲ Next.js 15.1.6
Creating an optimized production build ... ✓ Compiled successfully ✓ Linting and checking validity of types ✓ Collecting page dataError occurred prerendering page "/detail/%E6%96%87%E7%AB%A0". Read more: https://nextjs.org/docs/messages/prerender-errorError: ENOENT: no such file or directory, open 'D:\WorkSpace\My\Next\url-encode-error-next\public\%E6%96%87%E7%AB%A0.md' at Object.readFileSync (node:fs:448:20) at h (D:\WorkSpace\My\Next\url-encode-error-next\.next\server\app\detail\[name]\page.js:1:190139) at l (D:\WorkSpace\My\Next\url-encode-error-next\.next\server\app\detail\[name]\page.js:1:190247)Export encountered an error on /detail/[name]/page: /detail/%E6%96%87%E7%AB%A0, exiting the build. ⨯ Static worker exited with code: 1 and signal: null
Minimal Reproducible Example
Steps to Reproduce
- Create a file
public/文章.mdand add some content to it. - Implement
generateStaticParamsand file reading logic inapp/detail/[name]/page.tsx:
import path from "node:path";import * as fs from "node:fs";import matter from "gray-matter";
export async function generateStaticParams() { const notesDir = path.join(process.cwd(), "public"); const fileNames = fs.readdirSync(notesDir); return fileNames.map((fileName) => { const encodedName = encodeURIComponent(fileName.replace(/\.md$/, "")); return { name: encodedName }; });}
async function getMarkdownContent(name: string) { const decodedName = decodeURIComponent(name); // 解码路径参数 const filePath = path.join(process.cwd(), "public", `${decodedName}.md`); const fileContent = fs.readFileSync(filePath, "utf8"); const { content } = matter(fileContent); return content;}
export default async function NoteDetail({ params }: { params: { name: string } }) { const { name } = params; const content = await getMarkdownContent(name); return <div dangerouslySetInnerHTML={{ __html: content }} />;}
