# BizMatch SSR - Technische Dokumentation

## Was ist Server-Side Rendering (SSR)?

Server-Side Rendering bedeutet, dass die Angular-Anwendung nicht nur im Browser, sondern auch auf dem Server läuft und HTML vorab generiert.

---

## Unterschied: SPA vs. SSR vs. Prerendering

### 1. Single Page Application (SPA) - OHNE SSR

**Ablauf:**
```
Browser → lädt index.html
       → index.html enthält nur <app-root></app-root>
       → lädt JavaScript-Bundles
       → JavaScript rendert die Seite
```

**HTML-Response:**
```html
<!doctype html>
<html>
  <head><title>BizMatch</title></head>
  <body>
    <app-root></app-root>  <!-- LEER! -->
    <script src="main.js"></script>
  </body>
</html>
```

**Nachteile:**
- ❌ Suchmaschinen sehen leeren Content
- ❌ Langsamer "First Contentful Paint"
- ❌ Schlechtes SEO
- ❌ Kein Social-Media-Preview (Open Graph)

---

### 2. Server-Side Rendering (SSR)

**Ablauf:**
```
Browser → fragt Server nach /business/123
       → Server rendert Angular-App mit Daten
       → Server sendet vollständiges HTML
       → Browser zeigt sofort Inhalt
       → JavaScript lädt im Hintergrund
       → Anwendung wird "hydrated" (interaktiv)
```

**HTML-Response:**
```html
<!doctype html>
<html>
  <head>
    <title>Restaurant "Zum Löwen" | BizMatch</title>
    <meta name="description" content="Restaurant in München...">
  </head>
  <body>
    <app-root>
      <div class="listing-page">
        <h1>Restaurant "Zum Löwen"</h1>
        <p>Traditionelles deutsches Restaurant...</p>
        <!-- Kompletter gerendeter Content! -->
      </div>
    </app-root>
    <script src="main.js"></script>
  </body>
</html>
```

**Vorteile:**
- ✅ Suchmaschinen sehen vollständigen Inhalt
- ✅ Schneller First Contentful Paint
- ✅ Besseres SEO
- ✅ Social-Media-Previews funktionieren

**Nachteile:**
- ⚠️ Komplexere Konfiguration
- ⚠️ Server-Ressourcen erforderlich
- ⚠️ Code muss browser- und server-kompatibel sein

---

### 3. Prerendering (Static Site Generation)

**Ablauf:**
```
Build-Zeit → Rendert ALLE Seiten zu statischen HTML-Dateien
          → /business/123.html, /business/456.html, etc.
          → HTML-Dateien werden auf CDN deployed
```

**Unterschied zu SSR:**
- Prerendering: HTML wird **zur Build-Zeit** generiert
- SSR: HTML wird **zur Request-Zeit** generiert

**BizMatch nutzt SSR, NICHT Prerendering**, weil:
- Listings dynamisch sind (neue Einträge täglich)
- Benutzerdaten personalisiert sind
- Suche und Filter zur Laufzeit erfolgen

---

## Wie funktioniert SSR in BizMatch?

### Architektur-Überblick

```
┌─────────────────────────────────────────────────────────────┐
│                     Browser Request                          │
│                  GET /business/restaurant-123                │
└────────────────────────────┬────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────┐
│                    Express Server                            │
│                   (server.ts:30-41)                          │
├─────────────────────────────────────────────────────────────┤
│  1. Empfängt Request                                         │
│  2. Ruft AngularNodeAppEngine auf                            │
│  3. Rendert Angular-Komponente serverseitig                  │
│  4. Sendet HTML zurück                                       │
└────────────────────────────┬────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────┐
│              AngularNodeAppEngine                            │
│              (@angular/ssr/node)                             │
├─────────────────────────────────────────────────────────────┤
│  1. Lädt main.server.ts                                      │
│  2. Bootstrapped Angular in Node.js                          │
│  3. Führt Routing aus (/business/restaurant-123)             │
│  4. Rendert Component-Tree zu HTML-String                    │
│  5. Injiziert Meta-Tags, Titel                               │
└────────────────────────────┬────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────┐
│                 Angular Application                          │
│                 (Browser-Code im Server)                     │
├─────────────────────────────────────────────────────────────┤
│  • Komponenten werden ausgeführt                             │
│  • API-Calls werden gemacht (TransferState)                  │
│  • DOM wird SIMULIERT (ssr-dom-polyfill.ts)                  │
│  • HTML-Output wird generiert                                │
└─────────────────────────────────────────────────────────────┘
```

