SEO Optimization

The Pages Google Never Finds: A WordPress Internal Linking Guide

12 min read

You published the page. It’s in the sitemap, the copy is good, and six months later it has almost nothing to show for itself. Meanwhile a thinner page you barely thought about is pulling steady traffic.

Before you rewrite anything, check whether the quiet page is linked to from anywhere on your own site. A surprising number of WordPress pages aren’t. They exist at a URL, they sit in the sitemap, and no other page points at them — so Google reached them through a file you handed it rather than through your site’s actual structure, and it has almost no context for what the page is or how much you value it.

Google’s own link documentation is unusually direct about this. Below is what it says, which links Google can and can’t follow, why WordPress manufactures unlinked pages, and how to find yours without opening four hundred edit screens.


The Orphan Page Problem Your Sitemap Hides

Google’s link best practices doc opens by stating what links are for: “Google uses links as a signal when determining the relevancy of pages and to find new pages to crawl.” Two jobs in one sentence — discovery and relevance — and a page nothing links to is working against both. Then, in the internal-links section, comes the line worth pinning above your desk: “Every page you care about should have a link from at least one other page on your site.” That’s about as close to a rule as Google’s documentation gets, and it applies to every page you’d be annoyed to see ranking poorly.

Your XML sitemap doesn’t get you out of it. Google’s sitemap documentation is explicit that a sitemap “helps search engines discover URLs on your site, but it doesn’t guarantee that all the items in your sitemap will be crawled and indexed” — and the crawl-budget docs add that even once a page is crawled, “not every page that is crawled will necessarily be indexed.” The same sitemap page leads with the alternative: “If your site’s pages are properly linked, Google can usually discover most of your site.” Discovery is the easy part. Convincing Google a page matters is the part links do, and a sitemap entry carries no signal about importance, topic, or how the page relates to the rest of your site.

Where WordPress quietly makes orphans

Nobody decides to orphan a page. It happens structurally, and the same few patterns account for most of it:

  • Landing pages built for a campaign and deliberately kept out of the menu, then never linked from anywhere else
  • Older posts that fell off the paginated blog archive and were never referenced again
  • Custom post types registered without archive templates, so the only route in is a direct URL
  • Pages whose only inbound link lived in a widget, a sidebar, or a theme you’ve since replaced
  • Anything imported during a migration that arrived with its content intact and its context stripped

The blog archive case is the one that catches good sites. Your ten newest posts are linked from the archive’s first page, the next ten from page two, and everything past the last paginated page is reachable only if something else happens to mention it. On a site with three years of posts and no related-posts module, that’s most of your library.

WHY IT MATTERS

Internal links are among the few ranking inputs entirely in your hands. You can’t make anyone link to you, you can’t make Google print your title verbatim, and you can’t buy your way past a competitor’s domain. But you decide what every page on your own site points at, and you can fix it this afternoon with no budget, no outreach, and no permission from anyone.

The Links Google Can’t Follow

Before worrying about which pages to link, make sure the links you already have are the kind Google can use. This is narrower than most people assume.

It has to be an anchor element with an href

Google’s requirement is specific: “Generally, Google can only crawl your link if it’s an <a> HTML element (also known as anchor element) with an href attribute. Most links in other formats won’t be parsed and extracted by Google’s crawlers.” The docs then list the patterns filed under “not recommended” — Google may still attempt to parse some of them, but you can’t count on it — and they’re all things you’ll find in real themes and page builders:

  • <a routerLink="products/category"> — a framework attribute instead of an href
  • <span href="https://example.com"> — the right attribute on the wrong element
  • <a onclick="goto('https://example.com')"> — a click handler with no href at all

The href’s value matters too. Google wants something that resolves to an actual web address it can send a request to, so /products, /products.php?id=123, and a full https:// URL are all fine. Filed under not recommended, with the same “Google may still attempt to resolve this” caveat, is the JavaScript-in-the-href pattern: href="javascript:goTo('products')" and its cousins, which look and behave like links for a mouse user while giving a crawler nothing dependable to follow.

JavaScript is fine, until it isn’t

This trips people who’ve heard “Google can’t read JavaScript” and stopped there. The docs are clear that “links are also crawlable when you use JavaScript to insert them into a page dynamically as long as it uses the HTML markup shown above.” Render a real <a href> into the DOM and it counts; render a <div> with a click handler and you’re back in not-recommended territory. The distinction is the markup you end up with, not the language that produced it.

