Sites lose traffic after a redesign, and the design is rarely the reason. The addresses changed, and nothing told anyone: visitors, search engines or the sites linking to you, where things went.
The work that prevents it happens before launch. Afterwards it is recovery.
Export what you have now
You need the complete list of current addresses, from three sources because none is complete on its own.
The sitemap lists what the site thinks it publishes.
Analytics shows what people actually visited, including pages the sitemap missed.
Search Console shows what has inbound links and rankings, which is the set that actually matters, because those are the addresses other people wrote down.
With WP-CLI, the post list is direct:
wp post list --post_type=post,page --format=csv --fields=ID,post_name,post_title,post_status > urls.csv
Using WP-CLI goes into getting to it.
Classify every address
Three outcomes, and every URL gets one.
Same address. Nothing to do. Keeping addresses unchanged is always cheaper than redirecting them, and is a good reason to resist restructuring for its own sake.
New address. Needs a permanent redirect to its specific match.
Gone. Genuinely removed with no equivalent. Let it return a 404; that is honest, and it is better than sending someone to something unrelated.
The middle group is the work. The temptation is to redirect it all to the homepage, and that is the mistake this whole exercise exists to avoid: a visitor following a link to a specific article and landing on a homepage has to search again, and most leave. Search engines read it as a page that is gone instead of one that moved.
Permanent, not temporary
A 301 transfers the old page's standing to the new address and is cached by browsers. A 302 transfers nothing.
Choosing 302 by accident is common and quietly expensive. For the difference in full, see redirecting a domain or page.
Where to put the rules
For a handful, a redirect plugin is fine and gives non-technical people a way to add more later.
For hundreds, .htaccess is faster, because the redirect happens before WordPress loads at all. A plugin-based redirect boots the whole application to send someone away.
Redirect 301 /old-page/ /new-section/new-page/
RedirectMatch 301 ^/blog/([0-9]{4})/([0-9]{2})/(.*)$ /articles/$3
That second form handles a common case in one line: dropping dates from permalinks. Take a copy of .htaccess first. A mistake here produces a 500 or a loop across the whole site.
WordPress permalinks and .htaccess walks through how WordPress uses the same file.
Test before launch
On staging, with the new structure in place, check a sample of old addresses:
curl -I https://staging.example.com/old-page/
Confirm 301, and confirm the destination is the matching page instead of the homepage. Test a deep path as well as a top-level one; a rule that works at the root and mangles anything below it is common.
Watch the 404s afterwards
You will miss some. Nobody maps every address of a site with years of content.
For two weeks after launch, read the not-found report daily: from Search Console, or from the server's own access log, which is more immediate:
awk '$9==404 {print $7}' ~/logs/example.com | sort | uniq -c | sort -rn | head -30
Anything appearing repeatedly is a real address people are still requesting. Add it to the map. Ignore the noise about wp-login.php and similar. That is bots, not visitors.
Viewing website statistics walks through reading the log, and a custom error page softens the ones you have not caught yet.
Collapse chains
If this is the second redesign, some old addresses already redirect to addresses that are now changing again. Left alone that becomes A to B to C.
Update the original rules to point at the final destination. Each extra hop is a round trip, and long chains eventually stop being followed at all.
Extract the addresses that actually matter
A large site has thousands of addresses and a small number that carry the value. Redirecting all of them carefully is unnecessary; identifying the second group is the work.
awk '{print $7}' ~/logs/example.com | sed 's/?.*//' | sort | uniq -c | sort -rn | head -100 > top-paths.txt
wc -l < top-paths.txt
That gives the addresses real visitors requested, ranked. Combined with the pages that have inbound links from elsewhere, it is the list worth mapping by hand.
Everything below that line can be handled by a pattern rule or, where genuinely obsolete, allowed to return a 404, which is honest and is better than mapping a thousand addresses to approximate matches.
Patterns before individual rules
A redesign usually changes structure consistently, and a pattern captures hundreds of addresses in one line.
RedirectMatch 301 ^/blog/[0-9]{4}/[0-9]{2}/(.*)$ /articles/$1
RedirectMatch 301 ^/products/([0-9]+)-(.*)$ /shop/$2
Write the patterns first, then test them against the extracted list, then hand-map only what the patterns did not catch.
while read -r p; do
code=$(curl -s -o /dev/null -w '%{http_code}' -I "https://staging.example.com$p")
[ "$code" = "301" ] || echo "unmatched: $p"
done < paths.txt
That loop is the difference between believing the map is complete and knowing it, and it runs against staging before anything is live.
Query strings need explicit handling
The address people forget. A redirect rule matching on path alone ignores what follows the question mark, and the parameters are either dropped or appended in a way nobody intended.
RewriteCond %{QUERY_STRING} ^id=([0-9]+)$
RewriteRule ^/old-page$ /articles/%1? [R=301,L]
The trailing question mark discards the original query string; without it the old parameters are carried onto the new address, producing a page that works and a URL nobody wants indexed.
Old sites with addresses like ?p=123 are the common case, and those are frequently the ones with the most inbound links. WordPress permalinks and .htaccess walks through where those come from.
Check the rules did not slow the site
Several hundred individual redirect lines in .htaccess are evaluated on every request, including requests for images.
On a busy site that is measurable. Two ways to avoid it: prefer patterns over individual lines, and put the redirect rules in a location that is not consulted for static files where the server allows it.
Measure rather than assume, before and after. Telling whether a slow site is the server or the site walks through the measurement, and a redirect map is one of the few things that genuinely shows up in it.