🎁 Free starter workshopHaving a SW issue?
Back to blog
Web DevelopmentPerformanceWebOptimizationCore Web Vitals

Web App Performance Optimization

Lukáš HusoMay 5, 202610 min read
Web App Performance Optimization
Photo: Marc-Olivier Jodoin / Unsplash

The speed of a web application is not just a technical metric. It is a directly measurable factor that affects conversions, SEO rankings, and user satisfaction. Google knows this, which is why they started using Core Web Vitals as a ranking signal in 2021. Amazon found that every 100 ms of delay costs 1% of sales. And your users have a simple rule: if the page doesn't load content within 3 seconds, they go elsewhere.

53%
of users leave if a page takes more than 3 seconds to load
1%
of sales lost for every 100 ms of delay (Amazon)
30–50%
LCP improvement from image optimization alone

Core Web Vitals -- what they measure and why

Google defined three metrics that evaluate the user experience of loading and interacting with a page.

LCP (Largest Contentful Paint) measures how long it takes to render the largest visible element on the page -- typically a hero image, the main heading, or a large block of text. The target value is under 2.5 seconds. An LCP above 4 seconds is rated as poor by Google.

LCP is often the hardest metric to optimize because it depends on the entire chain: DNS lookup, TCP/TLS handshake, server response time, HTML download, CSS parsing, image download and decoding. A weak link at any point slows down the overall result.

INP (Interaction to Next Paint) replaced the older FID (First Input Delay) metric in March 2024. While FID measured only the first interaction, INP measures responsiveness to all interactions throughout the entire visit and reports the worst value. The target is under 200 ms. In practice, this means that after clicking a button, opening a menu, or typing text, the page must visually respond within 200 ms.

INP is particularly problematic for JavaScript-heavy applications. Long synchronous operations on the main thread block rendering and cause interface "freezes." The solution lies in breaking work into smaller tasks, using web workers for intensive computations, and minimizing main thread work.

CLS (Cumulative Layout Shift) measures visual stability -- how much elements on the page shift during loading. The target is below 0.1. Everyone has experienced the situation where they wanted to click a link and at the last moment the page shifted because an image or ad loaded above it. That is exactly what CLS penalizes.

Image optimization -- the biggest quick win

Images typically account for 40-60% of a page's total weight. Optimizing them is therefore the fastest path to improved performance.

Next-generation formats. WebP offers 25-35% smaller file sizes than JPEG at comparable quality. AVIF goes even further -- 50% savings compared to JPEG. Both formats are now supported by all modern browsers. If you're still serving JPEG and PNG, switch to WebP as the default format with JPEG fallback.

<picture>
  <source srcset="hero.avif" type="image/avif" />
  <source srcset="hero.webp" type="image/webp" />
  <img src="hero.jpg" alt="Hero image" />
</picture>

Responsive images. Don't serve a 2000px wide image to a mobile device with a 400px viewport. The srcset attribute and <picture> element allow you to serve the right size for each device. In Next.js, the Image component handles this automatically.

Lazy loading. Images below the fold (outside the visible viewport) should load only when the user approaches them. The native loading="lazy" attribute works in all modern browsers and requires no JavaScript. Be careful with the LCP element though -- it should have loading="eager" or no attribute at all to load as quickly as possible.

Proper dimensions. Always specify width and height attributes on <img> elements. This allows the browser to reserve space for the image before it downloads, eliminating layout shift (CLS). In CSS, use aspect-ratio for responsive images.

Code splitting and tree shaking

Modern JavaScript applications tend to grow in size. Framework, libraries, utilities, polyfills -- it's not unusual to see a bundle of 500 KB or more. But the user on the landing page needs only a fraction of this code.

Code splitting divides the application into smaller chunks that load on demand. In React and Next.js, you use dynamic() import for components that aren't needed on first render. Modal windows, charts, admin panels -- everything that appears only after user interaction.

const Chart = dynamic(() => import("../components/Chart"), {
  loading: () => <ChartSkeleton />,
});

Tree shaking automatically removes unused code during the build process. It only works with ES modules (import/export), however. If you import an entire library (import _ from "lodash"), the bundler cannot remove unused functions. Import specifically (import debounce from "lodash/debounce").

Bundle analysis. Before you start optimizing, you need to know what's taking up space. The webpack-bundle-analyzer tool (or @next/bundle-analyzer for Next.js) visualizes your bundle contents. You'll often find surprises -- a date formatting library taking up 70 KB when you use one function from it. Or two different HTTP clients because each library pulled in its own.

Font optimization

Web fonts are a silent performance killer. A typical Google Font is 20-100 KB, and if it's on the critical rendering path, it blocks text display.

font-display: swap. This CSS property tells the browser to display text immediately with a system font and swap it when the web font loads. Users see content immediately instead of blank space. The downside is a brief "flash" during the font swap (FOUT -- Flash of Unstyled Text), but that's significantly better than invisible text (FOIT -- Flash of Invisible Text).

Self-hosting instead of Google Fonts CDN. Surprisingly, self-hosting fonts is faster today than the Google Fonts CDN. The reason: the browser must establish new connections to fonts.googleapis.com and then to fonts.gstatic.com, adding latency. With self-hosting, the font is on the same domain as your page.

Subsetting. If your site uses only Latin characters, you don't need glyphs for Cyrillic, Greek, or Arabic. Font subsetting can reduce font size by 50-80%. The glyphhanger tool analyzes your HTML and creates a font containing only the characters used.

Caching strategies

Proper caching is the difference between a page that loads in 3 seconds and one that loads in 300 ms.

