Lighter: where its trading screen loses time, and what to fix

Measured from the Netherlands in a logged-in Chrome, October 2026.

In short. On a warm reload the screen is trade-ready after 4.8 s (median of 5 loads), 15 of 27 perpetual trading screens, possible rank 13 to 21; after an uncached reload 6.6 s. The largest bottleneck: An 8.7 MB vendor bundle at start-up. First change to try: Split vendor-ui-web3 so wallet and chain code loads after the trading screen; keep React and the trading UI in the first load.

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 reloadmedian 4.8 s, range 4.5 s–5.1 s; loads: 4.8 s, 5.1 s, 4.6 s, 4.5 s, 5.0 s
Uncached reloadmedian 6.6 s, range 6.4 s–7.1 s; loads: 7.1 s, 6.4 s, 6.6 s, 6.4 s, 6.6 s
Standing15 of 27 by median; possible rank 13 to 21. 2.3 s behind the fastest, Thalex (2.4 s): 1.9 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 ready5 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.

0 s1 s2 s3 s4 s
Order form2.0 s
Account panel2.7 s +0.7 s
Price2.8 s +0.1 s
Order book3.0 s +0.2 s
Recent trades3.1 s +0.1 s
Chart3.2 s +0.1 s
Trade-ready4.8 s +1.6 s settling

Stack

React, built with Vite; TradingView chart library (assets.lighter.xyz/library.js); Dynamic wallet SDK; AWS WAF bot-check scripts; QuickNode RPC.

Delivery

HTML and app files from S3 through CloudFront over HTTP/3, app files immutable for a year; market data from mainnet.zklighter.elliot.ai, served over HTTP/1.1 and without compression.

HostRoleCDN edge seenConnectResponse
app.lighter.xyzpageCloudFront Amsterdam13 ms13 ms
mainnet.zklighter.elliot.aimarket dataCloudFront Amsterdam14 ms11 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.

WhenWhat
0.02 sHTML arrives (CloudFront, HTTP/3).
0.16–0.70 sStartup scripts: vendor-ui-web3 (2.5 MB compressed, 8.7 MB unpacked), index (1.5 MB unpacked), dist (1.4 MB unpacked). vendor-react later starts 1.3 s of main-thread work.
0.91–1.63 sDynamic wallet assets: 769 KB, then a 2.1 MB icon file at 1.22 s; the same 2.1 MB file is fetched again at 3.16 s.
1.58–2.42 sMarket-data REST calls to mainnet.zklighter.elliot.ai, sent uncompressed (365 KB and 80 KB); several others take 1.3 s each (2.09–3.38 s).
1.60 sFirst paint.
1.93–2.95 sAWS WAF bot-check scripts load: jsapi.js (194 KB unpacked) and challenge.js (681 KB unpacked).
2.07 sThe live feed opens; handshake done at 3.07 s, 1.0 s later; first message at 3.40 s.
4.40 sPrice shows, 1.0 s after the first feed message; order book at 4.61 s.
5.44 sChart drawn: trade-ready in this load. 15 long tasks took 2.5 s of main thread.

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.

WhenWhat
0.0 sHTML arrives
0.6 sFirst market-data request
2.4 sFirst frame sent on a live feed
2.5 sOrder form ready
2.5 sFirst frame received on a live feed
3.0 saccount-table ready
3.0 saccount-info ready
3.1 sPrice ready
3.1 sChart ready
3.2 sOrder book ready
3.3 sRecent trades ready
5.5 sTrade-ready

255 requests before trade-ready; 10 long main-thread tasks (over 50 ms) before trade-ready, longest 263 ms; 103 script requests before the first panel; 23 requests over HTTP/1.1.

Request waterfalldiagnostic capture 1 October, uncached

63 of the 144 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 requestFont, media, other fileImagewaiting for the serverdownloading

