How We Built a Villa Storefront That Scores 95 Plus on Lighthouse

When I started building Moven, I made one technical commitment that shaped every decision after it: every property page had to score above 90 on Google Lighthouse. Not as a vanity metric. Because Lighthouse scores correlate directly with real-world performance, and real-world performance correlates with bookings. This post is about the specific decisions that got us there — and why we made them.
The Problem With How Most Villa Sites Are Built
Most villa websites are built on WordPress with a premium theme and a page builder. This is understandable — WordPress is familiar, themes are cheap, and page builders make it possible to create visually compelling pages without code. The problem is performance: a typical WordPress + Elementor stack ships 2–5 MB of JavaScript and CSS to every visitor, loads dozens of external resources, and relies on PHP rendering on the server for every page request.
When 100 people visit your site at the same time, that PHP server is doing 100 separate database queries and template renders. Under traffic spikes — say, after you post something that goes viral — shared WordPress hosting falls over. And even at normal load, the server-side rendering latency adds 400–800ms to every page load before a single byte of your content reaches the guest.
The Case for Static Generation
Our architecture choice — Next.js with Incremental Static Regeneration — was motivated by a simple principle: don't compute things at request time that you could compute at build time. A villa property page doesn't change by the minute. It might change when you update your content (new photos, new pricing, new amenities). Between those updates, every guest who visits the page should get the exact same HTML.
Static generation means that HTML is computed once at build time (or on revalidation) and stored. Subsequent requests get served the pre-computed HTML directly from a CDN edge node — no database query, no PHP render, no server-side computation. The result: Time to First Byte (TTFB) under 50ms. That's the first win.
ISR: Staying Fresh Without Rebuilding Everything
Pure static generation has a problem: if you update your villa's content, the page doesn't reflect it until you rebuild. For a platform with multiple operators making frequent content changes, rebuilding the entire site on every update isn't practical. This is where Incremental Static Regeneration (ISR) comes in.
With ISR, each page has a revalidation interval. When a request comes in after that interval has expired, Next.js serves the stale cached version to the current visitor and triggers a background regeneration for the next visitor. This gives us near-instant updates without the latency of server-side rendering. Our content editors update a villa page in Payload CMS, a webhook fires, and within 30–60 seconds the live page reflects the change.
Image Optimization: The Biggest Win
Images are typically 80–90% of a villa website's page weight. Getting image optimization right is the single highest-leverage technical improvement you can make. For Moven, we use Next.js's built-in image optimization component, which does several things automatically: converts images to WebP or AVIF (30–50% smaller than JPEG at equivalent quality), generates multiple sizes and serves the appropriate one based on the device's screen width, and lazy-loads images below the fold so the browser doesn't fetch photos the guest never scrolls to see.
The hero image — the first thing a guest sees — gets special treatment: it's priority-loaded and preloaded in the HTML head so it begins downloading before the browser has finished parsing the page. This is what moves LCP (Largest Contentful Paint) from 4–6 seconds to under 1.5 seconds. It's the single change that most improves perceived load speed.
Edge Caching and CDN Strategy
We deploy on Vercel, which operates a global edge network. Static pages and images are cached at edge nodes in cities close to your guests. A traveler in Bengaluru loading a villa page gets their HTML from an edge node in Mumbai or Singapore, not from a data center on the other side of the world. This makes the 50ms TTFB I mentioned earlier a realistic number, not a best-case scenario.
Cache headers are set to maximize CDN hit rates for static assets while ensuring dynamic content (availability calendars, pricing) is fetched fresh from the API. The split between content that's mostly static and data that changes frequently is one of the key architectural decisions that makes fast performance compatible with real-time data.
What 95 Plus Lighthouse Actually Means in Practice
A Lighthouse score above 90 in the Performance category means your LCP is under 2.5 seconds, your CLS is near zero (nothing shifts while the page loads), and your Total Blocking Time (TBT) is under 200ms. In practical terms: the page feels instant on modern devices, loads acceptably on mid-range phones on 4G, and gives Google enough positive signals to rank well in organic search.
We didn't achieve this by being clever. We achieved it by picking the right stack, making performance a first-class requirement from day one, and not adding features that would compromise it. Every new component goes through a performance review before shipping. It's a discipline, not a trick.


