{"record":{"id":"eb377dfa2bf98f98","repo":"facebook/docusaurus","slug":"expected-output-html-file-to-be-found-at-withtra","errorCode":null,"errorMessage":"Expected output HTML file to be found at ${withTrailingSlashPath} for permalink ${permalink}.","messagePattern":"Expected output HTML file to be found at (.+?) for permalink (.+?)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/docusaurus-utils/src/emitUtils.ts","lineNumber":100,"sourceCode":"  outDir: string,\n  trailingSlash: boolean | undefined,\n): Promise<Buffer> {\n  const withTrailingSlashPath = path.join(outDir, permalink, 'index.html');\n  const withoutTrailingSlashPath = (() => {\n    const basePath = path.join(outDir, permalink.replace(/\\/$/, ''));\n    const htmlSuffix = /\\.html?$/i.test(basePath) ? '' : '.html';\n    return `${basePath}${htmlSuffix}`;\n  })();\n\n  const possibleHtmlPaths = [\n    trailingSlash !== false && withTrailingSlashPath,\n    trailingSlash !== true && withoutTrailingSlashPath,\n  ].filter((p): p is string => Boolean(p));\n\n  const HTMLPath = await findAsyncSequential(possibleHtmlPaths, fs.pathExists);\n\n  if (!HTMLPath) {\n    throw new Error(\n      `Expected output HTML file to be found at ${withTrailingSlashPath} for permalink ${permalink}.`,\n    );\n  }\n  return fs.readFile(HTMLPath);\n}\n","sourceCodeStart":82,"sourceCodeEnd":106,"githubUrl":"https://github.com/facebook/docusaurus/blob/3f483e80e326cc646b54b83d564b3f0c4881b9a6/packages/docusaurus-utils/src/emitUtils.ts#L82-L106","documentation":"Thrown by readOutputHTMLFile() when neither the trailing-slash variant (permalink/index.html) nor the non-trailing-slash variant (permalink.html) exists in the output directory. The function computes both candidate paths based on the trailingSlash config, probes each with fs.pathExists, and only throws if both are missing. The JSDoc marks this as an internal invariant: it 'should never happen as it would lead to a 404.'","triggerScenarios":"An internal Docusaurus caller (e.g. the sitemap or SSR post-processing pass) asks for the rendered HTML of a permalink whose build output file was never written or was written to an unexpected path. This usually points to a mismatch between the permalink a plugin registered and the file the bundler actually emitted.","commonSituations":"A custom plugin generates a permalink route but its corresponding static HTML file is not emitted (e.g. the route is client-only with no SSR). A trailingSlash configuration change where stale output from a previous build lingers in the outDir. Slugs with unexpected characters that get encoded differently between the permalink registry and the filesystem.","solutions":["Clear the build output directory (remove the build/ or out/ folder) and rebuild from scratch — this is by far the most common cause if the error appears after a trailingSlash or permalink config change.","Inspect the permalink in the error; confirm the plugin that owns that route actually emits a static HTML file (plugins returning client-only routes with no SSR cannot be read this way).","Verify trailingSlash in docusaurus.config.js is set consistently — the candidate paths depend on its value, so a mismatch with previously built output triggers the error.","If you author a custom plugin, ensure addRoute() permalinks correspond to pages that produce index.html or permalink.html in the output."],"exampleFix":"// before — stale build output after trailingSlash change\n// run: rm -rf build && pnpm build\n\n// after — clean rebuild resolves the candidate path mismatch","handlingStrategy":"try-catch","validationCode":"import fs from 'fs-extra';\nimport path from 'path';\n\nasync function outputHtmlExists(outDir: string, permalink: string, trailingSlash?: boolean): Promise<boolean> {\n  const candidates = [\n    trailingSlash !== false && path.join(outDir, permalink, 'index.html'),\n    trailingSlash !== true && `${path.join(outDir, permalink.replace(/\\/$/, ''))}.html`,\n  ].filter(Boolean) as string[];\n  return (await Promise.all(candidates.map(p => fs.pathExists(p)))).some(Boolean);\n}\n\nif (!(await outputHtmlExists(outDir, permalink, trailingSlash))) {\n  throw new Error(`No built HTML for ${permalink}; rebuild from a clean outDir.`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await readOutputHTMLFile(permalink, outDir, trailingSlash);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Expected output HTML file')) {\n    // this is an internal invariant; clear outDir and rebuild\n  }\n  throw err;\n}","preventionTips":["Always build into a clean output directory (delete build/ first), especially after changing trailingSlash or permalink config.","Ensure custom plugin routes emit a static HTML file (avoid pure client-only routes that need SSR read-back).","Keep trailingSlash consistent across builds to avoid stale-path mismatches."],"tags":["html","build","trailing-slash","internal","permalink"],"backgroundTag":null,"analyzedSha":"3f483e80e326cc646b54b83d564b3f0c4881b9a6","analyzedAt":"2026-08-12T13:25:04.382Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}