Frontend Performance Optimization: Full-Stack Enhancement from Loading to Rendering

Master Core Web Vitals, resource optimization, rendering performance, caching strategies and monitoring

Frontend Performance Optimization: Full-Stack Enhancement from Loading to Rendering

Frontend performance directly impacts user experience and business conversion. This article explores core strategies and best practices for frontend performance optimization.

Core Web Vitals

Key Metrics

Core Web Vitals Metrics:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   LCP (Largest Contentful Paint)                   │
│   ├── Largest content paint time                   │
│   ├── Target: < 2.5s                               │
│   └── Affects: Images, fonts, large text blocks   │
│                                                     │
│   INP (Interaction to Next Paint)                  │
│   ├── Interaction to next paint                   │
│   ├── Target: < 200ms                              │
│   └── Affects: JS execution, event handling       │
│                                                     │
│   CLS (Cumulative Layout Shift)                    │
│   ├── Cumulative layout shift                     │
│   ├── Target: < 0.1                                │
│   └── Affects: Image size, dynamic content, fonts │
│                                                     │
└─────────────────────────────────────────────────────┘

Measurement Methods

// Using web-vitals library
import { onLCP, onINP, onCLS, onFCP, onTTFB } from 'web-vitals';

function sendToAnalytics(metric: Metric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating,
    delta: metric.delta,
    id: metric.id,
    navigationType: metric.navigationType,
  });

  // Use sendBeacon to ensure data is sent
  navigator.sendBeacon('/api/analytics', body);
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);

// Performance Observer API
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`${entry.name}: ${entry.startTime}ms`);
  }
});

observer.observe({ entryTypes: ['largest-contentful-paint', 'layout-shift'] });

Resource Loading Optimization

Image Optimization

// Modern image formats and responsive images
function OptimizedImage({ src, alt }: { src: string; alt: string }) {
  return (
    <picture>
      <source
        srcSet={`${src}.avif 1x, ${src}@2x.avif 2x`}
        type="image/avif"
      />
      <source
        srcSet={`${src}.webp 1x, ${src}@2x.webp 2x`}
        type="image/webp"
      />
      <img
        src={`${src}.jpg`}
        srcSet={`${src}.jpg 1x, ${src}@2x.jpg 2x`}
        alt={alt}
        loading="lazy"
        decoding="async"
        width={800}
        height={600}
      />
    </picture>
  );
}

// Image preloading
function preloadCriticalImage(src: string) {
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'image';
  link.href = src;
  link.fetchPriority = 'high';
  document.head.appendChild(link);
}

Font Optimization

/* Font display strategy */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap; /* Use system font until custom font loads */
  unicode-range: U+0000-00FF; /* Only load needed characters */
}

/* Preload critical fonts */
<link
  rel="preload"
  href="/fonts/custom.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

/* Font subsetting - reduce font file size */
/* Use fonttools or glyphhanger tools */

Code Splitting

// React lazy loading
import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

// Route-level code splitting
const routes = [
  {
    path: '/dashboard',
    component: lazy(() => import('./pages/Dashboard')),
    preload: () => import('./pages/Dashboard'),
  },
];

// Prefetch next likely page
function prefetchRoute(path: string) {
  const route = routes.find(r => r.path === path);
  route?.preload();
}

Resource Preloading

<!-- DNS prefetch -->
<link rel="dns-prefetch" href="//api.example.com" />

<!-- Preconnect -->
<link rel="preconnect" href="https://api.example.com" crossorigin />

<!-- Preload critical resources -->
<link rel="preload" href="/critical.css" as="style" />
<link rel="preload" href="/main.js" as="script" />

<!-- Prefetch next page resources -->
<link rel="prefetch" href="/next-page.js" />

<!-- Module preload -->
<link rel="modulepreload" href="/modules/app.js" />

Rendering Performance

Virtual List

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
    overscan: 5,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div
        style={{
          height: `${virtualizer.getTotalSize()}px`,
          position: 'relative',
        }}
      >
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {items[virtualItem.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

Avoid Layout Thrashing

// ❌ Triggers multiple reflows
function badLayout() {
  const elements = document.querySelectorAll('.item');
  elements.forEach(el => {
    const height = el.offsetHeight; // Read
    el.style.height = height + 10 + 'px'; // Write
  });
}

// ✅ Batch reads then batch writes
function goodLayout() {
  const elements = document.querySelectorAll('.item');
  const heights: number[] = [];

  // Batch reads
  elements.forEach(el => {
    heights.push(el.offsetHeight);
  });

  // Batch writes
  elements.forEach((el, i) => {
    el.style.height = heights[i] + 10 + 'px';
  });
}

// ✅ Use requestAnimationFrame
function animateWithRAF() {
  requestAnimationFrame(() => {
    // Execute DOM updates in next frame
    element.style.transform = 'translateX(100px)';
  });
}

React Rendering Optimization

// Use memo to avoid unnecessary re-renders
const ExpensiveComponent = memo(function ExpensiveComponent({ data }: Props) {
  return <div>{/* Complex rendering logic */}</div>;
});

// useMemo to cache computed results
function DataTable({ items }: { items: Item[] }) {
  const sortedItems = useMemo(() => {
    return [...items].sort((a, b) => a.name.localeCompare(b.name));
  }, [items]);

  return <Table data={sortedItems} />;
}

// useCallback to cache callback functions
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <Child onClick={handleClick} />;
}

// Use React.lazy and Suspense
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Skeleton />}>
      <HeavyComponent />
    </Suspense>
  );
}

Caching Strategies

