- Evidence
- 1 October capture: six REST requests to api.lyra.finance start at 1.10–1.15 s and finish at 2.75–3.28 s; the slowest waits 1.96 s for its first byte. Its three sockets need 0.92, 1.28 and 1.63 s to connect. Price and book wait until 3.02 s. Three of the six carry short cache lifetimes (max-age 300, 180 and 10 s) and the others none; every one is marked cf-cache-status: DYNAMIC, so the CDN passes each request to the origin. The capture does not show where the origin is.
- Gap observed
- 0.9 to 1.6 s for each of three socket connections; 1.6 to 2.2 s for each REST call
- Holds back
- Price (median 1.7 s in the timed loads), book (median 3.3 s in the timed loads).
- Waterfall rows
- row 20: 12 kB received, 135 kB unpacked, max-age=300; row 22: 2 kB received, 5 kB unpacked, max-age=10; row 23: 2 kB received, 13 kB unpacked, no cache header; row 24: 4 kB received, 43 kB unpacked, max-age=180; row 26: 179 B received, no cache header; row 27: 259 B received, 270 B unpacked, no cache header; row 43: 12 kB received, 657 kB unpacked, max-age=30
- Fix
- Give the public market-data responses a short cache lifetime and let the CDN use it, and measure where the 2 s goes (connection, server, queue) before moving servers.
- Where
- Cloudflare cache rules for api.lyra.finance public endpoints; api.lyra.finance hosting.
- Verify
- DevTools → Network → Headers on a public api.lyra.finance request: cf-cache-status HIT; Timing: waiting for server response under 200 ms.
Derive: where its trading screen loses time, and what to fix
Measured from the Netherlands in a logged-in Chrome, October 2026.
Three measurements appear on this page, and their times differ. Rank and results come from the campaign's timed reloads. The diagnostic capture and the recorded reload are single loads made with recording switched on, which slows the page: read them for the order of events and the gaps, not for totals.
Resultscampaign: 5 warm and 5 uncached reloads
| Warm reload | median 5.1 s, range 4.7 s–5.8 s; loads: 5.8 s, 4.7 s, 5.1 s, 5.2 s, 4.8 s |
| Uncached reload | median 8.3 s, range 7.1 s–8.9 s; loads: 8.7 s, 7.3 s, 8.3 s, 8.9 s, 7.1 s |
| Standing | 20 of 27 by median; possible rank 13 to 23. 2.6 s behind the fastest, Thalex (2.4 s): 2.1 times as long. The possible rank is the range the measurements leave open: it counts only the exchanges that clearly beat this one and those it clearly beats. Exchanges whose ranges overlap cannot be told apart. |
| Loads ready | 5 of 5 warm, 5 of 5 uncached |
Where the time goescampaign medians, warm reload
Panels in the order they first showed real, usable data on a warm reload. The coloured part of each row is the time that panel added after the one before it. The last row is trade-ready: every panel is ready and the page has stopped moving.
Stack
Next.js on Vercel; TradingView chart library (library.js); WalletConnect / web3modal, Alchemy RPC; Intercom.
Delivery
HTML from Vercel marked private, no-store, so never cached; market data from api.lyra.finance, whose responses the CDN does not cache (cf-cache-status: DYNAMIC), although some declare a short lifetime; the chart library is served with max-age=0, while the app's own script chunks are cached for a year.
| Host | Role | CDN edge seen | Connect | Response |
|---|---|---|---|---|
| app.derive.xyz | page | — | 13 ms | 28 ms |
| api.lyra.finance | market data | Cloudflare Amsterdam | 13 ms | 260 ms |
| api.derive.xyz | market data | Cloudflare Amsterdam | 14 ms | 256 ms |
Median of up to 10 attempts from the test machine. Response time includes the server's own time, so it is not a distance.
What happened, in orderdiagnostic capture 1 October, uncached
From one uncached diagnostic load recorded on 1 October 2026 (cache disabled, browser extensions active), so totals are slower than the campaign's timed loads; file names, sizes, headers, order of events and main-thread times are what the findings rely on. A main-thread time given for a script counts all the work that starts in that script, including code it calls in other files, so it shows where start-up work begins, not what one library costs. Endpoint paths were masked when the capture was saved.
| When | What |
|---|---|
| 0.02 s | HTML arrives (Vercel). |
| 0.18–0.88 s | Next.js chunks (up to 401 KB unpacked each). |
| 0.50 s | Page header shows the market. |
| 1.10–3.28 s | REST calls to api.lyra.finance take 1.6–2.2 s each. |
| 1.24 s | Three separate sockets to api.lyra.finance open; their handshakes finish at 2.15, 2.51 and 2.87 s (0.9–1.6 s); first messages at 2.66–3.28 s. |
| 1.32 s | First paint. |
| 2.94–4.62 s | A 657 KB (unpacked) api.lyra.finance response takes 1.7 s. |
| 3.02 s | Price and order book show. |
| 3.69 s | The chart library (library.js, 2.5 MB unpacked, max-age=0) is requested. |
| 5.21 s | Chart drawn: trade-ready in this load. |
Recorded warm reload, 3 October
One warm reload recorded with Chrome's performance trace. Recording slows the page, so read the order and the gaps, not the total.
| When | What |
|---|---|
| 0.2 s | HTML arrives |
| 1.2 s | First market-data request |
| 1.4 s | Price ready |
| 1.9 s | Order form ready |
| 2.2 s | First frame sent on a live feed |
| 2.2 s | balances ready |
| 2.6 s | First frame received on a live feed |
| 3.0 s | Recent trades ready |
| 3.2 s | Order book ready |
| 3.6 s | Chart ready |
| 4.6 s | Trade-ready |
282 requests before trade-ready; 10 long main-thread tasks (over 50 ms) before trade-ready, longest 100 ms; 78 script requests before the first panel; 0 requests over HTTP/1.1.
Request waterfalldiagnostic capture 1 October, uncached
67 of the 262 requests (the page itself, the longest and the largest) started before market data was ready in the 1 October uncached capture, in start order. The outlined part of a bar is waiting for the server; the solid part is downloading. Rows are numbered so a request can be pointed to. The numbers on the right are the size received and the total time. Vertical lines mark when a panel had data in this capture. Shaded rows are named in a finding below, tagged with its ID.
PageScriptData requestwaiting for the serverdownloading
Bottlenecks and fixes, largest firstdiagnostic capture 1 October, uncached
- Evidence
- 1 October capture: /tradingview/bundles/library.257d05210b16f5ddbfc2.js (2.5 MB unpacked) is requested at 3.69 s, after price and book at 3.02 s; chart at 5.21 s. The file name already carries a content hash, yet it is served with max-age=0, must-revalidate, so the browser asks the server about it on every visit; its stylesheet has the same header. The app's own script chunks are served with max-age=31536000, immutable. In the slowest warm campaign load (5.8 s) the chart canvas appears at 2.95 s, disappears from 3.16 to 3.77 s and comes back at 3.94 s before the chart passes at 4.37 s, which suggests the chart widget is built, torn down and built again.
- Gap observed
- 0.67 s between price/book and the chart-library request
- Holds back
- Chart (median 4.2 s in the timed loads).
- Fix
- Serve /tradingview/bundles/ with cache-control: max-age=31536000, immutable, like the app's own chunks, add a preload link for library.js on the trading route, and check whether the chart widget is mounted twice.
- Where
- Vercel headers for /tradingview/bundles/; the chart loader and the HTML head.
- Verify
- DevTools → Network: library.js shows "(disk cache)" on reload and starts before 1.5 s.
- Evidence
- Three WebSocket connections to api.lyra.finance open at 1.24 s, each paying its own slow handshake.
- Gap observed
- 0.9 to 1.6 s for each of three separate socket handshakes
- Holds back
- Not established: the captures do not show which panel waits for this.
- Fix
- Multiplex all subscriptions over one socket.
- Where
- Feed client.
- Verify
- DevTools → Network → WS: one api.lyra.finance socket.
- Evidence
- An api.lyra.finance response from 2.94 to 4.62 s (657 KB unpacked, 12 KB compressed, max-age=30) overlaps the wait for the chart, drawn at 5.21 s. By host and order in the timed loads it is probably /public/get_instruments; that match is not measured.
- Gap observed
- 1.68 s for the 657 KB API response
- Holds back
- Probably the chart (median 4.2 s in the timed loads): the timing points to it, but the captures do not show that this panel waits for it.
- Fix
- Request only the instruments the first screen needs, or send the request after the chart has started loading.
- Where
- The /public/get_instruments call to api.lyra.finance (probable match).
- Verify
- DevTools → Network, filter "get_instruments": response size and start time.
- Evidence
- All 20 older timed loads send /private/get_open_orders twice; in the loads checked in detail, /private/get_trigger_orders and /private/get_algo_orders are also each sent twice, about 15 ms apart (uncached: 2.590 and 2.603 s for open orders), each taking about 0.75 s. The request bodies are not recorded, so the two could differ in their parameters.
- Measured
- three order-list requests sent twice each
- Holds back
- Not established: the captures do not show which panel waits for this.
- Fix
- Share one request per order list between the components that need it.
- Where
- The callers of get_open_orders, get_trigger_orders and get_algo_orders.
- Verify
- DevTools → Network, filter "private/get_": one request per address on a load.
- Likely saving
- Small: three fewer requests of about 0.75 s each
- Evidence
- 1 October timed loads: 13 requests to app.derive.xyz/api/ingest between 2.9 and 4.6 s (uncached) and between 2.5 and 4.3 s (warm), all before trade-ready. /api/intents takes 1.48 s to return 13 bytes and /api/rewards/fees 1.0 s for 83 bytes. Intercom's widget script loads at 1.80 s and opens its own socket at 3.34 s. Whether any panel waits for these is not visible from outside.
- Measured
- 13 tracking requests before trade-ready; 1.48 s for a 13-byte response
- Holds back
- Not established: the captures do not show which panel waits for this.
- Fix
- Batch the /api/ingest events and send them after trade-ready; load Intercom after trade-ready; check why /api/intents and /api/rewards/fees take over a second.
- Where
- The analytics client behind /api/ingest; the Intercom loader; the handlers for /api/intents and /api/rewards/fees.
- Verify
- DevTools → Network, filter "ingest" and "intercom": no requests before the chart is drawn.
- Likely saving
- Unknown
- Evidence
- All 10 campaign loads: in the wait before trade-ready, counted from when the page's main parts are in place, the chart panel still has a running animation: 0.76 to 0.87 s on the 5 warm loads and 1.90 to 2.60 s on the 5 uncached ones (in the slowest, the main parts are in place at 6.30 s and trade-ready comes at 8.89 s). The chart itself passes its check a little later than that starting point, so the wait after the chart is shorter. Recorded warm load: chart at 3.57 s, trade-ready at 4.65 s, with the animation running in all 11 samples in between.
- Gap observed
- 0.76 to 0.87 s before trade-ready in the five warm loads and 1.9 to 2.6 s in the five uncached loads, counted from when the page's main parts are in place; 1.07 s after the chart in the recorded load
- Holds back
- Trade-ready itself: the page counts as ready only when nothing in a panel is still animating or loading.
- Fix
- Record a reload with DevTools → Animations and find what still animates in the chart after the candles are drawn, for example a toolbar loading spinner or a fade-in; end it when the data is in.
- Where
- The chart panel and its loading states.
- Verify
- DevTools → Animations during a reload: nothing in the chart container animates after the candles appear.
- Likely saving
- Up to ~0.8 s warm and ~2 s uncached
Grade B: the capture shows the problem directly. Grade C: the order of events in the capture points to it; the dependency is for the team to confirm. The order is our judgement of each finding's effect on trade-ready, largest first; it is not sorted by gap. A gap is the time between two observed events or the time one piece of work takes; gaps overlap and do not add up. Where a finding is about size or count and no time was measured, the row says what was measured instead. Holds back names the panel the finding delays. Nothing here is graded higher, because no fix was confirmed by changing a page and measuring again.
Savings are estimates from the timeline, not measured after a change. They overlap, so they do not add up; the summary gives the combined estimate.
How this was measured
Timings: 5 warm and 5 uncached reloads in a seeded random order across all exchanges, each judged against a checklist frozen for this page before the campaign; trade-ready is the first moment every checklist panel shows real data, nothing is still loading, and the layout holds still for 0.5 s. The findings on this page come from separate recorded loads, which are never part of the timings. Full method.