Kush Digital SolutionsRequest a quote

Lab — concept build — anchor: muji-kenya-hara

Eighteen seconds to the photograph

Semantic HTML and near-zero JavaScript on an image-heavy portfolio, where the photograph is the payload and everything else is overhead.

Not client work. jameskushphotography.com is our own site, and this is a concept rebuild of it — measured, argued, and deliberately unfinished. Both sets of numbers below were taken with a named tool on a named date. The rebuild's were taken against the published page, on the same CDN the baseline crossed, so the weight and score comparisons are like for like.

The jameskushphotography.com homepage as measured, a full-bleed street photograph beneath a minimal masthead.

jameskushphotography.com — Squarespace — as measured, 8 August 2026

The baseline, measured

Lighthouse, three consecutive runs — 8 August 2026

51

Performance score

18.2s / 18.2s / 17.6s

Largest contentful paint

3,110 KiB

Page weight

696 KB

Largest single file

~450 KB

Squarespace JavaScript

60ms

Server response

The rebuild, measured

Lighthouse, three consecutive runs, deployed to Cloudflare Pages — 9 August 2026

Measured against the deployed page.

99 / 99 / 100

Performance score

1.3s / 1.2s / 1.2s

Largest contentful paint

206 KiB

Page weight

64 KB

Largest single file

Zero bytes

JavaScript

0

Cumulative layout shift

Both columns now cross the internet: these runs were taken against the published page on Cloudflare's CDN, the same conditions the baseline was measured under, so the weight and score comparisons are like for like. The LCP comparison is not, and the reason belongs to the baseline rather than this page — the live site shuffles six photographs into its hero on every load, so which one becomes the largest contentful element is chance. Four re-runs returned 5.5s, 17.6s, 18.0s and 17.6s. Weight and server response reproduced exactly; LCP did not, and the 18.2s figure should never be quoted on its own.

Page weight — 3,110 KiB, decomposed

One photograph, one file — largest single contributor
696 KB
Squarespace JavaScript
~450 KB
Everything else, derived
1,964 KiB

The same page rebuilt — 206.1 KiB, on the same scale

The photograph that paints first
23.3 KiB
The other five, lazy and low priority
141.7 KiB
Four subset typefaces
33.3 KiB
Everything else, derived
7.8 KiB

Largest contentful paint — every run, not the best one

The baseline, over the internet

Run 01

18.2s

Run 02

18.2s

Run 03

17.6s

The rebuild, on the same axis

Run 01

1.6s

Run 02

1.6s

Run 03

1.6s

The vertical rule sits at 2.5s — the Core Web Vitals threshold for a good largest contentful paint. It marks the standard, not a result.

The site works. Twenty-one pages of galleries, every link on it resolving, a server that answers in 60ms. What it does not do is arrive. Three consecutive Lighthouse runs put the largest contentful paint — on a photography site, that is the photograph — at eighteen seconds.

Sixty milliseconds of that is the server. The remaining seventeen-and-a-half seconds were shipped.

Where the weight goes

Two files account for more than a third of the page. One is a photograph served at its original size to every device that asks for it, phone included. The other is the platform’s own JavaScript, none of which a portfolio needs to show a picture.

A note on units. Lighthouse reports the page total in KiB and individual resources in KB. The two differ by 2.4%, which is narrower than the spread between the three runs above, so the bar is drawn on the KiB total with the resource figures unconverted. Said out loud because a chart that quietly mixes units is wrong by an amount nobody can see.

The approach

Four moves, in the order they matter.

Serve the photograph, not the original. One <picture> element, AVIF first, WebP behind it, JPEG last, and a srcset that hands a 400px-wide phone a 400px-wide file. The photograph stays the payload; it stops being the whole payload.

Ship no framework. Semantic HTML, one stylesheet, and JavaScript only where something genuinely cannot be done without it — which on a gallery is the lightbox and nothing else. This page you are reading is built that way; so is every other page on this site.

Reserve the box before the image lands. Intrinsic width and height on every image, so the layout is final at first paint and the type does not jump when a photograph decodes. Cheap, structural, and invisible when it works.

Put nothing above the photograph. No announcement bar, no cookie banner racing the hero, no font that blocks text for 300ms. On a portfolio the first paint and the point of the page are the same object, and everything queued in front of it is a delay charged to the work.

What the rebuild did

It exists now, at /lab/photography-rebuild/demo. Same copy, same six photographs, same layout, same typefaces, same six-second hero rotation. It is a reconstruction, not a redesign — a free reimagining is a separate piece, and improving the design here would have quietly turned a measurement into a sales pitch.

Two hundred and six kibibytes, all in. Six photographs, four typefaces, the markup and the stylesheet. Zero bytes of JavaScript, which is not a boast but a build condition: scripts/check-budgets.mjs fails the build if that route ships any.

The photograph that paints first is 23 KiB of AVIF. It is smaller than the four subset typefaces behind it, which is the sort of thing you only find out by measuring, and it is why the typefaces were cut to the glyphs the page actually sets rather than shipped whole.

Where the eighteen seconds actually went

Reading the live page’s own source answered a question the baseline numbers could only pose. The six hero stills are not in the markup at all. A script runs after DOMContentLoaded, finds every photograph elsewhere on the page, hides the sections it took them from, shuffles them, and sets the first six as CSS background images.

The browser’s preload scanner cannot see a URL that does not exist yet. So the photograph — the entire point of the page — cannot begin downloading until the platform’s JavaScript has arrived, parsed and run. The sixty-millisecond server was never the problem, and neither, really, was the 696 KB file. The problem is that nothing could start.

One consequence is worth stating because it makes the baseline harder to quote than it looks: since the six are shuffled, which photograph becomes the largest contentful paint is decided by a coin toss on every visit. Re-running the same tool against the same URL on 2026-08-09 produced 5.5s, 17.6s, 18.0s and 17.6s — three runs that reproduce the original measurement almost exactly, and one that happened to draw a light photograph first. A page whose headline metric varies by a factor of three depending on chance is not a page with a slow image. It is a page with no priority order at all.

What is and is not comparable

Both columns were measured the same way: Lighthouse, three consecutive runs, against a page served over Cloudflare’s CDN. The weight and score columns are like for like. 3,110 KiB became 211 KiB, and 51 became 99.

The largest-contentful-paint column is not, and the reason belongs to the baseline rather than to this page. The live site shuffles six photographs into its hero on every load, so which image becomes the largest contentful element is decided by chance. Re-running it four times returned 5.5s, 17.6s, 18.0s and 17.6s — the same page, the same connection, a spread of twelve and a half seconds. Page weight and server response reproduced exactly across all four; LCP did not.

So the honest statement is this: the baseline’s LCP is somewhere between five and eighteen seconds depending on which photograph it happens to load, and the rebuild’s is 1.2s regardless of which one it loads, because every image on it is served at a size that suits the slot it fills. The 18.2s figure should never be quoted on its own, and it is not quoted on its own here.

The demo is published. Anyone can open it and run the same tool.

Build time, measured
24 min
Anchor
muji-kenya-hara
Status
Concept — not delivered work