[Port to dtq-dev] Issue dspace-customers#902: Add configurable crawler policy (robots.txt + SSR excludes) - #1450
[Port to dtq-dev] Issue dspace-customers#902: Add configurable crawler policy (robots.txt + SSR excludes)#1450jr-rk wants to merge 2 commits into
Conversation
Crawlers enumerating Discovery facet links produced an unbounded set of distinct URLs; the URL-keyed SSR cache never hit and the render workers saturated, so the site answered HTTP 504 for everyone. - robots.txt: always disallow scoped community/collection search and every facet URL (?f. / &f.). Upstream "Disallow: /search" only matches paths that start with /search, so these stayed crawlable. - SSR: exclude the scoped-search paths from rendering via universal.excludePathPatterns (config.yml, config.example.yml, and the three build-time environment defaults) so they serve a CSR shell. - New robots config section (disallowHandle / disallowBrowse / disallowBitstreams), all default false, switchable per instance via config.yml or DSPACE_ROBOTS_* env vars with no template edit. Ports and generalizes mendelu 29b88bf (adapted from 9.x ssr. to this branch's universal. key), TUL 0eaea26, ZCU-PUB 11eb4d0, VSB-TUO acae3b4. Refs dataquest-dev/dspace-customers#902 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dspace-skills improvementsHigh impact1. 2. 12 unresolved 3. 4. node_modules donor-reuse is manual despite lockfile-identical clusters being known. Medium5. Trigger phrases duplicated across frontmatter, 6. Demo-video capture recipe written 3x ( 7. Registry parsed by two hand-rolled awk scripts ( 8. Three separate "3-iteration" caps, no shared counter: Low9. Two vocabularies for same coverage concept — 10. 11. 12. |
There was a problem hiding this comment.
Pull request overview
Adds a configurable crawler policy to reduce crawler-triggered SSR load.
Changes:
- Blocks scoped search and facet URLs in robots.txt.
- Adds optional handle, browse, and bitstream restrictions.
- Adds SSR exclusion patterns and configuration wiring.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/robots.txt.ejs |
Adds crawler directives and optional blocks. |
server.ts |
Passes crawler settings to EJS. |
src/config/robots-config.interface.ts |
Defines crawler configuration. |
src/config/app-config.interface.ts |
Registers crawler configuration. |
src/config/default-app-config.ts |
Provides safe defaults. |
src/environments/environment.ts |
Adds development SSR exclusions. |
src/environments/environment.production.ts |
Adds production SSR exclusions. |
src/environments/environment.test.ts |
Adds test defaults and exclusions. |
config/config.yml |
Adds runtime policy defaults. |
config/config.example.yml |
Documents available settings. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Copilot review (and an independent verification pass) found that the two scoped-search SSR excludes and their matching robots.txt lines never match anything on this 7.6.5 branch: collection/community routing has no `:id/search` child route here (that shape is DSpace 9.x, where the mendelu source commit came from), and scoped search on 7.6.5 is `/search?scope=<uuid>&f.*` — already covered by the pre-existing `Disallow: /search` and `^/search` SSR exclusion. - Remove the dead `^/(communities|collections)/[uuid]/search` patterns from config.yml, config.example.yml, environment.ts, environment.production.ts, environment.test.ts, and the matching dead robots.txt Disallow lines. - Keep the effective, new parts: the generic facet trap (Disallow: /*?f. and /*&f.) and the three opt-in toggles — those are unaffected by the routing question. - Extract the toggle-line assembly into buildOptionalRobotsDisallows() (src/config/robots.util.ts), rendered as a single EJS local from server.ts, and add a unit test (robots.util.spec.ts) covering each toggle and the no-blank-line-in-group invariant Copilot flagged as untested. Not applied: rewriting isExcludedFromSsr to inspect facet query keys. The documented incident (scoped-search facet enumeration) is already covered by the existing /search SSR exclusion; changing core SSR routing logic for every request is out of scope for this fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed the Copilot review. Removed the dead SSR patterns. On 7.6.5 scoped search is Added a regression test. Toggle-line assembly moved to Did not rewrite |
milanmajchrak
left a comment
There was a problem hiding this comment.
Is this really a backport? I think this is a new feature.
From where is this backported?
infographics visualization:
pr1450-crawler-policy-infographic.html
Problem
dtq-devhas no crawler policy. Onrepozitar.mendelu.cz(DSpace 9.1) crawlers — Baiduspider and others — walked Discovery facet links. Every facet combination is a distinct URL, so the SSR page cache (keyed by URL) never hits and each request costs a full Angular render plus Discovery REST queries. That combination space is effectively unbounded, the PM2/SSR workers stayed saturated, and the whole site answered HTTP 504 — home page included — until the crawler backed off.Four customer branches hardened
robots.txtindependently for this and adjacent reasons (mendelu, TUL, ZCU-PUB, VSB-TUO), so every new customer re-hits the same wall. This ports one configurable crawler policy to the integration branch instead of copying four variants.Fixes dataquest-dev/dspace-customers#902 · relates to dataquest-dev/dspace-customers#846
Root cause
robots.txthad no rule for Discovery facet URLs (?f.author=…,&f.subject=…, …) on any path.Disallow: /searchcovers the base search route by prefix, but a crawler that ignores robots.txt entirely (or a search-engine bot that partially honours it) can still request an unbounded number of facet permutations on paths robots.txt doesn't name.Correction vs. this PR's earlier revision: an earlier commit also added two
universal.excludePathPatternsregexes (^/collections/[uuid]/search,^/communities/[uuid]/search) and matchingrobots.txtlines, mirroring the mendelu 9.x source commit. GitHub Copilot's review caught that these are dead on this 7.6.5 branch: there is no:id/searchchild route oncollection-page-routing.module.ts/community-page-routing.module.tshere (that route shape only exists on 9.x), and scoped search on 7.6.5 is/search?scope=<uuid>&f.*— already served by the pre-existing, unchanged^/searchSSR-exclude pattern. A follow-up commit removed both the dead SSR patterns and the dead robots.txt lines.universal.excludePathPatternsis therefore unchanged fromdtq-devbase in this PR — verified:git diff origin/dtq-dev...HEAD -- src/environments/environment.ts src/environments/environment.production.ts config/config.yml config/config.example.ymltouches therobots:config section only, none of theexcludePathPatternsarrays.Change set
Always on —
src/robots.txt.ejs:Disallow: /*?f.andDisallow: /*&f.— blocks any URL carrying a Discovery facet filter, on any path, regardless of what comes before the query string. Two rules because the facet can be the first query parameter or a later one, and robots.txt has no "either" syntax.Optional blocks — new
robotsconfig section, all defaultfalse:disallowHandle→Disallow: /handle(TUL / ZCU-PUB)disallowBrowse→Disallow: /browse(TUL / ZCU-PUB)disallowBitstreams→Disallow: /bitstream/+/bitstreams/(VSB-TUO)src/config/robots-config.interface.ts(new), registered inapp-config.interface.ts+DefaultAppConfig;server.tsbuilds the optional lines viabuildOptionalRobotsDisallows()(src/config/robots.util.ts, unit-tested inrobots.util.spec.ts) and passes the result as a single EJS local, so the template has no conditional logic of its own. Switchable per instance viaconfig.ymlorDSPACE_ROBOTS_*env vars — no template edit required.Intentionally out of scope: a blanket
Disallow: /handleas a default — it would de-index the persistent identifiers customers rely on for handle redirects, hence the opt-in toggle, off by default. Also out of scope: making the SSR decision (isExcludedFromSsrinserver.ts) query-aware to catch facet params on non-/searchpaths (e.g. entity pages) for crawlers that ignore robots.txt entirely — the documented incident is already covered by the existing/searchSSR exclusion; this would be a separate, larger change to core SSR routing. Tracked as a follow-up, not silently dropped.Test evidence
Run in the worktree (
E:/workspace/issue-902-crawler-policy), Yarn 1.22.22, Node 20, full CI order:Served
/robots.txt, defaults (rendered from the actual template in this branch):No
/handle,/browse, or/bitstream(s)lines at default config — confirmed by booting the built server bundle twice: once with no env vars, once withDSPACE_ROBOTS_DISALLOWHANDLE=true DSPACE_ROBOTS_DISALLOWBROWSE=true DSPACE_ROBOTS_DISALLOWBITSTREAMS=true, and diffing the two livecurl /robots.txtresponses — the toggle set appeared in the second response with no template change.Before / after evidence page (human-watchable): https://claude.ai/code/artifact/892966ee-3015-4146-b10d-d6f81cb700e9. Note some of that page's SSR-routing-table content reflects the earlier, now-reverted
excludePathPatternsrevision — treat this PR body and the diff as authoritative over that page for the SSR-exclude claims.No screen-recording: this change has no rendered UI (it's the
/robots.txtroute + config plumbing), and the full backend stack wasn't started locally (Docker daemon down here). Perreference/pr-quality.md, the observable proof for a no-UI change is the served output above rather than a browser video.Risk & rollback
/handle,/browse,/bitstream(s)) is opt-in and off by default./search(e.g./entities/<type>/<uuid>?f.author=…), sinceisExcludedFromSsrmatches on path only. Pre-existing on this branch, not introduced or worsened by this PR; see "Intentionally out of scope" above.robotsconfig section is additive with safefalsedefaults; no data migration, no schema change;excludePathPatternsis untouched.Notes / assumptions
dtq-dev/fe-crawler-policy. That ref is impossible while adtq-devbranch exists (git directory/file conflict), so this usesufal/fe-crawler-policy— incustomers/registry.yml,ufal'sfe_branch/be_branchis itselfdtq-dev(notcustomer/ufal), so aufal/…work-branch prefix targets the integration branch by convention, same as the existingufal/fe-admin-sidebar-ssr-padding. This change is not UFAL-specific.29b88bf36b(source of the facet-trap idea; its SSR-pattern half didn't port cleanly to 7.6.5's routing, see Root cause), TUL0eaea26586, ZCU-PUB11eb4d0f6e, VSB-TUOacae3b4532(toggle sources).Fixes dataquest-dev/dspace-customers#902creates a cross-reference but does not auto-close the issue on merge (different repo) — close it manually after merge.Review-driven changes