Browser cache with immutable assets. Static assets (JS, CSS, images) should have a hash in the filename (app.a1b2c3.js) and Cache-Control: max-age=31536000, immutable. The browser downloads them once and doesn't revalidate for a year. When code changes, the hash changes and the browser downloads the new version.

Stale-while-revalidate. For HTML pages and API responses, the stale-while-revalidate strategy works well. The browser immediately displays the cached version and verifies in the background whether a newer one exists. Users see content immediately and get the updated version on the next load.

Service Worker for offline. For progressive web applications (PWA), Service Workers enable caching the entire application and serving it offline. Workbox from Google simplifies implementation and offers pre-built strategies for different content types.

CDN -- content closer to the user

A Content Delivery Network distributes static assets to servers around the world. When a user in Prague requests an image, they get it from a server in Frankfurt (20 ms) instead of the origin in the US (150 ms).

For projects targeting an international audience, CDN is especially relevant. Cloudflare offers a generous free tier and in addition to CDN adds DDoS protection, automatic compression (Brotli), and edge caching. Vercel and Netlify have CDN integrated.

Important: CDN helps with static assets but doesn't fix a slow backend. If your API responds in 2 seconds, CDN won't change that. Focus first on server response time (TTFB should be under 600 ms) and only then address distribution.

SSR versus SSG -- the impact on performance

How you generate HTML has a direct impact on performance.

Static Site Generation (SSG) pre-generates HTML at build time. Pages are served as static files from CDN -- no server-side computation, minimal TTFB. Ideal for content that doesn't change frequently: marketing pages, blogs, documentation.

Server-Side Rendering (SSR) generates HTML on the server with each request. TTFB is higher (the server must perform computation), but the content is always current. Suitable for personalized content, dashboards, e-commerce with dynamic pricing.

Incremental Static Regeneration (ISR) combines the advantages of both approaches. A page is pre-generated at build time but regenerates in the background after the TTL expires with current data. Users always get a fast cached version that is at most X seconds old.

For most marketing websites and blogs, SSG is the optimal choice. For applications with user-generated content, consider SSR or ISR.

Measurement -- lab data versus field data

One of the most common mistakes is optimizing solely based on Lighthouse scores.

Lab data (Lighthouse, WebPageTest) measures performance under controlled conditions -- on a specific device, with a specific connection, without browser extensions. They're excellent for diagnostics and comparing before and after optimization. But they don't tell you what experience your actual users are having.

Field data (Chrome UX Report, Web Vitals library) measures performance from real users on real devices with real connections. Google uses field data for its ranking signal, not lab data. A page with a Lighthouse score of 95 can have poor Core Web Vitals in field data if most users visit it on slow mobile devices.

How to measure correctly. Use Lighthouse for diagnostics and identifying issues. Track field data through Google Search Console (Core Web Vitals section) or through the web-vitals library integrated into your analytics. Set up RUM (Real User Monitoring) for continuous performance tracking.

WebPageTest.org is an indispensable tool for deep analysis. The filmstrip shows exactly what the user sees at every tenth of a second. The waterfall diagram reveals which resources are blocking rendering. And the ability to test from different locations and on different devices provides a realistic picture.

A practical optimization roadmap

1. Audit

Measure the current state -- Lighthouse, Core Web Vitals in Search Console, bundle analysis. You cannot optimize without data.

2. Quick wins -- images

Switch to WebP/AVIF, add lazy loading, set proper dimensions. This alone often improves LCP by 30-50%.

3. Code splitting

Defer loading of components not needed on first render. Analyze the bundle and remove unnecessary dependencies.

4. Caching and CDN

Immutable cache for static assets, stale-while-revalidate for dynamic content, CDN for global distribution.

5. Continuous monitoring

Set up RUM (Real User Monitoring), track field data, and regularly compare against benchmarks.

If you don't know where to start, follow this order -- from highest impact to lowest.

First, measure the current state. Run a Lighthouse audit, check Core Web Vitals in Search Console, and analyze bundle size.

Second, optimize images. Switch to WebP/AVIF, add lazy loading, set proper dimensions. This alone often improves LCP by 30-50%.

Third, implement code splitting. Defer loading of components that aren't needed on first render. Analyze the bundle and remove unnecessary dependencies.

Fourth, configure caching. Immutable cache for static assets, stale-while-revalidate for dynamic content, CDN for global distribution.

Fifth, optimize fonts. Self-hosting, font-display: swap, subsetting.

Measure each step and compare with the previous state. Performance optimization is an iterative process, not a one-time action.

Conclusion

Performance optimization is not a technical detail to be addressed at the end of a project. It is a fundamental aspect of user experience that affects the success of the entire application. The good news is that most optimizations don't require rewriting the application -- a systematic approach, the right measurement tools, and the discipline to implement best practices from the start of development are enough. The investment in performance returns through better conversions, higher search engine rankings, and happier users.

Get your custom price

Our configurator shows you an indicative price for your project in 2 minutes.

Related Articles

SEO Disasters: Websites Google Can't Find

SEO Disasters: Websites Google Can't Find

Real stories of SEO catastrophes — from invisible SPAs to buying thousands of backlinks. How not to destroy your site in Google's eyes.

April 9, 20267 min read
Web Application Security: OWASP Top 10 in Practice
Web DevelopmentSecurityOWASP

Web Application Security: OWASP Top 10 in Practice

A practical guide to OWASP Top 10 vulnerabilities. Real-world examples, prevention in modern frameworks, and how to secure your web app.

March 31, 20269 min read
Hosting Horror Stories: Why It Matters Where Your App Runs
Web DevelopmentHostingServer

Hosting Horror Stories: Why It Matters Where Your App Runs

An app on a laptop under a desk, unlimited hosting that crashes at 100 users, a provider that went bankrupt overnight. Real stories.

May 7, 20267 min read