What we measured building a fast Arabic-English website
The short answerWe built nexdune.com in Arabic and English and measured its speed at the key steps instead of guessing. The biggest gain came from starting the 3D scenes only after the page had loaded: on the English page, Largest Contentful Paint (LCP) fell from 2,771 to 2,426 ms, in one trace before the change and one after, on an emulated phone. The thing we first blamed for the slower Arabic page, balanced heading lines, made no real difference once we measured again, so we kept it.
How we measured
Every figure here comes from the build reports we write after each step, with the tool, the settings and the date. We measured the production build running on our own machine, not the development server. These are the three tools we relied on:1
| Tool | Settings | What it measured |
|---|---|---|
| Browser performance trace | Phone emulation, 4× slower CPU, slow 4G, empty cache | LCP and layout shift (CLS), before and after |
| Lighthouse 12.8.2 | Mobile, production build served locally without compression | Score, LCP, Total Blocking Time (TBT) |
| Playwright | 4× slower CPU, about 1.6 Mbps, 150 ms latency, 8 runs per variant, interleaved | Two versions of the same page side by side |
To be plain about it: the machine was busy with other programs while we measured, with 36–60 % CPU load that was not ours. So we say how many runs stand behind each number: the LCP before and after moving the 3D is one trace on each side, while the Arabic heading comparison alternated the two variants on the same machine at the same time. The site is not live yet; once it is, it will be served compressed, so its numbers will differ from these.1
Starting the 3D after load: the biggest gain
The home page has four 3D scenes. Before the change, the three.js library (about 660 KB), one 3D model (1.96 MB) and the WebGL setup all loaded before the page finished loading, competing with the text a visitor sees first. We changed that: no 3D scene starts until the load event has fired.1
| Page | LCP before | LCP after | CLS after |
|---|---|---|---|
| English /en/ | 2,771 ms | 2,426 ms | 0.00 |
Google’s threshold for a good LCP is 2.5 seconds from when the page starts loading.5 In this trace the new figure is under it, with little to spare. Our largest element is the main heading itself, text rather than an image, and the server answered in 13–15 ms. So the delay was in drawing the page on the device, not in the network.1
Arabic headings: a hypothesis that did not hold
In the first measurements the Arabic page was slower. Tracing it, the first layout took 1.58 s on the slowed CPU, against 6 ms for a later re-layout, on a page of about 1,100 elements. When we removed the six heading rules that balance line lengths (text-wrap: balance) from a test copy, the text appeared in 2.2, 2.0 and 2.1 s.1
The idea made sense. Balancing spreads words evenly across lines, which is costly enough that browsers limit it to short blocks, six lines or fewer in Chromium.7 But that measurement was taken before the 3D moved after load. So we measured again on the current build, alternating the two variants:2
| Variant | Lighthouse LCP (median of 5) | Playwright first contentful paint (median of 8) |
|---|---|---|
| Balanced headings | 2156 ms | 1212 ms |
| No balancing | 2206 ms | 1228 ms |
The difference sits inside the run-to-run noise, which was over 300 ms. Worse, removing the balancing hurt the design: on phones 320 to 376 px wide, the Arabic main heading ended with one word alone on its last line. We checked every width from 320 to 432 px in 4 px steps so we would not miss a case.2
Decision: we kept the balancing. The lesson we now apply on every project: an old number is not enough to act on. Measure the current version before you change anything, because the cause can move after every change.
What still slows the page
We ran Lighthouse on the production build. This is the first run, the only one that finished with complete values for both pages:1
| Page | Score | LCP | TBT | CLS |
|---|---|---|---|---|
| Arabic / | 70 | 2.0 s | 1,950 ms | 0.004 |
| English /en/ | 72 | 1.8 s | 1,580 ms | 0.002 |
Our target of 90 was not met in these measurements, and we are not hiding it. Layout shift was excellent in every run. LCP met 2.5 s only in this first run; on the busy machine the other runs gave 3.4–8.8 s. The bigger problem is Total Blocking Time, the total time the page cannot respond to a click, tap or key press.6 It comes from what runs after load: WebGL setup and model parsing, then the 3D render loops and the animation ticker that drives scrolling and motion, all of which keep running. In the test browser WebGL is drawn by the CPU rather than a graphics card, so in some later runs the CPU never went quiet, and Lighthouse scored 0 because it found no idle window to measure at.1
The options we are weighing: start the 3D on the visitor’s first interaction or when the browser is idle, rather than on load, and compress that 1.96 MB model. We will measure the launch build the same way.
Two languages, two real pages
Each language is its own pre-rendered page at its own address: / for Arabic and /en/ for English. Nothing swaps text inside a single page. Each page names itself and its twin with hreflang tags, as Google asks: every language version lists itself and all the others.8
The language button is an ordinary link to the other page, and it carries what the visitor had in front of them: one snapshot in session storage, used once on arrival only if it is under 60 seconds old and meant for that page, then deleted. Before building it we measured the original design and found that the raw scroll position does not survive the switch (10549 px became 10428), because the browser and the scroll effects recalculate positions after the switch. So we bring the visitor back to the section that was at the top of their screen, at the same offset into it. The switch fades over 420 ms, and the fade is dropped for anyone who has reduced motion turned on.3
When sending fails
The “Start your project” card sends each request to Telegram and to email at the same time. If either one gets through, the visitor sees the success message. If both fail (the server answers 502), the card shows a failure pane with a Try again button and a WhatsApp button carrying the request (services, budget, timing, contact details and the first 500 characters of the message), so the request is not lost. 34 unit tests cover this part, including Telegram down while email still works.4
Building a bilingual site? What we would tell you
- Measure on an emulated phone before and after every change, and write the date and settings next to the number.
- Defer anything heavy that is not the first content, such as 3D or video, until after load.
- Never act on an old number: measure the current version again.
- Check Arabic headings at 320 to 376 px wide; that is where a single word ends up alone on a line.
- Give each language its own address, with hreflang tags that point both ways.
- Plan a fallback for when the form fails to send, so a customer’s request is never lost.