CI Pipeline / backend-test (push) Successful in 5m39s
CI Pipeline / frontend-test (push) Successful in 27s
CI Pipeline / frontend-build (push) Successful in 30s
CI Pipeline / backend-lint (push) Successful in 9s
CI Pipeline / backend-test (pull_request) Successful in 5m24s
CI Pipeline / frontend-test (pull_request) Successful in 22s
CI Pipeline / frontend-build (pull_request) Successful in 30s
CI Pipeline / quality-gate (push) Successful in 2s
CI Pipeline / quality-gate (pull_request) Successful in 2s
CI Pipeline / backend-lint (pull_request) Successful in 8s
- Replace await import('./main.server.js') with lazy getEngine()
(original path was wrong — main.server.ts lives in src/, not root)
- AngularNodeAppEngine handles manifest registration internally;
no manual bootstrap import needed
- Add npm scripts: build:ssr, build:prerender, serve:ssr:radio-station
- Add PRERENDER.md build documentation
Verified: clean build prerenders all 5 routes, 54 tests pass
92 lines
2.8 KiB
TypeScript
92 lines
2.8 KiB
TypeScript
import {
|
|
AngularNodeAppEngine,
|
|
createNodeRequestHandler,
|
|
isMainModule,
|
|
writeResponseToNodeResponse,
|
|
} from '@angular/ssr/node';
|
|
import * as http from 'node:http';
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
let appEngine: AngularNodeAppEngine | null = null;
|
|
function getEngine(): AngularNodeAppEngine {
|
|
if (!appEngine) {
|
|
appEngine = new AngularNodeAppEngine();
|
|
}
|
|
return appEngine;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
export default 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) {
|
|
await writeResponseToNodeResponse(response, res);
|
|
} else {
|
|
next();
|
|
}
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
if (isMainModule(import.meta.url)) {
|
|
const httpServer = http.createServer();
|
|
httpServer.on('request', async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
|
const handler = (await import('./server.js')).default;
|
|
handler(req, res, () => {
|
|
res.statusCode = 404;
|
|
res.end('Not Found');
|
|
});
|
|
});
|
|
httpServer.listen(4200, () => {
|
|
console.log(`Node SSR server listening on http://localhost:4200`);
|
|
});
|
|
}
|