We audited a site last year where the blog listing had a “Load more” button instead of pagination. It looked modern, it worked fine for humans, and it injected post cards as <div> elements with click handlers. The nine posts above the fold were linked; the other two hundred had nothing dependable pointing at them. Nobody had touched the content in a year, because the content wasn’t the problem. Swapping the cards to real anchors and adding numbered pagination underneath the button fixed in one afternoon what a year of rewriting hadn’t. Google’s own advice on the JavaScript case is to check rather than assume: if you’re inserting anchor text with JavaScript, use the URL Inspection Tool in Search Console and confirm it’s present in the rendered HTML — the same check works for JS-inserted links.

Anchor Text Is the Other Half of the Signal

A crawlable link tells Google the page exists; the anchor text tells it what the page is about. Google defines anchor text (also called link text) as the visible text of a link, and describes good anchor text as descriptive, reasonably concise, and relevant both to the page it’s on and the page it points to. The docs single out the phrases everyone defaults to — “Click here,” “Read more,” and a bare “website” or “article” as the linked words are all shown under “Bad (too generic).” There’s a test in the documentation that beats any rule: read only the anchor text, out of context, and see whether it’s specific enough to make sense by itself. If you can’t tell what the destination is, neither can Google.

Overcorrecting has its own failure mode. Google’s example of bad anchor text isn’t only the vague kind — it’s also a link that swallows an entire sentence, and cramming keywords into anchor text is explicitly a violation of the spam policies. Somewhere between “click here” and a thirty-word run-on is a short descriptive phrase that names the destination.

The details people miss

Three smaller points from the same doc:

  • For an image used as a link, Google uses the alt attribute of the img element as the anchor text. An image link with empty alt is a link with no anchor text.
  • If an <a> element is empty for some reason, Google can fall back to the title attribute as anchor text. Useful for icon links, though real text is better.
  • Don’t chain links next to each other. The docs point out it’s harder for readers to tell them apart, and you lose the surrounding text that gives each one context — the words before and after a link matter.

On how many links a page should have, the guidance is refreshingly unmathematical: “There’s no magical ideal number of links a given page should contain. However, if you think it’s too much, then it probably is.”

QUICK REFERENCE

Crawlable: an <a> element with an href that resolves to a real address · JavaScript-inserted links using that same markup.

Not recommended (Google may attempt to parse these, but don’t rely on it): <span href> · <a> with only an onclick · href="javascript:..." · framework attributes like routerLink.

Anchor text: descriptive and concise · readable out of context · image links use alt · empty anchors fall back to title · no keyword stuffing · don’t chain links together.

The rel Attributes, and What WordPress Does Behind Your Back

Google documents three rel values for qualifying a link, and for ordinary links you expect Google to fetch and parse normally you don’t need any of them.

  • rel="sponsored" for advertisements and paid placements. The docs note that nofollow was previously recommended here and is still acceptable, though sponsored is preferred.
  • rel="ugc" for user-generated content such as comments and forum posts.
  • rel="nofollow" when the other values don’t apply and you’d rather Google not associate your site with the linked page.

You can combine them as a space- or comma-separated list. Two caveats matter. Links carrying these attributes “will generally not be followed,” but the linked pages “may be found through other means, such as sitemaps or links from other sites, and thus they may still be crawled” — this isn’t a blocking mechanism. And for links within your own site, Google points you at the robots.txt disallow rule instead; sculpting your internal link graph with nofollow is not what that attribute is for.

Core is already tagging your comment links

WordPress handles a chunk of this for you, which is worth knowing before you install a plugin to do it again. Since version 5.3, core’s wp_rel_ugc() adds rel="nofollow ugc" to anchor elements inside comment content, hooked to pre_comment_content — so it’s applied when the comment is saved, not at render time. The commenter’s own URL gets handled separately in get_comment_author_link(), which includes ugc and adds external and nofollow when the URL isn’t internal to your site; since WordPress 6.2 a comment_author_link_rel filter lets you change that for trusted contributors.

One piece of old advice to retire: wp_targeted_link_rel(), the function that appended rel="noopener" to links with a target (and, before 5.6, noreferrer alongside it), is marked deprecated as of WordPress 6.7 in core source. If you’re carrying a snippet that hooks it, that snippet is now maintaining something core no longer wants maintained.

Auditing Internal Links Without Losing a Week

Reviewing links page by page is where this project dies. On a 400-page site nobody opens 400 editors, so treat it as a data problem instead. Start with two lists: your sitemap gives you every URL that’s supposed to exist (/wp-sitemap.xml on core since WordPress 5.5, or your SEO plugin’s equivalent if it replaced the core one), and a crawl of your own site starting at the homepage gives you every internal URL something actually links to. Diff the two, and anything present in the sitemap but absent from the crawl is an orphan. That list is usually shorter and more actionable than people expect.

