Skip to content
WP Visibility

Internal links and site structure guide

Update Internal Links That Point Through Redirects or to Duplicates

Compare the URL each internal link uses with its final and canonical URL, confirm the pages are equivalent, then point the link straight at the page readers should reach.

Published

On this page

Point each internal link at the final, canonical address of the page it means, once you have confirmed the redirect target or canonical page really is the same content. Links that pass through a redirect still work for readers, so this is cleanup rather than an emergency. It removes a wasted request per click, keeps your links consistent, and matches what Google asks for. Leave a link alone when the redirect target answers a different question from the one the link promised; fix the sentence instead.

Google’s guide to consolidating duplicate URLs, checked September 27, 2026, says: “When linking within your site, link to the canonical URL rather than a duplicate URL. Linking consistently to the URL that you consider to be canonical helps Google understand your preference.” Its documentation on HTTP status codes, checked the same day, says Google’s crawlers follow up to 10 redirect hops by default. A single redirect is not a crawling problem. The reason to fix it is consistency and a direct route for readers.

Three addresses per link. Linked URL: The href written in your content or menu. Final URL: Where the reader lands after redirects. Canonical URL: The address the page says it prefers.
When all three match, the link is clean. When they differ, check that the pages are equivalent before replacing the link.

Compare linked, final, and canonical URLs

Every internal link has up to three addresses worth comparing:

  • Linked: the href written in your content or menu.
  • Final: where the browser ends up after any redirects.
  • Canonical: the address the final page declares in <link rel="canonical">.

When all three match, the link is clean. The crawl.py script in the small-site link audit records the first two in links.csv, with a redirected flag. To add the third, save this as canonicals.py in the same folder and run it:

# canonicals.py: list linked pages whose canonical differs from the URL you link to.
import csv, re, urllib.request

CANON = re.compile(r'<link[^>]+rel=["\']canonical["\'][^>]*>', re.I)
HREF = re.compile(r'href=["\']([^"\']+)["\']', re.I)
rows = list(csv.DictReader(open("links.csv", encoding="utf-8")))
for url in sorted({r["final_url"] for r in rows if r["status"] == "200"}):
    request = urllib.request.Request(url, headers={"User-Agent": "canonical-check"})
    html = urllib.request.urlopen(request, timeout=15).read().decode("utf-8", "replace")
    tag = CANON.search(html)
    canonical = HREF.search(tag.group(0)).group(1) if tag and HREF.search(tag.group(0)) else "(none)"
    if canonical != url:
        print(url, "->", canonical)

Each line is a page you link to whose declared canonical is somewhere else. “(none)” means the page prints no canonical tag. WordPress core prints one only on singular views such as posts and pages (rel_canonical reference, checked September 27, 2026), so an archive has none unless a plugin or theme adds it. That is not a defect by itself. Together with the redirected rows, this gives you the full list of links to review.

Recognize the common patterns

Most rows fall into a handful of patterns. The examples use garden.example, a fictional site.

Pattern Example linked URL Usually means Action
Protocol http://garden.example/guides/ Old links from before HTTPS Replace with https
Host https://www.garden.example/guides/ The site settled on the other host Replace with the chosen host
Trailing slash /guides/pruning With slashed permalinks, WordPress redirects to the slashed address Add the slash
Old slug /tomato-pruning/ The post was renamed Replace with the current address, after checking
Tracking parameter /services/?utm_source=newsletter A campaign link pasted into content Remove the parameter
Comment reply /guides/pruning/?replytocom=12 A theme or comment link Usually leave; it is generated, not written
Retired page /spring-offer/ redirecting to /services/ A page removed and redirected to a broader one Check equivalence first

The first four are mechanical: the page is the same, only the address changed. The last three need a decision. One gap: crawl.py skips links to any host other than the one you start it on, so links to the other host never reach links.csv; search the database for that host with wp db search, as Find and repair broken internal links describes. The host and slash redirects can come from WordPress itself: redirect_canonical sends requests to the host in your site address and passes the path through user_trailingslashit, which adds a slash when your permalink structure ends in one. The replytocom links are printed by get_comment_reply_link. All three references checked September 27, 2026.

Confirm the destination is equivalent

Before replacing a link, open the old sentence and the final page side by side and ask: does the final page answer what the sentence promised?

Consider an illustrative case. A pruning article says “see our [tomato pruning calendar] for dates”. The old calendar page was retired and now redirects to the general guides hub. Replacing the link with the hub’s address would make the anchor lie: the reader expects dates and gets a list of articles. The better fixes are to link the specific page that now holds the dates, if one exists, or to rewrite the sentence and remove the link.

Keep a parameter only where it changes what the reader sees and the link needs that view, such as a filtered list of products in one size. Parameters used only to label where a click came from do not belong in links between your own pages.

When the final page and its canonical disagree, the canonical is the page’s own statement of its preferred address. Link the canonical, unless it looks wrong, for example a canonical pointing to the homepage from every post. That is a template or plugin setting to fix first, not a reason to change your links.

Change the stored link, wherever it lives. Menus and synced patterns first, since one edit fixes every page that shows them; the Synced Patterns documentation, checked September 27, 2026, notes that editing a synced pattern updates it everywhere it is used. Then post content. For a few links, edit by hand. For many links to one old address, use the backed-up, dry-run search-replace approach described in Find and repair broken internal links, which also covers finding where each link is stored.

Keep the redirects in place after you update your own links. People and other sites still use the old addresses. If you use WP Visibility’s Redirects module, it warns when you save a rule whose target is the source of another exact-path rule, and when you change a published post’s slug it rewrites exact-path rules whose target is the old path or its full URL so they point at the new one. It issues one redirect per request, so a chain still costs visitors two hops, and it does not edit links inside your content. Redirect a changed URL documents those rules.

Then run the crawl again and check:

  1. The redirected column is empty for every link you changed.
  2. canonicals.py no longer lists those destinations.
  3. No new 404 rows appeared, which would mean a replacement URL was mistyped.
  4. The anchors you touched still describe their destination.

Redirect cleanup checklist

  • Redirected and non-canonical destinations listed from the crawl.
  • Mechanical patterns (protocol, host, slash, renamed slug) replaced directly.
  • Retired-page redirects checked for equivalence; sentences rewritten where the target is broader.
  • Tracking parameters removed from links between your own pages.
  • Menus and patterns updated before post content; bulk edits dry-run first.
  • Redirects left in place for outside visitors.
  • A second crawl shows no redirected rows for the changed links.

Read next

WordPress SEO with your own assistant.

WP Visibility is $99 a year for unlimited sites, client sites included, with a 30-day refund. Use its SEO tools in WordPress or connect a supported assistant. Read how proposal review and permissions work.