fix(web): bundle docs sources at build time so the worker has no fs

The worker deploy from #3859 returned HTTP 500 on /docs with
`Error: [unenv] fs.readFile is not implemented yet!` (captured via
`wrangler tail`). `loadDocSource` was calling `node:fs/promises`
`readFile` inside an RSC; even though the page is generated as SSG
(`●`), Cloudflare Workers' unenv shim does not implement filesystem
reads, so any code path that reaches the worker (cache miss, prerender
fallback) fails.

Move the read to a prebuild step that emits a TypeScript module:

- `web/scripts/generate-docs-content.mjs` reads each section's source
  from `<repo-root>/docs/` and writes
  `web/lib/docs-content.generated.ts` containing
  `export const DOC_SOURCES: Record<string, string>`.
- `web/scripts/prepare-build.mjs` invokes the generator after the
  cache prune, so every `bun run build` and `bunx opennextjs-cloudflare
  build` regenerates the constant module from the live `docs/`.
- `web/lib/docs-source.ts` now reads `DOC_SOURCES[file]` synchronously
  with no Node I/O.
- `web/app/[locale]/docs/page.tsx` drops the `Promise.all` since reads
  are synchronous.
- `web/.gitignore` excludes the generated file (kept generated, not
  source-of-truth).

Effect: the bundle ships every doc as a string literal. The worker has
no `fs.readFile` call to fail. `docs/` remains the only place an
editor needs to touch.
This commit is contained in:
YeonGyu-Kim
2026-05-08 16:38:14 +09:00
parent 770825422d
commit 57b9d42537
5 changed files with 46 additions and 13 deletions
+1
View File
@@ -53,3 +53,4 @@ next-env.d.ts
/playwright-report/
/blob-report/
/playwright/.cache/
lib/docs-content.generated.ts
+4 -6
View File
@@ -8,12 +8,10 @@ import { loadDocSource } from "@/lib/docs-source"
export default async function DocsPage() {
const t = await getTranslations("docs")
const sectionsWithSource = await Promise.all(
DOC_SECTIONS.map(async (section) => ({
...section,
source: await loadDocSource(section.file),
})),
)
const sectionsWithSource = DOC_SECTIONS.map((section) => ({
...section,
source: loadDocSource(section.file),
}))
return (
<DocsShell
+7 -7
View File
@@ -1,9 +1,9 @@
import { readFile } from "node:fs/promises"
import path from "node:path"
import { DOC_SOURCES } from "./docs-content.generated"
const DOCS_ROOT = path.resolve(process.cwd(), "..", "docs")
export async function loadDocSource(file: string): Promise<string> {
const fullPath = path.join(DOCS_ROOT, file)
return readFile(fullPath, "utf8")
export function loadDocSource(file: string): string {
const source = DOC_SOURCES[file]
if (source === undefined) {
throw new Error(`Unknown doc file: ${file}`)
}
return source
}
+31
View File
@@ -0,0 +1,31 @@
import { readFile, writeFile } from "node:fs/promises"
import path from "node:path"
const SECTIONS = [
{ file: "guide/overview.md" },
{ file: "guide/installation.md" },
{ file: "guide/orchestration.md" },
{ file: "guide/agent-model-matching.md" },
{ file: "guide/team-mode.md" },
{ file: "reference/cli.md" },
{ file: "reference/configuration.md" },
{ file: "reference/features.md" },
{ file: "manifesto.md" },
]
const DOCS_ROOT = path.resolve(process.cwd(), "..", "docs")
const OUTPUT = path.resolve(process.cwd(), "lib", "docs-content.generated.ts")
const sources = {}
for (const s of SECTIONS) {
sources[s.file] = await readFile(path.join(DOCS_ROOT, s.file), "utf8")
}
const out =
"// Generated by scripts/generate-docs-content.mjs - DO NOT EDIT\n" +
"export const DOC_SOURCES: Record<string, string> = " +
JSON.stringify(sources, null, 2) +
"\n"
await writeFile(OUTPUT, out)
console.log("Generated " + OUTPUT + " with " + SECTIONS.length + " docs sources")
+3
View File
@@ -1,7 +1,10 @@
import { rmSync } from "node:fs"
import { execSync } from "node:child_process"
const buildCachePaths = [".next/cache/fetch-cache"]
for (const filePath of buildCachePaths) {
rmSync(filePath, { force: true, recursive: true })
}
execSync("node ./scripts/generate-docs-content.mjs", { stdio: "inherit" })