---

## Wichtige Dateien und ihre Rolle

### 1. `server.ts` - Express Server

```typescript
const angularApp = new AngularNodeAppEngine();

server.get('*', (req, res, next) => {
  angularApp.handle(req)  // ← Rendert Angular serverseitig
    .then((response) => {
      if (response) {
        writeResponseToNodeResponse(response, res);
      }
    });
});
```

**Rolle:**
- HTTP-Server (Express)
- Nimmt Requests entgegen
- Delegiert an Angular SSR Engine
- Sendet gerenderte HTML-Responses zurück

---

### 2. `src/main.server.ts` - Server Entry Point

```typescript
import './ssr-dom-polyfill';  // ← WICHTIG: DOM-Mocks laden

import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.server';

const bootstrap = () => bootstrapApplication(AppComponent, config);

export default bootstrap;
```

**Rolle:**
- Entry Point für SSR
- Lädt DOM-Polyfills **VOR** allen anderen Imports
- Bootstrapped Angular im Server-Kontext

---

### 3. `dist/bizmatch/server/index.server.html` - Server Template

**WICHTIG:** Diese Datei wird **beim Build erstellt**, nicht manuell geschrieben!

```bash
# Build-Prozess erstellt automatisch:
npm run build:ssr
  → dist/bizmatch/server/index.server.html  ✅
  → dist/bizmatch/server/server.mjs         ✅
  → dist/bizmatch/browser/index.csr.html    ✅
```

**Quelle:**
- Angular nimmt `src/index.html` als Vorlage
- Fügt SSR-spezifische Meta-Tags hinzu
- Generiert `index.server.html` für serverseitiges Rendering
- Generiert `index.csr.html` für clientseitiges Rendering (Fallback)

**Warum nicht im Git?**
- Build-Artefakte werden nicht eingecheckt (`.gitignore`)
- Jeder Build erstellt sie neu
- Verhindert Merge-Konflikte bei generierten Dateien

**Fehlerquelle bei neuem Laptop:**
```
git clone → dist/ Ordner fehlt
          → index.server.html fehlt
          → npm run serve:ssr crasht ❌

Lösung:   → npm run build:ssr
          → index.server.html wird erstellt ✅
```

---

### 4. `src/ssr-dom-polyfill.ts` - DOM-Mocks

```typescript
const windowMock = {
  document: { createElement: () => ({ ... }) },
  localStorage: { getItem: () => null },
  navigator: { userAgent: 'node' },
  // ... etc
};

if (typeof window === 'undefined') {
  (global as any).window = windowMock;
}
```

**Rolle:**
- Simuliert Browser-APIs in Node.js
- Verhindert `ReferenceError: window is not defined`
- Ermöglicht die Ausführung von Browser-Code im Server
- Kritisch für Libraries wie Leaflet, die `window` erwarten

**Warum notwendig?**
- Angular-Code nutzt `window`, `document`, `localStorage`, etc.
- Node.js hat diese APIs nicht
- Ohne Polyfills: Crash beim Server-Start

---

### 4. `ssr-dom-preload.mjs` - Node.js Preload Script

```javascript
import { isMainThread } from 'node:worker_threads';

if (!isMainThread) {
  // Skip polyfills in worker threads (sass, esbuild)
} else {
  globalThis.window = windowMock;
  globalThis.document = documentMock;
}
```

**Rolle:**
- Wird beim `dev:ssr` verwendet
- Lädt DOM-Mocks **VOR** allen anderen Modulen
- Nutzt Node.js `--import` Flag
- Vermeidet Probleme mit early imports

**Verwendung:**
```bash
NODE_OPTIONS='--import ./ssr-dom-preload.mjs' ng serve
```

---

### 5. `app.config.server.ts` - Server-spezifische Config

Enthält Provider, die nur im Server-Kontext geladen werden:
- `provideServerRendering()`
- Server-spezifische HTTP-Interceptors
- TransferState für API-Daten

---

## Rendering-Ablauf im Detail

### Phase 1: Server-Side Rendering

