From fc1e08946c6df9c7f312606ce35107e12a049232 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 13 Aug 2026 15:33:30 +0200 Subject: [PATCH] TUL/Exclude /browse, /search and admin paths from SSR (port of upstream #4332) Crawlers were feeding on SSR-rendered /browse pages: DSpace reflects unknown query params into generated links, and non-decoding crawlers turn the escaped separator into ever-growing amp;value params - an infinite URL space. On 7.5 every such URL is a full SSR render (~21 backend calls), which took the site down on Aug 6-9. Ports upstream dspace-7_x PR #4332 (excludePathPatterns) natively: excluded paths are served as CSR shell with no links to follow. Co-Authored-By: Claude Fable 5 --- server.ts | 18 +++++++++++++++++- src/config/universal-config.interface.ts | 10 ++++++++++ src/environments/environment.production.ts | 21 ++++++++++++++++++++- src/environments/environment.test.ts | 21 ++++++++++++++++++++- src/environments/environment.ts | 21 ++++++++++++++++++++- 5 files changed, 87 insertions(+), 4 deletions(-) diff --git a/server.ts b/server.ts index 3e10677a8b1..bb8a0b34cf3 100644 --- a/server.ts +++ b/server.ts @@ -55,6 +55,7 @@ import { APP_CONFIG, AppConfig } from './src/config/app-config.interface'; import { extendEnvironmentWithAppConfig } from './src/config/config.util'; import { logStartupMessage } from './startup-message'; import { TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model'; +import { SsrExcludePatterns } from './src/config/universal-config.interface'; /* @@ -228,7 +229,7 @@ export function app() { * The callback function to serve server side angular */ function ngApp(req, res) { - if (environment.universal.preboot) { + if (environment.universal.preboot && req.method === 'GET' && (req.path === '/' || !isExcludedFromSsr(req.path, environment.universal.excludePathPatterns))) { // Render the page to user via SSR (server side rendering) serverSideRender(req, res); } else { @@ -545,6 +546,21 @@ function start() { } } +/** + * Check if SSR should be skipped for path + * + * @param path + * @param excludePathPattern + */ +function isExcludedFromSsr(path: string, excludePathPattern: SsrExcludePatterns[]): boolean { + const patterns = excludePathPattern.map(p => + new RegExp(p.pattern, p.flag || '') + ); + return patterns.some((regex) => { + return regex.test(path) + }); +} + /* * The callback function to serve health check requests */ diff --git a/src/config/universal-config.interface.ts b/src/config/universal-config.interface.ts index c088dcd6579..90958e0c182 100644 --- a/src/config/universal-config.interface.ts +++ b/src/config/universal-config.interface.ts @@ -1,7 +1,17 @@ import { Config } from './config.interface'; +export interface SsrExcludePatterns { + pattern: string | RegExp; + flag?: string; +} + export interface UniversalConfig extends Config { preboot: boolean; async: boolean; time: boolean; + + /** + * Patterns to be used as regexes to match url's path and check if SSR is disabled for it. + */ + excludePathPatterns: SsrExcludePatterns[]; } diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts index 09b5f19ade6..544066431cd 100644 --- a/src/environments/environment.production.ts +++ b/src/environments/environment.production.ts @@ -7,6 +7,25 @@ export const environment: Partial = { universal: { preboot: true, async: true, - time: false + time: false, + excludePathPatterns: [ + { + pattern: '^/communities/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { + pattern: '^/collections/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { pattern: '^/browse/' }, + { pattern: '^/search' }, + { pattern: '^/community-list$' }, + { pattern: '^/statistics/?' }, + { pattern: '^/admin/' }, + { pattern: '^/processes/?' }, + { pattern: '^/notifications/' }, + { pattern: '^/access-control/' }, + { pattern: '^/health$' }, + ], } }; diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 0bb36da61f3..abc19b23410 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -10,7 +10,26 @@ export const environment: BuildConfig = { universal: { preboot: true, async: true, - time: false + time: false, + excludePathPatterns: [ + { + pattern: '^/communities/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { + pattern: '^/collections/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { pattern: '^/browse/' }, + { pattern: '^/search' }, + { pattern: '^/community-list$' }, + { pattern: '^/statistics/?' }, + { pattern: '^/admin/' }, + { pattern: '^/processes/?' }, + { pattern: '^/notifications/' }, + { pattern: '^/access-control/' }, + { pattern: '^/health$' }, + ], }, // Angular Universal server settings. diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 67a586718de..6f3b8751e9c 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -12,7 +12,26 @@ export const environment: Partial = { universal: { preboot: false, async: true, - time: false + time: false, + excludePathPatterns: [ + { + pattern: '^/communities/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { + pattern: '^/collections/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { pattern: '^/browse/' }, + { pattern: '^/search' }, + { pattern: '^/community-list$' }, + { pattern: '^/statistics/?' }, + { pattern: '^/admin/' }, + { pattern: '^/processes/?' }, + { pattern: '^/notifications/' }, + { pattern: '^/access-control/' }, + { pattern: '^/health$' }, + ], }, // The REST API server settings.