42 Deployment-Grundlagen – Von React zu Static Files

Ein häufiges Missverständnis: “React ist eine Node.js-Anwendung”. Falsch. React läuft im Browser – komplett. Node.js ist nur das Build-Tool, nicht die Runtime. Nach npm run build entsteht statischer Web-Content: HTML, JavaScript, CSS. Kein Server-Prozess, keine Node-Abhängigkeiten, kein npm start in Production.

Das hat fundamentale Konsequenzen: Eine React-App kann auf jedem Webserver gehostet werden, der statische Dateien ausliefern kann. Apache, Nginx, S3 Bucket, CDN – alles funktioniert. Kein Express-Server nötig, keine Port-Konfiguration, keine Process-Manager.

42.1 Der Build-Prozess: Von Source zu Static

Development-Modus mit Vite:

npm run dev
# Startet Development-Server auf http://localhost:5173
# Hot Module Replacement (HMR)
# Source Maps für Debugging
# Unminified Code

Das ist nicht für Production. Es ist ein Development-Server mit Live-Reload.

Production Build:

npm run build
# Output: dist/ Verzeichnis

Was passiert beim Build?

  1. TypeScript → JavaScript: TSX/TS wird zu JS kompiliert
  2. Bundling: Alle Module werden zu wenigen Files kombiniert
  3. Minification: Code wird komprimiert (Whitespace weg, Variablen gekürzt)
  4. Tree-Shaking: Ungenutzter Code wird entfernt
  5. Asset-Hashing: Files bekommen Content-Hash im Namen (main-a3f2d8b9.js)
  6. Code-Splitting: Lazy-loaded Components → Separate Chunks
  7. CSS-Extraktion: CSS aus JS extrahiert zu .css Files
  8. Source Maps (optional): Für Production-Debugging

Vorher (Source):

src/
  ├── App.tsx
  ├── components/
  │   ├── Header.tsx
  │   ├── Footer.tsx
  │   └── Dashboard.tsx
  ├── utils/
  │   ├── api.ts
  │   └── helpers.ts
  ├── styles/
  │   └── global.css
  └── main.tsx

Nachher (Build Output):

dist/
  ├── index.html
  ├── assets/
  │   ├── index-a3f2d8b9.js          # Main bundle
  │   ├── vendor-c9e4f1a2.js         # Dependencies (React, etc.)
  │   ├── Dashboard-b7d3e5f8.js      # Lazy-loaded chunk
  │   ├── index-f4e8a2c1.css         # Extracted CSS
  │   └── logo-d4f7a9b3.svg          # Static assets
  └── favicon.ico

index.html (generiert):

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My React App</title>
    <link rel="stylesheet" href="/assets/index-f4e8a2c1.css">
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/assets/index-a3f2d8b9.js"></script>
  </body>
</html>

Das ist alles. Keine Server-Logic, keine Templates, kein Rendering. Browser lädt HTML, lädt JS, führt React aus.

42.2 Warum funktioniert das ohne Server?

React ist eine Client-Side-Bibliothek. Der gesamte Code läuft im Browser:

  1. Browser requested https://example.com/
  2. Server sendet index.html
  3. Browser parst HTML, sieht <script src="/assets/index-*.js">
  4. Browser lädt JavaScript
  5. JavaScript startet, initialisiert React
  6. React rendert UI im <div id="root">
  7. Routing, State-Management, API-Calls – alles im Browser

Kein Backend-Code läuft. API-Calls gehen an separate Backend-Services, aber die React-App selbst ist purer Frontend-Code.

42.3 Hosting-Optionen: Static File Server genügt

42.3.1 Option 1: Nginx (Production-Standard)

# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name example.com;
    
    root /var/www/myapp/dist;
    index index.html;
    
    # Client-Side Routing Support
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # Cache Static Assets
    location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Gzip Compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}

Wichtig: try_files $uri $uri/ /index.html;

Das ist kritisch für Client-Side Routing. User navigiert zu /products/123 → Server hat keine products/123.html → Ohne try_files gibt’s 404. Mit try_files → Server sendet index.html → React Router übernimmt.

Deploy:

# Build
npm run build