```
1. Request kommt an: GET /business/restaurant-123

2. Express Router:
   → server.get('*', ...)

3. AngularNodeAppEngine:
   → bootstrapApplication(AppComponent, serverConfig)
   → Angular läuft in Node.js

4. Angular Router:
   → Route /business/:slug matched
   → ListingDetailComponent wird aktiviert

5. Component Lifecycle:
   → ngOnInit() wird ausgeführt
   → API-Call: fetch('/api/listings/restaurant-123')
   → Daten werden geladen
   → Template wird mit Daten gerendert

6. TransferState:
   → API-Response wird in HTML injiziert
   → <script>window.__NG_STATE__ = {...}</script>

7. Meta-Tags:
   → Title-Service setzt <title>
   → Meta-Service setzt <meta name="description">

8. HTML-Output:
   → Komplettes HTML mit Daten
   → Wird an Browser gesendet
```

**Server-Output:**
```html
<!doctype html>
<html>
  <head>
    <title>Restaurant "Zum Löwen" | BizMatch</title>
    <meta name="description" content="Traditionelles Restaurant...">
  </head>
  <body>
    <app-root>
      <!-- Vollständig gerenderte Component -->
      <div class="listing-detail">
        <h1>Restaurant "Zum Löwen"</h1>
        <p>Adresse: Hauptstraße 1, München</p>
        <!-- etc. -->
      </div>
    </app-root>

    <!-- TransferState: verhindert doppelte API-Calls -->
    <script id="ng-state" type="application/json">
      {"listings":{"restaurant-123":{...}}}
    </script>

    <script src="main.js" defer></script>
  </body>
</html>
```

---

### Phase 2: Client-Side Hydration

```
1. Browser empfängt HTML:
   → Zeigt sofort gerenderten Content an ✅
   → User sieht Inhalt ohne Verzögerung

2. JavaScript lädt:
   → main.js wird heruntergeladen
   → Angular-Runtime startet

3. Hydration beginnt:
   → Angular scannt DOM
   → Vergleicht Server-HTML mit Client-Template
   → Attachiert Event Listener
   → Aktiviert Interaktivität

4. TransferState wiederverwenden:
   → Liest window.__NG_STATE__
   → Überspringt erneute API-Calls ✅
   → Daten sind bereits vorhanden

5. App ist interaktiv:
   → Buttons funktionieren
   → Routing funktioniert
   → SPA-Verhalten aktiviert
```

**Wichtig:**
- **Kein Flickern** (Server-HTML = Client-HTML)
- **Keine doppelten API-Calls** (TransferState)
- **Schneller First Contentful Paint** (HTML sofort sichtbar)

---

## SSR vs. Non-SSR: Was wird wann gerendert?

### Ohne SSR (`npm start`)

| Zeitpunkt | Server | Browser |
|-----------|--------|---------|
| T0: Request | Sendet leere `index.html` | - |
| T1: HTML empfangen | - | Leeres `<app-root></app-root>` |
| T2: JS geladen | - | Angular startet |
| T3: API-Call | - | Lädt Daten |
| T4: Rendering | - | **Erst jetzt sichtbar** ❌ |

**Time to First Contentful Paint:** ~2-3 Sekunden

---

### Mit SSR (`npm run serve:ssr`)

| Zeitpunkt | Server | Browser |
|-----------|--------|---------|
| T0: Request | Angular rendert + API-Call | - |
| T1: HTML empfangen | - | **Inhalt sofort sichtbar** ✅ |
| T2: JS geladen | - | Hydration beginnt |
| T3: Interaktiv | - | Event Listener attached |

**Time to First Contentful Paint:** ~200-500ms

---

## Prerendering vs. SSR: Wann wird gerendert?

### Prerendering (Static Site Generation)

```
Build-Zeit (npm run build):
  → ng build
  → Rendert /business/1.html
  → Rendert /business/2.html
  → Rendert /business/3.html
  → ...
  → Alle HTML-Dateien auf Server deployed

Request-Zeit:
  → Nginx sendet vorgefertigte HTML-Datei
  → KEIN Server-Side Rendering
```

**Vorteile:**
- Extrem schnell (statisches HTML)
- Kein Node.js-Server erforderlich
- Günstig (CDN-Hosting)

**Nachteile:**
- Nicht für dynamische Daten geeignet
- Re-Build bei jeder Änderung nötig
- Tausende Seiten = lange Build-Zeit

---

### SSR (Server-Side Rendering)

```
Build-Zeit (npm run build:ssr):
  → ng build (Client-Bundles)
  → ng build (Server-Bundles)
  → KEINE HTML-Dateien generiert

Request-Zeit:
  → Node.js Server empfängt Request
  → Angular rendert HTML on-the-fly
  → Frische Daten aus DB
  → Sendet HTML zurück
```

