fix(web): shrink GROQ query weight + gate SanityLive behind draft mode - #137
Open
damianrosellen1 wants to merge 3 commits into
Open
fix(web): shrink GROQ query weight + gate SanityLive behind draft mode#137damianrosellen1 wants to merge 3 commits into
damianrosellen1 wants to merge 3 commits into
Conversation
…rojection settingsBundleQuery (312 KB), siteNavQuery (79 KB), siteSettingsQuery (77 KB) and siteCookieBannerQuery (77 KB) were exported but never imported outside the queries folder — settings.ts itself marked the bundle 'safe to delete if unused'. Together they built ~546 KB of GROQ template strings on every server cold start for nothing. errorSettingsQuery embedded modulesQuery (77 of its 79 KB) although no error page renders modules — LocaleNotFoundContent reads only notFoundTitle/notFoundBody. Dropping the projection shrinks the query to 1.8 KB; the hand type loses its dead modules field. Query-size harness (esbuild compile of the query modules): - errorSettingsQuery 78.9 KB -> 1.8 KB - string exports at module init 1.20 MB -> 0.46 MB Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
buildRichTextMediaQuery repeated the full module.media / module.carousel / module.contentRefs projections (~19 KB) on every one of its three nesting levels — ~57 KB for the snippet alone and ~77 KB per page query. @sanity/client POSTs anything over 14,800 chars and next-sanity's sanityFetch issues two fetches per query (sync-tags probe + data), so every uncached render uploaded ~154 KB of GROQ text per page query. Module projections now live only on the top level of a rich-text body. Nested module.text bodies keep resolving blocks + link marks through the full depth; media modules inside a *nested* text module arrive with raw stored fields only and are not expected to render — top-level rich text remains the module surface. Query-size harness: - richTextMediaQuery 58.7 KB -> 22.1 KB - homeQuery / pageBySlugQuery / workQuery 77.3 KB -> 40.7 KB (−47%) - projectBySlugQuery 63.6 KB -> 27.1 KB Verified against the live dataset: trimmed homeQuery returns the full document (200, modules intact); /work/design still renders all five rich-text-embedded media figures; page HTML sizes unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e tags <SanityLive /> mounted for every visitor as soon as SANITY_API_READ_TOKEN was set (any deploy with Visual Editing): one SSE connection per open tab plus a revalidateSyncTagsAction server-action round trip per publish event per tab. It had to — the sanityFetch data-cache entries carried only next-sanity's sanity:* sync tags (revalidate: false, 1y cacheLife), which nothing but a mounted SanityLive ever revalidates. - fetchSanityData: every sanityFetch wrapper now also tags its cache entry with the matching SANITY_CACHE_TAGS constant, so the existing /api/revalidate webhook invalidates page and shell data directly - /api/revalidate: siteNav + siteCookieBanner join the allow-list; siteSettings, siteNav, siteCookieBanner, errorSettings map to their new tags (kept in sync with SANITY_CACHE_TAGS) - layout: SanityLive mounts for Draft Mode (Presentation live preview), and — as a freshness fallback — whenever a read token is set but no SANITY_REVALIDATE_SECRET is configured, so webhook-less setups keep the previous behavior Verified against the running production build: HMAC-signed webhook posts for page/siteNav/siteSettings/siteCookieBanner/errorSettings revalidate their tags (200, revalidated: true) and the page re-serves fresh; a full browser page load makes zero requests to *.sanity.io (previously an SSE connection to /data/live/events). Draft-mode requests still mount Live. Note: client JS is unchanged — the @sanity/client/stega chunks on public pages come from useOptimistic (next-sanity/hooks) in ModulesRendererClient, not from SanityLive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Follow-up to #136, implementing the cross-repo audit findings (same audit as farbstudio) for this repo. Stacked on
fix/web-ttfb-cls— retarget tomainonce #136 merges.1. Dead query weight (
3cf1317)settingsBundleQuery(312 KB),siteNavQuery(79 KB),siteSettingsQuery(77 KB) andsiteCookieBannerQuery(77 KB) were exported but never imported outside the queries folder — ~546 KB of GROQ template strings built on every server cold start.errorSettingsQueryembeddedmodulesQuery(77 of its 79 KB) although no error page renders modules.errorSettingsQuery: 78.9 KB → 1.8 KBErrorSettingsDocumentloses its deadmodulesfield2. Rich-text projection blow-up (
c29df3d)Unlike farbstudio (where
contentRefswas the driver — ours is lean), the blow-up here wasbuildRichTextMediaQuery(3): it repeated the fullmodule.media/module.carousel/module.contentRefsprojections (~19 KB) per nesting level. @sanity/client POSTs anything over 14,800 chars andsanityFetchfires two fetches per query (sync-tags probe + data) → ~154 KB upload per page query per uncached render.Module projections now live only at the top level of a rich-text body; nested
module.textkeeps resolving blocks + link marks through the full depth.richTextMediaQueryhomeQuery/pageBySlugQuery/workQueryprojectBySlugQueryTrade-off (deliberate): media modules embedded inside a nested text module arrive with raw stored fields only and aren't expected to render — top-level rich text remains the module surface. Verified: trimmed
homeQueryreturns the full document against the live dataset;/work/designstill renders all five rich-text-embedded media figures; page HTML sizes unchanged.3. SanityLive gated behind Draft Mode (
f643caa)<SanityLive />mounted for every visitor onceSANITY_API_READ_TOKENwas set: an SSE connection per tab + arevalidateSyncTagsActionserver-action round trip per publish event per tab. It had to —sanityFetchcache entries carried onlysanity:*sync tags (revalidate: false, 1y cacheLife), which nothing but a mounted SanityLive revalidates.The fix makes the webhook the published-content invalidation path, then gates Live:
sanityFetchwrapper tags its cache entry with the matchingSANITY_CACHE_TAGSconstant/api/revalidatecovers the shell singletons (siteNav+siteCookieBanneradded to the allow-list;siteSettings/errorSettingsnow map to tags)SanityLivemounts for Draft Mode — and, as a freshness fallback, whenever a token is set but noSANITY_REVALIDATE_SECRETis configured (webhook-less setups keep today's behavior)Verified end-to-end on the production build: HMAC-signed webhook posts for
page/siteNav/siteSettings/siteCookieBanner/errorSettings→revalidated: trueand pages re-serve fresh; a full browser page load performs zero requests to*.sanity.io(previously an SSE connection to/data/live/events). No token leak existed before (next-sanity only sendsbrowserTokenin draft mode) — the win is connection + action traffic. Client JS is unchanged: the remaining @sanity/client/stega chunks come fromuseOptimistic(next-sanity/hooks) inModulesRendererClient, not from Live — possible follow-up.Not done (deliberately)
loading.tsxdeletion (farbstudio plan): our routes are SSG, the skeleton never streams on initial load; the 300 ms delay from fix(web): faster server response + no layout shift on load and transitions #136 already removes the transition flash while keeping feedback for slow draft-mode navigations.Verified
pnpm typecheck+pnpm formatclean, production build green, all routes still SSG/ISR🤖 Generated with Claude Code