0 s0 s1 s1 s2 s2 s3 s3 s4 s4 s5 s5 s1app.lighter.xyz3 kB390 ms2app.lighter.xyz/index-BhW…411 kB420 msLI-13app.lighter.xyz/vendor-ui…2.5 MB539 ms4app.lighter.xyz/dist-YaNZ…278 kB421 ms5app.lighter.xyz/Notificat…13 kB403 ms6app.lighter.xyz/shared-DG…44 kB413 ms7app.lighter.xyz/useAgentC…16 kB407 ms8app.lighter.xyz/Modal-Bax…109 kB414 ms9app.lighter.xyz/NewVersio…16 kB409 ms10app.lighter.xyz/Banners-B…120 kB414 ms11app.lighter.xyz/useHorizo…7 kB405 ms12app.lighter.xyz/TradeHist…7 kB407 ms13app.lighter.xyz/SavedFeeC…37 kB410 ms14app.lighter.xyz/types-B6R…1 kB403 ms15app.lighter.xyz/useTokenC…2 kB405 ms16app.lighter.xyz/ChartSkel…3 kB408 ms17app.lighter.xyz/ActionBut…2 kB402 ms18app.lighter.xyz/lib-BO3b3…34 kB410 ms19app.lighter.xyz/Textarea-…1 kB405 ms20app.lighter.xyz/timeAgo-B…1 kB405 ms21app.lighter.xyz/useGuildQ…3 kB406 ms22app.lighter.xyz/useSignMe…2 kB407 ms23iconic.dynamic-static-ass…976 kB406 ms24app.dynamicauth.com553 ms25mainnet.zklighter.elliot.…83 kB847 ms26mainnet.zklighter.elliot.…375 kB836 ms27mainnet.zklighter.elliot.…7 kB864 ms28mainnet.zklighter.elliot.…1 kB864 ms29mainnet.zklighter.elliot.…1 kB1142 ms30app.lighter.xyz/Inter-DiV…353 kB51 ms31mainnet.zklighter.elliot.…767 B992 ms32o4507652019322880.ingest.…20 B938 ms33o4507652019322880.ingest.…20 B936 ms34data-api.fun.xyz386 B852 ms35data-api.fun.xyz388 B851 ms36data-api.fun.xyz385 B848 ms37mainnet.zklighter.elliot.…763 B1295 ms38mainnet.zklighter.elliot.…41 kB1293 ms39mainnet.zklighter.elliot.…802 B1290 ms40data-api.fun.xyz383 B840 ms41assets.lighter.xyz2 kB594 ms421a79ba1d4fa2.edge.sdk.aws…312 kB532 ms43iconic.dynamic-static-ass…976 kB209 ms44app.dynamicauth.com610 B723 ms45mainnet.zklighter.elliot.…805 B1102 ms46mainnet.zklighter.elliot.…776 B1149 ms47shy-frosty-sponge.quiknod…541 B645 ms48frog.fun.xyz149 B627 ms49api.fun.xyz203 B621 ms50mainnet.zklighter.elliot.…805 B692 ms51app.dynamicauth.com761 B507 ms52data-api.fun.xyz388 B503 ms53data-api.fun.xyz387 B606 ms54data-api.fun.xyz382 B605 ms55data-api.fun.xyz382 B603 ms56mainnet.zklighter.elliot.…60 kB462 ms57mainnet.zklighter.elliot.…40 kB489 ms58api.sunbreak.comopen59mainnet.zklighter.elliot.…1 kB412 ms60mainnet.zklighter.elliot.…60 kB521 ms61mainnet.zklighter.elliot.…40 kB519 ms62mainnet.zklighter.elliot.…60 kB515 ms63mainnet.zklighter.elliot.…40 kB512 msprice 4.4 sorder book 4.6 schart 5.4 s
Download this figure as an image:

Bottlenecks and fixes, largest firstdiagnostic capture 1 October, uncached