**Vorteile:**
- Immer aktuelle Daten
- Personalisierte Inhalte
- Keine lange Build-Zeit

**Nachteile:**
- Server-Ressourcen erforderlich
- Langsamer als Prerendering (Rendering kostet Zeit)
- Komplexere Infrastruktur

---

### BizMatch: Warum SSR statt Prerendering?

**Gründe:**

1. **Dynamische Listings:**
   - Neue Businesses werden täglich hinzugefügt
   - Prerendering würde tägliche Re-Builds erfordern

2. **Personalisierte Daten:**
   - Benutzer sehen unterschiedliche Inhalte (Favoriten, etc.)
   - Prerendering kann nicht personalisieren

3. **Suche und Filter:**
   - Unendliche Kombinationen von Filtern
   - Unmöglich, alle Varianten vorzurendern

4. **Skalierung:**
   - 10.000+ Listings → Prerendering = 10.000+ HTML-Dateien
   - SSR = 1 Server, rendert on-demand

---

## Client-Side Hydration im Detail

### Was ist Hydration?

**Hydration** = Angular "erweckt" das Server-HTML zum Leben.

**Ohne Hydration:**
- HTML ist statisch
- Buttons funktionieren nicht
- Routing funktioniert nicht
- Kein JavaScript-Event-Handling

**Nach Hydration:**
- Angular übernimmt Kontrolle
- Event Listener werden attached
- SPA-Routing funktioniert
- Interaktivität aktiviert

---

### Hydration-Ablauf

```typescript
// 1. Server rendert HTML
<button (click)="openModal()">Details</button>

// 2. Browser empfängt HTML
// → Button ist sichtbar, aber (click) funktioniert NICHT

// 3. Angular-JavaScript lädt
// → main.js wird ausgeführt

// 4. Hydration scannt DOM
angular.hydrate({
  serverHTML: '<button>Details</button>',
  clientTemplate: '<button (click)="openModal()">Details</button>',

  // Vergleich: HTML matches Template? ✅
  // → Reuse DOM node
  // → Attach Event Listener
});

// 5. Button ist jetzt interaktiv
// → (click) funktioniert ✅
```

---

### Probleme bei Hydration

#### Problem 1: Mismatch zwischen Server und Client

**Ursache:**
```typescript
// Server rendert:
<div>Server Time: {{ serverTime }}</div>

// Client rendert:
<div>Server Time: {{ clientTime }}</div>  // ← Unterschiedlich!
```

**Folge:**
- Angular erkennt Mismatch
- Wirft Warnung in Console
- Re-rendert Component (Performance-Verlust)

**Lösung:**
- TransferState nutzen für gemeinsame Daten
- `isPlatformServer()` für unterschiedliche Logik

---

#### Problem 2: Browser-only Code wird im Server ausgeführt

**Ursache:**
```typescript
ngOnInit() {
  window.scrollTo(0, 0);  // ← CRASH: window ist undefined im Server
}
```

**Lösung:**
```typescript
import { isPlatformBrowser } from '@angular/common';
import { PLATFORM_ID } from '@angular/core';

constructor(@Inject(PLATFORM_ID) private platformId: Object) {}

ngOnInit() {
  if (isPlatformBrowser(this.platformId)) {
    window.scrollTo(0, 0);  // ← Nur im Browser
  }
}
```

---

## TransferState: Verhindert doppelte API-Calls

### Problem ohne TransferState

```
Server:
  → GET /api/listings/123  ← API-Call 1
  → Rendert HTML mit Daten

Browser (nach JS-Load):
  → GET /api/listings/123  ← API-Call 2 (doppelt!)
  → Re-rendert Component
```

**Problem:**
- Doppelter Netzwerk-Traffic
- Langsamere Hydration
- Flickern beim Re-Render

---

### Lösung: TransferState

**Server-Side:**
```typescript
import { TransferState, makeStateKey } from '@angular/platform-browser';

const LISTING_KEY = makeStateKey<Listing>('listing-123');

ngOnInit() {
  this.http.get('/api/listings/123').subscribe(data => {
    this.transferState.set(LISTING_KEY, data);  // ← Speichern
    this.listing = data;
  });
}
```

**HTML-Output:**
```html
<script id="ng-state" type="application/json">
  {"listing-123": {"name": "Restaurant", "address": "..."}}
</script>
```

