# Static client serving — how-to

**Date:** 2026-05-17
**Backlog:** [BL-20260417-001](../_specifications/BACKLOG.md) — DyNTS-kliens kiszolgálás képesség
**Status:** ✅ done (audit + docs)

## TL;DR

A `DyNTS_App_Server` MÁR most ki tudja szolgálni a klienst (SPA-kat, statikus
oldalakat) az API route-ok mellett ugyanazon a hoszton. Az implementáció
2026-03-30 óta (commit `8ffe535`) része a frameworknek; a BL-20260417-001
backlog entry post-dated a fejlesztést. Ez a how-to a meglévő capability-t
dokumentálja.

## Konfigurációs felület

A static client serving akkor aktiválódik, ha a `DyNTS_App_Server`
subclass-od `getStaticClientSettings()`-et override-olja és egy
[`DyNTS_StaticClient_Settings`](../src/_models/interfaces/static-client-settings.interface.ts)
objektumot ad vissza:

```ts
import { DyNTS_StaticClient_Settings } from '@futdevpro/nts-dynamo';

export interface DyNTS_StaticClient_Settings {
  root: string;                  // a static fájlok mappája (abszolút v. cwd-relatív)
  fallbackPath?: string;          // SPA fallback (pl. 'index.html')
  assetCacheMaxAge?: number;      // asset Cache-Control max-age (sec)
  assetCacheImmutable?: boolean;  // 'immutable' direktíva (Angular hashed fájlokra)
  fallbackCacheMaxAge?: number;   // fallback file Cache-Control max-age (sec)
}
```

## Példa subclass (Angular SPA)

```ts
import path from 'path';
import { DyNTS_App_Server, DyNTS_StaticClient_Settings } from '@futdevpro/nts-dynamo';

export class MyApp_Server extends DyNTS_App_Server {
  // ... egyéb override-ok (getAppParams, getPortSettings, getApiBasePath, stb.) ...

  override getStaticClientSettings(): DyNTS_StaticClient_Settings | undefined {
    return {
      // Az Angular `dist/<app-name>` mappa abszolút path-ja
      root: path.resolve(__dirname, '../../client/dist/my-app'),

      // SPA — minden ismeretlen route a kliens-routerre megy
      fallbackPath: 'index.html',

      // Hashed assetek (Angular alapból ad nekik hash-et) — 1 év, immutable
      assetCacheMaxAge: 31_536_000,
      assetCacheImmutable: true,

      // index.html-t SOHA ne cache-elje a böngésző (deploy után friss kell)
      fallbackCacheMaxAge: 0,
    };
  }
}
```

## Mit csinál a framework a háttérben

A `DyNTS_App_Server.mountStaticClient()` ([app.server.ts ~1421-1484](../src/_services/server/app.server.ts))
ezt automatikusan beállítja az API route-ok regisztrációja UTÁN:

1. **`Express.static(root)` middleware** mount a `/` alatt. Ha `assetCacheMaxAge`
   meg van adva, `setHeaders` callback-en keresztül beállítja a `Cache-Control:
   max-age=<N>[, immutable]` header-t.
2. **SPA fallback** (ha `fallbackPath` adott): minden ismeretlen GET kérésre
   `res.sendFile(fallbackPath)` + `Cache-Control: max-age=<fallbackCacheMaxAge>`.
3. **Default 404** (ha `fallbackPath` hiányzik): a beépített
   [`DyNTS_defaultNotFoundPageHtml`](../src/_collections/default-not-found-page.const.ts)
   kerül kiszolgálásra `status: 404` + `Content-Type: text/html`.
4. **HTTP + HTTPS** mindkettő: a flow azonos a `this.openExpress` és
   `this.secureExpress` instance-ekhez (ha mindkettő be van állítva).

## Config matrix — mikor melyik mezőt használd

| Use case | `root` | `fallbackPath` | `assetCacheMaxAge` | `assetCacheImmutable` | `fallbackCacheMaxAge` |
|---|---|---|---|---|---|
| **SPA** (Angular, React) hashed assetekkel | ✅ kötelező | `'index.html'` | `31_536_000` (1 év) | `true` | `0` (mindig friss) |
| **SPA** dev mode (rebuild gyakori) | ✅ | `'index.html'` | `0` | `false` | `0` |
| **Statikus oldal** (nincs SPA-router) | ✅ | hagyd ki | `3600` (1 óra) vagy ahogy jó | `false` | n/a |
| **Csak API** (nincs frontend) | NE override-old a `getStaticClientSettings()`-et — akkor `undefined`-ot ad vissza és a `mountStaticClient` early-return-öl | | | | |

