From Next.js to Astro: How we made a high-traffic news platform with 830K monthly visits faster, more stable, and more reliable.

The Context
With approximately 830,000 monthly visits and 1.9 million pageviews, this news platform needed to deliver fast, reliable access to content, even during sudden traffic spikes.
But heavy JavaScript, frequent server errors, and shifting page elements were affecting the reading experience. The challenge was to improve performance and stability without disrupting a platform that thousands of readers rely on every day.
Our Services
The Mission
Our mission was to make the platform faster, more stable, and easier to use, while ensuring it could handle high traffic without compromising the reading experience.
The site ran on Next.js, with React rendering on every page. For a publication that is mostly text, images and ads, that meant shipping and running a lot of JavaScript that readers never needed. Three problems showed up in the real-user data.
Errors under load. In the week before the migration, the site returned an average of 12,852 HTTP 500 errors a day, with a worst day of 25,596.
Layout shift. Ads, embeds and images loaded without reserved space, so content moved as the page filled in. CLS at the 75th percentile was 0.051 across the site and 0.144 on the homepage, where readers land first. Only 83.8% of page views had a "good" CLS score.
Slow tails. Most readers got a reasonable Largest Contentful Paint, but the slowest tenth waited over two seconds (LCP p90 of 2,163 ms), and the homepage LCP p75 was 1,751 ms.
We cut over to Astro on 22 October and then tuned the new stack in small, measured deploys, checking real-user data after each one.
Pages are now rendered on the server by Astro, with Alpine.js for the few interactive pieces such as navigation and search. There is no client-side React app to download and start, and the HTML itself is 10% smaller (86 KB to 77 KB).
The consent manager now loads first, and telemetry and ad start-up wait for it. Ad zones and article embeds only activate when they come within about 600 px of the viewport, and tweets load on demand instead of with the page.
Image dimensions are inferred so the browser reserves the right space, ad slots hold their height before the ad arrives, and social embeds get a placeholder of the right size. Fonts are self-hosted WOFF2 subsets with the headline font preloaded, so text no longer reflows when a web font arrives.
Article and card images are served responsively, with visible images prioritised and the rest deferred. On Cloudflare, we set cache rules for fonts and images (7 days at the edge, 1 day in the browser) and fingerprinted static assets so they can be cached safely.
22 Oct, 18:50 — Cutover to Astro
HTTP 500s fall from thousands a day to a handful.
22 Oct, 21:49 and 22:16 — Consent first, lazy ads and embeds
Third parties are deferred, offscreen ads and embeds wait until they're needed, and a mobile navigation shift is removed.
23 Oct, 17:36 — Reserved media and ad slots
Space is held for images and ads, and CMS requests run concurrently. LCP p90 drops from 2,132 ms to 1,624 ms.
24 Oct, 00:30–01:11 — Cache rules, responsive images, self-hosted fonts
Static-asset revalidations fall from about 15% to zero, the cache hit share climbs from about 8% to about 25%, and CLS p75 drops to effectively zero.
24 Oct, 12:26 and 25 Oct, 16:35 — Homepage image priority, reserved social embed space
CLS reaches 0 and stays there every day from 26 Oct.
26 Oct and 27 Oct — Editorial photo optimisation, on-demand tweet embeds
Page load keeps falling, to a p75 of 2,676 ms on 28 Oct.
Server errors are effectively gone. From 23 October the site has returned an average of 4 HTTP 500 errors a day, down from 12,852, a 99.97% drop, while serving the same audience.
The slowest page loads got the biggest win. LCP p75 fell 15% (1,176 ms to 1,003 ms) and LCP p90 fell 24% (2,163 ms to 1,649 ms). On the homepage, LCP p75 fell 33%, from 1,751 ms to 1,176 ms. The share of page views with a "good" LCP rose from 92.0% to 95.5%.
Layout shift is gone. CLS p75 has been 0 every day since 26 October, down from 0.051. On the homepage it went from 0.144 to 0.021 across the after period. The share of page views with a "good" CLS score rose from 83.8% to 93.5%.
Pages finish loading sooner. Page load times were already trending down in the week before the migration, so we measure from the 21 October level rather than the weekly average. Against that day, page load p75 fell from 3,201 ms to 2,850 ms on 27 October, the last full day measured (−11%), and to 2,676 ms on 28 October. Page load p90 fell from 5,912 ms to 5,053 ms (−15%).
The CDN now does its job. Before the change, about 15% of static-asset requests were revalidated with the origin and only 7.6% were served straight from the edge cache. After the cache rules went live, revalidations dropped to effectively zero and the hit share rose to about 25–28% a day.
99.97%
Fewer HTTP 500 errors
0
CLS p75 since 26 Oct
−24%
LCP p90
−33%
Homepage LCP p75
Beyond the technical metrics, these improvements create a stronger foundation for the platform's growth.
The result is a faster, more dependable platform built to support both its readers and its future growth.
| Metric | Before (14–21 Oct) | After (23–28 Oct) | Change |
|---|---|---|---|
| LCP p75 | 1,176 ms | 1,003 ms | −15% |
| LCP p90 | 2,163 ms | 1,649 ms | −24% |
| Homepage LCP p75 | 1,751 ms | 1,176 ms | −33% |
| CLS p75 | 0.051 | 0 daily since 26 Oct | — |
| Homepage CLS p75 | 0.144 | 0.021 | — |
| Good CLS share | 83.8% | 93.5% | — |
| Good LCP share | 92.0% | 95.5% | — |
| Page load p75 | 3,201 ms (21 Oct) | 2,850 ms (27 Oct) | −11% |
| Static asset cache hit share | 7.6% | ~25% (from 25 Oct) | — |
| HTTP 500s per day | 12,852 | 4 | −99.97% |
| HTML size | 86 KB | 77 KB | −10% |
Real-user data from Cloudflare Web Analytics (sampled) and Cloudflare HTTP request analytics. 28 Oct is a partial day.
Interaction stayed fast throughout, with INP p75 at 68 ms, well inside Google's "good" threshold of 200 ms.
Slow pages, server errors, or poor Core Web Vitals? Let's identify what's holding your website back and find the right solution.