Skip to content

perf: weekly react best-practices audit fixes - #4164

Merged
smakosh merged 6 commits into
mainfrom
claude/upbeat-johnson-3b76et
Sep 21, 2026
Merged

smakosh merged 6 commits into
mainfrom
claude/upbeat-johnson-3b76et

Conversation

@smakosh

@smakosh smakosh commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Weekly React/Next.js best-practices audit of the four Next.js apps (apps/ui, apps/code, apps/playground, apps/docs) against Vercel's react-best-practices guide. All prior audit rounds are merged on main, so this round covers code merged since #4058 plus one long-standing bundle problem on the marketing surface that turned out to be the dominant finding. No routing or user-visible behavior changes; nothing touches export const dynamic.

Fixes

Bundle size

  • Avoid Barrel File Imports (rule 2.1) — packages/shared was un-tree-shakeable, shipping the model catalogue on every ui route. The root @llmgateway/shared barrel re-exports from ./components/index.js, whose graph reaches the models-directory components and through them the full @llmgateway/models catalogue. Because the package declared no sideEffects, bundlers had to keep every re-export, so any client component importing the barrel for one symbol (the root providers.tsx for TimeZoneProvider, the navbar, the FAQ…) dragged the whole component library plus the catalogue into its route. Declaring "sideEffects": false (the package has no side-effectful modules: no bare imports, no CSS, no global mutations) lets the bundler tree-shake the barrels. Measured on the built ui client-reference manifests, per-route referenced client JS (uncompressed):

    • / 2076 KB → 977 KB, /blog 2002 KB → 891 KB, /compare/open-router 1996 KB → 888 KB — catalogue bytes on all of them 976 KB → 0 KB
    • /models keeps only the 137 KB of catalogue chunks it actually uses (was 976 KB), and the dashboard project page now references 0 catalogue bytes

    This is a one-line change in packages/shared (allowed as strictly required by the fix) and benefits every app that bundles the package.

  • Client bundling of the model catalogue via the footer (rules 2.1/2.4) — apps/ui. The marketing Footer was "use client" and imported listedProviders from providers-catalog, which uses the whole catalogue to count active models per provider — a use tree-shaking cannot remove, so with the fix above alone the catalogue would have returned to all ~46 footer-bearing marketing routes. Footer is now a server component (it reads getConfig() directly; Newsletter stays a client island), and the one client consumer — the AllModels wrapper — takes footer as a slot filled by its server pages (standard RSC composition). The numbers above are measured with both fixes in place.

  • Defer Non-Critical Third-Party Libraries (rule 2.3) — compare-page heroes, apps/ui. HeroCompare (all seven /compare/* pages) shipped framer-motion in the initial bundle solely for a one-shot blur/slide hero entrance. It now uses the same animate-hero-enter CSS utilities the main landing hero was already migrated to (visually equivalent keyframes, plus the reduced-motion handling the JS variant lacked) and becomes a server component. No other component in the compare graph imports motion, so the library leaves those pages' first load entirely.

  • Unnecessary client components — comparison tables, apps/ui. The seven static feature-comparison tables (comparison*.tsx, ~270 lines of constant JSX each) were "use client" with zero hooks or handlers, so every /compare/* page shipped and hydrated the whole table. The directives are removed; the tables render as server components and only the existing AuthLink leaves hydrate. All importers are server pages.

  • Conditional Module Loading (rules 2.2/2.3) — apps/playground zip download. image-download.ts statically imported fflate, putting the zip library in the Image Studio's initial bundle although it is only needed in the click-triggered "download all" path. It now loads via import() in parallel with the image bytes, matching the repo's existing on-demand html-to-image/jspdf pattern.

Hydration correctness

  • Prevent Hydration Mismatch (rule 6.5) — apps/ui feature-page demos. generateMockActivityData() built the demo dataset for the SSR'd errors-monitoring and performance-monitoring demos with unseeded crypto randomness on every render, so the statically generated HTML and the hydrating client always disagreed on every stat — a guaranteed mismatch, and React 19 re-renders the whole subtree. The generator now uses a seeded PRNG (mulberry32), so server and client produce identical data; the demos are also stable across re-renders now (random-in-render also violates the purity the React Compiler assumes).

Interaction logic

  • Put Interaction Logic in Event Handlers (rule 5.8) — apps/playground Image Studio. The Flex service-tier toggle persisted its cookie through a useEffect watching the state (including a spurious write on every mount). The cookie write moved into the change handler; the effect is gone.

Considered and deliberately skipped

  • Anything involving export const dynamic — intentional runtime-env loading; excluded per repo policy (no finding touched one this round).
  • Lounge points query serialized behind /user/me (useLoungePoints, enabled: !!user — rules 1.5/4.3): a real one-round-trip waterfall for signed-in members, but the session cookie is httpOnly, so the clean fix threads a server-derived signed-in hint through a new context — auth-signal plumbing, not a surgical perf diff. Left for a dedicated change.
  • Playground API routes awaiting getUser() before parsing/validating the body (rule 1.4): parallelizing saves single-digit milliseconds of body-parse time, and auth-before-parse has a mild unauthenticated-DoS rationale; not worth the churn.
  • Stale-selection resets via effects on model switch (image controls/page, realtime voice — rule 5.1): pre-existing, commented as deliberate, one extra render on a rare interaction.
  • The new Image Studio code is otherwise clean — refcounted off-render preview decoding (useGalleryImage), parallel server fetches, React-Compiler-covered render paths. Likewise the new provider OG card routes (static lookups hoisted, Promise.all'd data), the apps/code formatting unification (module-level formatters, next/dynamic charts), and apps/docs (only content changed since the last audit; the AI search panel is already lazily imported).

Verification

  • turbo run build --filter=ui: ✓ Compiled successfully, and pnpm exec tsc --noEmit in apps/ui passes. The build's static-export stage fails in this sandbox on /compare/litellm/opengraph-image (SELF_SIGNED_CERT_IN_CHAIN — the TLS-intercepting proxy breaks that route's outbound fetch). This is environmental and pre-existing: the identical failure occurs on this sandbox before any of these changes, and was documented in the previous audit rounds.
  • Full pnpm build for the remaining workspaces (shared, models, playground, code, docs, admin, api, gateway) passes.
  • Bundle numbers above measured by scanning .next/static/chunks for catalogue markers and summing the chunks referenced by each route's client-reference manifest, before vs. after.
  • pnpm format clean; lint-staged (eslint + prettier) passed on every commit; image-download.spec.ts (5 tests) passes.
  • No visual changes intended: the compare hero plays the same entrance via CSS, the comparison tables render identical markup server-side, the footer renders identical markup, and the feature demos show the same style of mock data (fixed values instead of random ones). Per repo policy screenshots are only for dashboard UI changes; none of these screens changed appearance.

Note: this session pushes to its designated branch (claude/upbeat-johnson-3b76et) rather than the chore/react-bp-audit-2026-09-21 naming convention — same situation as previous audit rounds (#3518/#3784/#3865/#3966); future audits should locate this PR by title.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CepaNxrPkU6gcEXT23M7WL


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added the site footer to model listings and category pages, including text, vision, image, video, tools, embeddings, and web-search pages.
    • Service-tier preferences now persist immediately when changed.
  • Improvements

    • Updated comparison-page animations for smoother, more efficient rendering while preserving the existing presentation.
    • Image downloads continue to support ZIP creation with improved loading behavior.

The zip library is only needed inside the click-triggered
downloadImagesAsZip path, so import it dynamically instead of
shipping it in the image page's initial bundle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CepaNxrPkU6gcEXT23M7WL
The package has no side-effectful modules, but without the
declaration bundlers keep every barrel re-export, so one
TimeZoneProvider import from the root barrel shipped the whole
component library plus the model catalogue (~1MB of client JS)
on every ui route.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CepaNxrPkU6gcEXT23M7WL
The footer was a client component importing providers-catalog,
which uses the whole model catalogue to count active models per
provider - an import tree-shaking cannot remove, shipped on
every marketing page. The footer now reads getConfig() as a
server component, and the AllModels client wrapper takes it as
a slot from its server pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CepaNxrPkU6gcEXT23M7WL
HeroCompare shipped framer-motion on all seven /compare pages
for a one-shot entrance; it now uses the landing hero's
animate-hero-enter CSS utilities (with reduced-motion support)
and renders server-side. The seven static comparison tables had
"use client" with no hooks or handlers; without the directive
only the AuthLink leaves hydrate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CepaNxrPkU6gcEXT23M7WL
The SSR'd errors/performance monitoring demos generated their
dataset with unseeded crypto randomness during render, so the
prerendered HTML always hydration-mismatched and React 19
re-rendered the whole subtree. A seeded PRNG makes server and
client renders identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CepaNxrPkU6gcEXT23M7WL
The Flex tier toggle persisted its cookie via an effect
watching the state, which also wrote spuriously on mount. The
write moved into the change handler.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CepaNxrPkU6gcEXT23M7WL
@coderabbitai

coderabbitai Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Understand this PR’s impact

Explore downstream dependencies and potential security impact with Blast Radius.

View blast radius →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 342a11fd-fe7b-46c6-b23c-d3bd612a07e6

📥 Commits

Reviewing files that changed from the base of the PR and between e10899c and 09b249c.

📒 Files selected for processing (26)
  • apps/playground/src/components/playground/image-page-client.tsx
  • apps/playground/src/lib/image-download.ts
  • apps/ui/src/app/models/discounted/page.tsx
  • apps/ui/src/app/models/embeddings/page.tsx
  • apps/ui/src/app/models/image-to-image/page.tsx
  • apps/ui/src/app/models/page.tsx
  • apps/ui/src/app/models/reasoning/page.tsx
  • apps/ui/src/app/models/text-to-image/page.tsx
  • apps/ui/src/app/models/text/page.tsx
  • apps/ui/src/app/models/tools/page.tsx
  • apps/ui/src/app/models/video/page.tsx
  • apps/ui/src/app/models/vision/page.tsx
  • apps/ui/src/app/models/web-search/page.tsx
  • apps/ui/src/components/compare/hero-compare.tsx
  • apps/ui/src/components/landing/comparison-azure-foundry.tsx
  • apps/ui/src/components/landing/comparison-bedrock.tsx
  • apps/ui/src/components/landing/comparison-github-copilot.tsx
  • apps/ui/src/components/landing/comparison-litellm.tsx
  • apps/ui/src/components/landing/comparison-portkey.tsx
  • apps/ui/src/components/landing/comparison-vercel.tsx
  • apps/ui/src/components/landing/comparison.tsx
  • apps/ui/src/components/landing/footer.tsx
  • apps/ui/src/components/models/all-models.tsx
  • apps/ui/src/components/models/category-page.tsx
  • apps/ui/src/lib/mock-feature-data.ts
  • packages/shared/package.json
💤 Files with no reviewable changes (7)
  • apps/ui/src/components/landing/comparison-litellm.tsx
  • apps/ui/src/components/landing/comparison-portkey.tsx
  • apps/ui/src/components/landing/comparison-github-copilot.tsx
  • apps/ui/src/components/landing/comparison.tsx
  • apps/ui/src/components/landing/comparison-bedrock.tsx
  • apps/ui/src/components/landing/comparison-azure-foundry.tsx
  • apps/ui/src/components/landing/comparison-vercel.tsx

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


Walkthrough

The pull request updates playground persistence and image downloads, adds server-provided footers to model pages, converts landing components to server components, replaces hero motion wrappers with CSS animations, seeds mock data generation, and marks the shared package as side-effect free.

Changes

Playground updates

Layer / File(s) Summary
Service-tier persistence
apps/playground/src/components/playground/image-page-client.tsx
Service-tier changes now update state and the preference cookie in the same handler.
On-demand image ZIP loading
apps/playground/src/lib/image-download.ts
fflate loads dynamically in parallel with image blob requests.

Model page footer rendering

Layer / File(s) Summary
Footer component contract
apps/ui/src/components/landing/footer.tsx, apps/ui/src/components/models/all-models.tsx
Footer uses server-side configuration. AllModels accepts a supplied footer instead of rendering one locally.
Model page footer wiring
apps/ui/src/app/models/*.tsx, apps/ui/src/components/models/category-page.tsx
Model listing pages and the category page pass <Footer /> to AllModels.

Landing component rendering

Layer / File(s) Summary
Comparison server components
apps/ui/src/components/landing/comparison*.tsx
Comparison components no longer declare "use client".
CSS-based hero animation
apps/ui/src/components/compare/hero-compare.tsx
Hero entrance animations now use CSS classes instead of AnimatedGroup and motion variants.

Deterministic mock data

Layer / File(s) Summary
Seeded activity generator
apps/ui/src/lib/mock-feature-data.ts
Mock activity values now use a module-local mulberry32 generator with seed 0x11a0.

Shared package metadata

Layer / File(s) Summary
Package side-effects metadata
packages/shared/package.json
The shared package declares sideEffects: false.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Refactor

Suggested reviewers: steebchen

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 18 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request as performance and React best-practices fixes from a weekly audit. It matches the main changes, including server components, tree-shaking, dynamic imports…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 18 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@smakosh
smakosh merged commit 63f7572 into main Sep 21, 2026
36 of 37 checks passed
@smakosh
smakosh deleted the claude/upbeat-johnson-3b76et branch September 21, 2026 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants