Performance: Parallelize independent boot API requests (server status/config + public extensions) - #23020
Conversation
UmbServerConnection.connect() awaited server status and configuration sequentially even though they are independent reads; run them with Promise.allSettled so both errors surface (the app cannot function without either) while saving a round-trip. During app startup, public (login) extension registration was awaited before the auth flow; kick it off in parallel and await it only before routing, where the login screen actually needs it. Each serialized call costs a full management-API round-trip, which is negligible locally but ~150 ms each on high-latency (e.g. Cloud) hosts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @iOvergaard's task in 4m 31s —— View job PR ReviewTarget: Parallelizes two independent boot-sequence request pairs:
Suggestions
Approved with Suggestions for improvementGood to go, but please carefully consider the importance of the suggestions. The parallelization logic is sound: |
There was a problem hiding this comment.
Pull request overview
Parallelizes two independent boot-time API call chains in the backoffice to reduce time-to-interactive on high-latency hosts, without altering observable behaviour or error handling.
Changes:
UmbServerConnection.connect()runs#setStatus()and#setServerConfiguration()viaPromise.allSettled, then rethrows a single error or anAggregateErrorif any failed.- App startup kicks off
registerPublicExtensions()before the auth flow and only awaits it just before routing, overlapping it with the token request.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/Umbraco.Web.UI.Client/src/packages/core/server/server-connection.ts | Run status and configuration fetches concurrently; aggregate errors so neither rejection is lost. |
| src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts | Start public extension registration eagerly and await it just before routing, in parallel with auth init. |
Move isConnected.setValue(true) out of #setStatus() into connect() after the allSettled check, so the observable never reflects a partially established connection when configuration fails but status succeeded. Addresses review feedback on the parallelized connect(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the review.
|
AndyButland
left a comment
There was a problem hiding this comment.
All looks good @iOvergaard. Code changes make sense to consolidate the #isConnected setting only after both requests succeed, and the parallelisation makes sense.
I've verified login, backoffice rendering and noted via dev tools that the configuration and status request now no longer run sequentially.
Reconciles app.element.ts with #23020 (parallelized public extensions). Kept the boot gate (await the app-entry-point initializer before routing) and restored a blocking inline `await registerPublicExtensions()` instead of the parallelized deferred form — a marginally slower but more robust boot, identical to the release/17.5.0 fix (no empty-first-pass timing reliance). extension-initializer-base.ts, the unit test, the acceptance test and playwright config merge cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Two boot-sequence requests that were awaited serially, but are independent, now run in parallel:
UmbServerConnection.connect()—server/statusandserver/configurationwere awaited one after the other. They share no data, so they now run viaPromise.allSettled.allSettled(notPromise.all) is deliberate: both are required for the backoffice to function, so any failure is collected and rethrown for the existing error-page handling, and a rejection in one call never leaves the other's rejection unhandled.app.element.ts) — public (login) extension registration was fully awaited before the auth flow. It's now kicked off up front and awaited only just before routing (where the login screen actually needs it), so itsmanifest/publicfetch overlaps the auth/token request.Why
Each serialized hop is one management-API round-trip. That's negligible on localhost (~14 ms) but ~150 ms each on high-latency hosts (e.g. Umbraco Cloud), where they sit on the critical boot path before the backoffice is interactive. Tracing a document-workspace load on Cloud showed these as a serial chain (
status → configuration → … → public extensions), so parallelizing the independent links shortens time-to-interactive on first paint with no behavioural change.Notes
registerPublicExtensionsusestryExecute, which resolves rather than rejects).How to test
server/statusandserver/configurationshould now overlap, as shouldmanifest/publicwith the token request.🤖 Generated with Claude Code