# Upload zu Server
scp -r dist/* user@server:/var/www/myapp/dist/

# Nginx reload
ssh user@server 'sudo systemctl reload nginx'

42.3.2 Option 2: Apache (.htaccess)

# dist/.htaccess
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  
  # Wenn File existiert, liefere es aus
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  
  # Sonst: index.html
  RewriteRule . /index.html [L]
</IfModule>

# Cache Headers
<FilesMatch "\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2)$">
  Header set Cache-Control "max-age=31536000, public, immutable"
</FilesMatch>

Deploy auf Shared Hosting:

npm run build
# FTP Upload von dist/ zu public_html/

42.3.3 Option 3: Netlify (Zero-Config)

# netlify.toml
[build]
  command = "npm run build"
  publish = "dist"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

Deploy:

# Via Netlify CLI
netlify deploy --prod

# Oder: Git Push → Auto-Deploy
git push origin main

Netlify regelt automatisch: - HTTPS - CDN - Client-Side Routing - Atomic Deploys - Rollbacks

42.3.4 Option 4: Vercel (Next.js-Hersteller)

// vercel.json
{
  "rewrites": [
    { "source": "/(.*)", "destination": "/" }
  ]
}

Deploy:

vercel --prod

42.3.5 Option 5: AWS S3 + CloudFront

# Build
npm run build

# Upload zu S3
aws s3 sync dist/ s3://my-react-app-bucket --delete

# Invalidate CloudFront Cache
aws cloudfront create-invalidation --distribution-id E1234567890 --paths "/*"

S3 Bucket Configuration:

CloudFront Distribution:

42.3.6 Option 6: GitHub Pages

npm install --save-dev gh-pages
// package.json
{
  "homepage": "https://username.github.io/repo-name",
  "scripts": {
    "predeploy": "npm run build",
    "deploy": "gh-pages -d dist"
  }
}
npm run deploy

Vite Config für GitHub Pages:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  base: '/repo-name/', // Wichtig für Subpath!
});

42.4 Client-Side Routing: Das 404-Problem

Problem: User bookmarked /products/123. Bei direktem Aufruf:

Browser: GET https://example.com/products/123
Server: "Ich habe keine products/123.html" → 404

React Router kann nicht laufen, weil JS nie geladen wurde.

Lösung: Server muss immer index.html ausliefern, egal welcher Path.

Verschiedene Server-Konfigurationen:

Server Konfiguration
Nginx try_files $uri $uri/ /index.html;
Apache .htaccess mit RewriteRule . /index.html [L]
Express app.get('*', (req, res) => res.sendFile('index.html'))
Netlify [[redirects]] in netlify.toml
Vercel rewrites in vercel.json
S3 Error Document = index.html

42.5 Performance: Warum Hash-Names?

Build-Output hat Hash im Dateinamen:

index-a3f2d8b9.js
index-f7c2e1a4.css

Grund: Browser-Caching.

Ohne Hash:

<script src="/main.js"></script>

Browser cached main.js. Neues Deployment → main.js ändert sich. Browser nutzt alte gecachte Version. User sieht veralteten Code.

Mit Hash:

<script src="/assets/index-a3f2d8b9.js"></script>

Code ändert sich → Hash ändert sich → Neuer Filename → Browser lädt neue Version.

Cache-Strategie:

# Hashed Assets: Cache Forever
location ~* \.[a-f0-9]{8}\.(js|css)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# HTML: Never Cache
location ~* \.html$ {
    expires -1;
    add_header Cache-Control "no-store, no-cache, must-revalidate";
}

HTML wird nie gecached (enthält neue Hashes). JS/CSS mit Hash werden ewig gecached.

42.6 Preview: Lokales Testing des Builds

npm run build
npm run preview

Vite startet Server für dist/ – simuliert Production-Verhalten lokal. Kein HMR, kein Dev-Server, nur Static File Serving.

Alternativ: Globaler Static Server

npm install -g serve
serve -s dist -p 3000

42.7 SSR vs. CSR vs. SSG: Begriffe klären

CSR (Client-Side Rendering) – Standard React: - Server sendet leeres HTML - JavaScript rendert UI im Browser - Vite, Create React App → CSR

SSR (Server-Side Rendering) – Next.js, Remix: - Server rendert HTML mit Daten - JavaScript hydrated im Browser - Braucht Node.js-Server in Production

SSG (Static Site Generation) – Next.js, Gatsby: - HTML wird zur Build-Zeit generiert - Jede Route → Separate HTML-Datei - Static Hosting möglich

Dieses Kapitel behandelt CSR – klassisches React ohne SSR.

42.8 Häufige Fehler

Fehler 1: Hardcoded localhost URLs

// ❌ Falsch: Development-URL hardcoded
const API_URL = 'http://localhost:3000/api';

fetch(`${API_URL}/users`);
// In Production: Keine API auf localhost!

// ✓ Richtig: Environment-Variable
const API_URL = import.meta.env.VITE_API_URL;

Fehler 2: Dev-Dependencies in Production

//  Falsch: React in devDependencies
{
  "devDependencies": {
    "react": "^18.2.0"  // React IST Production-Dependency!
  }
}

//  Richtig
{
  "dependencies": {
    "react": "^18.2.0"
  },
  "devDependencies": {
    "vite": "^5.0.0",
    "typescript": "^5.0.0"
  }
}

Build-Tools in devDependencies, alles was im Bundle landet in dependencies.

Fehler 3: Source Maps in Production

// vite.config.ts
export default defineConfig({
  build: {
    sourcemap: false  // ✓ In Production keine Source Maps
  }
});

Source Maps zeigen original Code → Security Risk, größere Files.

Fehler 4: Console.logs vergessen

// ❌ Debug-Logs in Production
console.log('User data:', userData);
console.log('API Response:', response);

// ✓ Conditional Logging
if (import.meta.env.DEV) {
  console.log('Debug:', data);
}

// ✓ Oder: Strip beim Build (Vite macht das automatisch für console.log)

Fehler 5: Absolute Paths ohne Base

// ❌ Falsch: Absolute Path ohne Base-Config
<img src="/logo.svg" />
// Funktioniert auf https://example.com/
// Bricht auf https://example.com/subpath/

// ✓ Richtig: Relative oder mit base
<img src="./logo.svg" />

// Oder vite.config.ts
export default defineConfig({
  base: '/subpath/'
});

42.9 Deployment-Checkliste

React-Apps sind statischer Web-Content. Kein Node.js in Production, kein komplexes Server-Setup. HTML + JS + CSS, deploybar auf jeden Webserver weltweit. Das macht React-Deployments simpel, günstig und skalierbar – aber Client-Side Routing braucht Server-Config, und Environment-Management ist kritisch.