For inventory on the WordPress side, WP-CLI beats any admin screen: wp post list --post_type=post,page --post_status=publish --fields=ID,post_title,post_name hands you the full set in one command, ready to sort against your crawl.

Fix templates before you fix pages

Most orphans come from a missing template feature rather than a missing sentence, so template-level fixes clear them in bulk while page-level fixes clear one at a time. Related posts underneath a single post, a breadcrumb trail, category and tag archives that are actually linked from somewhere, real numbered pagination on the blog — each of those puts hundreds of pages back on the map at once. If your theme renders breadcrumbs, mark them up as BreadcrumbList structured data too, so the hierarchy is machine-readable rather than merely visible; that’s one of the schema types amplifi.schema generates automatically across a site.

Then do the contextual pass by hand, because that’s the part templates can’t fake. When you write a new post, link to the two or three older pieces it genuinely builds on, then go back to those older pieces and link forward to the new one. Cluster your content around the few pages you most want to rank and let the cluster point inward. That’s an hour a week that compounds.

OPEN SOURCE

amplifi.schema ships inside the amplifi.plugins suite — a single free, open-source WordPress plugin bundling our AI SEO, schema, alt-text, consent, and security features. One install, one update, everything toggleable. Full source on GitHub.

View on GitHub

Whatever you change, verify it in the served HTML rather than in a preview. Fetch the live URL, look at the markup, and confirm your links are real anchors with real hrefs and real text between them — page builders and JavaScript-heavy themes are perfectly capable of showing you a link in the editor and shipping something else to the crawler.

Frequently Asked Questions

An orphan page is a page on your site that no other page on your site links to. A sitemap does not fix it. Google’s sitemap documentation states that a sitemap helps search engines discover URLs on your site but does not guarantee that all the items in it will be crawled and indexed, and the crawl-budget documentation adds that not every page that is crawled will necessarily be indexed. A sitemap entry also carries no information about how the page relates to the rest of your site or how important you consider it. Google’s link best practices state that every page you care about should have a link from at least one other page on your site.

Yes, provided the JavaScript produces the right markup. Google’s documentation states that links are crawlable when JavaScript inserts them into a page dynamically, as long as the result is an anchor element with an href attribute. Elements that only behave like a link, such as a span with an href, an anchor with only an onclick handler, or an href whose value is a JavaScript expression, are listed as not recommended; Google may still attempt to parse or resolve them, but you should not rely on it. If you insert anchor text with JavaScript, Google recommends using the URL Inspection Tool to confirm it appears in the rendered HTML, and the same check is useful for JavaScript-inserted links.

Google’s documentation says there is no magical ideal number of links a given page should contain, and adds that if you think it is too much, then it probably is. There is no published threshold to stay under. The practical guidance is to link where a reader would genuinely benefit from the other page, give each link surrounding context rather than chaining several together, and avoid stuffing keywords into anchor text, which Google treats as a violation of its spam policies.

No. Google’s guidance on qualifying outbound links states that for links within your own site you should use the robots.txt disallow rule rather than the nofollow value. It also notes that links marked with these rel attributes will generally not be followed, but the linked pages may still be found through other means such as sitemaps or links from other sites, so nofollow is not a blocking mechanism. The three documented values are sponsored for advertisements and paid placements, ugc for user-generated content such as comments and forum posts, and nofollow for cases where the other values do not apply.

Yes. Since WordPress 5.3, core’s wp_rel_ugc() function adds a rel value of nofollow ugc to anchor elements within comment content, and it is hooked to pre_comment_content, so it applies when the comment is saved rather than when it is displayed. The commenter’s own website URL is handled separately by get_comment_author_link(), which always includes ugc and adds external and nofollow when the URL is not internal to your site. Since WordPress 6.2 a comment_author_link_rel filter lets you adjust that. Separately, the older wp_targeted_link_rel() function is marked deprecated as of WordPress 6.7.

Compare two lists. The first is every URL that should exist, which you can take from your XML sitemap; WordPress core has generated one at wp-sitemap.xml since version 5.5, and SEO plugins typically replace it with their own. The second is every internal URL a crawler can actually reach by starting at your homepage and following links. Any URL that appears in the sitemap but never in the crawl is an orphan. On the WordPress side, WP-CLI gives you the inventory quickly with a command such as wp post list, passing a post type, a published status, and a fields list.

Not sure how many of your pages nothing links to? We’ll diff your sitemap against a real crawl and hand you the list.

Built by amplifi.studio — see also Google Can’t Index What It Can’t Find: A WordPress XML Sitemap Guide.