Skip to content

Conversation

@rmgpinto
Copy link
Contributor

@rmgpinto rmgpinto commented Dec 16, 2025

ref https://linear.app/ghost/issue/BER-3095

Extracted the link redirects code from the middleware to the router. This avoids route comparison on every requests Ghost receives.


Note

Moves link redirect handling out of global middleware and into a dedicated router for clearer routing and fewer per-request checks.

  • New routers/link-redirects.js registers GET handler at linkRedirects.service.relativeRedirectPrefix() + '*'; site.js mounts this router
  • LinkRedirectsService: adds relativeRedirectPrefix() and removes in-method prefix checks; handleRequest now assumes routing filtered paths
  • Tests: add coverage for relativeRedirectPrefix, update redirect behavior tests, and drop non-matching-prefix cases

Written by Cursor Bugbot for commit 93fa62f. This will update automatically on new commits. Configure here.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 16, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This change extracts link redirect route registration into a new frontend router module exposing handleRedirects(siteApp), and updates site setup to call that handler instead of mounting the service middleware directly. The LinkRedirectsService adds relativeRedirectPrefix() returning a normalized relative prefix and removes an early guard in handleRequest, causing URL construction and lookup to run unconditionally. Unit tests were added for relativeRedirectPrefix() and two handleRequest tests were removed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and concisely summarizes the main architectural change: refactoring link redirects from middleware to a dedicated router pattern.
Description check ✅ Passed The description is well-related to the changeset, providing context (issue reference), rationale (performance improvement), and details about the implementation across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ghost/core/core/server/services/link-redirection/LinkRedirectsService.js (1)

80-86: Well-designed public API for route registration.

The method correctly combines the baseURL pathname with the redirect prefix.

