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 data
Error occurred prerendering page "/detail/%E6%96%87%E7%AB%A0". Read more: https://nextjs.org/docs/messages/prerender-error
Error: 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 }} />;
}
