feat: adopt Cache Components + Turbopack (Next 16.2.10 stable)#422
Open
ignmandagaran wants to merge 25 commits into
Open
feat: adopt Cache Components + Turbopack (Next 16.2.10 stable)#422ignmandagaran wants to merge 25 commits into
ignmandagaran wants to merge 25 commits into
Conversation
- Bump next + eslint-config-next to 16.3.0-canary.60 line (16.3 needed for Cache Components: instant export + codemod + dev-overlay validation) - Switch dev/build scripts to Turbopack (Next 16 default); drop --webpack - Remove webpack() glsl loader config (mirrored by existing turbopack.rules) - Remove experimental.ppr (flag removed in Next 16; superseded by cacheComponents) - Remove experimental_ppr segment config from blog route
- next-sanity 12.1.6 -> 13.1.1 (native Cache Components support: cacheLife profile, 'use cache'-aware fetches, tag-based Live invalidation) - react/react-dom 19.0.1 -> 19.2.7 (satisfies next-sanity peer ^19.2.3; React 19.2 is what Next 16 targets) - @sanity/client ^7.23.0, sanity/@sanity/vision ^5.29.0 (v13 peer floors) - @types/react(-dom) ^19.2 Build verified green; no behavior change (cacheComponents not yet enabled).
Enable Next.js Cache Components (cacheComponents: true) with next-sanity v13's Live integration, and walk every route to a passing build. Content routes now Partial-Prerender (static shell + streamed data); index/landing pages prerender static. Config - next.config: cacheComponents: true + Sanity cacheLife profile (Sanity Live drives on-demand revalidation, so the default 15min TTL is replaced). Sanity data layer (src/service/sanity) - sanityFetch (Live), sanityFetchCached (Live wrapped in "use cache" — cached published reads, still Live-invalidated via cacheTag), sanityFetchStatic (non-Live client.fetch for generateStaticParams/generateMetadata). - Translate removed route-segment config: drop force-static (post/showcase/ careers [slug]) and dynamic/fetchCache/revalidate (scores API). Shared shell - ContentWrapper: isolate usePathname() behind <Suspense>; dynamic-param routes (all canvas-blacklisted) fall back to page content in the plain container. - Navbar/Footer: stream interactive (motion) content via <Suspense>; cache the shared layout fetches (counts, company info). - Root layout: draftMode + SanityLive behind Suspense. Routes - post/showcase/careers/blog [slug]: split static vs Live fetch, defer params behind <Suspense>, cache content with "use cache". - home/showcase/people/services index: cache published list fetchers. - .md endpoints + sitemap: cache their data so the handlers prerender. - brands: deterministic mobile trim (was Math.random) + stream motion title. - lab: instant = false (request-time user-agent redirect, documented Block). AGENTS.md/CLAUDE.md regenerated by next dev for Next 16.
… workarounds motion@12.0.0-alpha.2 evaluated Math.random() during render (an SSR/prerender hazard under Cache Components); the stable line is prerender-safe. Upgrading lets the navbar, footer, and brands render back in the static shell instead of being streamed behind <Suspense> to dodge the hazard. - motion 12.0.0-alpha.2 -> ^12.42.0 - MotionValue constructor now requires an initial value: new MotionValue() -> new MotionValue(0) (postprocessing, inspectables, use-fade-animation) - locked-door: guard lockedDoor before animate() (stricter v12 overloads reject the possibly-undefined target) - Remove the navbar/footer/brands <Suspense> motion workarounds - Copyright year now resolves on the client (reading the current year at render is unstable IO; was previously masked by the footer Suspense wrap) Build green (89/89); content routes still Partial Prerender.
Turbopack does not run this project's Tailwind v3 PostCSS pipeline (verified with minimal and full postcss configs, ESM and CJS): `@tailwind`/`@apply` pass through unprocessed, so the site renders with ~85 CSS rules, no custom fonts, no brand styling. Turbopack also can't load the GLSL shaders (no loader equivalent). Cache Components is bundler-agnostic, so reverting the bundler keeps the entire adoption intact — content routes still Partial Prerender, index pages static. - dev/build scripts back to --webpack - restore the webpack() GLSL raw-loader/glslify-loader config - restore postcss.config.mjs (postcss-import + tailwindcss/nesting + tailwindcss) Verified: dev renders fully styled (819 CSS rules, Geist font, black bg, shaders load); `next build --webpack` green with cacheComponents (◐ routes preserved). Revisiting Turbopack later would require migrating Tailwind v3 -> v4 (native Turbopack support via @tailwindcss/postcss).
Trim the explanatory comments added during adoption to concise one-liners (why, not what), matching surrounding comment density.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The blog index only awaits params and renders cached fetchers ("use cache"),
so the params read doesn't gate any uncached work — the route still prerenders
(`/blog` and category pages stay static with the full post list in the HTML;
the optional catch-all stays Partial Prerender). Verified via prod build.
careers/[slug] has no unstable client components, so the wrap was unnecessary — its pages now prerender static. post/[slug] and showcase/[slug] keep theirs (a verified build fails without them: client embeds/gallery read Date.now()/runtime data at prerender); added a comment noting why each is load-bearing.
The Mux player reads Date.now() at render, which can't run during prerender under Cache Components. Load it via dynamic(ssr: false) wrapped in <Suspense> with a skeleton that renders the Mux poster (the video's first frame, so no layout shift). This makes <Video> self-contained, so showcase/[slug] no longer needs a page-level Suspense (its only prerender blocker was the gallery video). - video-mux.tsx: the Mux renderer, lazy-loaded client-only - video.tsx: dynamic(ssr:false) + Suspense + poster VideoSkeleton - showcase/[slug]: drop the now-redundant page-level Suspense Verified: build green (89/89), Mux player absent from prerendered HTML (no hazard), poster in the static shell, videos load + play client-side.
post/[slug] had three client embeds blocking prerender. With all handled, the
route drops its page-level Suspense and prerenders.
- tweet: wrap getTweet() in "use cache" + cacheLife('max') (tweets are
immutable) — fetched once and cached instead of hitting the syndication API
per request
- sandbox: load Sandpack via dynamic(ssr: false) + Suspense with a skeleton
(it reads Date.now() at render); split into sandbox-impl.tsx
- post/[slug]: drop the now-redundant page-level Suspense
3 tasks
…e) (#423) * prototype(canvas-route-groups): scaffolding for structural canvas Replaces the usePathname + BLACKLISTED_PATHS check with a route-group approach: - CanvasLayer: the single persistent canvas, mounted once in the root layout, visibility driven by the isCanvasInPage store flag (no usePathname). - SetCanvasMode: client effect setter a group layout renders to declare canvas on/off — runs client-side, so no prerender hazard, no <Suspense>. - PageShell: the (pages) main+Footer chrome, extracted so both the canvas and plain groups can share it. Wiring (route moves + root/layout swap) is the next step; see notes. * prototype(canvas-route-groups): wire structural canvas via route groups Replace ContentWrapper's usePathname + BLACKLISTED_PATHS with route groups: - (canvas)/ and (plain)/ groups; (content)/ sub-layout shares PageShell chrome - root layout mounts the persistent CanvasLayer (no usePathname, no Suspense) - SetCanvasMode in each group layout toggles canvasVisible; isCanvasInPage stays sticky so the WebGL Scene persists across navigations - showcase index (canvas) / [slug] (plain) split across groups - not-found sets canvas mode; 21 cross-refs + contact path updated Build green (89/89). Runtime canvas behavior pending browser QA.
The laboratory server action called the Live sanityFetch, whose cacheTag() throws outside a "use cache" scope under Cache Components, so the action 500'd and the arcade rendered no experiments. Use sanityFetchStatic (non-Live, published) which is safe in a server action.
sitemap.ts used sanityFetchStatic (non-Live), so the prerendered XML sitemap had no Sanity cacheTags and only refreshed on redeploy or the 1y backstop. Switch to sanityFetchCached so Sanity Live invalidates it on publish, matching the .md routes.
The /lab page read headers() (user-agent) at the top to redirect mobile visitors, which forces it dynamic under Cache Components and required the canary-only `export const instant = false`. Move the redirect to proxy.ts (scoped to /lab) so the page no longer reads request data and prerenders as static. Behaviour is unchanged; the redirect now runs before render.
Cache Components, cacheLife, and PPR are no longer canary-gated — they ship in stable 16.2.10. With the /lab instant=false opt-out removed (redirect moved to proxy), the app builds clean on stable. Route classifications are unchanged except /lab, which is now prerendered static. Pins next + eslint-config-next to 16.2.10.
Document the stable-16.2.10/webpack setup, the three Sanity fetch modes and when to use each, the proxy-not-pages rule for request-time logic, and tag-based revalidation. Placed outside the next-dev-managed block so it survives regeneration.
Remove the generated nextjs-agent-rules block from the committed file. Note: next dev regenerates it on dev runs, so it may reappear as an uncommitted change; the project-specific section is kept separate so it survives.
…GLSL Turbopack officially supports raw-loader but not glslify-loader. The 5 shaders using #pragma glslify: require/export are flattened with the glslify CLI (which resolves + dedups the ashima noise helpers), so plain raw-loader can load every .glsl file. The now-inlined utils/value-remap and utils/basic-light are deleted.
Drop the --webpack flag from dev/build (Turbopack is the Next 16 default), simplify turbopack.rules to raw-loader only, and remove the now-dead webpack() loader function. Remove glslify-loader/glsl-noise/ glsl-constants; add postcss-import as an explicit dep so Turbopack's resolver doesn't rely on a transitive hoist. Tailwind v3 PostCSS chain verified working under Turbopack. AGENTS.md updated to match.
…urbopack The lockfile had postcss pinned at 8.0.0 (the first 8.x release, 2020). Turbopack's PostCSS transform silently no-ops plugins driven by that version — Tailwind's @tailwind/@apply/theme() directives passed through raw in both dev and production, with no error. Bumping to postcss@8.5.10 fixes it: verified in a worktree and the real tree, dev and build, that every CSS chunk now has zero raw directives and full expanded output (preflight, utilities, theme colors), confirmed visually in the browser. No Tailwind v3->v4 migration was needed — this was never a Turbopack/ Tailwind-v3 incompatibility.
3 tasks
…cement The motion 12.42 upgrade changed the inspectable transform MotionValues from new MotionValue() to new MotionValue(0). motion's animate() tweens from the value's current state, so the old undefined snapped instantly while 0 tweens the frame in from origin/zero-scale. On the 404 security-cam view the frames could be left mispositioned depending on load timing (intermittent, fixed on refresh). Init resting scale to 1 and snap the first placement (duration 0); only real selections animate.
ignmandagaran
marked this pull request as ready for review
July 20, 2026 18:15
|
Too many files changed for review. ( Bypass the limit by tagging |
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.
Summary
Adopts Next.js Cache Components (
cacheComponents: true) and lands the app on stable Next 16.2.10 running on Turbopack. Content routes now Partial Prerender (static shell + streamed request-time data); index/landing pages prerender fully static. Sanity data is cached via next-sanity v13's Live integration, so cached content still revalidates on-demand through Sanity Live.Cache Components,
cacheLife, and PPR are no longer canary-gated — they ship in stable 16.2.x — so the app runs on a real release, not a nightly canary.Built with the
next-cache-components-adoptionskill.Changes
next+eslint-config-nextpinned to16.2.10. No canary channel needed.devandbuild(next.config.tsturbopack.rules; the--webpackflag is gone). The earlier "styles break under Turbopack" was a stalepostcss@8.0.0lockfile pin, not a Tailwind-v3 incompatibility — bumped topostcss ^8.5.10withpostcss-importas an explicit dep, so thepostcss.config.mjschain (postcss-import→tailwindcss/nesting→tailwindcss) runs cleanly. GLSL shaders now load throughraw-loaderalone: theglslify#pragmarequires were flattened into the.glslfiles, so noglslify-loaderis required.cacheLifeprofile wired intonext.configso Sanity Live drives revalidation.Math.random()at render (SSR hazard under Cache Components); stable is prerender-safe.sanityFetch(Live, draft/preview) ·sanityFetchCached(Live +"use cache", published render) ·sanityFetchStatic(non-Live —generateStaticParams/generateMetadata, and any non-use cachecontext such as server actions).post/showcase/careers/blog[slug]→<Suspense>forparams+"use cache"content; home/showcase/people/services index → cached list fetchers;.mdendpoints + sitemap cached./labmobile redirect moved toproxy.tsso the page no longer readsheaders()and prerenders○static — this removed the only 16.3-canary-only dependency (export const instant), unblocking stable.sanityFetch, whosecacheTag()throws outside a"use cache"scope, so it 500'd and rendered no experiments. Switched tosanityFetchStatic.sitemap.xmlrevalidates on publish — wassanityFetchStatic(no Sanity tags → redeploy-only); switched tosanityFetchCachedso Sanity Live invalidates it, matching the pages and.mdroutes.new MotionValue(0);animate()tweens from the current value, so they grew in from origin/zero-scale and could be left mispositioned depending on load timing (intermittent). Init resting scale to1and snap the first placement; only real selections animate.next-cache-components-adoptionskill and trimmed the auto-generatedAGENTS.mdblock, keeping a project-specifics section.Verification
next buildgreen on Next 16.2.10 + Turbopack — content routes◐ Partial Prerender, index pages○ Static,/lab○ Static./lab(ƒ → ○); every other page kept its class./sitemap.xmlprerenders withsanity:*cache-tags; the 404 scene's wall frames render correctly across repeated reloads.Follow-ups
Checklist