From 1e48404accd1cb842e565999978c3f0af7177d94 Mon Sep 17 00:00:00 2001 From: Matus Kasak Date: Thu, 20 Aug 2026 12:39:34 +0200 Subject: [PATCH] JCU/fix(ssr): honour a configurable allowedHosts so not-found routes return 404 behind a proxy Angular's SSR engine only renders a request when its Host header matches `ui.baseUrl`'s hostname; any other host silently falls back to CSR, which always answers HTTP 200, so "not found" pages can never return a 404. The JCU test instance is served behind nginx on dev-6.pc while the deployed ui.baseUrl stays http://localhost:4000, so SSR was disabled for that host and `/route-does-not-exist-ui-test` answered 200 instead of 404 (dspace-ui-tests notFoundPage.spec.ts / UNIVERSAL-016). Add an optional `ssr.allowedHosts` config list that is merged with the baseUrl hostname when constructing the CommonEngine, and list dev-6.pc for the JCU deployment. Matched by hostname only (port ignored); harmless in production, where the public hostname already matches ui.baseUrl. Co-Authored-By: Claude Opus 4.8 --- config/config.example.yml | 9 +++++++++ config/config.yml | 7 +++++++ server.ts | 11 +++++++++-- src/config/ssr-config.interface.ts | 12 ++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index 76d0d4a597e..4ad7ef96b8f 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -44,6 +44,15 @@ ssr: - pattern: "^/access-control/" - pattern: "^/health$" + # Extra hostnames allowed to be server-side rendered, on top of the hostname of ui.baseUrl + # (which is always allowed). Angular's SSR only renders when the request "Host" header matches + # an allowed host; any other host silently falls back to CSR (which always answers 200, so a + # "not found" page can never return a 404). When the app is served behind a reverse proxy on a + # hostname different from ui.baseUrl, list that hostname here. Compared by hostname only (the + # port is ignored). Defaults to none. + # allowedHosts: + # - my-repository.example.com + # Whether to enable rendering of Search component on SSR. # If set to true the component will be included in the HTML returned from the server side rendering. # If set to false the component will not be included in the HTML returned from the server side rendering. diff --git a/config/config.yml b/config/config.yml index 3171e9bbdd5..16936cf214e 100644 --- a/config/config.yml +++ b/config/config.yml @@ -51,6 +51,13 @@ themes: href: assets/custom/images/favicons/manifest.webmanifest ssr: + # Hosts allowed to be server-side rendered in addition to ui.baseUrl's hostname (always allowed). + # The JCU test instance is served behind nginx on dev-6.pc while ui.baseUrl stays http://localhost:4000, + # so without dev-6.pc listed here SSR falls back to CSR for that host and "not found" pages answer 200 + # instead of 404. Compared by hostname only (port ignored). Harmless in production, where the public + # hostname already matches ui.baseUrl. + allowedHosts: + - dev-6.pc excludePathPatterns: - pattern: "^/communities/[a-f0-9-]{36}/browse(/.*)?$" flag: "i" diff --git a/server.ts b/server.ts index 568e41b3a71..69e76f8308e 100644 --- a/server.ts +++ b/server.ts @@ -247,9 +247,16 @@ function ngApp(req, res, next) { function serverSideRender(req, res, next, sendToUser: boolean = true) { const { protocol, originalUrl, baseUrl, headers } = req; // "allowedHosts" specifies which hosts are allowed to be rendered via SSR. - // By default, this is set to the host of the UI's baseUrl. + // The host of the UI's baseUrl is always allowed; any extra hosts configured via + // ssr.allowedHosts are added so SSR keeps working when the app is reached through a + // reverse proxy on a hostname that differs from baseUrl (otherwise those requests + // silently fall back to CSR, which always answers 200 and can never return a 404). + const allowedHosts = [ ...new Set([ + new URL(environment.ui.baseUrl).hostname, + ...(environment.ssr.allowedHosts ?? []), + ]) ]; const commonEngine = new CommonEngine({ enablePerformanceProfiler: environment.ssr.enablePerformanceProfiler, - allowedHosts: [ new URL(environment.ui.baseUrl).hostname ], + allowedHosts, }); // Render the page via SSR (server side rendering) commonEngine diff --git a/src/config/ssr-config.interface.ts b/src/config/ssr-config.interface.ts index 16bb3f54d66..17379cff556 100644 --- a/src/config/ssr-config.interface.ts +++ b/src/config/ssr-config.interface.ts @@ -48,6 +48,18 @@ export interface SSRConfig extends Config { */ excludePathPatterns: SsrExcludePatterns[]; + /** + * Extra hostnames that are allowed to be server-side rendered, in addition to the hostname of + * {@link UIServerConfig#baseUrl} (which is always allowed). + * + * Angular's SSR engine only renders a request when its `Host` header matches one of the allowed + * hosts; any other host silently falls back to client-side rendering (which always answers 200, + * so "not found" pages can never return a 404). When the app is reached through a reverse proxy + * on a hostname that differs from `ui.baseUrl` (e.g. a shared test box), list that hostname here + * so SSR keeps working. Compared by hostname only, the port is ignored. Defaults to none. + */ + allowedHosts?: string[]; + /** * Whether to enable rendering of search component on SSR */