Skip to content

Will Aitchison: A portfolio as a contact sheet

12.9 → 3.7seconds to the first big picture on a throttled phone, Squarespace to new

I designed and built Will Aitchison's new site as a contact sheet, the direction he picked, with every shoot in one numbered sequence and no picture cropped. It's built for him to run from his own Sanity Studio, with rules that stop a change breaking the layout.

12.9 → 3.7seconds to the first big picture on a throttled phone, Squarespace to new

How a shoot gets from Will's Studio to a visitorThree rows, read as a Z. At the back, Will's Sanity Studio, on his phone or laptop, where he publishes to the Content Lake, which holds only the documents and the original files. In front of the Studio, one rules file: the Studio checks its limits as Will edits, and the build runs the same checks again before anything deploys (step 5). Publishing then runs by itself: the Lake's webhook goes to a small relay, a Cloudflare Worker, which checks the signature and how fresh the call is, then starts a build (step 6). The build, on GitHub Actions, encodes each picture once, restoring and saving the files by their sha1 in an encode cache on R2 (step 4), and deploys to a static host on Cloudflare Workers. At the front, the visitor's browser, where every picture keeps its own shape and nothing moves as they load (step 3). Nothing runs from the browser back to the Lake: visitors never call Sanity.Will's StudioSanity, phone or laptopContent LakeDocuments and originalsRelayCloudflare WorkerRulesOne fileBuildGitHub ActionsEncode cacheR2Static hostCloudflare WorkersVisitor's browser3456
A shoot's way from Will's Studio to a visitor, publishing by itself. Each number is a decision below. Press one to read it.Scroll sideways to see all of it
How a shoot gets from Will's Studio to a visitorThree rows, read as a Z. At the back, Will's Sanity Studio, on his phone or laptop, where he publishes to the Content Lake, which holds only the documents and the original files. In front of the Studio, one rules file: the Studio checks its limits as Will edits, and the build runs the same checks again before anything deploys (step 5). Publishing then runs by itself: the Lake's webhook goes to a small relay, a Cloudflare Worker, which checks the signature and how fresh the call is, then starts a build (step 6). The build, on GitHub Actions, encodes each picture once, restoring and saving the files by their sha1 in an encode cache on R2 (step 4), and deploys to a static host on Cloudflare Workers. At the front, the visitor's browser, where every picture keeps its own shape and nothing moves as they load (step 3). Nothing runs from the browser back to the Lake: visitors never call Sanity.Will's StudioSanity, phone or laptopContent LakeDocuments and originalsRelayCloudflare WorkerRulesOne fileBuildGitHub ActionsEncode cacheR2Static hostCloudflare WorkersVisitor's browser3456
A shoot's way from Will's Studio to a visitor, publishing by itself. Numbers match the decisions below.Scroll sideways to see all of it

Diagram. A shoot's way from Will's Studio to a visitor, publishing by itself. Numbers match the decisions below.

A shoot's way from Will's Studio to a visitor, publishing by itself. Numbers match the decisions below.

willaitchison.com still shows his old Squarespace site until the domain moves. Screens of the new site are from 24 Oct 2026.

01Problem

Will Aitchison is a fashion and sportswear photographer in Newcastle. His clients include END. Clothing, Stone Island, Nike and Salomon. His brief was short. It mustn't look like a template, the target was "high fashion editorial", there'd be no write-⁠ups, and he wanted "just a nice sequence" rather than project pages. The sections would stay Overview, Still Life and Info.

In the first version, the shoots and their order lived in a file in the code, so every change Will wanted had to go through me. Moving his content into Sanity, with his own Studio, came later. Every service sits on a free tier, and Vercel's free plan is non-⁠commercial, so the live site had to move to Cloudflare. The only pictures to hand were the files his old site served, which are soft on big screens.

Before: Squarespace. The old site set each row to one height. The new one keeps every picture's own shape.

After: the contact sheet. The old site set each row to one height. The new one keeps every picture's own shape.

The old site set each row to one height. The new one keeps every picture's own shape.

