Skip to article body
A porter seen from behind carrying a huge load of white sacks down a Cairo street at dusk
A porter seen from behind carrying a huge load of white sacks down a Cairo street at dusk

Web Development · Field Notes

The Page Carried the Whole CMS

Our server loaders returned entire Prismic documents, and SvelteKit wrote every byte of them into the HTML.

By Shehab Waleed, Founder, Cairo Studio 3 min read

During a performance pass on our site, we looked at the size of the HTML itself, before any image or script. The homepage was 275KB. The universe page was 265KB. A service page was 400KB. None of them show anywhere near that much text.

Where the bytes came from

A SvelteKit page renders on the server, then hydrates in the browser. To hydrate, the browser needs the same data the server used, so SvelteKit writes the return value of every server load into the HTML as a script. Whatever the load returns, the visitor downloads.

Our loads returned what Prismic returned. The homepage carousel needed a title, a slug and a poster for each project. It received each project whole: galleries, credits, every slice on the case study. Service pages carried the full bodies of their related services, which are whole pages of their own.

Return what the component reads

The fix is dull and it works. For each loader we listed the fields its components read, and returned only those. Two helpers do most of it. One drops named fields from a document:

function omitData(doc, keys) {
return {
...doc,
data: Object.fromEntries(
Object.entries(doc.data)
.filter(([key]) => !keys.includes(key))
)
};
}

The other builds a small object for each project the carousel shows, with only the fields it draws.

const carouselProject = (doc) => ({
id: doc.id,
uid: doc.uid,
data: {
title: doc.data.title,
poster: doc.data.poster,
year: doc.data.year
}
});

That one is simplified. Ours reads a few different fields, with the same idea.

Why HTML size matters more than it looks

HTML is the first thing the browser downloads, and nothing else starts until it arrives. On a phone network, 200KB of data the visitor will never see sits directly in front of the first paint. Lighthouse also counts the bytes requested before the largest paint when it simulates a slow phone, so the same waste shows up in the score.

We saw a similar pattern when PageSpeed's test machine had no graphics card: the number reflected work the page did that no visitor benefited from.

What to watch for

Trimming has one risk. A field can be read by a child component three files away, and if you drop it, that component renders empty without any error. We check each trimmed page by eye, and we keep the field list next to the component that reads it.

Checking the result needs nothing special.

curl -s https://cairo-studio.com/en | wc -c

We run that against each template whenever a loader changes. The homepage is 87KB today, a little above where we left it, because the carousel has gained projects since.

Share
X LinkedIn Facebook WhatsApp Email
A hand lifting one small parcel from a large pile

Send the one it reads.

Before
275KB
After
82KB
Today
87KB
Before
265KB
After
62KB
Today
63KB
Before
400KB
After
204KB
Today
206KB

Uncompressed HTML. Today means a curl of the live site on 2 November 2026.

Everything a server load returns is written into the page for hydration. If a load returns whole API or CMS responses, the visitor downloads all of it, whether or not it is shown.

It changes where the request runs, not how much data the page needs. Return only the fields the components read, whichever kind of load you use.

Prismic queries can limit fields for linked documents with fetchLinks, and that helps. We still trim on the server, because a slice often needs less than a whole linked document.

We build and run modern framework sites end to end, from the CMS model to the server they deploy on.

See our framework work

More from the Journal

view all →