An orphan page has no internal link leading to it from the part of your site you crawled. It may still receive visits from a sitemap, an external link, or a campaign. To find these pages, compare the URLs you know exist with the destinations found in your internal links.
Review each mismatch before changing anything. A useful article may need a link from its category; an old campaign page may be deliberately isolated.
Compare known URLs with linked URLs
Build two sets:
- Known public URLs: published CMS records, application routes, sitemap URLs, landing-page reports, Search Console exports, and migration maps.
- Linked URLs: canonical internal destinations extracted from rendered pages, menus, breadcrumbs, pagination, and body content.
Normalise scheme, host, path, trailing slash, percent encoding, and irrelevant tracking parameters before comparison. Redirecting sources, canonical variants, non-HTML assets, and intentionally private URLs should not be counted as orphan content.
The first pass is set subtraction:
func orphanCandidates(known, linked map[string]struct{}) []string {
candidates := make([]string, 0)
for url := range known {
if _, found := linked[url]; !found {
candidates = append(candidates, url)
}
}
sort.Strings(candidates)
return candidates
}
Before comparing the sets, crawl rendered HTML, resolve relative href values, stay on the intended hosts, ignore fragments, and retain the source page, anchor, placement, status, and canonical for each edge. A raw string search through HTML will miss relative links and can count URLs that only appear in scripts or data.
Review valuable pages first
Start with pages that still serve a business purpose, receive search impressions or visits, have external links, or have produced inquiries. Check whether the content is current and whether a better replacement exists. The Search Console API does not provide a per-URL external-link count, so that signal needs another documented source.
Get the right URL list from your CMS
Laravel: Export the public URLs of published records and compare them with the crawl. Review the results before editing content; automatically appending links to stored HTML can create irrelevant recommendations.
Next.js: Compare the route or CMS inventory with links extracted from built and rendered pages. A regular expression over MDX source alone will miss links emitted by layouts and components and may count content that never publishes.
WordPress: Export published permalinks, then compare them with destinations found by a crawl of rendered posts, menus, and archive templates. Core WordPress does not maintain a reliable reverse-link index in postmeta, so a generic SQL query against an assumed metadata key will miss ordinary links in post content and templates.
Decide what each page needs
| Decision | Use when |
|---|---|
| Add relevant links | The page is current, useful, and belongs in a hub, category, related-content, or navigation path |
| Redirect | One genuinely equivalent successor exists |
noindex | The page still serves users or a campaign but should not be a search landing page |
404 or 410 | The resource is gone and has no suitable replacement |
| Leave as-is | The isolated access pattern is intentional, documented, and still accurate |
Do not redirect unrelated pages to a parent merely to retain an assumed signal. Do not add every candidate to a global footer. Link a useful page from a relevant category, guide, or related page. Remove it only when it no longer has a purpose.
Repeat the comparison after a migration, bulk publication, routing change, or category cleanup. Keep a note of intentionally isolated pages so they do not return as unexplained findings in every audit.