Minor: Consider renaming fullURLWithRedirectPrefix to redirectPath since it's a path segment, not a full URL.

     redirectPrefix() {
-        const fullURLWithRedirectPrefix = `${this.#baseURL.pathname}${this.#redirectURLPrefix}`;
-        return fullURLWithRedirectPrefix;
+        return `${this.#baseURL.pathname}${this.#redirectURLPrefix}`;
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 532426d and e29c039.

📒 Files selected for processing (3)
  • ghost/core/core/frontend/web/routers/link-redirects.js (1 hunks)
  • ghost/core/core/frontend/web/site.js (2 hunks)
  • ghost/core/core/server/services/link-redirection/LinkRedirectsService.js (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24779
File: ghost/core/core/server/services/members/members-api/controllers/RouterController.js:34-51
Timestamp: 2025-09-03T12:28:11.174Z
Learning: The extractRefererOrRedirect function in Ghost's RouterController (ghost/core/core/server/services/members/members-api/controllers/RouterController.js) is working as intended according to maintainer kevinansfield. The current handling of autoRedirect, redirect parameter parsing, and referrer logic does not need the security-related changes suggested around autoRedirect coercion, relative URL handling, or same-origin checks.
📚 Learning: 2025-09-03T12:28:11.174Z
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24779
File: ghost/core/core/server/services/members/members-api/controllers/RouterController.js:34-51
Timestamp: 2025-09-03T12:28:11.174Z
Learning: The extractRefererOrRedirect function in Ghost's RouterController (ghost/core/core/server/services/members/members-api/controllers/RouterController.js) is working as intended according to maintainer kevinansfield. The current handling of autoRedirect, redirect parameter parsing, and referrer logic does not need the security-related changes suggested around autoRedirect coercion, relative URL handling, or same-origin checks.

Applied to files:

  • ghost/core/core/frontend/web/routers/link-redirects.js
  • ghost/core/core/frontend/web/site.js
  • ghost/core/core/server/services/link-redirection/LinkRedirectsService.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Ghost admin serves admin-x apps from `/ghost/assets/{app-name}/{app-name}.js` URLs

Applied to files:

  • ghost/core/core/frontend/web/site.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Ghost-CLI tests
  • GitHub Check: Legacy tests (Node 22.18.0, mysql8)
  • GitHub Check: Acceptance tests (Node 22.18.0, mysql8)
  • GitHub Check: Lint
  • GitHub Check: Unit tests (Node 22.18.0)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Build & Push Docker Image
🔇 Additional comments (3)
ghost/core/core/server/services/link-redirection/LinkRedirectsService.js (1)

95-116: Handler logic is correct for the router-based approach.

The method properly handles the redirect flow: lookup → dispatch event → redirect (or pass to next middleware if not found). The next() call on line 101 when no link is found will correctly result in a 404 via downstream error handlers.

ghost/core/core/frontend/web/site.js (1)

54-54: Correct placement in the middleware/router stack.

The handler registration maintains the same position as the original middleware, ensuring link redirects are processed early before custom redirects and other route handlers.

ghost/core/core/frontend/web/routers/link-redirects.js (1)

1-5: Clean router implementation that achieves the PR objective.

The explicit GET route registration with redirectPrefix() + '*' ensures this handler only runs for redirect URLs, avoiding the middleware overhead on every request.

@rmgpinto rmgpinto force-pushed the link-redirects-router branch from e29c039 to 5369c84 Compare December 16, 2025 10:16
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on January 3

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e29c039 and 5369c84.

📒 Files selected for processing (3)
  • ghost/core/core/frontend/web/routers/link-redirects.js (1 hunks)
  • ghost/core/core/frontend/web/site.js (2 hunks)
  • ghost/core/core/server/services/link-redirection/LinkRedirectsService.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/core/frontend/web/routers/link-redirects.js
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24779
File: ghost/core/core/server/services/members/members-api/controllers/RouterController.js:34-51
Timestamp: 2025-09-03T12:28:11.174Z
Learning: The extractRefererOrRedirect function in Ghost's RouterController (ghost/core/core/server/services/members/members-api/controllers/RouterController.js) is working as intended according to maintainer kevinansfield. The current handling of autoRedirect, redirect parameter parsing, and referrer logic does not need the security-related changes suggested around autoRedirect coercion, relative URL handling, or same-origin checks.
📚 Learning: 2025-09-03T12:28:11.174Z
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24779
File: ghost/core/core/server/services/members/members-api/controllers/RouterController.js:34-51
Timestamp: 2025-09-03T12:28:11.174Z
Learning: The extractRefererOrRedirect function in Ghost's RouterController (ghost/core/core/server/services/members/members-api/controllers/RouterController.js) is working as intended according to maintainer kevinansfield. The current handling of autoRedirect, redirect parameter parsing, and referrer logic does not need the security-related changes suggested around autoRedirect coercion, relative URL handling, or same-origin checks.

Applied to files:

  • ghost/core/core/server/services/link-redirection/LinkRedirectsService.js
  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-02-06T10:30:53.440Z
Learnt from: allouis
Repo: TryGhost/Ghost PR: 22127
File: .github/scripts/release-apps.js:72-74
Timestamp: 2025-02-06T10:30:53.440Z
Learning: In the Ghost repository, the path to defaults.json is correctly structured as 'ghost/core/core/shared/config/defaults.json' with an intentional double 'core' in the path.

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Core Ghost backend logic should be placed in `ghost/core/core/server/` directory structure organized by purpose (api, services, models, data/schema, etc.)

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Ghost admin serves admin-x apps from `/ghost/assets/{app-name}/{app-name}.js` URLs

Applied to files:

  • ghost/core/core/frontend/web/site.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build & Push Docker Image
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (2)
ghost/core/core/server/services/link-redirection/LinkRedirectsService.js (2)

80-86: LGTM!

The redirectPrefix() method correctly constructs and returns the full redirect path prefix by combining the base URL pathname with the redirect URL prefix.


95-116: Router correctly filters requests by prefix—code change is safe.

The router at ghost/core/core/frontend/web/routers/link-redirects.js properly mounts the handler on linkRedirects.service.redirectPrefix() + '*', ensuring only redirect-prefixed requests reach handleRequest. This satisfies the requirement for prefix filtering without the inline check. The change improves maintainability by delegating path filtering to the router layer as intended.

@rmgpinto rmgpinto force-pushed the link-redirects-router branch from 5369c84 to bbfd884 Compare December 16, 2025 10:20
@rmgpinto rmgpinto force-pushed the link-redirects-router branch from bbfd884 to 88cd61d Compare December 16, 2025 10:36
@rmgpinto rmgpinto force-pushed the link-redirects-router branch from 88cd61d to 38d312c Compare December 16, 2025 10:50
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ghost/core/test/unit/server/services/link-redirection/LinkRedirectsService.test.js (1)

68-88: Add test coverage for the routePath() method.

The test suite adds coverage for redirectPrefix() but the service also introduces a routePath() method (line 95-97 in LinkRedirectsService.js) that has no corresponding tests. Since this is a new public API, it should have test coverage to verify it returns the expected value (/r/).

Add a test case for routePath():

     });
