These are field notes from taking a WooCommerce store with a mobile PageSpeed score of 41 to a custom Next.js platform on Google Cloud Run, and from tuning the sites that followed. They are written for developers and technical leads. The short version: most of the gains came from the server and the network path, not from micro-optimizing components.
Start with the LCP breakdown
Largest Contentful Paint is the metric that usually decides a mobile score. Before changing code, split it into its four parts: time to first byte, resource load delay, resource load duration and element render delay. Lighthouse and Chrome DevTools both show this breakdown. Whichever part is largest is where to work first.
On the original store, time to first byte alone was about 8 seconds. No amount of front-end work could have fixed that. On the rebuilt platform it is about 0.2 seconds.
Server response and the hosting path
- Serve pages statically or from a cache wherever the content allows it, and revalidate in the background.
- Count the sequential network calls behind one request. A cache that makes two storage round trips per read is slower than one with a small in-memory layer in front.
- Watch cold starts on scale-to-zero platforms. Minimum instances cost money; measure whether you need them.
- Test the direct service address against the custom domain. The route in front of your app can matter more than the app.
Render-blocking CSS and fonts
- With atomic CSS such as Tailwind, the whole stylesheet is often small enough to inline. Next.js has an experimental
inlineCssoption that replaces the stylesheet link with a style tag, trading cache reuse for a faster first visit. - Self-host fonts with
next/fontso there is no extra connection to a font provider, and prefer variable fonts to several static weights. - Preload only the fonts used above the fold. A monospace font used in small labels can load later.
- Use font fallbacks with adjusted metrics so text does not shift when the web font arrives.
JavaScript and hydration
- Keep server components as the default and push client components to the leaves of the tree.
- Load analytics and other third-party scripts after the page is interactive.
- Prefer CSS transitions over JavaScript animation libraries for entrance effects; start them from CSS so they do not wait for hydration.
- Avoid infinite animations on large elements. They keep the compositor busy and can cost paint time on low-end phones.
- Check the main-thread work before first paint in a performance trace. On our pages it is around 100 ms.
Images and layout stability
- Give the LCP image
fetchPriority="high"or eager loading; the Next.js docs recommend this over thepreloadprop in most cases. - Serve modern formats at the sizes the layout actually uses, with sizes set correctly on responsive images.
- Reserve space for every image, embed and ad slot so the layout does not shift. Google calls a CLS below 0.1 good.
Measuring honestly
- Lab scores vary. Run Lighthouse several times and report the median. PageSpeed Insights tests from Google's network, so it often scores higher than a laptop or a CI runner far from your host.
- Field data wins. Once a site has enough traffic, Chrome UX Report data in PageSpeed Insights and Search Console shows what real visitors experience, and that is what Google uses.
- Test the production build on the production path. Development servers and preview deployments tell you little about real performance.
- Watch accessibility while you animate. Automated contrast checks can catch text mid-fade. Keep fades short so an audit sees the finished state.