Preserving Query Parameters Through Redirects
A redirect that silently drops the query string breaks campaign attribution; one that blindly preserves it creates an indexable duplicate for every tracking variant. The rule has to be explicit.
When to Use This Approach
Work through this when:
- Paid or email traffic landing on redirected URLs shows as direct or organic in analytics.
- A migration introduced pattern-based redirects that construct new destination URLs.
- Tracking parameters are appearing on indexed URLs after a redirect.
Two Failure Modes, Opposite Directions
Dropping and preserving are both wrong for some parameter classes, so a single global policy cannot be correct.
Implementation Steps
Step 1: Audit what your redirects currently do
#!/usr/bin/env bash
# Test each rule with a representative query string
while IFS=$'\t' read -r from to; do
eff=$(curl -s -o /dev/null -w '%{redirect_url}' \
"https://example.com${from}?utm_source=test&utm_campaign=audit")
case "$eff" in
*utm_source=test*) verdict="preserved" ;;
"") verdict="no redirect" ;;
*) verdict="DROPPED" ;;
esac
printf '%-40s %-12s %s\n' "$from" "$verdict" "$eff"
done < redirect-map.tsv
Validation: Group the output by rule type. Pattern-based rules that build a new destination almost always show DROPPED, while simple one-to-one rules usually preserve.
Step 2: Classify parameters by whether they must survive
// lib/redirect-params.js
export const SURVIVES_REDIRECT = new Set([
// attribution — must reach the destination or reporting breaks
'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
'gclid', 'fbclid', 'msclkid', 'ref', 'affiliate',
// functional state the destination needs
'lang', 'currency',
]);
export const NEVER_SURVIVES = new Set([
// the destination defines its own content and ordering
'sort', 'order', 'page', 'view',
// legacy parameters from the old URL structure
'id', 'p', 'cat', 'sess',
]);
SEO impact: Legacy parameters from the pre-migration structure are the ones that create duplicates at the new URL, and they are exactly the ones a naive “preserve everything” rule keeps.
Validation: Any parameter in neither set should be logged and classified deliberately, not silently defaulted.
Step 3: Implement one merge function
// lib/merge-query.js
import { SURVIVES_REDIRECT } from './redirect-params';
export function mergeQuery(incomingSearch, destinationUrl) {
const dest = new URL(destinationUrl);
const incoming = new URLSearchParams(incomingSearch);
for (const [key, value] of incoming) {
if (!SURVIVES_REDIRECT.has(key)) continue;
if (dest.searchParams.has(key)) continue; // destination wins on conflict
dest.searchParams.append(key, value);
}
dest.searchParams.sort(); // deterministic ordering
return dest.toString();
}
SEO impact: Sorting makes two equivalent redirects produce byte-identical destinations, which prevents the redirect itself from generating URL variants.
Validation: Assert that permuting the incoming parameter order yields an identical destination string.
Step 4: Apply it inside the redirect, not after it
The tempting mistake is a second redirect that strips parameters, which turns every campaign click into a two-hop journey.
// middleware.js
import { mergeQuery } from '@/lib/merge-query';
import { lookupRedirect } from '@/lib/redirect-map';
export function middleware(request) {
const url = new URL(request.url);
const target = lookupRedirect(url.pathname); // final destination, already flattened
if (!target) return NextResponse.next();
const destination = mergeQuery(url.search, new URL(target, url.origin).toString());
return NextResponse.redirect(destination, 301);
}
SEO impact: One hop preserves the maximum signal and the minimum latency. The chain-flattening discipline is covered in flattening redirect chains in edge middleware.
Validation: curl -s -o /dev/null -w '%{num_redirects}\n' -L 'https://example.com/old-path?utm_source=news' returns 1.
Step 5: Verify attribution and indexation separately
They are different tests and passing one says nothing about the other.
# Attribution: parameters arrive at the destination
curl -s -o /dev/null -w '%{url_effective}\n' -L \
'https://example.com/old-product?utm_source=email&sort=price&id=4471'
# Expect: /new-product/?utm_source=email (sort and id dropped)
# Indexation: the destination canonicalises the survivors away
curl -s 'https://example.com/new-product/?utm_source=email' \
| grep -oP '(?<=rel="canonical" href=")[^"]+'
# Expect: https://example.com/new-product/ (no parameters)
Validation: Both must hold. Attribution without canonicalisation puts campaign URLs in the index; canonicalisation without attribution loses the reporting.
The Full Path of a Campaign Click
Following one click end to end shows why the two tests are separate and where each parameter is dropped.
SEO Impact Summary
| Signal | What improves | What breaks if misconfigured |
|---|---|---|
| Redirect efficiency | One hop from campaign URL to destination, with no cleanup redirect | A second redirect to strip parameters doubles the latency and dilutes the signal |
| Index composition | Tracking variants are canonicalised away at the destination | Preserved legacy parameters create indexable duplicates of the new URL |
| Attribution accuracy | Paid and email traffic is credited correctly after a migration | Dropped parameters reclassify campaign traffic as direct |
| Migration safety | Legacy parameters from the old structure do not follow content to its new home | Old identifiers arriving at new URLs can trigger unintended behaviour |
Measurable signals to watch:
- Redirect hop count for campaign URLs, asserted at one in CI.
- Share of paid sessions reported as direct, which should fall after the fix.
- Indexed URL count containing
utm_, which should stay at zero.
Edge Cases and Gotchas
Parameter handling and chain flattening interact: a cleanup redirect added to strip parameters converts every campaign click into two hops.
A parameter can be attribution in one place and content in another
ref is a referral tag on marketing URLs and a reference identifier in some catalogues. Classify per path prefix if the collision is real, rather than picking one global answer that is wrong half the time.
Fragment identifiers never reach the server Anything after the hash is client-side only, so a redirect cannot preserve or drop it — the browser reapplies it to the destination. Do not attempt to handle it at the edge.
Encoded parameters must not be double-encoded
Building the destination with URLSearchParams and then string-concatenating another query string is the usual cause. Construct once, serialise once.
Redirect maps generated from a spreadsheet often include query strings in the source path
A rule keyed on /old?id=1 will not match /old?id=1&utm_source=x. Match on path, then handle parameters in the merge function, or the rule silently misses real traffic.
Frequently Asked Questions
Do query parameters survive a 301 automatically? It depends on the layer. Most CDN and framework implementations append the original query string by default, but pattern-based rules that construct a new URL usually drop it. Test each rule with a query string — the two behaviours look identical in configuration.
Should tracking parameters survive a redirect? Yes. Dropping them breaks attribution for every paid and email click landing on a redirected URL. They are safe to preserve provided the destination canonicalises them away, so they reach analytics without entering the index.
What if the destination already has its own parameters? The destination’s win on conflict, because they are part of the target you chose. Merge rather than replace: keep the destination’s values, add the allowed incoming ones that do not collide, drop the rest.
Does preserving parameters affect how much signal a 301 passes?
Not materially. What matters far more is that the redirect resolves in a single hop to a 200 — chains and loops cost signal, while a query string on the destination does not, provided the canonical resolves it. The auditing method is in auditing redirect hops after a headless migration.
Part of: Redirect Chain Management
Related
- Flattening Redirect Chains in Edge Middleware — the single-hop discipline this rule has to fit inside
- Canonicalising Sorted and Filtered Listings — how the destination removes the preserved parameters from the index
- Auditing Redirect Hops After a Headless Migration — finding the rules that need this treatment