Spilled Palette Studio: Art Gallery at 95+ Lighthouse
Aesthetic-first Next.js 14 portfolio for digital artist Aswathi Bindhu Jawahar — dynamic masonry grids, Cloudinary image optimization, and Framer Motion interactions at 95+ Lighthouse.
$ outcome
Shipped a 95+ Lighthouse gallery the artist updates herself through a single JSON file — no code changes, no developer needed.
$ cat stack.json
- Next.js 14
- Tailwind CSS
- Framer Motion
- Cloudinary
- Vercel
$ timeline
$ session.log
# Representative output from the launch build (May 2026)
$ pnpm build
✓ Compiled successfully
✓ Generating static pages — no errors, all collection routes pre-rendered
$ npx lighthouse https://spilledpalettestudio.com --only-categories=performance --preset=desktop
Performance: 95+ (desktop and mobile)
Overview

Spilled Palette Studio is a digital art portfolio for artist Aswathi Bindhu Jawahar, designed to feel like a quiet luxury physical space. The art is the hero. The technology stays invisible.
I’m a Senior Systems Engineer who took this build to learn modern frontend properly, so I chose Next.js 14, Tailwind CSS, and Framer Motion and held myself to production numbers: 95+ Lighthouse on desktop and mobile, 2 curated collections live at launch, and a single artworks-list.json the artist edits herself. No code changes needed for new work.
1. The Problem
Digital art galleries face a unique set of technical contradictions:
- High visual fidelity vs. performance: the artwork files are massive, but the website must load instantly.
- Dynamic filtering vs. SEO: users need to filter artwork by category (
Botanical,Sanctuary Series) without sacrificing search engine discoverability. - Immersive UX vs. clean design: interactions should feel alive but must not distract from the subtle, minimalist aesthetic of the artwork itself.
2. Architecture

Architecture: Visitor → Vercel Edge → Next.js 14 App Router → Tailwind CSS + Framer Motion → Cloudinary CDN
| Layer | Choice | Why |
|---|---|---|
| Framework | Next.js 14 App Router | Static gallery routes pre-rendered; client islands only for filtering |
| Styling | Tailwind CSS | Custom quiet-luxury theme: off-whites, soft charcoals, Playfair Display + Inter |
| Motion | Framer Motion | Masonry grid transitions and cross-fading category filters |
| Media | Cloudinary CDN | Format negotiation (WebP/AVIF) + responsive srcsets at the edge |
| Hosting | Vercel | CI/CD per push; edge serving for the static shell |
3. Implementation
Serving raw, high-resolution artwork files directly would cripple performance, so image engineering was the core build work. Original high-res .tiff and .png files are converted to WebP and AVIF based on client browser capabilities, and responsive srcsets keep mobile users from downloading 4K images.
The design language is intentionally subdued. Using Tailwind CSS, I crafted a custom theme utilizing off-whites (#FDFBF7), soft charcoals (#2C2C2C), and elegant typography pairings (Playfair Display for headings, Inter for body text).

Clean, sophisticated layout using Tailwind CSS
Category filtering runs instantly on the client side via Framer Motion, artwork cross-fading in and out of the viewport. No route change, no SEO cost, since every collection also exists as a static route.

Real-time artwork filtering with smooth layout animations

The full Collection grid: 98 artworks, seven collections, one filter bar
The build and Lighthouse evidence is the session log above: a clean static build with all collection routes pre-rendered, and 95+ Performance on desktop and mobile measured at launch.
4. Operations
A static gallery is cheap to run. That was the point from the first commit:
- Hosting: Vercel free tier. Static pages served from the edge, no server to babysit.
- Media: Cloudinary free tier covers the launch catalog; format negotiation happens at the CDN, so origin bandwidth stays flat as traffic grows.
- Ownership: the artist adds collections by appending to
artworks-list.json. No routing changes, no redeploy knowledge needed beyond a git push triggering CI. - Monitoring: Lighthouse CI on the deploy pipeline catches regressions in image weight before they reach production.
5. Results
- Performance: 95+ Lighthouse across desktop and mobile at launch.
- Catalog: 2 curated collections live, with an inquiry channel per piece connecting buyers to the artist.
- Ownership: 1 JSON file (
artworks-list.json) stands between the artist and publishing new work. No code changes, no developer needed.
Figures as of May 2026.
The handoff test
The real acceptance test for this build wasn’t the Lighthouse number. It was whether the artist could publish new work without calling me, and the single artworks-list.json catalog passes it: append an entry, push, done. TIFF and PNG originals become WebP/AVIF with responsive srcsets, collection routes pre-render statically in Next.js 14, the Vercel edge serves them at 95+ on desktop and mobile. My judgment call, and one I could be wrong about: I treated performance as a design constraint from day one rather than a polish pass at the end, which meant slower early progress on the visual design the artist actually cared about. She got the gallery she wanted. She just waited longer for the first pretty screenshot than she should have.
What I’d Do Differently
One design regret: client-side filtering state lives entirely in memory, so a shared filtered URL doesn’t reproduce the view. Encoding the active collection in a query param would have made curation shareable for free.
One scope regret: I shipped no blurred-placeholder pipeline. First paint on slow connections shows layout boxes, not art. Cloudinary’s blur-up placeholders would have closed that gap for a few lines of loader config.
“The fastest gallery is the one that ships no JavaScript until the visitor asks for motion.”
Continue Reading
Migrating something similar?
I led this deployment end to end — scope, cutover, and guardrails. Tell me what you are running and I will reply with a migration sketch.