Problem/Motivation
When Enforce clean and canonical URLs is enabled in the Redirect module settings, the module normalises incoming URLs by sorting query string parameters alphabetically and issuing a 301 redirect to the canonical form.
This logic is correct for standard page requests. However, it is also applied to Views AJAX requests (exposed filters, AJAX pagination, etc.), which carry query strings whose parameter order is determined by the browser/JS at runtime.
Because these are AJAX requests, the browser follows the 301 transparently, but the round-trip breaks the expected JSON response cycle — or causes errors depending on CORS and cookie policy — making Views AJAX features unreliable when this setting is active.
The redirect itself is technically correct (the sorted URL resolves fine), but it should never be issued for AJAX requests. The fix is not about the sorting logic — it is about skipping the redirect entirely for AJAX requests.
Steps to reproduce
Enable the Redirect module.
Go to admin/config/search/redirect/settings and enable "Enforce clean and canonical URLs".
Create a View with AJAX enabled and at least one exposed filter.
Visit the View page and submit the exposed filter or click a pagination link.
In browser DevTools → Network, observe the AJAX request, e.g.:
GET /my-view?page=1&field_tag=foo
The module responds with:
301 → /my-view?field_tag=foo&page=1 (parameters sorted alphabetically)
The browser follows the redirect — the AJAX handler receives an unexpected response and the Views component fails.
Expected behavior
AJAX requests (identified via the X-Requested-With: XMLHttpRequest header) must be excluded from the canonical URL enforcement logic. No redirect should ever be issued in response to an AJAX request.
Proposed resolution
In the event subscriber that handles canonical URL enforcement (likely RedirectRequestSubscriber or equivalent), add an early return for AJAX requests before the redirect logic runs:
// Skip canonical redirect for AJAX requests.
if ($request->isXmlHttpRequest()) {
return;
}
This single guard is sufficient. The query string sorting and redirect logic can remain unchanged for all non-AJAX requests.
Remaining tasks
Verify the fix also covers Drupal's ajax_form=1 requests and BigPipe partials, which may use different headers.
Add a kernel test: AJAX request with unsorted query string → expect 200, not 301.
Comments