02Decisions

  1. Research first, then let him choose from real options

    Before any code, I studied about 60 photographers' and fashion sites, how they're built and how they move. I mocked three looks with Will's own photos, editorial, magazine and archive, alongside a run of smaller spikes. Then on the first night I built two directions in one app: an editorial sequence with three openers and a plain start, and a contact sheet with every shoot numbered in one run. Will picked the contact sheet, which grew out of the archive look. On 19 October I removed everything else, and the sheet became the whole site.

  2. Measure the ground against his pictures, then keep it black and white

    Will shoots dark. Two thirds of his pictures have a dark edge. I measured them against each candidate ground, and on black whole edges were lost, so a dark ground was out. Pure white was out too: most photographers' sites use it, and they all look alike. The research picked a deeper stone with one red mark, like an editor's grease pencil. I chose a paler stone, #EDECE8, with plain black and white type and no accent. The cost is one picture, which loses its edge on that grey. On the deeper stone, none did.

  3. Never crop, and never blur

    The site is there to show Will's work, so every picture keeps the shape he shot it in, and none is cropped. Each shoot is laid out from a recipe on a 12-column grid, and every tile takes its picture's own shape. There are no blur placeholders either, because a blurred preview makes fashion work look cheap. Each box is sized from the picture's width and height, and the picture fades in on the grey once it's decoded, so nothing moves as pictures load.

    Own shapes. Shoots 013 to 016, upright and wide pictures side by side.

    Own shapes. Shoots 013 to 016, upright and wide pictures side by side.
  4. Encode each picture once, and name it by its content

    The build turns every original into AVIF, WebP and JPEG at up to eight widths, six from today's files. Each file is named from the original's sha1 and a hash of the settings, so browsers cache it for a year, and changing a setting renames every file. The one clip works the same way, with ffmpeg pinned so it re-⁠encodes byte for byte. Its poster is the clip's first frame, so the switch to motion can't be seen.

  5. One rules file for Will's Studio and the build

    Will's Studio lists the order on the site, his shoots, Info, and a Help page that is his written guide. The layout has limits, such as how wide a shoot's row can get, so 17 limits live in one rules file. The Studio checks them as he edits, with messages that say what to do. The build runs them again, because the Studio's checks only run in the Studio. An error stops the build, and the last good site stays up.

  6. Let a publish rebuild the site by itself

    Sanity's webhook calls a small Cloudflare Worker. It checks the signature in constant time, refuses anything over five minutes old and starts a GitHub build. The build reuses an R2 cache, so a picture is only encoded once, and deploys to Cloudflare Workers. The relay has 16 tests. In a test on 25 October, a publish was live on the site about two and a half minutes later.

  7. Let Will overrule me

    On desktop I'd set his name large along the foot of the first screen, with Info alone in the top right. Will wanted his name at the top and Info beside the Overview and Still life switch. I had reasons for mine, but it's his site, so it changed to what he wanted.

    My layout. His name moved from the foot of the first screen to the top, and Info joined the switch.

    His pick. His name moved from the foot of the first screen to the top, and Info joined the switch.

    His name moved from the foot of the first screen to the top, and Info joined the switch.

03Result

12.9 → 3.7until the largest picture shows on a throttled phone, old site against new, in seconds

  • His Squarespace site
  • The new site

The site went live as a preview on 11 October and moved to Cloudflare on 25 October, with the publish path on. See it live (opens in a new tab). It holds 46 shoots and 69 pictures, one of them a looping clip, with 20 shoots tagged still life. The Studio went live on 20 October with Will's content imported.

On the same phone test, the first screen's pictures fell from 0.99 MB to 0.15 MB. On desktop with no throttling, the largest picture shows in 0.65 seconds, down from 0.94.

It isn't finished. willaitchison.com still points at Squarespace, search engines are kept out until launch, and Will hasn't published anything himself yet.

The viewer 0:12

The viewer open at 1440 px: a Salomon trainer photograph on grass, centred on the pale grey, with the credit 008 Salomon for END. Clothing and 3 of 4 below it, Close at the top right, and Prev and Next at the lower right.
A picture glides to full size, steps through its shoot, and glides back onto its tile. The Salomon shoot on the sheet, the third picture glides from its tile to full size, the arrow key steps to 4 of 4, the next step crosses into shoot 009 and the credit changes, Close glides the picture back onto its tile.
  • Next.js
  • TypeScript
  • Sanity
  • GSAP
  • sharp
  • ffmpeg
  • Cloudflare Workers
  • R2
  • GitHub Actions