blog / performance-optimization-web-apps

Web Performance: 10 Quick Wins für schnellere Websites

Performance ist User Experience

Eine Sekunde längere Ladezeit = 7% weniger Conversions. Das ist nicht nur Theorie - ich habe es in Production gesehen.

Hier sind 10 Quick Wins, die ich bei jedem Projekt umsetze, um Websites spürbar schneller zu machen.

1. Lazy Loading für Bilder

Vorher:

<!-- Alle Bilder werden sofort geladen, auch off-screen -->
<img src="hero.jpg" alt="Hero Image">
<img src="feature1.jpg" alt="Feature 1">
<img src="feature2.jpg" alt="Feature 2">

Nachher:

<!-- Native Lazy Loading - Browser entscheidet wann -->
<img src="hero.jpg" alt="Hero Image" loading="eager">
<img src="feature1.jpg" alt="Feature 1" loading="lazy">
<img src="feature2.jpg" alt="Feature 2" loading="lazy">

Resultat: Initial Page Load um 60% schneller!

2. Moderne Bildformate verwenden

<picture>
  <!-- Browser wählt das beste Format -->
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="Fallback">
</picture>

Savings:

  • AVIF: ~50% kleiner als JPEG
  • WebP: ~30% kleiner als JPEG

Automatisierung mit build tools:

// vite.config.js
import { imagetools } from 'vite-imagetools';

export default {
  plugins: [
    imagetools({
      defaultDirectives: new URLSearchParams({
        format: 'avif;webp;jpg',
        quality: '80'
      })
    })
  ]
}

3. Code Splitting und Dynamic Imports

Problem:

// Alles wird upfront geladen
import { HugeChart } from './huge-chart-library'; // 500kb!
import { RarelyUsedFeature } from './rarely-used';

function App() {
  return <Dashboard />;
}

Bundle Size: 800kb

Lösung:

// Lazy load nur wenn gebraucht
const HugeChart = lazy(() => import('./huge-chart-library'));
const RarelyUsedFeature = lazy(() => import('./rarely-used'));

function App() {
  const [showChart, setShowChart] = useState(false);

  return (
    <Dashboard>
      {showChart && (
        <Suspense fallback={<Spinner />}>
          <HugeChart />
        </Suspense>
      )}
    </Dashboard>
  );
}

Bundle Size: 150kb initial, Rest on-demand!

4. Debouncing und Throttling

Problem: Zu viele API Calls

// Search Input triggert bei jedem Keystroke
function SearchBox() {
  const handleSearch = async (query) => {
    const results = await api.search(query);
    setResults(results);
  };

  return <input onChange={(e) => handleSearch(e.target.value)} />;
}
// User tippt "React" → 5 API Calls!

Lösung: Debouncing

import { debounce } from 'lodash';

function SearchBox() {
  const handleSearch = debounce(async (query) => {
    const results = await api.search(query);
    setResults(results);
  }, 300); // Warte 300ms nach letztem Keystroke

  return <input onChange={(e) => handleSearch(e.target.value)} />;
}
// User tippt "React" → 1 API Call!

Custom Debounce Hook:

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

// Usage:
const searchTerm = useDebounce(inputValue, 500);
useEffect(() => {
  if (searchTerm) {
    searchAPI(searchTerm);
  }
}, [searchTerm]);

5. Resource Hints

<!-- Preconnect: Verbinde früh zu externen Domains -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://api.example.com">

<!-- DNS Prefetch: Nur DNS lookup -->
<link rel="dns-prefetch" href="https://analytics.google.com">

<!-- Preload: Kritische Ressourcen früh laden -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

<!-- Prefetch: Niedrige Priorität für nächste Seite -->
<link rel="prefetch" href="/about-page-bundle.js">

Impact: Spart 100-300ms bei externen Requests!

6. Virtualisierung für lange Listen

Problem:

// 10.000 Items rendern
function UserList({ users }) {
  return (
    <div>
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}
// 10.000 DOM Nodes = Browser stirbt

Lösung: react-window

import { FixedSizeList } from 'react-window';

function UserList({ users }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      <UserCard user={users[index]} />
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      itemCount={users.length}
      itemSize={80}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}
// Nur sichtbare Items werden gerendert!

Resultat: 10.000 Items fühlen sich wie 20 an!

7. Memoization und React.memo

Problem:

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <ExpensiveChild data={staticData} />
      {/* Re-rendert bei jedem Click! */}
    </>
  );
}

Lösung:

// Child rendert nur bei Props-Change
const ExpensiveChild = React.memo(({ data }) => {
  console.log('Rendering ExpensiveChild');
  return <div>{/* expensive rendering */}</div>;
});

function Parent() {
  const [count, setCount] = useState(0);

  // useMemo für berechnete Werte
  const processedData = useMemo(() => {
    return expensiveCalculation(staticData);
  }, [staticData]);

  // useCallback für Funktionen
  const handleClick = useCallback(() => {
    doSomething();
  }, []);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <ExpensiveChild
        data={processedData}
        onClick={handleClick}
      />
    </>
  );
}

8. Web Workers für CPU-intensive Tasks

// main.js
const worker = new Worker('/worker.js');

worker.postMessage({ data: largeDataset, operation: 'sort' });

worker.onmessage = (e) => {
  console.log('Sorted data:', e.data);
  // UI bleibt responsive!
};

// worker.js
self.onmessage = (e) => {
  const { data, operation } = e.data;

  if (operation === 'sort') {
    const sorted = data.sort((a, b) => a - b);
    self.postMessage(sorted);
  }
};

Use Cases:

  • Große Daten sortieren/filtern
  • Komplexe Berechnungen
  • Bild-/Video-Processing
  • Parsing großer Files

9. Compression und Minification

Server-side:

// Express.js mit Compression
const compression = require('compression');
app.use(compression());

// Nginx
gzip on;
gzip_types text/css application/javascript image/svg+xml;
gzip_min_length 1000;

// Brotli (besser als gzip)
brotli on;
brotli_types text/css application/javascript;

Savings: 70-80% kleinere File Sizes!

Build-time Optimization:

// Vite Production Build
export default {
  build: {
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true, // Remove console.logs
        drop_debugger: true
      }
    }
  }
}

10. Caching Strategien

HTTP Caching:

// Express.js
app.use(express.static('public', {
  maxAge: '1y', // Static assets
  immutable: true
}));

// Nginx
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
  expires 1y;
  add_header Cache-Control "public, immutable";
}

Service Worker Caching:

// sw.js
const CACHE_NAME = 'v1';
const urlsToCache = [
  '/',
  '/styles.css',
  '/script.js'
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then((cache) => cache.addAll(urlsToCache))
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((response) => response || fetch(event.request))
  );
});

Monitoring: Messen ist wichtig!

// Web Vitals tracken
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  console.log(metric);
  // Send to your analytics
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);

Tools die ich täglich nutze:

  • Lighthouse - Chrome DevTools
  • WebPageTest - Detaillierte Analysen
  • Bundle Analyzer - Bundle Size tracken
  • Chrome Performance Tab - Profiling

Zusammenfassung

Diese 10 Quick Wins haben bei meinen Projekten:

  • 3-5s schnellere Ladezeiten
  • 60% kleinere Bundle Sizes
  • Bessere Core Web Vitals
  • Zufriedenere User

Start mit dem Größten Impact:

  1. Lazy Loading für Bilder
  2. Code Splitting
  3. Modern Image Formats
  4. Compression

Dann optimiere weiter basierend auf Messungen!

Ressourcen


Welche Performance-Optimierungen nutzt du? Teile deine Tipps!