Skip to content

Optimize performance: defer module loading and reduce polling intervals - #2424

Open
MusabShakeel576 wants to merge 1 commit into
masterfrom
claude/solid-app-login-performance-j47otj
Open

Optimize performance: defer module loading and reduce polling intervals#2424
MusabShakeel576 wants to merge 1 commit into
masterfrom
claude/solid-app-login-performance-j47otj

Conversation

@MusabShakeel576

Copy link
Copy Markdown
Contributor

Summary

This PR significantly improves app performance by deferring non-critical module evaluation, reducing unnecessary polling intervals, and optimizing data fetching patterns. The changes focus on reducing CPU usage on low-end devices and improving perceived responsiveness during login.

Key Changes

Module Loading Optimization (metro.config.js)

  • Enabled inlineRequires to defer module evaluation until first use, moving ~94% of cold start time from "JS Bundle Execution Before React Root" to when modules are actually needed
  • Configured nonInlinedRequires to preserve eager loading for ordering-sensitive polyfills and React runtime

Login Flow Optimization (hooks/useUser.ts)

  • Eliminated unnecessary safeAA derivation when the login response already contains the safe address
  • Made checkBalance and fetchPoints non-blocking by removing await, allowing navigation to proceed while these background queries complete
  • Added detailed comments explaining the rationale for each optimization

Public Client Caching (lib/wagmi.ts)

  • Implemented client caching to reuse viem public clients per chain instead of creating new instances on every call
  • Prevents continuous rebuilding of the same object graph and preserves request deduplication

Polling Interval Reductions

  • Token balances (useBalances.ts): 5s → 60s (19 parallel requests per run)
  • Vault queries (useVault.ts): 3s → 30s (fallback only; SSE drives live updates)
  • Price feeds (useTotalSavingsUSD.ts): 5s → 60s (native token prices)
  • Card details (useCardDetails.ts): 5s → 20s (Rain spending power)

All polling intervals now serve as fallbacks for SSE/real-time updates rather than primary data sources, reducing unnecessary requests from idle clients.

Request Timeout & Error Handling

  • Added timeout handling for Blockscout requests (10s) to prevent indefinite loading states
  • Created dedicated axios instance for Blockscout to prevent JWT token leakage to third-party explorers
  • Improved JWT error reporting to only warn on Solid API endpoints, reducing noise from legitimate public/third-party requests

Sentry Sampling Adjustments (app/_layout.tsx)

  • Reduced profiling sample rate: 50% → 10% (profiling has per-frame overhead)
  • Reduced session replay sample rate: 50% → 10% (preserves error replays at 100%)
  • Rationale: Half of sessions were carrying both profiling and replay simultaneously, competing with app performance on low-end Android devices

Query Optimization (hooks/useVault.ts)

  • Parallelized vault balance fetching across all vaults instead of sequential chain-by-chain processing
  • Reduces query time from sum of all chains to the slowest single chain

Implementation Details

  • All changes maintain backward compatibility and existing functionality
  • SSE invalidation ensures real-time updates are not delayed by increased polling intervals
  • Comments added throughout to explain performance trade-offs and design decisions
  • No breaking changes to public APIs

https://claude.ai/code/session_01UNaLEk8QE73jaaW6pbRef4

…onds

Three separate causes behind the slow launch, addressed in the order they
cost the user time.

Cold start. Sentry's app.start.cold spans put ~94% of a 4.7s cold start
in "JS Bundle Execution Before React Root" — no network involved, just
Metro evaluating every module in the graph before React renders, because
it hoists every require to the top of its module. Metro now defers each
require to its first point of use. Modules imported for their side
effects rather than their exports are pinned in nonInlinedRequires: the
polyfill chain index.js deliberately orders, plus NativeWind's CSS
interop. experimentalImportSupport is restated because returning this
object replaces Expo's own getTransformOptions instead of merging.

Sentry's profiler and mobile session replay both ran on half of all
sessions, alongside Amplitude's replay plugin, on hardware where every
session I sampled came from a low-end Android device. Both drop to a
tenth; replays on error are untouched.

Login. handleLogin awaited six things before navigating. Two of them do
not need to be there. safeAA existed only to read an address the login
response already returned, at the cost of a Turnkey round trip plus the
RPC calls toSafeSmartAccount makes — the smart-account client itself is
only needed to sign, and the flows that sign build it themselves. It is
now the fallback for an account with no address on file, which is the
same condition that already triggered the sync. checkBalance and
fetchPoints are started rather than awaited; both re-run in
usePostSignupInit once the app is up.

Polling. An idle home screen was issuing ~370 requests a minute: token
balances every 5s (19 parallel calls plus the token processing each
time), vault balances every 3s, card balance and native prices every 5s.
useActivitySSE already invalidates both tokenBalances and vault on a
balance event, so these are a fallback and now run like one. This is also
the most plausible source of the 210k upstream-connection 503s in Sentry,
where the app is on both ends of the loop.

Also: Blockscout had no timeout on either call path, so a stalled
explorer held the balance skeleton open indefinitely — both now carry the
same deadline as Alchemy. Blockscout and LI.FI moved off the global axios
instance, which attaches the user's access token to every request on
native. The interceptor's missing-token warning now fires only for calls
to our own services, instead of once per public request.

useTotalVaultBalance awaited each vault's chains in turn; one Promise.all
covers all of them. publicClient built a fresh viem client per call, seven
times per token-balance run, on a timer.

Corrects a wrong note in lib/wagmi.ts: multicall batching was never off.
@wagmi/core's createConfig defaults batch to { multicall: true }, so the
commented-out line was a no-op and uncommenting it would change nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UNaLEk8QE73jaaW6pbRef4
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
solid-app Ignored Ignored Aug 30, 2026 8:41am
solid-app-staging Ignored Ignored Aug 30, 2026 8:41am

Request Review

@claude

claude Bot commented Aug 30, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

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