## Asset + fallback cache stratégia (SPA)

A két mező együttes használata az **alapja az Angular/React deploy-friendly caching**-nek:

- **Hashed assetek** (`main.<hash>.js`, `styles.<hash>.css`, stb.) ÉLETBEN nem
  változnak — biztonságosan `immutable` + 1 év max-age. A böngésző sose kérdezi
  meg újra a szerverről, amíg a HTML egy új hash-elt nevet nem hivatkozik be.
- **`index.html`** a "katalógus" ami a hashed assetekre mutat. Deploy után
  AZONNAL friss kell legyen, különben a kliens régi assetekre hivatkozó régi
  index.html-t kap → 404-ek a már nem létező hashed nevekre.
  → `fallbackCacheMaxAge: 0` (no-cache).

Az `assetCacheMaxAge` undefined-ja default (Express.static alapértelmezett) cache
viselkedést hagy meg — production deploy-okra mindig explicit állítsd be.

## Integration test minta

A meglévő integration test fixture ([`DyNTS_AppIntegrationTest_Mock`](../src/_modules/mock/app-integration-test.mock.ts)) +
spec ([app-extended.integration.spec.ts](../src/_modules/socket/app-extended.integration.spec.ts))
mutatja a teljes flow-t:

- A spec `beforeAll`-ban létrehoz egy temp dir-t, kitölti pár static fájllal,
  beállítja a mock `integrationStaticRoot` static property-jét, majd elindítja
  az appot.
- A spec asszertálja:
  - `started === true` (az app HTTP listenert kapott)
  - GET nemlétező path → `404` + a default not-found HTML kiszolgálva
  - GET `/index.html` → `200` + a temp dir-ben található static content
  - GET `/api/test-0/test-base`, `/api/test-0/test-simple` → API kiszolgálva (az API
    route-ok ELŐBB regisztráltak, mint a static fallback)
- `afterAll`-ban leállítja az appot és kitakarítja a temp dir-t.

## Edge case-ek + gotchák

- **Mounting sorrend:** a `mountStaticClient` az `_routingModules` regisztráció
  UTÁN fut, így az API route-ok prioritást élveznek. Tehát a `/api/...` mindig
  az API-hoz megy, akkor is, ha a static root-ban véletlenül lenne `api/`
  almappa.
- **`fallbackPath` relatív** a `root`-hoz képest (`sendFile(fallbackPath, { root })`).
- **Default 404 HTML:** szándékosan minimalista (`DyNTS_defaultNotFoundPageHtml`),
  hogy ne ütközzön a kliens stílusával. Ha custom 404-et akarsz, add meg a
  `fallbackPath`-t (az SPA-router majd kezelje belül a "page not found"-ot).
- **CSP / security header-ek:** a `mountStaticClient` NEM állít be CSP-t —
  ha kell, használj `helmet` middleware-t az API route-ok elé.

## Releváns kód- és test-fájlok

- [`src/_services/server/app.server.ts`](../src/_services/server/app.server.ts) — `mountStaticClient()` private + `getStaticClientSettings?()` opt-in override
- [`src/_models/interfaces/static-client-settings.interface.ts`](../src/_models/interfaces/static-client-settings.interface.ts) — config interface
- [`src/_collections/default-fallback-cache-max-age.const.ts`](../src/_collections/default-fallback-cache-max-age.const.ts) — default `0` (no-cache)
- [`src/_collections/default-not-found-page.const.ts`](../src/_collections/default-not-found-page.const.ts) — default 404 HTML
- [`src/_modules/mock/app-integration-test.mock.ts`](../src/_modules/mock/app-integration-test.mock.ts) — integration test fixture mintaként
- [`__documentations/nts-integration-tests-2026-03-17.md`](./nts-integration-tests-2026-03-17.md) — első integration test bevezetése a unified host behavior-ra

## Kapcsolódó backlog entry-k

- **BL-20260420-001..004** (dynamo-nts) — `DyNTS_FileLog_Service` + admin endpoints. Mind ✅ done.
- BL-20260417-001 (ez) — a kliens kiszolgálási képesség. ✅ done — implementáció már része a frameworknek; ez a doc a hivatkozás.
