Repository navigation
fix(docs): serve llms-full.txt as a static file written at build time, out of Vercel's 20 MB ISR fallback cap - #23640
Merged
objectstack-fleet[bot] merged 4 commits intoOct 15, 2026
Conversation
…prerendering an ISR route Vercel refuses a prerender fallback body over 20 MB; the concatenation measured 20,032,800 bytes at e611e2b. A public/ file is a static asset. Claude-Session: https://claude.ai/code/session_01ESFcvW3staenyBCGq7kcDH Co-authored-by: Claude <noreply@anthropic.com>
…de paths absolute Claude-Session: https://claude.ai/code/session_01ESFcvW3staenyBCGq7kcDH Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ESFcvW3staenyBCGq7kcDH Co-authored-by: Claude <noreply@anthropic.com>
…d task A turbo cache-hit replay restored .next/** only, so a replayed build would deploy without /llms-full.txt and report success. Claude-Session: https://claude.ai/code/session_01ESFcvW3staenyBCGq7kcDH Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
席位核对 ·
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #23625
Clause-②: no
/llms-full.txtis now a static file thatapps/docs/scripts/build-llms-full.mjswrites intoapps/docs/public/during the docs build. The prerendered route handler it replaces is gone. The URL, the bytes, the content type and theX-Robots-Tag: noindexheader all stay the same. What changes is how Vercel receives the file: as a static asset, not as an ISR page. The 20 MB ISR fallback cap was failing every production docs deploy, and it no longer applies to this file.What was red (A1)
docs-deploy.ymlscheduled runs38663939159(01:07Z) and38695971788(07:01Z) both failed at step 13, "Deploy the prebuilt output to production". Step 11 (vercel build --prod) and step 12 (output assertion) were green, per the run's job steps. The last green run was38618717424(2026-10-14 18:51Z).FALLBACK_BODY_TOO_LARGE, "ISR page llms-full.txt is 20.00 MB, over the 20 MB limit". That string is not in thevercel@60.1.3CLI dist (grep). So the check runs server-side, at upload.How big, how fast (A3)
To measure, I ran the same generator (
getLLMTextoversource.getPages(), joined by blank lines) on each commit afterpnpm installand the docs build's three spec generators (gen:schema,gen:docs,gen:upgrade-guide). That includes the build-timecontent/docs/protocol-upgrade/pages. One611e2bdathe result is byte-identical to the route's own prerendered body (below).0d91eaa6e8c657f7dd05be3525951290bca2989aa483f(last green deploy)252775d1d(first red deploy)e611e2bda(this branch's base)23e689dc7(main merged in)next.config.mjsrecorded that day.e611e2bda): the auto-generatedreferences/**pages are about 66% of the bytes (ui3.22 MB,api3.06 MB,data2.61 MB,system1.51 MB). The build-timeprotocol-upgrade/**pages are 9.4%. The largest single page isprotocol-upgrade/18, at 1.46 MB. Growth follows the spec surface, not hand-written docs. So any fix that only trimmed the file back under 20 MB would have failed again within weeks.Why a
public/file escapes the cap (A4)vercel@60.1.3pins,@vercel/next@15.0.1. ItsgetStaticFiles()globspublic/**after the build command and emits each file as a static output. Prerendered routes are emitted as prerenders with a fallback body, and that body is what the deploy API measured. A route handler withrevalidate = falseis a prerender (.next/server/app/llms-full.txt.body+.meta,routeCache.kind: APP_ROUTE).en/docs/protocol-upgrade/18.html, at 3,916,137 bytes (19.6% of the cap). Next isreferences/api/protocol.htmlat 2,422,127.VERCEL_TOKENin this container.The change
apps/docs/app/llms-full.txt/route.tsis deleted, so the file has one producer.apps/docs/scripts/build-llms-full.mjs, run by the docsbuildscript after the spec generators and beforenext build:.source/with fumadocs-mdx's ownpostInstall(), which is whatnext builddoes at config load. On a fresh checkout,pnpm installwrites.source/beforegen:upgrade-guidecreatescontent/docs/protocol-upgrade/, so without this step those three pages (1.87 MB) would be silently missing.lib/source.tsas plain Node, so the text comes from the samegetLLMTextandsource.getPages()the other agent-reader routes use, not from a copy. fumadocs-mdx's Node loader (fumadocs-mdx/noderegister()) compiles each page with the samesource.config.ts.public/llms-full.txtand printsN pages, B bytes.process.features.typescript, Node 22.18+), so the build fails rather than deploying without the file.apps/docs/scripts/llms-full/resolve-hooks.mjs: the Node spelling of the resolutions the Next build already performs. These are the two tsconfigpathsaliases, plus bare imports fromcontent/**resolved from the app, the same reasonturbopack.resolveAliasexists.apps/docs/.gitignoreignores/public/llms-full.txt.next.config.mjscomment now gives the current size and says the noindex header covers the static file.docs-deploy.ymlandvercel.jsonare untouched;turbo.jsonaddspublic/llms-full.txtto the@objectstack/docs#buildoutputs (patch round,b7c786c2). No bypass flag.Proof that this changes delivery only
e611e2bda: the route's prerendered body was 20,032,800 bytes, sha256cbbf6f782618778397483dcbea544cc1bc1c144d002e409807e7d2bae1986d0b. The generator, reading the.source/that same build left behind, produced the same sha256.40af08370), I ranapps/docs/vercel.json'sbuildCommandfrom a fresh-checkout state: no.next,.source/regenerated withoutprotocol-upgrade,protocol-upgrade/deleted. It succeeded (turbo: 2 tasks, 0 cached). The build printedbuild-llms-full: 418 pages, 20,032,800 bytes, and the route table no longer lists/llms-full.txt./llms.mdx/docs/**, each onegetLLMText(page)) and the orderllms.txtlists them in. Result:PERMUTATION-EQUAL: 418/418 pages consumed. Thee611e2bdaroute body is alsoPERMUTATION-EQUALagainst them. A one-byte mutant of it fails (FAIL at offset 9886232). I reran this ond777181dbwith the same result..source/index in a different order on every run: four runs offumadocs-mdxon one tree gave four different index hashes. So the route's page order already changed from build to build. One difference within a single build: this file's order no longer matches/llms.txt's, because the generator andnext buildeach regenerate.source/. Neither file's order was ever stable, and nothing reads it.next starton that build and fetchedGET /llms-full.txt. Response:200,Content-Type: text/plain; charset=UTF-8,X-Robots-Tag: noindex,Content-Length: 20032800, bytescmp-equal topublic/llms-full.txt./llms.txtand/docs/ai/agents.mdxstill answer 200. The old route answeredtext/plain;charset=UTF-8with the same header.What this PR can and cannot prove
docs-deploy.ymlrun serving/llms-full.txt, and that run happens after merge: the next run is at :43 past 00/06/12/18 UTC, or sooner via the workflow's "Run workflow" button.Tests and gates (head
f7ce0ddb6: this branch withorigin/main23e689dc7merged in)f7ce0ddb6, afterpnpm installand the three spec generators:node scripts/build-llms-full.mjsprinted418 pages, 20,040,773 bytes, andpnpm --filter @objectstack/docs typecheck(fumadocs-mdx && next typegen && tsc --noEmit) exited 0. The app has no test script.tsconfig.jsonincludes**/*.ts(x)only, so the two new.mjsscripts are outside tsc. The build and the proofs above are their tests.f7ce0ddb6,node scripts/pm/dispatch-gates.mjs --commandsderived 38 commands. All exited 0 (the same 38 also exited 0 atd777181db, before the merge), and the--ranreconciliation with exit codes says "38 derived famil(ies) accounted for — 38 run, 0 NOT-MEASURED (a DERIVED zero)". The list includescheck:llms-txt,check-doc-frontmatter,check-docs-nav-label(+ self-tests),check:docs-locale-catch-all,check:published-files,check:turbo-task-graph,check:test-source-alias,check:nul-bytes,check:watch-hint-literalandcheck-undeclared-dep-imports.check:llms-txtreadspackages/spec/llms.txt(itsTARGET), the hand-kept summary inside the spec tarball, not the docs site's/llms.txtor/llms-full.txt. It is unaffected and green.Build Docs(ci.yml) runs this samevercel.jsoncommand on the PR. That check-run is the CI half of the build proof.@objectstack/docsisprivate: trueand publishes nothing, so this PR takesskip-changeset.Acceptance notes
next devno longer serves/llms-full.txtuntil a build, ornode scripts/build-llms-full.mjsinapps/docs, has written the file. The old route rendered it on demand in dev. Production is unaffected.next buildalone took 208 s there.@objectstack/docs#buildnow declarespublic/llms-full.txt(patch round,b7c786c2); a cache-hit replay restored it byte-identical (measured onb7c786c2, report on docs deploy is red: llms-full.txt reached 20 MB, Vercel's cap for an ISR page, so every production docs deploy now fails #23625 comment 6188441795). Both production paths also setTURBO_FORCE./docs/protocol-upgrade/18, at 3.9 MB of HTML. Its source page grows with every 18.x upgrade entry. Owner: none.Patch round (seat-added)
b7c786c2addspublic/llms-full.txtto the@objectstack/docs#buildoutputs in rootturbo.json, so a turbo cache-hit replay restores the file this PR now builds (the dev's open question, option B, taken by the seat; claim addendum on #23625 comment 6187979278). Both production paths force execution today, so this closes a latent gap, not a live one.Generated by Claude Code