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:
generateStaticParams
generates the path/detail/%E6%96%87%E7%AB%A0
(URL-encoded “文章”).- However,
fs.readFileSync
tries 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/文章.md
and add some content to it. - Implement
generateStaticParams
and 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 }} />;
}