
3Fils Dubai: What a Michelin-Recognised Restaurant Website Actually Ships
A measured teardown of the 3Fils restaurant platform — real byte counts for images and JavaScript, how the animation libraries are split per route, and the honest cost of an animation-led build.
Most restaurant website case studies are adjectives. “Cinematic.” “Immersive.” “Blazing fast.” None of that is checkable, so none of it is useful to you.
So this one is numbers. I built the web platform for 3Fils, a Michelin Bib Gourmand restaurant in Dubai, and instead of describing it I went and measured what it actually sends to a browser — bytes, formats, chunk counts, cache headers. Everything below you can verify yourself with your browser’s network tab.
Measurements were taken in August 2026 from an Indian connection, which means they hit Vercel’s Mumbai edge. Your numbers will differ by region, and the site will keep changing after this is published.
What the homepage actually sends
A restaurant homepage is mostly photographs of food. That is the whole problem: the thing that sells the restaurant is also the thing that makes the page heavy.
| What | Measured |
|---|---|
| HTML document | 153 KB |
| Visible text | ~1,335 words |
| JavaScript chunks | 17 |
| JavaScript over the wire | 399 KB compressed (1,246 KB uncompressed) |
| Optimised image requests | 122 |
| Host / framework | Vercel, Next.js |
Two of those deserve attention. 399 KB of JavaScript is not a small number — I will come back to it honestly at the end. And 122 image requests sounds alarming until you see what each one weighs.
Images: WebP goes in, AVIF comes out
Every image on the site is committed as WebP already — the source files live at /images/*.webp rather than as JPEGs straight off a camera. Those then go through Next.js image optimisation at quality 85.
Here is one real dish photo, measured at three of the widths the page requests:
| Request | Bytes | Format |
|---|---|---|
| Source file | 141,128 | WebP |
| Delivered at 640w | 52,061 | AVIF |
| Delivered at 1080w | 65,302 | AVIF |
| Delivered at 1920w | 85,679 | AVIF |
A phone asking for the 640-wide version downloads 52 KB instead of the 141 KB source — and it never sees the 1920-wide file at all. The full ladder offered is 384, 640, 750, 828, 1080, 1200, 1920, 2048 and 3840 pixels wide, so a laptop and a budget Android are genuinely served different files.
The lesson is not “use Next.js.” It is that the format conversion happens twice: once by hand before commit (to WebP), once automatically at request time (to AVIF). Skipping the manual step is the most common reason a site with an image optimiser still feels slow — the optimiser is doing damage control on a 4 MB original.
The animation libraries are split per route
This is the part I did not expect to find as cleanly as I did, and it is the most copyable idea here.
Smooth-scroll and animation libraries are heavy. The instinct is to install them globally, initialise them in a root layout, and move on. That means every visitor downloads the animation engine even on a page with no animation.
What the live site actually does, checked by searching the shipped bundles for each library’s internal signatures:
| Route | Chunks | Animation library present |
|---|---|---|
| Homepage | 17 | Framer Motion |
| /menu | 20 | Framer Motion + GSAP |
| /about | 18 | Framer Motion |
| /contact | 16 | Framer Motion |
GSAP appears on /menu and nowhere else. Somebody visiting the homepage to check opening hours never pays for the timeline engine that drives the menu experience.
You get this by importing the heavy library inside the component that uses it rather than in a shared layout, and letting the bundler do route-level splitting. It is a small discipline with a real payoff, and it is invisible in a screenshot — which is exactly why case studies never mention it.
Sentry is present across routes, which is the correct call for error monitoring: telemetry that only runs on some pages tells you very little.
Rendering: prerendered, then cached at the edge
The response headers tell the rendering story without any guesswork:
X-Nextjs-Prerender: 1— the HTML was built ahead of time, not assembled per requestX-Nextjs-Stale-Time: 300— it revalidates on a five-minute windowX-Vercel-Cache: HIT— the request was served from cache, not from the originX-Vercel-Id: bom1::iad1::…— served from the Mumbai edge; the origin sits in US East
That last line matters more than it looks. The origin is thousands of kilometres from both Dubai and India. Because the page is prerendered and cached at the edge, the distance stops mattering for most visits. A restaurant site that rendered per request would feel very different in the Gulf.
Five minutes of staleness is a deliberate trade: menu and hours changes go live quickly, but every visitor in between is served from cache.
The honest part: 399 KB of JavaScript
I am not going to pretend that number is good. For a site that is fundamentally photographs, text and a menu, 399 KB compressed is a lot of JavaScript, and it is the price of the animation-led approach the brand wanted.
It buys something real — the scroll and transition quality is the reason the client came to us, and on a modern phone it holds up. But if you are a restaurant owner reading this and your goal is bookings rather than atmosphere, you should know that a mostly-static site with restrained animation would ship a fraction of that and convert perfectly well.
The right question is never “is this fast?” It is “what am I buying with this weight, and do I want it?” For a Michelin-recognised restaurant selling an experience, spending it on motion is defensible. For a neighbourhood takeaway, it is not.
What you can take from this
Four things transfer to almost any image-heavy site, regardless of framework:
- Convert images before you commit them. Ship WebP or AVIF sources. An optimiser fed 4 MB JPEGs is doing damage control.
- Serve a real width ladder. A phone downloading a desktop-sized image is the single most common waste on restaurant sites.
- Import heavy libraries where they are used, not in the layout. Route-level splitting is free once your imports are honest.
- Prerender and cache at the edge if your audience is not near your origin — which, for any restaurant with international customers, is most of them.
Check your own site
Everything above came from a browser network tab and response headers. No special tooling, no access to the codebase. You can run the same check on your own restaurant site in about ten minutes:
- Open the network tab, filter to images, and sort by size. Anything over 200 KB on a phone-width viewport is a problem.
- Filter to JS and read the total. Then ask what each library is buying you.
- Look at the response headers on your homepage. If nothing indicates caching, every visitor is waiting on your origin.
If the numbers you find are uncomfortable, that is useful information — it is a lot easier to argue for a rebuild with a byte count than with a feeling that the site seems sluggish. I have written a plainer walkthrough of the underlying metrics in the Core Web Vitals guide, and the rest of the 3Fils build is documented on the project page.
Want a fast, SEO-friendly website for your business?
I build high-performance Next.js websites and web apps that load fast, rank on Google, and turn visitors into customers. Book a free, no-obligation consultation and let's talk about your project.