+
+    describe('routePath', function () {
+        it('returns the Express route path', function () {
+            const instance = new LinkRedirectsService({
+                linkRedirectRepository: {},
+                config: {
+                    baseURL: new URL('https://localhost:2368/blog/')
+                }
+            });
+            assert.equal(instance.routePath(), '/r/');
+        });
+    });
 
     describe('handleRequest', function () {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5369c84 and 38d312c.

📒 Files selected for processing (4)
  • ghost/core/core/frontend/web/routers/link-redirects.js (1 hunks)
  • ghost/core/core/frontend/web/site.js (2 hunks)
  • ghost/core/core/server/services/link-redirection/LinkRedirectsService.js (1 hunks)
  • ghost/core/test/unit/server/services/link-redirection/LinkRedirectsService.test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/core/frontend/web/routers/link-redirects.js
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24779
File: ghost/core/core/server/services/members/members-api/controllers/RouterController.js:34-51
Timestamp: 2025-09-03T12:28:11.174Z
Learning: The extractRefererOrRedirect function in Ghost's RouterController (ghost/core/core/server/services/members/members-api/controllers/RouterController.js) is working as intended according to maintainer kevinansfield. The current handling of autoRedirect, redirect parameter parsing, and referrer logic does not need the security-related changes suggested around autoRedirect coercion, relative URL handling, or same-origin checks.
📚 Learning: 2025-09-03T12:28:11.174Z
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24779
File: ghost/core/core/server/services/members/members-api/controllers/RouterController.js:34-51
Timestamp: 2025-09-03T12:28:11.174Z
Learning: The extractRefererOrRedirect function in Ghost's RouterController (ghost/core/core/server/services/members/members-api/controllers/RouterController.js) is working as intended according to maintainer kevinansfield. The current handling of autoRedirect, redirect parameter parsing, and referrer logic does not need the security-related changes suggested around autoRedirect coercion, relative URL handling, or same-origin checks.

Applied to files:

  • ghost/core/test/unit/server/services/link-redirection/LinkRedirectsService.test.js
  • ghost/core/core/server/services/link-redirection/LinkRedirectsService.js
  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Core Ghost backend logic should be placed in `ghost/core/core/server/` directory structure organized by purpose (api, services, models, data/schema, etc.)

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-02-06T10:30:53.440Z
Learnt from: allouis
Repo: TryGhost/Ghost PR: 22127
File: .github/scripts/release-apps.js:72-74
Timestamp: 2025-02-06T10:30:53.440Z
Learning: In the Ghost repository, the path to defaults.json is correctly structured as 'ghost/core/core/shared/config/defaults.json' with an intentional double 'core' in the path.

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-04-23T15:44:52.549Z
Learnt from: 9larsons
Repo: TryGhost/Ghost PR: 21866
File: ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js:10-19
Timestamp: 2025-04-23T15:44:52.549Z
Learning: The existing implementation in `ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js` using `path.parse(req.url).base` is secure against path traversal attacks as it properly extracts only the filename component without any directory parts.

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-08-11T19:39:00.428Z
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24651
File: ghost/core/test/utils/urlUtils.js:53-57
Timestamp: 2025-08-11T19:39:00.428Z
Learning: In Ghost's test utilities, when fixing specific issues like async behavior, it's preferred to maintain existing error handling patterns (even if suboptimal) to keep PRs focused on their primary objective. Error handling improvements can be addressed in separate, dedicated PRs.

Applied to files:

  • ghost/core/core/frontend/web/site.js
📚 Learning: 2025-11-25T14:28:50.351Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T14:28:50.351Z
Learning: Ghost admin serves admin-x apps from `/ghost/assets/{app-name}/{app-name}.js` URLs

Applied to files:

  • ghost/core/core/frontend/web/site.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build & Push Docker Image
  • GitHub Check: Setup
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
ghost/core/core/server/services/link-redirection/LinkRedirectsService.js (2)

80-97: LGTM! Well-structured methods for router integration.

The redirectPrefix() method correctly composes the full path including subdirectory, which is used by the router for route registration. The implementation properly leverages the baseURL pathname that was normalized in the constructor.


106-127: LGTM! Clean handler implementation for route-mounted usage.

The handleRequest() method correctly delegates prefix filtering to the router level (via route pattern matching) and focuses on redirect resolution and execution. The URL construction and error handling are appropriate.

ghost/core/core/frontend/web/site.js (1)

18-18: Router implementation correctly exports and registers link redirect routes.

The integration at lines 18 and 54 of site.js properly imports and invokes the link-redirects router. The router correctly exports a function that accepts siteApp, registers a GET route at the redirect prefix, and invokes the link redirection service handler. The change successfully replaces the previous global middleware approach with a router-based handler.

@rmgpinto rmgpinto force-pushed the link-redirects-router branch from 38d312c to c8861c0 Compare December 16, 2025 10:55
@rmgpinto
Copy link
Contributor Author

rmgpinto commented Jan 6, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 6, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@rmgpinto rmgpinto force-pushed the link-redirects-router branch 4 times, most recently from 65713ab to 086cbcf Compare January 8, 2026 09:45
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on February 3

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@rmgpinto rmgpinto force-pushed the link-redirects-router branch 2 times, most recently from 2921378 to 8a2ac85 Compare January 8, 2026 10:46
Copy link
Contributor

@9larsons 9larsons left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll need to look deeper into this to understand the original behavior/intent. Could you resolve the conflict so we can see if the cursor bot comment is valid? More tests may need updating.

Is it actually cleaner to use this router? I'm finding that a confusing abstraction vs. using siteApp.get with the redirects service directly in site.js.

@rmgpinto rmgpinto force-pushed the link-redirects-router branch 2 times, most recently from 23da93b to 8a2ac85 Compare January 8, 2026 16:27
ref https://linear.app/ghost/issue/BER-3095

Extracted the link redirects code from the middleware to the router.
This avoids route comparison on every requests Ghost receives.
@rmgpinto rmgpinto force-pushed the link-redirects-router branch from 8a2ac85 to 8c59cd2 Compare January 20, 2026 14:23
These tests do not check anything substantive after the changes made to remove `req.originalUrl.startsWith(fullURLWithRedirectPrefix)`.
@9larsons
Copy link
Contributor

9larsons commented Jan 20, 2026

Removed tests that are no longer relevant; handleRequest should no longer receive redirects as they are handled in the router. We have redirect related e2e tests in ghost/core/test/e2e-server/click-tracking.test.js and testing the router would effectively be testing Express.

We should test this on staging before merging, however I'm in favor of this change overall. We should see better performance with the /r/ endpoint which is one of the highest-traffic sources for the container to handle.

Copy link
Contributor

@9larsons 9larsons left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved but would like to see tested on staging, especially with a subdirectory setup.

@Ghost-Slimer
Copy link

Deployed to staging with ID: 3960

How does this work?

@9larsons 9larsons merged commit 94a2917 into main Jan 22, 2026
38 checks passed
@9larsons 9larsons deleted the link-redirects-router branch January 22, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants