We rebuilt our agency site in Next.js 16: INP/LCP before and after
2 May 2026 · 5 min read · TheAIgency
TL;DR. We rebuilt theaigency.ma on Next.js 16 App Router (with the new webpack mode), added GSAP stagger reveals and Lenis smooth scroll, and watched LCP drop from 2.8s to 1.4s and INP from 280ms to 145ms. The takeaway isn't "use the new shiny" — it's that App Router lets you stream content predictably and keep INP under the 200ms threshold even with heavy animations.
Why we rebuilt
The old site was a Next.js 14 Pages Router build. Fast enough on Lighthouse, slow enough on real Moroccan 4G. INP was the pain — every navigation interaction had a ~280ms delay because the bundle ran a single giant client component.
The new Core Web Vitals reality matters: INP replaced FID in March 2024 and the March 2026 Google core update increased CWV weight. We needed to be inside the green band — LCP under 2.5s, INP under 200ms, CLS under 0.1 — without sacrificing the kind of editorial animation we sell to clients.
What changed (the receipts)
| Metric | Before (Pages, v14) | After (App Router, v16) |
|---|---|---|
| LCP (75th percentile, real users) | 2.8s | 1.4s |
| INP | 280ms | 145ms |
| CLS | 0.06 | 0.02 |
| Total JS (gzipped, initial page) | 187 kB | 114 kB |
| Hydration time on a $200 Android | ~1.4s | ~620ms |
Numbers from the Vercel Analytics 75th-percentile dashboard, May 2026 vs February 2026. Mobile-only.
What actually moved the needle
- Server Components by default. The marketing pages (home, services, blog, about) are all RSC. Only the concierge widget, header dropdown, and brief form are client. Client JS dropped 39%.
- Webpack mode (--webpack flag). Next.js 16's webpack is more aggressive at tree-shaking + code-splitting than the default turbopack dev mode. We trade a bit of dev-server speed for smaller production bundles.
- GSAP loaded only where used. The stagger reveals are wired through a thin React client component (GsapStagger) that lazily registers ScrollTrigger. No GSAP on routes that don't need it.
- Lenis smooth scroll, scoped. One
SmoothScrollProviderat the root, but it's a client component the size of a postage stamp — Lenis is lazy-imported. Doesn't block the LCP. - Image strategy. Hero and tier card images are CSS background-images (decorative). For the case study heros and blog covers we use next/image with priority on the above-the-fold one.
What broke
Two things bit us:
1. Hydration mismatches with theme persistence. next-themes plus the smooth-scroll provider was double-rendering on first paint. Fixed by mounting the theme provider inside a Suspense boundary and gating the smooth-scroll wiring on a useEffect.
2. Lenis + GSAP timing. If both initialise on the same frame, GSAP can install ScrollTrigger handlers before Lenis reroutes the scroll events. Solution: register Lenis first, then GSAP in the next animation frame.
What we'd do differently
We over-invested in animation polish before measuring INP. The single biggest win came from the RSC restructure, not from any animation tweak. If we did it again: ship the boring App Router migration first, measure, then add animation.
Also: get on Vercel Analytics or PageSpeed Insights from day one. INP is hard to fix retroactively because the worst offender is usually a single component that nobody flagged.
If you want this for your site
This is exactly what our Web product line ships. Spark and Signal both come App Router-by-default with the same CWV discipline. For more aggressive performance work — a heavy SaaS or e-commerce migration — that's a Studio engagement.