# Programmatic SEO directories: a tiered internal linking plan

> A four-tier internal link plan for programmatic SEO directories, with click depth and orphan rate checks and a rule for when to stop generating thin pages.

Maciej Wiatr, 2026-11-08. https://maciejwiatr.com/blog/programmatic-seo-directories-internal-linking

Programmatic SEO directories live or die on internal links. The homepage carries most of the authority. A directory with tens of thousands of category × location pages and listings has to pass that authority down through a few layers of hubs without wasting it on pages that shouldn't exist. This post sets out a tiered link plan, shows how to measure click depth and orphan rate, and explains when to stop generating thin combinations.

## How do programmatic SEO directories structure internal links?

Most directories use a pyramid. [Dirstarter](https://dirstarter.com/blog/programmatic-seo-directory-websites) describes the usual shape. The homepage links to top categories and featured listings. Category hubs link down to listings and across to category × location pages. Listings link back up through breadcrumbs and sideways to similar listings.

Google's hard rules are short. Its [link best practices](https://developers.google.com/search/docs/crawling-indexing/links-crawlable) say: "Every page you care about should have a link from at least one other page on your site." Links must be `<a>` elements with an `href`. Anchors should be "descriptive, reasonably concise, and relevant."

The best evidence that this structure matters at scale comes from Snyk. In a [Lumar webinar](https://www.lumar.io/webinars-events/webinar-on-demand-seo-case-study-how-to-index-millions-of-pages-with-technical-seo/), the team reported that only about 400k of roughly 2M pages were indexed, and crawlers reached about a third of the site. They built directory pages that grouped packages by shared criteria and linked them from every package page. Organic clicks more than doubled in six months.

The plan I'd use has four tiers. This diagram shows how the links flow:

```mermaid
flowchart TD
    A[Homepage] --> B[Curated top hubs]
    A --> F[Freshly added listings]
    B --> C[Category x location pages]
    C --> D[Listing pages]
    D --> E[Nearby sibling listings]
    D --> G[Breadcrumbs back up]
    H[A-Z index] --> C
```

### Tier 1: a curated set of top hubs

The homepage should link to a short list of category and location hubs that you pick by hand, plus a feed of new listings. Botify's [write-up on Ziff Davis](https://botify.com/blog/botifyconnect-2018-global-botify-community-collaborates-on-internal-linking) shows why the feed helps. Slow-indexing pages went into a chronological feed on the homepage, which Google crawled about every 4 minutes, and indexing became "near-immediate."

## How many links should a hub page have to its category × location pages?

Google says there's "no magical ideal number of links a given page should contain." No source gives a number for directories either.

What the data does show is that most combinations earn very little. In [US Tech Automations' worked example](https://ustechautomations.com/resources/blog/seo-for-online-directories-2026), 38 of 60 city × category pages earned fewer than 10 impressions a month. The 22 differentiated hubs earned about 14,000. That's the publisher's own unverified example, but the pattern is typical.

So weight the hub's links instead of treating them as all or nothing. This is my framework; no source states it:

- Sort the hub's category × location links by listing count, or by search demand where you have it.
- Show the top block prominently on the hub itself.
- Paginate the rest, or move them to an A-Z index.
- Leave out combinations below your listing threshold entirely (more on that below).

Botify's 2018 data supports giving strong pages more links. Pages with 10+ inlinks ranked at least 2 positions higher than pages with 3 or fewer. Pages with 90 to 100 inlinks were crawled 100% more often than pages with 0 to 10. Those figures are correlations from conference summaries. Use them for direction and don't treat them as proof.

## Should every listing page link to related listings, and how many?

Yes. A related-listings block is the cheapest way to create sideways links on every page.

The strongest controlled evidence is a [SearchPilot split test](https://www.searchpilot.com/resources/case-studies/seo-split-test-lessons-nearby-location-links) on a US multi-location site with about 8,000 regional pages. Each test page got links to its 6 nearest neighbouring pages, which worked out at about 12 extra inlinks per page. Before the test, those pages had only "sporadic" links such as breadcrumbs. Test pages gained 7% organic traffic against the control group, and the team rolled the change out sitewide.

Six is one tested number. It isn't a proven optimum. I'd start at six, pick neighbours by distance or shared category, and generate them from the same database query that builds the page.

### Tier 4: breadcrumbs and A-Z indexes

Breadcrumbs give every listing a path back to its category and location pages. An A-Z index gives every surviving combination at least one link that doesn't depend on the hub's sort order.

Both fail quietly when they're rendered on the client. Google says JavaScript-inserted links are fine if they use standard `<a href>` markup. `<span href>` and `onclick`-only links are "not recommended." Check the rendered HTML of your listing widgets and index pages, not just what the browser shows. Anchors should name the category and place. "View more" doesn't count as descriptive.

More patterns for this tier are in the [internal linking hub](https://maciejwiatr.com/programmatic-seo/internal-linking).

## How do you avoid orphan pages on a programmatic directory?

Generate links from the same data that generates pages. If a row creates a URL, it should also create at least one placement on a hub, a category × location page, a sibling block or the A-Z index.

Don't count the sitemap as a link. US Tech puts it plainly: every listing should be reachable "from a real hub page, not just a sitemap entry." Google's 2015 doorway guidance asks whether a page is "difficult or impossible to navigate to from other parts of your site," as covered in [scoring city pages before you publish](https://maciejwiatr.com/blog/programmatic-local-seo-city-page-data).

Watch for one less obvious failure: listings whose only link path runs through noindexed pages. In 2017 John Mueller [said](https://www.seroundtable.com/google-long-term-noindex-follow-24990.html) a page left on "noindex, follow" for a long time is eventually treated like "noindex, nofollow," so its outlinks stop counting. That claim is disputed and there's no official document on it. Still, if noindexed thin combos are the only route to some listings, those listings can end up orphaned in practice.

## What click depth is acceptable for long-tail directory pages?

Google gives no threshold. Mueller, [reported by SEJ](https://www.searchenginejournal.com/google-click-depth-matters-more-than-url-structure-for-seo/256779/), said what counts is how many clicks it takes to reach a page from the homepage. URL slashes don't matter. He didn't call click depth a ranking factor. The "3 clicks" rule comes from practitioners.

The practitioner ranges:

| Source | Guidance |
|---|---|
| Dirstarter | Listings within 3 to 4 clicks |
| Botify | "Very few" pages deeper than 5 to 6 |
| Botify 2018 data | 58.2% of pages under 4 clicks crawled, vs 31.5% beyond 8 |

I'd aim for every listing within 4 clicks and treat anything past 6 as a bug to fix. Both are heuristics.

## Should category × location pages with zero or one listing be linked, noindexed or not generated at all?

It depends on whether the page will ever fill.

- **Zero listings, likely to stay that way.** Google's [faceted navigation doc](https://developers.google.com/search/docs/crawling-indexing/crawling-managing-faceted-navigation) says to "return an HTTP 404 status code when a filter combination doesn't return results." The [crawl budget guide](https://developers.google.com/crawling/docs/crawl-budget) says soft 404s "will continue to be crawled, and waste your budget," and noindex still costs a fetch. So don't generate the URL and don't link to it.
- **Empty now, expected to fill.** Mueller [said in 2021](https://www.seroundtable.com/google-soft-404s-or-noindex-empty-pages-31263.html) there wouldn't be "any noticable difference" between noindex and a soft 404. Google picks the page up again once new internal links appear.
- **One listing.** Google has no threshold. Dirstarter says three results "reads as thin." US Tech calls two listings with no unique framing thin "by definition." The spam policies list pages "closer to search results than a clearly defined, browseable hierarchy" as doorway abuse.

My own rule: generate and link a combination only if it has at least three listings, or clear search demand. Below that, send users to the parent category or location hub. The flow looks like this:

```mermaid
flowchart TD
    A[New combination row] --> B{Any listings?}
    B -->|No| C[Do not generate]
    B -->|Yes| D{Three or more?}
    D -->|Yes| E[Generate and link]
    D -->|No| F{Search demand?}
    F -->|Yes| E
    F -->|No| G[Link parent hub]
```

Canonicalizing near-duplicate categories to each other is a weaker fix. Google calls canonicals "generally less effective in the long term" than blocking crawl for faceted variants.

## How do you measure whether internal linking is getting directory pages crawled and indexed?

Use a crawl, your database and Search Console together.

1. **Crawl the site.** Export click depth and inlink counts, then group them by template.
2. **Diff against the database.** URLs in the database but missing from the crawl are orphans. Orphan rate is that count divided by the total.
3. **Split sitemaps by template.** Each one is capped at 50,000 URLs, per [Google's sitemap docs](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap). The [Page indexing report](https://support.google.com/webmasters/answer/7440203) can then be filtered per sitemap. Its example tables stop at 1,000 rows, so treat them as samples.
4. **Track the two "not indexed" states.** "Discovered, currently not indexed" means found but not crawled. If it makes up a large share, the crawl budget guide applies to you. "Crawled, currently not indexed" is common and "doesn't always signal a problem."
5. **Sample with the URL Inspection API.** The [quota](https://developers.google.com/webmaster-tools/limits) is 2,000 queries a day per property, so rotate samples by template.
6. **Pull clicks per page.** The Search Analytics API exports up to 50,000 rows a day per search type, [25,000 per request](https://developers.google.com/search/blog/2022/10/performance-data-deep-dive), paged with `startRow`.

The orphan diff is a few lines:

```python
db_urls = set(open("db_urls.txt").read().split())
crawled = set(open("crawl_urls.txt").read().split())
orphans = db_urls - crawled
print(len(orphans), round(len(orphans) / len(db_urls) * 100, 1), "%")
```

When the indexing report points to pages that should go, the [Search Console noindex audit](https://maciejwiatr.com/blog/noindex-programmatic-pages-search-console-audit) covers the next step.

## What I'd check in review

- Every listing has a crawlable `<a href>` from a hub, a sibling block or the A-Z index.
- No listing is reachable only through noindexed pages.
- Hub links are sorted by listing count or demand.
- Zero-listing combinations return 404 or don't exist.
- Orphan rate and click depth are tracked per template, every crawl.
