Describe the bug
The shouldSkipRedirect function in fetchRedirect.ts incorrectly skips valid cross-domain redirects when the pathname of the original request matches the pathname of the redirect destination. This causes 404 errors instead of proper redirects when using Safe Redirect Manager, Yoast redirects, or any redirect plugin configured to redirect to an external domain with the same path.
Location: packages/core/src/utils/fetchRedirect.ts, lines 24-37
Problematic code:
function shouldSkipRedirect(link: string, redirect: string, sourceUrl: string) {
const linkURL = new URL(link, sourceUrl);
const redirectURL = new URL(redirect, sourceUrl);
if (skipURLs.some((path) => redirectURL.pathname.includes(path))) {
return true;
}
const linkParams = linkURL.searchParams;
const redirectParams = redirectURL.searchParams;
linkParams.sort();
redirectParams.sort();
return (
linkURL.pathname.replace(/\/$/, '') === redirectURL.pathname.replace(/\/$/, '') &&
linkParams.toString() === redirectParams.toString()
);
}
The issue occurs because:
fetchRedirect makes a HEAD request to WordPress and receives a 302 redirect to an external domain (e.g., https://www.publix.com/recipe/my-recipe/)
removeSourceUrl is called, which strips the external domain and returns just the pathname (/recipe/my-recipe/)
shouldSkipRedirect compares only the pathnames: /recipe/my-recipe/ === /recipe/my-recipe/
- Since pathnames match,
shouldSkipRedirect returns true and the redirect is incorrectly skipped
- The function throws "Unable to redirect" and returns
{ location: null, status: 0 }
- The frontend displays a 404 error instead of following the redirect
Steps to Reproduce
- Set up a WordPress multisite with HeadstartWP
- Install Safe Redirect Manager (or any redirect plugin)
- Configure a redirect:
- From:
/recipe/my-recipe/ (on subsite, e.g., espanol.example.com)
- To:
https://www.example.com/recipe/my-recipe/ (external domain, same path)
- Status: 302
- Configure HeadstartWP with
redirectStrategy: '404' in headstartwp.config.js
- Visit
https://espanol.example.com/recipe/my-recipe/ on the Next.js frontend
- Expected: User is redirected to
https://www.example.com/recipe/my-recipe/
- Actual: User sees a 404 error page
Expected behavior
Cross-domain redirects should be followed regardless of whether the pathname matches. The shouldSkipRedirect function should compare full URLs (including hosts) or explicitly allow cross-domain redirects.
Proposed fix
Add a host comparison check before comparing pathnames. If the redirect is to a different host, it should never be skipped:
function shouldSkipRedirect(link: string, redirect: string, sourceUrl: string) {
const linkURL = new URL(link, sourceUrl);
const redirectURL = new URL(redirect, sourceUrl);
if (skipURLs.some((path) => redirectURL.pathname.includes(path))) {
return true;
}
// Cross-domain redirects should never be skipped
if (linkURL.host !== redirectURL.host) {
return false;
}
const linkParams = linkURL.searchParams;
const redirectParams = redirectURL.searchParams;
linkParams.sort();
redirectParams.sort();
return (
linkURL.pathname.replace(/\/$/, '') === redirectURL.pathname.replace(/\/$/, '') &&
linkParams.toString() === redirectParams.toString()
);
}
Alternative fix: Preserve the full URL in fetchRedirect before calling removeSourceUrl, and pass the original redirect URL to shouldSkipRedirect for proper comparison.
Screenshots, screen recording, code snippet
Debug log showing the issue:
When config.debug.redirects is enabled, you can see:
REDIRECT https://publix.test/spanish-en/recipe/my-recipe/ {
status: 302,
location: 'https://www.publix.com/recipe/my-recipe/'
}
But the redirect is skipped because after removeSourceUrl:
link = /recipe/my-recipe/
redirect = /recipe/my-recipe/
- Pathnames match → redirect skipped → 404 returned
Environment information
- HeadstartWP Version: 1.3.3
- Node Version: 20.x
- Next.js Version: 15.x
WordPress information
- WordPress Version: 6.7.x
- PHP Version: 8.2
- Safe Redirect Manager: 2.2.x (issue also affects Yoast redirects and other redirect plugins)
- Multisite: Yes (but issue also affects single-site with cross-domain redirects)
Additional context
This bug affects any scenario where:
- A redirect is configured from one domain to another
- The pathname on both domains is identical
redirectStrategy: '404' is used in HeadstartWP config
Common use cases affected:
- Consolidating content from subsites to a main site
- Migrating translated content between language-specific domains
- Redirecting draft/unpublished content to canonical URLs on production
Workaround
As a workaround, we implemented a custom error handler that:
- Pre-fetches SRM redirect data from app settings
- Manually checks for cross-domain redirects before falling back to HeadstartWP's
handleError
- Makes a separate HEAD request that properly handles cross-domain redirects
This workaround is functional but adds complexity and additional network requests.
Describe the bug
The
shouldSkipRedirectfunction infetchRedirect.tsincorrectly skips valid cross-domain redirects when the pathname of the original request matches the pathname of the redirect destination. This causes 404 errors instead of proper redirects when using Safe Redirect Manager, Yoast redirects, or any redirect plugin configured to redirect to an external domain with the same path.Location:
packages/core/src/utils/fetchRedirect.ts, lines 24-37Problematic code:
The issue occurs because:
fetchRedirectmakes a HEAD request to WordPress and receives a 302 redirect to an external domain (e.g.,https://www.publix.com/recipe/my-recipe/)removeSourceUrlis called, which strips the external domain and returns just the pathname (/recipe/my-recipe/)shouldSkipRedirectcompares only the pathnames:/recipe/my-recipe/===/recipe/my-recipe/shouldSkipRedirectreturnstrueand the redirect is incorrectly skipped{ location: null, status: 0 }Steps to Reproduce
/recipe/my-recipe/(on subsite, e.g.,espanol.example.com)https://www.example.com/recipe/my-recipe/(external domain, same path)redirectStrategy: '404'inheadstartwp.config.jshttps://espanol.example.com/recipe/my-recipe/on the Next.js frontendhttps://www.example.com/recipe/my-recipe/Expected behavior
Cross-domain redirects should be followed regardless of whether the pathname matches. The
shouldSkipRedirectfunction should compare full URLs (including hosts) or explicitly allow cross-domain redirects.Proposed fix
Add a host comparison check before comparing pathnames. If the redirect is to a different host, it should never be skipped:
Alternative fix: Preserve the full URL in
fetchRedirectbefore callingremoveSourceUrl, and pass the original redirect URL toshouldSkipRedirectfor proper comparison.Screenshots, screen recording, code snippet
Debug log showing the issue:
When
config.debug.redirectsis enabled, you can see:But the redirect is skipped because after
removeSourceUrl:link=/recipe/my-recipe/redirect=/recipe/my-recipe/Environment information
WordPress information
Additional context
This bug affects any scenario where:
redirectStrategy: '404'is used in HeadstartWP configCommon use cases affected:
Workaround
As a workaround, we implemented a custom error handler that:
handleErrorThis workaround is functional but adds complexity and additional network requests.