Speed up mobile cold-start to first content#14259
Open
raymondjacobson wants to merge 1 commit intomainfrom
Open
Speed up mobile cold-start to first content#14259raymondjacobson wants to merge 1 commit intomainfrom
raymondjacobson wants to merge 1 commit intomainfrom
Conversation
|
f766778 to
02af354
Compare
The navigator + RootScreen previously waited on a network re-confirmation of
the locally-cached account before rendering, gating the entire UI on a
~2.7s API call. Both gates now release as soon as we know the answer from
local storage; the server check continues in the background.
- Remove the accountStatus return-null gate in NavigationContainer.
hasAccount/accountHandle come from useCurrentAccount's synchronous
placeholderData (sourced from local storage), so getStateFromPath has
the values it needs from the first render.
- Drop the RootScreen isLoaded gate; render Stack.Navigator immediately,
derive showHomeStack from the cached user, dismiss the native splash on
first commit. Move the player-reset side effect to useEffectOnce. Chat
connect still waits on accountStatus === SUCCESS.
- Add an in-memory sync cache to LocalStorage (preloadAccountSyncCache)
because the existing getJSONValueSync silently returned null on RN —
AsyncStorage.getItem returns a Promise that the catch-and-return-null
path swallowed. Mobile preloads the account/user keys at module load
and App gates render on that ~30-50ms read so
useCurrentAccount.placeholderData has real values for the first render
(no sign-on flash on a logged-in cold start).
- Pass a TrendingLineupSkeletons (TrackLineup with empty data + isPending)
to ScreenSecondaryContent so the deferred-render window shows real
skeletons instead of a blank page. Skip the iOS FadeIn entrance when a
skeleton is provided to avoid the opacity-0 flash on swap-in.
- Memoize TrendingScreen's header function so Screen.setOptions doesn't
re-set the header on every parent re-render, which was remounting
AccountPictureHeader and re-firing the profile-picture image fetch.
- Pass TrendingHeader (the Tracks/Underground/Winners pills) as both the
skeleton and children of ScreenPrimaryContent. Same JSX in same position
reconciles to the same instance across the isScreenReady flip — the
pills appear immediately and don't pop in / shift content.
- Stabilize Artwork's imageSource by memoizing on URI string instead of
recomputing { uri } each render. Without this, AnimatedImage saw a new
source object every parent re-render and reloaded the image (visible as
a flash on cold start, especially the top-left profile picture).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
02af354 to
ecc26cb
Compare
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
Cold-start to first Trending content on mobile was ~7.6s; this PR drops it by removing two redundant gates that blocked the navigator on a network re-confirmation of an already-cached account, plus a few skeleton/flash fixes that surfaced once content rendered earlier.
Headline numbers (release build, iOS sim, signed-in cold start):
nav_container.ready: 5,190ms → ~3,400ms (-1.7s)trending.fetch.start: 5,557ms → ~4,800ms (-700ms)trending.fetch.end: 7,604ms → ~6,700ms (-900ms)The PR is paired with the prior `Add perf marks` commit which adds the instrumentation used to measure these segments.
Changes
Remove redundant account-status gates
NavigationContainerno longer returnsnullwhileuseAccountStatusisIDLE/LOADING. The deep-link handler readshasAccount/accountHandlefromuseCurrentAccount's sync placeholder, which is populated from local storage — no need to wait on the network.RootScreen.isLoadedremoved.Stack.Navigatorrenders immediately.showHomeStackis derived from cached values that are present on the first render. The native splash dismisses on first commit. Player reset moved touseEffectOnce. Chat connect still waits onaccountStatus === SUCCESS.Make sync local-storage actually sync on RN
LocalStorage.getJSONValueSyncwas silently returningnullon mobile becauseAsyncStorage.getItemreturns a Promise — the catch-and-return-null path swallowed it. Added an in-memory sync cache (preloadSyncKeys/preloadAccountSyncCache) and a tiny gate inside<App>that waits for the ~30–50ms preload before mounting. Without this, removing the gates would flash the sign-on screen on every cold start.Skeletons + flash fixes
TrendingScreennow passes aTrendingLineupSkeletons(TrackLineupwith empty data +isPending) to<ScreenSecondaryContent>so the deferred-render window shows real-looking skeletons instead of an empty page.ScreenSecondaryContentskips the iOSFadeInwhen a skeleton is provided — the skeleton is already the visual placeholder, fading the swap-in caused a brief opacity-0 flash.TrendingScreenmemoizes itsheaderfunction. Without it, every parent re-render produced a new function reference,Screen.setOptions({ header })re-ran, and React Navigation rebuilt the header — remountingAccountPictureHeaderand re-firing the profile-picture image fetch (visible flashing on cold start).Test plan
audius://trending,audius://feed, profile URLs) routes correctlyFollow-ups
useAccountStatusto confirm nothing relies on the now-removed gating semantics🤖 Generated with Claude Code