**Client-Side:**
```typescript
ngOnInit() {
  const cachedData = this.transferState.get(LISTING_KEY, null);

  if (cachedData) {
    this.listing = cachedData;  // ← Wiederverwenden ✅
  } else {
    this.http.get('/api/listings/123').subscribe(...);  // ← Nur wenn nicht cached
  }

  this.transferState.remove(LISTING_KEY);  // ← Cleanup
}
```

**Ergebnis:**
- ✅ Nur 1 API-Call (serverseitig)
- ✅ Kein Flickern
- ✅ Schnellere Hydration

---

## Performance-Vergleich

### Metriken

| Metrik | Ohne SSR | Mit SSR | Verbesserung |
|--------|----------|---------|--------------|
| **Time to First Byte (TTFB)** | 50ms | 200ms | -150ms ❌ |
| **First Contentful Paint (FCP)** | 2.5s | 0.5s | **-2s ✅** |
| **Largest Contentful Paint (LCP)** | 3.2s | 0.8s | **-2.4s ✅** |
| **Time to Interactive (TTI)** | 3.5s | 2.8s | -0.7s ✅ |
| **SEO Score (Lighthouse)** | 60 | 95 | +35 ✅ |

**Wichtig:**
- TTFB ist langsamer (Server muss rendern)
- Aber FCP viel schneller (HTML sofort sichtbar)
- User-Wahrnehmung: SSR fühlt sich schneller an

---

## SEO-Vorteile

### Google Crawler

**Ohne SSR:**
```html
<!-- Google sieht nur: -->
<app-root></app-root>
<script src="main.js"></script>
```

→ ❌ Kein Content indexiert
→ ❌ Kein Ranking
→ ❌ Keine Rich Snippets

---

**Mit SSR:**
```html
<!-- Google sieht: -->
<title>Restaurant "Zum Löwen" | BizMatch</title>
<meta name="description" content="Traditionelles Restaurant in München">
<h1>Restaurant "Zum Löwen"</h1>
<p>Adresse: Hauptstraße 1, 80331 München</p>
<div itemscope itemtype="https://schema.org/Restaurant">
  <span itemprop="name">Restaurant "Zum Löwen"</span>
  <span itemprop="address">München</span>
</div>
```

→ ✅ Vollständiger Content indexiert
→ ✅ Besseres Ranking
→ ✅ Rich Snippets (Sterne, Adresse, etc.)

---

### Social Media Previews (Open Graph)

**Ohne SSR:**
```html
<!-- Facebook/Twitter sehen nur: -->
<title>BizMatch</title>
```

→ ❌ Kein Preview-Bild
→ ❌ Keine Beschreibung

---

**Mit SSR:**
```html
<meta property="og:title" content="Restaurant 'Zum Löwen'" />
<meta property="og:description" content="Traditionelles Restaurant..." />
<meta property="og:image" content="https://bizmatch.net/images/restaurant.jpg" />
<meta property="og:url" content="https://bizmatch.net/business/restaurant-123" />
```

→ ✅ Schönes Preview beim Teilen
→ ✅ Mehr Klicks
→ ✅ Bessere User Experience

---

## Zusammenfassung

### SSR in BizMatch bedeutet:

1. **Server rendert HTML vorab** (nicht erst im Browser)
2. **Browser zeigt sofort Inhalt** (schneller First Paint)
3. **JavaScript hydrated im Hintergrund** (macht HTML interaktiv)
4. **Kein Flickern, keine doppelten API-Calls** (TransferState)
5. **Besseres SEO** (Google sieht vollständigen Content)
6. **Social-Media-Previews funktionieren** (Open Graph Tags)

### Technischer Stack:

- **@angular/ssr**: SSR-Engine
- **Express**: HTTP-Server
- **AngularNodeAppEngine**: Rendert Angular in Node.js
- **ssr-dom-polyfill.ts**: Simuliert Browser-APIs
- **TransferState**: Verhindert doppelte API-Calls

### Wann wird was gerendert?

- **Build-Zeit:** Nichts (kein Prerendering)
- **Request-Zeit:** Server rendert HTML on-the-fly
- **Nach JS-Load:** Hydration macht HTML interaktiv

### Best Practices:

1. Browser-Code mit `isPlatformBrowser()` schützen
2. TransferState für API-Daten nutzen
3. DOM-Polyfills für Third-Party-Libraries
4. Meta-Tags serverseitig setzen
5. Server-Build vor Deployment testen