Service Worker Caching

// sw.js
const CACHE_NAME = 'app-v1';
const STATIC_ASSETS = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
];

// Cache static assets on install
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(STATIC_ASSETS);
    })
  );
});

// Network-first strategy
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/api/')) {
    event.respondWith(networkFirst(event.request));
  } else {
    event.respondWith(cacheFirst(event.request));
  }
});

async function cacheFirst(request: Request): Promise<Response> {
  const cached = await caches.match(request);
  if (cached) return cached;

  const response = await fetch(request);
  const cache = await caches.open(CACHE_NAME);
  cache.put(request, response.clone());
  return response;
}

async function networkFirst(request: Request): Promise<Response> {
  try {
    const response = await fetch(request);
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    const cached = await caches.match(request);
    if (cached) return cached;
    throw error;
  }
}

HTTP Caching

// Express cache header setup
app.use('/static', express.static('public', {
  maxAge: '1y',
  immutable: true,
}));

app.get('/api/data', (req, res) => {
  res.set({
    'Cache-Control': 'public, max-age=300, stale-while-revalidate=600',
    'ETag': generateETag(data),
  });
  res.json(data);
});

// Versioned asset URLs
const assetUrl = `/app.${hash}.js`;

Browser Storage

// IndexedDB for caching large data
import { openDB } from 'idb';

const db = await openDB('app-cache', 1, {
  upgrade(db) {
    db.createObjectStore('api-cache', { keyPath: 'url' });
  },
});

async function cachedFetch(url: string): Promise<Response> {
  const cached = await db.get('api-cache', url);

  if (cached && Date.now() - cached.timestamp < 300000) {
    return new Response(JSON.stringify(cached.data));
  }

  const response = await fetch(url);
  const data = await response.json();

  await db.put('api-cache', {
    url,
    data,
    timestamp: Date.now(),
  });

  return new Response(JSON.stringify(data));
}

Network Optimization

HTTP/2 and HTTP/3

# Nginx HTTP/2 configuration
server {
    listen 443 ssl http2;

    # Server push
    location / {
        http2_push /styles.css;
        http2_push /app.js;
    }
}

Compression

// Express compression middleware
import compression from 'compression';

app.use(compression({
  level: 6,
  threshold: 1024,
  filter: (req, res) => {
    if (req.headers['x-no-compression']) {
      return false;
    }
    return compression.filter(req, res);
  },
}));

// Brotli compression (better ratio)
import shrinkRay from 'shrink-ray-current';

app.use(shrinkRay());

Resource Hints

// Dynamic resource hints
function addResourceHint(type: 'preload' | 'prefetch', url: string) {
  const link = document.createElement('link');
  link.rel = type;
  link.href = url;
  document.head.appendChild(link);
}

// Preload based on user behavior
function onHover(event: MouseEvent) {
  const link = (event.target as HTMLElement).closest('a');
  if (link?.href) {
    addResourceHint('prefetch', link.href);
  }
}

Performance Monitoring

Custom Performance Metrics

// Custom performance marks
performance.mark('app-init-start');

await initializeApp();

performance.mark('app-init-end');
performance.measure('app-init', 'app-init-start', 'app-init-end');

// Get measurement results
const measures = performance.getEntriesByType('measure');
console.log('App init time:', measures[0].duration);

// Long task monitoring
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 50) {
      console.warn('Long task detected:', entry);
      reportLongTask(entry);
    }
  }
});

observer.observe({ entryTypes: ['longtask'] });

Error and Crash Monitoring

// Global error capture
window.addEventListener('error', (event) => {
  reportError({
    type: 'uncaught',
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
  });
});

// Promise error capture
window.addEventListener('unhandledrejection', (event) => {
  reportError({
    type: 'unhandledrejection',
    reason: event.reason,
  });
});

// Memory monitoring
if ('memory' in performance) {
  setInterval(() => {
    const memory = (performance as any).memory;
    if (memory.usedJSHeapSize > memory.jsHeapSizeLimit * 0.9) {
      reportMemoryWarning(memory);
    }
  }, 30000);
}

Build Optimization

// Vite configuration optimization
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          utils: ['lodash-es', 'date-fns'],
        },
      },
    },
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    },
  },
});

Best Practices Summary

Frontend Performance Best Practices:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Loading Optimization                              │
│   ├── Code splitting and lazy loading             │
│   ├── Resource compression and optimization       │
│   ├── CDN distribution                            │
│   └── Preload critical resources                  │
│                                                     │
│   Rendering Optimization                           │
│   ├── Virtual list for large data                 │
│   ├── Avoid layout thrashing                      │
│   ├── CSS animations over JS                      │
│   └── Optimize React rendering                    │
│                                                     │
│   Caching Strategies                               │
│   ├── Service Worker caching                      │
│   ├── HTTP cache headers                          │
│   ├── Local storage caching                       │
│   └── API response caching                        │
│                                                     │
│   Monitoring System                                │
│   ├── Core Web Vitals                             │
│   ├── Custom performance metrics                  │
│   ├── Error monitoring                            │
│   └── User experience monitoring                  │
│                                                     │
└─────────────────────────────────────────────────────┘
MetricTargetOptimization Focus
LCP< 2.5sOptimize first-screen resources
INP< 200msReduce JS execution
CLS< 0.1Reserve space
FCP< 1.8sReduce blocking resources
TTFB< 800msOptimize server response

Performance optimization is an ongoing process. Establish monitoring systems and continuously iterate to maintain great user experience.


Performance is the foundation of user experience. Every millisecond matters for user retention.