fix: ensure SSR always fetches fresh assets from database
CI Pipeline / backend-test (push) Successful in 5m31s
CI Pipeline / frontend-test (push) Successful in 23s
CI Pipeline / frontend-build (push) Failing after 27s
CI Pipeline / backend-lint (push) Successful in 8s
CI Pipeline / backend-test (pull_request) Successful in 5m24s
CI Pipeline / frontend-test (pull_request) Successful in 23s
CI Pipeline / frontend-build (pull_request) Failing after 27s
CI Pipeline / backend-lint (pull_request) Successful in 8s
CI Pipeline / quality-gate (push) Successful in 2s
CI Pipeline / quality-gate (pull_request) Successful in 2s

Root cause: Dockerfile copied only browser/ to nginx; server/server.mjs
was built but never deployed or executed. Prerender baked DEFAULT_CONFIG
into static HTML with no API at build time, causing stale content on
every initial paint.

Changes:
- Dockerfile: serve stage uses node:22-alpine, installs nginx via apk,
  copies both browser/ and server/ bundles from build stage
- docker-entrypoint.sh: starts Angular SSR Node process on port 4200,
  waits for readiness, exports SSR_API_BASE_URL, then exec nginx
- nginx.conf.template: adds upstream ssr_backend block (127.0.0.1:4200),
  proxies / to SSR backend, static assets served directly with 1y cache
- app.server.routes.ts: all routes switched from RenderMode.Prerender
  to RenderMode.Server for zero stale content
- server.ts: removed dead prerender code (PRERENDERED_ROUTES,
  isPrerenderedRoute, allowStaticRouteRender)
- main.server.ts: injects SSR-aware apiBaseUrl via APP_CONFIG
  (reads SSR_API_BASE_URL env var)
- server.spec.ts: updated 5 test suites + added 5 new suites (31 tests)
  covering server.ts cleanup, RenderMode.Server, entrypoint lifecycle,
  Dockerfile build, nginx upstream proxy, and API URL injection
- vitest.config.ts: added server.spec.ts to include pattern

Request flow: Browser -> nginx (port 80) -> SSR server (port 4200)
-> Angular SSR -> HttpClient -> localhost:80/api/station-config
-> nginx proxies to api:8000 -> fresh data rendered in HTML
This commit is contained in:
robot committed 2026-08-07 05:33:40 +00:00
1 parent 2ece38e52d
commit 328d067d97
9 files changed
+243 -146

No files matched your search

+3 -47
View File
@@ -4,44 +4,12 @@ import {
isMainModule,
writeResponseToNodeResponse,
} from '@angular/ssr/node';
import { AngularAppEngine } from '@angular/ssr';
import * as http from 'node:http';
// Enable serving prerendered static HTML from disk. Without this flag the
// engine falls back to SSR for every route, ignoring the prerendered output.
AngularAppEngine.ɵallowStaticRouteRender = true;
/**
* Public routes prerendered at build time (SSG — Static Site Generation).
* These five routes are compiled to static HTML during `ng build` and
* served directly from disk at runtime for optimal performance and SEO.
* The list here mirrors `prerender-routes.txt` and the Prerender entries
* in `src/app/app.server.routes.ts`.
*/
export const PRERENDERED_ROUTES: readonly string[] = [
'/',
'/about',
'/schedule',
'/events',
'/contact',
];
/**
* Normalise a request pathname and check against prerendered routes.
* Handles both bare paths (`/about`) and trailing-slash variants (`/about/`).
*/
export function isPrerenderedRoute(url: string): boolean {
const pathname = new URL(url, 'http://localhost').pathname;
const normalised = pathname.endsWith('/') && pathname !== '/'
? pathname.slice(0, -1)
: pathname;
return PRERENDERED_ROUTES.includes(normalised);
}
/**
* Lazily instantiate AngularNodeAppEngine so the engine boots only when
* the first request arrives. The engine handles both prerendered route
* lookups and on-demand SSR bootstrapping internally.
* the first request arrives. All routes use RenderMode.Server — every
* request triggers on-demand SSR that fetches fresh API data before rendering.
*/
let appEngine: AngularNodeAppEngine | null = null;
function getEngine(): AngularNodeAppEngine {
@@ -53,22 +21,10 @@ function getEngine(): AngularNodeAppEngine {
/**
* Handle incoming requests using the Angular SSR engine.
*
* Prerendered routes (/ , /about, /schedule, /events, /contact) are served
* as pre-generated static HTML from the build output. All other routes
* fall back to server-side rendering (SSR) or the fallback 404 handler.
* Every request is rendered on the server with fresh data from the API.
*/
const requestHandler = createNodeRequestHandler(async (req, res, next) => {
try {
const prerendered = isPrerenderedRoute(req.url || '/');
if (prerendered) {
console.log(`[SSR] Prerendered route: ${req.url}`);
}
// AngularNodeAppEngine handles manifest registration internally —
// no manual bootstrap import needed. For prerendered routes it
// returns pre-generated static HTML; for SSR routes it bootstraps
// the Angular app on demand.
const response = await getEngine().handle(req);
if (response) {