LI-1An 8.7 MB vendor bundle at start-up.grade B
Evidence
vendor-ui-web3-Ce0k8c-G.js is 2.5 MB compressed and 8.7 MB unpacked (0.16–0.70 s), next to index-BhWkk5MS.js (1.5 MB unpacked) and dist-YaNZUMeE.js (1.4 MB); 1.3 s of main-thread work starts in vendor-react; 15 long tasks take 2.5 s before trade-ready. On the 3 October warm reload, with the files cached, one task still runs 263 ms at 0.95 s.
Gap observed
2.5 s of main-thread time across 15 long tasks
Holds back
Start-up as a whole; which panel waits for this work is not established.
Waterfall rows
row 3: 2.5 MB received, 8.7 MB unpacked, public, max-age=31536000, immutable
Fix
Split vendor-ui-web3 so wallet and chain code loads after the trading screen; keep React and the trading UI in the first load.
Where
Vite manualChunks configuration.
Verify
DevTools → Coverage: unused bytes in vendor-ui-web3 on first load.
LI-2Trade-ready comes 1.5 s after the last panel has its data: the chart is still animating.grade B
Evidence
Campaign warm loads: the last panel is ready at 3.3 s (median) and trade-ready follows at 4.8 s; the chart is last in 3 of the 5 loads and the recent-trades panel in 2. In the recorded load the wait is 2.2 s: the chart has 6 running animations in 23 samples, the last at 5.41 s, and trade-ready is at 5.51 s. The same animation condition fails in all 10 timed loads we could replay. In some campaign loads the chart also changes size (744×600 to 451×469) during the wait, or a cover over the chart is still up. Three more pairs of candle requests go out after the chart has its first data (at 3.38, 3.90 and 4.43 s in the recorded load, each taking about 0.26 s), which may be what the chart is still showing a loading state for. In the slowest uncached load the wait is 2.2 s (trade-ready 7.10 s).
Gap observed
1.5 s between the median time of the last panel and the median trade-ready time (two separate medians); 2.2 s inside 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. Check whether the later candle requests (/api/v1/candles and /api/v1/markPriceCandles) keep the loading state up, and give the chart its final size from the start.
Where
The chart panel and its loading states.
Verify
DevTools → Animations during a reload: nothing in the chart container animates after the candles appear.
LI-3Market data is sent uncompressed over HTTP/1.1, and the feed takes 1 s to connect.grade B
Evidence
mainnet.zklighter.elliot.ai responses carry no content-encoding (a 374 KB response is sent as 375 KB) and use HTTP/1.1: 19 requests in the 1 October capture, 23 before trade-ready in the 3 October recording. The feed handshake takes 1.0 s (2.07–3.07 s), with the first message at 3.40 s. The timed loads name the start-up requests to this host: /api/v1/orderBookDetails, /tokenlist, /assetDetails, /systemConfig and /layer1BasicInfo, each taking 0.3 to 0.6 s; the 374 KB response is probably orderBookDetails.
Gap observed
1.0 s from creating the feed socket to its handshake
Holds back
Not established: the captures do not show which panel waits for this.
Fix
Enable gzip or brotli and HTTP/2 on mainnet.zklighter.elliot.ai, and check why the socket handshake takes 1 s.
Where
API gateway or CDN in front of the market-data service.
Verify
DevTools → Network: content-encoding on /api/v1/orderBookDetails and protocol h2 or h3; WS timing.
LI-4Bot-check and wallet assets load before the market is on screen.grade C
Evidence
1 October capture: AWS WAF scripts jsapi.js (0.2 MB unpacked) at 1.93 s and challenge.js (0.7 MB) at 2.42–2.95 s; the Dynamic wallet list (0.8 MB) at 0.91 s and the Dynamic icon file (2.1 MB unpacked, 1.0 MB compressed, cached for 10 minutes) twice, all before the price at 4.40 s. The timed loads add 6 calls to data-api.fun.xyz/v1/initialize and 4 to /v1/rgstr between 2.0 and 4.6 s, and version.json fetched twice.
Gap observed
1.02 s across the AWS WAF script-loading window
Holds back
Not established: the captures do not show which panel waits for this.
Fix
Load the WAF challenge only when the protected action needs it, the wallet icons once and on wallet open, and initialise the fun.xyz SDK once.
Where
WAF integration, Dynamic SDK set-up and the fun.xyz initialisation.
Verify
DevTools → Network: no awswaf or iconic request before the first price paint; one "fun.xyz/v1/initialize" request.
LI-5One font file is 352 KB.grade B
Evidence
1 October capture: app.lighter.xyz/Inter-DiVDrmQJ.woff2 is 352 KB (requested at 1.60 s); the second font, Geist Mono, is 23 KB. Font files limited to the characters and weights a page uses are typically much smaller.
Measured
one 352 KB font file
Holds back
Not established: the captures do not show which panel waits for this.
Fix
Ship a subset of Inter with only the character ranges and weights the app uses.
Where
The font files and @font-face rules in the app build.
Verify
DevTools → Network, filter "woff2": the Inter file is clearly smaller than 352 KB.
Likely saving
Small on a reload (the file is cached for a year); less to download on a first visit
LI-6Two account lookups fail with HTTP 400 on every load.grade B
Evidence
All 20 older timed loads: /api/v1/account and /api/v1/accountsByL1Address both return HTTP 400. In one uncached load the first takes 0.6 s (3.00–3.59 s); in a warm load the second takes 0.37 s (4.52–4.89 s). The 1 October capture shows two HTTP 400 responses as well. The request parameters are not recorded, so the cause is unknown.
Measured
two account requests answered with HTTP 400
Holds back
Not established: the captures do not show which panel waits for this.
Fix
Check which parameters these two requests are sent with at start-up and why the server rejects them.
Where
The account bootstrap that calls /api/v1/account and /api/v1/accountsByL1Address.
Verify
DevTools → Network, filter "status-code:400": none during a page load.
Likely saving
None measured

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.