{"id":"6a26dd74ddc38a01aa372060","title":"2026 06 08 Issue 202","path":"how-to/committee/release-notes/2026-06-08-issue-202","contentMarkdown":"# 08-Jun-2026 — legacy site URL redirect mapping with scraping, auto-mapping, and admin UI [#202](https://github.com/nbarrett/ngx-ramblers/issues/202)\n\n## [build 671](https://github.com/nbarrett/ngx-ramblers/actions/runs/27146990563) — [commit 92f4c30](https://github.com/nbarrett/ngx-ramblers/commit/92f4c3010b26acd2ad99ecd4b8f6bac0c8e42414)\n\n_____\n\nWhen migrating a legacy site (e.g. www.kentramblers.org.uk) to NGX Ramblers, external\norganisations and search engines hold links to specific URLs on the old site. This feature\nensures those links continue to work by automatically discovering, mapping, and redirecting\nlegacy URLs to their new equivalents.\n\nNavigate to **Admin > Legacy Redirects** and select the **Scrape** tab.\n\n- Enter the legacy domain (e.g. `www.kentramblers.org.uk`)\n- Configure options: max pages to crawl, delay between requests, and whether to respect robots.txt\n- Click **Run Scrape** to start the crawler\n- Real-time progress is displayed via WebSocket as the crawler discovers pages\n- Fragment identifiers (e.g. `#WW`) are recorded as separate mappable URLs\n- Metadata is captured for each URL: page title, HTTP status, and content type\n\nThe scraper can be re-run at any time to discover new or changed pages. Existing URLs are\npreserved and only new ones are added.\n\nAfter scraping, click **Auto-Map** to run the mapping algorithm. It matches legacy URLs\nto new NGX pages using multiple strategies:\n\n- **Walk URL match** (high confidence) — matches against imported walk/event URLs\n- **Slug match** (high confidence) — compares normalised path slugs against CMS page paths\n- **Title similarity** (medium confidence) — token overlap between scraped titles and page names\n- **Path pattern** (medium confidence) — recognises common patterns like `/walks/*`, `/gallery/*`, `/contact*`\n\nEach mapping receives a confidence score (high, medium, low, or unmapped).\n\nThe **Mappings** tab shows all discovered URLs with their proposed targets:\n\n- **Filter** by status (pending, accepted, ignored) or confidence level\n- **Search** across URLs and titles\n- **Sort** by any column\n- **Accept** a mapping to activate the redirect\n- **Edit** the target URL with autocomplete against all known NGX pages and walks\n- **Ignore** URLs that don't need redirects (e.g. admin pages)\n- **Bulk Accept** all high-confidence mappings with one click\n- **Delete** unwanted entries\n\nThe **Summary** tab shows a dashboard with:\n\n- Total URLs discovered\n- Mapped vs unmapped counts\n- Progress bar showing accepted/pending/ignored breakdown\n- Breakdown by confidence level and status\n\nOnce mappings are accepted, the system serves **301 permanent redirects** for matching\nincoming requests. This happens automatically via Express middleware — no additional\nconfiguration is needed.\n\n- Redirects are cached in memory and refreshed every 5 minutes\n- Redirect hit counts are tracked for monitoring (batched writes every 60 seconds)\n- API requests (`/api/*`) are never redirected\n\nEach NGX site can have its own set of legacy URL mappings. To configure which legacy\ndomains should trigger redirect lookups, add a `legacy-redirect` config key in the\nsystem settings with:\n\n```json\n{\n\"legacyDomains\": [\"www.kentramblers.org.uk\", \"kentramblers.org.uk\"],\n\"cacheRefreshMinutes\": 5,\n\"hitFlushIntervalSeconds\": 60\n}\n```\n\nMultiple legacy domains are supported per site.\n\n---\n\n**Shared Model:**\n- `projects/ngx-ramblers/src/app/models/legacy-url-redirect.model.ts` — interfaces and enums\n\n**Server — Database:**\n- `server/lib/mongo/models/legacy-url-mapping.ts` — Mongoose schema (collection: legacyUrlMappings)\n- `server/lib/mongo/models/legacy-scrape-run.ts` — Mongoose schema (collection: legacyScrapeRuns)\n\n**Server — Routes:**\n- `server/lib/mongo/routes/legacy-url-mapping.ts` — CRUD + /bulk-update-status, /auto-map, /summary, /target-urls\n- `server/lib/mongo/routes/legacy-scrape-run.ts` — CRUD routes\n\n**Server — Core Logic:**\n- `server/lib/legacy-redirect/redirect-middleware.ts` — Express middleware with in-memory cache and batched hit counting\n- `server/lib/legacy-redirect/auto-mapper.ts` — multi-strategy mapping algorithm\n- `server/lib/legacy-redirect/legacy-url-scraper.ts` — starts at the home page and follows links outward to find every internal page, fetching each one via the integration worker\n- `server/lib/legacy-redirect/legacy-redirect-ws-handler.ts` — WebSocket handler for long-running scrape operations\n\n**Frontend — Services:**\n- `projects/ngx-ramblers/src/app/services/legacy-redirect/legacy-url-mapping.service.ts`\n- `projects/ngx-ramblers/src/app/services/legacy-redirect/legacy-scrape-run.service.ts`\n\n**Frontend — Admin Component:**\n- `projects/ngx-ramblers/src/app/pages/admin/legacy-redirects/legacy-redirects.component.ts`\n- `projects/ngx-ramblers/src/app/pages/admin/legacy-redirects/legacy-redirects.component.sass`\n\n- `projects/ngx-ramblers/src/app/models/config.model.ts` — added LEGACY_REDIRECT ConfigKey\n- `projects/ngx-ramblers/src/app/models/websocket.model.ts` — added LEGACY_URL_SCRAPE EventType\n- `projects/ngx-ramblers/src/app/modules/admin/admin-routing.module.ts` — added legacy-redirects route\n- `server/lib/mongo/controllers/extended-group-event.ts` — exported convertTitleToSlug for reuse\n- `server/lib/server.ts` — registered routes and redirect middleware\n- `server/lib/websockets/websocket-server.ts` — registered WebSocket handler\n\n- `legacyUrlMappings` — compound unique index on (legacyDomain, legacyPath, legacyFragment)\n- `legacyScrapeRuns` — scrape operation history with audit logs\n\n- `GET/POST/PUT/DELETE /api/database/legacy-url-mapping` — standard CRUD\n- `POST /api/database/legacy-url-mapping/bulk-update-status` — bulk status changes\n- `POST /api/database/legacy-url-mapping/auto-map` — trigger auto-mapping\n- `GET /api/database/legacy-url-mapping/summary` — aggregate counts\n- `GET /api/database/legacy-url-mapping/target-urls` — autocomplete data\n- `GET/POST/PUT/DELETE /api/database/legacy-scrape-run` — scrape history CRUD\n- WebSocket: `LEGACY_URL_SCRAPE` event type for long-running scrape+map operations","contentHtml":"<h1>08-Jun-2026 — legacy site URL redirect mapping with scraping, auto-mapping, and admin UI <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/202\">#202</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/27146990563\">build 671</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/92f4c3010b26acd2ad99ecd4b8f6bac0c8e42414\">commit 92f4c30</a></h2>\n<hr>\n<p>When migrating a legacy site (e.g. <a href=\"http://www.kentramblers.org.uk\">www.kentramblers.org.uk</a>) to NGX Ramblers, external\norganisations and search engines hold links to specific URLs on the old site. This feature\nensures those links continue to work by automatically discovering, mapping, and redirecting\nlegacy URLs to their new equivalents.</p>\n<p>Navigate to <strong>Admin &gt; Legacy Redirects</strong> and select the <strong>Scrape</strong> tab.</p>\n<ul>\n<li>Enter the legacy domain (e.g. <code>www.kentramblers.org.uk</code>)</li>\n<li>Configure options: max pages to crawl, delay between requests, and whether to respect robots.txt</li>\n<li>Click <strong>Run Scrape</strong> to start the crawler</li>\n<li>Real-time progress is displayed via WebSocket as the crawler discovers pages</li>\n<li>Fragment identifiers (e.g. <code>#WW</code>) are recorded as separate mappable URLs</li>\n<li>Metadata is captured for each URL: page title, HTTP status, and content type</li>\n</ul>\n<p>The scraper can be re-run at any time to discover new or changed pages. Existing URLs are\npreserved and only new ones are added.</p>\n<p>After scraping, click <strong>Auto-Map</strong> to run the mapping algorithm. It matches legacy URLs\nto new NGX pages using multiple strategies:</p>\n<ul>\n<li><strong>Walk URL match</strong> (high confidence) — matches against imported walk/event URLs</li>\n<li><strong>Slug match</strong> (high confidence) — compares normalised path slugs against CMS page paths</li>\n<li><strong>Title similarity</strong> (medium confidence) — token overlap between scraped titles and page names</li>\n<li><strong>Path pattern</strong> (medium confidence) — recognises common patterns like <code>/walks/*</code>, <code>/gallery/*</code>, <code>/contact*</code></li>\n</ul>\n<p>Each mapping receives a confidence score (high, medium, low, or unmapped).</p>\n<p>The <strong>Mappings</strong> tab shows all discovered URLs with their proposed targets:</p>\n<ul>\n<li><strong>Filter</strong> by status (pending, accepted, ignored) or confidence level</li>\n<li><strong>Search</strong> across URLs and titles</li>\n<li><strong>Sort</strong> by any column</li>\n<li><strong>Accept</strong> a mapping to activate the redirect</li>\n<li><strong>Edit</strong> the target URL with autocomplete against all known NGX pages and walks</li>\n<li><strong>Ignore</strong> URLs that don&#39;t need redirects (e.g. admin pages)</li>\n<li><strong>Bulk Accept</strong> all high-confidence mappings with one click</li>\n<li><strong>Delete</strong> unwanted entries</li>\n</ul>\n<p>The <strong>Summary</strong> tab shows a dashboard with:</p>\n<ul>\n<li>Total URLs discovered</li>\n<li>Mapped vs unmapped counts</li>\n<li>Progress bar showing accepted/pending/ignored breakdown</li>\n<li>Breakdown by confidence level and status</li>\n</ul>\n<p>Once mappings are accepted, the system serves <strong>301 permanent redirects</strong> for matching\nincoming requests. This happens automatically via Express middleware — no additional\nconfiguration is needed.</p>\n<ul>\n<li>Redirects are cached in memory and refreshed every 5 minutes</li>\n<li>Redirect hit counts are tracked for monitoring (batched writes every 60 seconds)</li>\n<li>API requests (<code>/api/*</code>) are never redirected</li>\n</ul>\n<p>Each NGX site can have its own set of legacy URL mappings. To configure which legacy\ndomains should trigger redirect lookups, add a <code>legacy-redirect</code> config key in the\nsystem settings with:</p>\n<pre><code class=\"language-json\">{\n&quot;legacyDomains&quot;: [&quot;www.kentramblers.org.uk&quot;, &quot;kentramblers.org.uk&quot;],\n&quot;cacheRefreshMinutes&quot;: 5,\n&quot;hitFlushIntervalSeconds&quot;: 60\n}\n</code></pre>\n<p>Multiple legacy domains are supported per site.</p>\n<hr>\n<p><strong>Shared Model:</strong></p>\n<ul>\n<li><code>projects/ngx-ramblers/src/app/models/legacy-url-redirect.model.ts</code> — interfaces and enums</li>\n</ul>\n<p><strong>Server — Database:</strong></p>\n<ul>\n<li><code>server/lib/mongo/models/legacy-url-mapping.ts</code> — Mongoose schema (collection: legacyUrlMappings)</li>\n<li><code>server/lib/mongo/models/legacy-scrape-run.ts</code> — Mongoose schema (collection: legacyScrapeRuns)</li>\n</ul>\n<p><strong>Server — Routes:</strong></p>\n<ul>\n<li><code>server/lib/mongo/routes/legacy-url-mapping.ts</code> — CRUD + /bulk-update-status, /auto-map, /summary, /target-urls</li>\n<li><code>server/lib/mongo/routes/legacy-scrape-run.ts</code> — CRUD routes</li>\n</ul>\n<p><strong>Server — Core Logic:</strong></p>\n<ul>\n<li><code>server/lib/legacy-redirect/redirect-middleware.ts</code> — Express middleware with in-memory cache and batched hit counting</li>\n<li><code>server/lib/legacy-redirect/auto-mapper.ts</code> — multi-strategy mapping algorithm</li>\n<li><code>server/lib/legacy-redirect/legacy-url-scraper.ts</code> — starts at the home page and follows links outward to find every internal page, fetching each one via the integration worker</li>\n<li><code>server/lib/legacy-redirect/legacy-redirect-ws-handler.ts</code> — WebSocket handler for long-running scrape operations</li>\n</ul>\n<p><strong>Frontend — Services:</strong></p>\n<ul>\n<li><code>projects/ngx-ramblers/src/app/services/legacy-redirect/legacy-url-mapping.service.ts</code></li>\n<li><code>projects/ngx-ramblers/src/app/services/legacy-redirect/legacy-scrape-run.service.ts</code></li>\n</ul>\n<p><strong>Frontend — Admin Component:</strong></p>\n<ul>\n<li><p><code>projects/ngx-ramblers/src/app/pages/admin/legacy-redirects/legacy-redirects.component.ts</code></p>\n</li>\n<li><p><code>projects/ngx-ramblers/src/app/pages/admin/legacy-redirects/legacy-redirects.component.sass</code></p>\n</li>\n<li><p><code>projects/ngx-ramblers/src/app/models/config.model.ts</code> — added LEGACY_REDIRECT ConfigKey</p>\n</li>\n<li><p><code>projects/ngx-ramblers/src/app/models/websocket.model.ts</code> — added LEGACY_URL_SCRAPE EventType</p>\n</li>\n<li><p><code>projects/ngx-ramblers/src/app/modules/admin/admin-routing.module.ts</code> — added legacy-redirects route</p>\n</li>\n<li><p><code>server/lib/mongo/controllers/extended-group-event.ts</code> — exported convertTitleToSlug for reuse</p>\n</li>\n<li><p><code>server/lib/server.ts</code> — registered routes and redirect middleware</p>\n</li>\n<li><p><code>server/lib/websockets/websocket-server.ts</code> — registered WebSocket handler</p>\n</li>\n<li><p><code>legacyUrlMappings</code> — compound unique index on (legacyDomain, legacyPath, legacyFragment)</p>\n</li>\n<li><p><code>legacyScrapeRuns</code> — scrape operation history with audit logs</p>\n</li>\n<li><p><code>GET/POST/PUT/DELETE /api/database/legacy-url-mapping</code> — standard CRUD</p>\n</li>\n<li><p><code>POST /api/database/legacy-url-mapping/bulk-update-status</code> — bulk status changes</p>\n</li>\n<li><p><code>POST /api/database/legacy-url-mapping/auto-map</code> — trigger auto-mapping</p>\n</li>\n<li><p><code>GET /api/database/legacy-url-mapping/summary</code> — aggregate counts</p>\n</li>\n<li><p><code>GET /api/database/legacy-url-mapping/target-urls</code> — autocomplete data</p>\n</li>\n<li><p><code>GET/POST/PUT/DELETE /api/database/legacy-scrape-run</code> — scrape history CRUD</p>\n</li>\n<li><p>WebSocket: <code>LEGACY_URL_SCRAPE</code> event type for long-running scrape+map operations</p>\n</li>\n</ul>\n"}