ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

Web Performance Optimization

Optimize web application performance using Core Web Vitals as a framework. Covers LCP, FID, CLS optimization, bundle analysis, lazy loading, image optimization, and the performance budget that keeps your app fast as it grows.

Performance is a feature. A 100ms increase in page load time reduces conversion rates by 1%. A page that takes 3 seconds to load loses 53% of mobile visitors. Performance optimization is not a nice-to-have — it directly affects revenue, user satisfaction, and search ranking.


Core Web Vitals

Google’s Core Web Vitals are the standard for measuring user-perceived performance:

MetricGoodNeeds WorkPoorMeasures
LCP (Largest Contentful Paint)< 2.5s2.5-4s> 4sLoading speed
FID (First Input Delay)< 100ms100-300ms> 300msInteractivity
CLS (Cumulative Layout Shift)< 0.10.1-0.25> 0.25Visual stability
INP (Interaction to Next Paint)< 200ms200-500ms> 500msResponsiveness

LCP Optimization

Critical Rendering Path

<!-- Preload the LCP image -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">

<!-- Inline critical CSS -->
<style>
  /* Only styles needed for above-the-fold content */
  .hero { min-height: 60vh; background: var(--bg); }
  .hero img { width: 100%; height: auto; }
</style>

<!-- Defer non-critical CSS -->
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

Image Optimization

<!-- Modern format with fallbacks -->
<picture>
  <source srcset="/hero.avif" type="image/avif">
  <source srcset="/hero.webp" type="image/webp">
  <img src="/hero.jpg" alt="Hero" width="1200" height="600"
       loading="eager" fetchpriority="high" decoding="async">
</picture>

<!-- Responsive images -->
<img srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
     sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 1200px"
     src="hero-800.webp" alt="Hero" width="1200" height="600">

Font Optimization

/* Use font-display: swap to prevent invisible text */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap;
  font-weight: 400;
}
<!-- Preload critical fonts -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>

CLS Optimization

Reserve Space for Dynamic Content

/* Always set dimensions on images */
img { width: 100%; height: auto; aspect-ratio: 16 / 9; }

/* Reserve space for ads */
.ad-slot { min-height: 250px; }

/* Contain layout shifts from web fonts */
.text-content { font-size-adjust: 0.5; }

Avoid Layout-Shifting Patterns

// BAD: Injecting content above existing content
document.querySelector('.header').insertAdjacentHTML('afterbegin', banner);

// GOOD: Reserve space, then populate
// CSS: .banner-slot { min-height: 60px; }
document.querySelector('.banner-slot').innerHTML = banner;

Bundle Optimization

Code Splitting

// Route-based splitting (React)
const OrderPage = React.lazy(() => import('./pages/OrderPage'));
const AdminPage = React.lazy(() => import('./pages/AdminPage'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/orders" element={<OrderPage />} />
        <Route path="/admin" element={<AdminPage />} />
      </Routes>
    </Suspense>
  );
}

Tree Shaking

// BAD: Import entire library
import _ from 'lodash';
_.debounce(fn, 300);

// GOOD: Import only what you need
import debounce from 'lodash/debounce';
debounce(fn, 300);

Bundle Analysis

# Webpack
npx webpack-bundle-analyzer stats.json

# Vite
npx vite-bundle-visualizer

# Next.js
ANALYZE=true next build

Performance Budget

performance_budget:
  javascript:
    total: 200KB gzipped
    per_route: 100KB gzipped
  css:
    total: 50KB gzipped
  images:
    per_image: 200KB
    hero: 100KB
  fonts:
    total: 100KB
  metrics:
    lcp: 2.5s
    fid: 100ms
    cls: 0.1
    tti: 3.5s

Enforce in CI:

# bundlesize in package.json
"bundlesize": [
  { "path": "dist/js/*.js", "maxSize": "200 kB" },
  { "path": "dist/css/*.css", "maxSize": "50 kB" }
]

Anti-Patterns

Anti-PatternConsequenceFix
No performance budgetBundle grows uncheckedSet and enforce budgets in CI
Loading everything upfrontSlow initial loadCode splitting + lazy loading
Unoptimized imagesLCP > 4 secondsWebP/AVIF, responsive images, CDN
Render-blocking resourcesWhite screen for secondsAsync CSS, deferred JS
No performance monitoringRegression goes unnoticedRUM + synthetic monitoring

Performance optimization is a continuous practice, not a one-time project. Without budgets and monitoring, every feature addition degrades performance until users notice.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →