perf: optimize playground Lighthouse score#596
Conversation
|
Update: pushed a small follow-up commit ( The previous Docusaurus playground guarded GraphiQL's localStorage behavior behind cookie preferences. Since this PR moves the interactive state into a standalone static route, the static route now passes a transient no-op storage object to GraphiQL so it avoids browser storage entirely while preserving the deferred interactive behavior. Implementation note: this PR intentionally keeps endpoint deep links on Re-verified after the update:
|
|
Verification refresh on current head
Command: npx --yes lighthouse@12.8.2 http://127.0.0.1:4169/playground/ \
--only-categories=performance,accessibility,best-practices,seo \
--chrome-flags='--headless --no-sandbox' \
--output=json \
--output-path=/tmp/tailcall-pr596-lighthouse.json \
--quietResults:
Runtime smoke:
Implementation note: this preserves |
/claim #216
Summary
This PR gets
/playground/to a Lighthouse 100 by using the smallest practical version of the architecture change already suggested in the issue discussion: isolate the route from the full Docusaurus app shell.Instead of migrating the entire site to Astro, Next.js streaming, or another framework, this PR turns only
/playground/into a lightweight static route:?u=<endpoint>deep links still auto-load the interactive playgroundWhy this approach
The earlier issue comments called out the core problem clearly: reaching 100 on Lighthouse, especially mobile, is not realistic while the measured route pays the full Docusaurus runtime cost. One comment noted that this would likely require “a huge rewrite from Docusaurus to Astro Starlight or Next.js Server Streaming”; another agreed that “the current stack need[s] to be replaced to reach a 100% score on Lighthouse, especially on mobile.”
This PR takes the smallest viable version of that idea.
A full-site framework migration would be high-risk and far outside the scope of a single
/playground/performance issue. A route-level static shell gives the measured route the same practical benefit — no Docusaurus hydration, no global bundle tax, no eager GraphiQL payload — without forcing the rest of the docs site to move frameworks.That makes this a bounded workaround rather than a broad rewrite:
/playground/changes.In short: the existing stack is the bottleneck for this specific Lighthouse target, so this PR sidesteps the stack only where the target is measured.
Lighthouse results
Measured against a production build served locally at:
http://127.0.0.1:3000/playground/PWA is intentionally ignored per the issue instructions.
Mobile
1.001.001.001.000.6s0.8s0ms0.6s0Desktop
1.001.001.001.000.2s0.2s0ms0.2s0Functional smoke test
Verified the deferred interactive path at:
/playground/?u=https://countries.trevorblades.com/Confirmed:
?u=window.GraphiQL.createFetcherexecutes against the endpoint successfullySmoke query returned:
{ "code": "AD", "name": "Andorra" }Test plan
npx --yes prettier --check static/playground/index.htmlnpm run buildnpm run test:cypress/playground//playground//playground/?u=https://countries.trevorblades.com/Notes
The deferred GraphiQL path uses the documented UMD distribution order: React UMD, ReactDOM UMD, then
graphiql.min.js. I tested ESM CDN imports first, but GraphiQL/CodeMirror produced runtime errors in that path. The UMD path is more stable for this standalone route and keeps the first-load Lighthouse path clean because those assets load only after endpoint intent.Closes #216