fix(docs): get docs.stackpanel.com/docs/* working again#20
fix(docs): get docs.stackpanel.com/docs/* working again#20cooper (czxtm) merged 4 commits intomainfrom
Conversation
…r.js
Closes the regression where every `/docs/*` route on docs.stackpanel.com
returns `500 Internal Server Error` while `/` and `/api/search` work.
Root cause: alchemy@2.0.0-beta.20's `Cloudflare.Worker.prepareBundle`
runs `.open-next/worker.js` through `cloudflareRolldown` even when
`isExternal: true`. The re-bundle rewrites OpenNext's dynamic
`import("./server-functions/default/handler.mjs")` so its wrapper
resolver returns `undefined`, which then throws
`TypeError: Cannot destructure property 'name' of '(intermediate value)'`
inside `createGenericHandler`. Static routes survive because they're
served by the ASSETS binding without ever entering the broken handler.
Fix: a 2-edit patch to alchemy that adds `WorkerProps.bundle?: boolean`
(default `true`). When `bundle: false`, `prepareBundle` short-circuits —
reads `props.main` directly and returns a single-file `Bundle.BundleOutput`
whose hash is `sha256(content)`. No rolldown step, byte-identical upload.
This change applies the patch to our local alchemy install via bun's
`patchedDependencies` and turns it on in apps/docs.
Patch is the same one I just pushed to czxtm/alchemy-effect:main as the
proposed upstream PR; once it ships in a beta release we can drop both
the patch file and the `bundle: false` prop.
bd: closes stackpanel-49t
Other changes:
- bun.lock cleanup: removes `bun2nix` (already removed from the workspace
package.json in d82d13a but not from the lockfile).
PR SummaryMedium Risk Overview This introduces a Bun Lockfile/package metadata is updated to apply the patch (and cleans up stale Reviewed by Cursor Bugbot for commit 1d47562. Configure here. |
|
Preview |
Follow-up to the previous commit. The `bundle: false` opt-out was the right opt-out, but I had a wrong mental model of `.open-next/worker.js`: it's a ~2KB OpenNext entrypoint that *expects* to be passed through a wrangler-style bundler that resolves the relative `./cloudflare/*.js` imports and inlines them. Uploading it byte-for-byte fails with `Uncaught Error: No such module "cloudflare/images.js" — imported from "worker.js"`. The PR #20 first attempt confirmed the patch *itself* works (CI got past the rolldown step and uploaded 2.22 KB to Cloudflare); just the file content was incomplete. So: - `bun run build:worker` now also runs `wrangler deploy --dry-run --outdir=.open-next/dist`, which produces a self-contained `.open-next/dist/worker.js` (~10 MiB gzipped — under CF's limit). Wrangler bundles statics and *preserves* the runtime `import()` calls the way OpenNext expects. - `apps/docs/alchemy.run.ts` points `main:` at `.open-next/dist/worker.js` and keeps `bundle: false` so alchemy uploads the wrangler artifact byte-for-byte instead of running rolldown again. Net effect: the same `bunx alchemy deploy` that was 500-ing on every `/docs/*` route should now serve the real fumadocs UI.
Without --minify, the bundled worker.js was 70.1 MiB uncompressed — just over Cloudflare's 64 MiB uncompressed Workers script limit (the gzipped 10 MiB limit was fine). With --minify, 57.7 MiB uncompressed / 9.1 MiB gzipped.
|
Docs preview |
…s/* fix) This is the actual cause of every `/docs/*` route returning 500 — not the alchemy bundling bug from the previous commits, which was masking the symptom by failing the deploy outright. Once the worker actually deployed, runtime traffic surfaced: GET https://docs.pr-20.stackpanel.com/docs - Ok @ 12:55:10 (error) Error: [unenv] fs.mkdir is not implemented yet! at l6 (worker.js:112349:269) at worker.js:112349:8151 That stack lands on `ts-morph`'s `FileSystemDocumentCache.mkdir`, pulled in transitively via: src/mdx-components.tsx → fumadocs-typescript (createGenerator + AutoTypeTable UI) → fumadocs-typescript/ui (AutoTypeTable React component) → @ts-morph/common + ts-morph (TypeScript compiler API + virtual FS) The `<AutoTypeTable />` MDX tag doesn't actually need the runtime component — `remarkAutoTypeTable` in source.config.ts already expands those tags into plain markdown tables at build time, so the rendered output never contains a live `<AutoTypeTable>` element. (Verified: `rg AutoTypeTable apps/docs/content` returns no matches.) The runtime registration was dead code. Removing it: - drops ts-morph (~25 MiB) from the runtime worker bundle - drops the request-time `fs.mkdir` call that crashed every dynamic fumadocs page render - shrinks the bundled `worker.js` from 57.7 MiB → 31.0 MiB uncompressed and 9.0 MiB → 4.0 MiB gzipped (well under both Workers limits) If we ever need a *runtime* AutoTypeTable in the future, the cache must be a non-FS implementation (or omitted entirely) so it works under Workers' `nodejs_compat` polyfill.
Summary
Fixes
docs.stackpanel.com/docs/*(and every other dynamic Next route on the docs Worker) returning500 Internal Server Errorwhile/and/api/searchwork.This turned out to be three separate bugs stacked on top of each other, where each one masked the next. Each commit peels off one layer.
1. Deploy-time: alchemy re-bundles the OpenNext entrypoint
alchemy@2.0.0-beta.20'sCloudflare.Worker.prepareBundleruns.open-next/worker.jsthroughcloudflareRolldowneven whenisExternal: true. The re-bundle rewrites OpenNext's runtimeimport("./server-functions/default/handler.mjs")so its wrapper resolver returnsundefinedand the deployed Worker dies on first request withThis is what was failing every
Deploy DocsCI job since the alchemy@2 migration — the user-facing 500s were the previous still-running deploy.Fix: patch
alchemy(via bun'spatchedDependencies) to addWorkerProps.bundle?: boolean(defaulttrue). Whenfalse,prepareBundlereadsprops.mainand uploads it byte-for-byte (hash =sha256(content)) — no rolldown step. Same patch is up on my fork as the proposed upstream PR: czxtm/alchemy-effect@df6ed57 / standalone repro at https://github.com/czxtm/repro-alchemy-bundle-false.2. Build-time: OpenNext's
worker.jsisn't self-containedOpenNext's
.open-next/worker.jsis a ~2KB entrypoint with explicitly comments saying the static./cloudflare/*.jsimports "Will be resolved by wrangler build". Uploading byte-for-byte givesUncaught Error: No such module "cloudflare/images.js".Fix: run
wrangler deploy --dry-run --outdir=.open-next/dist --minifyafteropennextjs-cloudflare build. Wrangler bundles the static imports while preserving the dynamicimport()paths the way OpenNext expects. Pointmain:at the wrangler output and keepbundle: falseso alchemy uploads the wrangler artifact byte-for-byte. (Without--minify, the bundle is 70 MiB uncompressed — over CF's 64 MiB Workers script limit. With--minify, 32 MiB uncompressed / 4 MiB gzipped after fix #3.)3. Runtime:
fumadocs-typescriptcallsfs.mkdiron every dynamic page renderOnce the Worker actually deployed, runtime traffic surfaced the bug the user was hitting all along.
wrangler tailondocs.pr-20.stackpanel.com:That stack lands inside
ts-morph'sFileSystemDocumentCache.mkdir, pulled in transitively fromapps/docs/src/mdx-components.tsx:createFileSystemGeneratorCache(".next/fumadocs-typescript")callsfs.mkdiron first render, which the Workersnodejs_compatpolyfill (unenv) doesn't implement.The runtime registration was dead code:
<AutoTypeTable />in MDX is expanded at build time byremarkAutoTypeTableinsource.config.ts, so the rendered output never contains a live<AutoTypeTable>element — verifiedrg AutoTypeTable apps/docs/contentreturns no matches.Fix: drop
AutoTypeTableand the runtimecreateGeneratorfrommdx-components.tsx. Drops ts-morph entirely from the runtime worker bundle (~25 MiB).Verification
PR preview at
docs.pr-20.stackpanel.com://docs<title>Stackpanel</title>)/docs/agent<title>CLI / Agent</title>)/docs/cli/docs/reference/api/searchBundle size: 70 MiB → 32 MiB uncompressed, ~10 MiB → 4 MiB gzipped.
Cleanup followups
Filed as separate issues for after merge:
stackpanel-49t: closeable — alchemybundle: falsepatch + the docs fixes are this PR. Drop the patch + thebundle: falseprop once cloudflareRolldown's dynamic-import handling is fixed upstream (PR proposed at https://github.com/czxtm/alchemy-effect ready to be opened against alchemy-run/alchemy-effect).<AutoTypeTable />MDX with a non-FS cache implementation (or omitting the runtime cache entirely) if we ever want to use it in MDX content.