In short
- Every page you care about needs at least one internal link from another page. Orphan pages are found late or not at all.
- Keep important pages within about three clicks of the homepage, using hub pages for each topic.
- Add contextual links from related pages with descriptive, varied anchor text. Nav and footer links help reach, not relevance.
- Find gaps with a crawl: list sitemap URLs with fewer than two internal links or more than three clicks deep, then link them from sentences on related pages.
- Use real <a href> links (next/link in Next.js), never click handlers, and never nofollow your own pages.
On a site with 5 to 500 pages, internal linking is the cheapest SEO work you can do. Every page you care about should have at least one link from another page (Google says so directly), sit within about three clicks of the homepage, and get links from related pages with anchor text that says what the target is about. Use plain <a href> links (in Next.js, <Link>), not click handlers, and don't put nofollow on your own pages. That covers most of it. The rest of this guide is how to find the gaps and fix them.
Why internal links matter
Internal links do four jobs:
- Discovery. Google finds most pages by following links. A sitemap helps, but a URL that only the sitemap knows about tends to be crawled late, if at all. This is a common cause of "Discovered – currently not indexed" (see how to fix it).
- Crawl depth. Pages close to the homepage are found and recrawled sooner. Pages buried six clicks deep behind pagination are not.
- Relevance. Anchor text and the text around a link tell Google what the target page is about. "Pricing for teams" says more than "click here".
- Importance (PageRank, in plain words). Each page passes some of its weight to the pages it links to. Pages that many of your pages link to look important. Pages nothing links to look like they don't matter, even to you.
On a small site you can't rely on volume. Each link carries more weight, and a single missing link can leave a page invisible.
The structure to aim for
- 1HomepageLinks to every hub and your top pages
- 2HubsOne per topic: /guides, /features, /integrations
- 3PagesEach linked from its hub
- 4Related pagesContextual links between pages on the same topic
No orphan pages
An orphan page has no internal links pointing to it. People reach it only from search or a direct link, and crawlers only from your sitemap. Agent-built sites create orphans all the time: the agent adds app/guides/new-thing/page.tsx and nothing else. The fix is one link from a hub and one or two from related pages.
Click depth of three or less
"Three clicks from the homepage" is a rule of thumb, not a Google rule, but it is a good target for small sites and easy to measure. If a page needs more, add it to a hub or the homepage, add breadcrumbs, or replace long pagination with category pages.
Hub pages
A hub lists and briefly introduces every page on one topic: a guides index, a features overview, an integrations page. It gives crawlers one place to find them and gives each page a relevant inbound link. A good hub is a real page with a sentence or two per item, not just a list of titles.
Contextual links and anchor text
A contextual link sits in the body text, next to the sentence it explains. These are the most useful links you can add, because the surrounding text gives the anchor meaning. Google's guidance: anchor text should be descriptive, reasonably concise and relevant to both pages.
| Anchor | Verdict | Why |
|---|---|---|
| “our Next.js SEO checklist” | Good | Says exactly what the target is |
| “set a canonical on every page” | Good | Describes the topic in natural words |
| “click here”, “read more” | Weak | Tells nobody anything about the target |
| “best SEO tool for Next.js SEO” on 40 pages | Avoid | Identical, keyword-stuffed anchors look manufactured |
Navigation and footer vs in-content links
Navigation and footer links appear on every page. They are good for reach (they keep hubs and key pages one click from anywhere) but say little about relevance, because they are the same everywhere. In-content links are fewer and more specific. Use both: nav and footer for the handful of pages that matter most, contextual links for everything else. Don't try to fit every page into the footer.
How to find link opportunities
- Search your own site
For a page you want to support, search Google for
site:your-site.com "topic". The results are pages Google already associates with that topic, and each is a candidate for a link. - Search your own content
Grep your source for mentions of the topic that aren't linked yet:
Terminal # Pages that mention "canonical" but don't link to the canonical guide grep -ril "canonical" app/ content/ | xargs grep -L "/guides/canonical-tags" - Use Search Console queries per page
In Search Console, open Performance, filter by page, and look at the Queries tab. Those queries are what Google already thinks the page is about. Use them as anchor text from related pages, varied rather than identical.
- Crawl your site
A crawler shows inbound link counts and click depth for every page. Sort by inbound links ascending: the pages with zero or one internal link are your list. Any SEO crawler does this; on a small site, a script with no dependencies (Node 18 or later) is enough:
link-audit.mjs // node link-audit.mjs https://your-site.com // Lists sitemap URLs with under 2 internal links or more than 3 clicks deep. // Pass the homepage's final URL (after any www or https redirect). const start = new URL(process.argv[2]); const depth = new Map([[start.href, 0]]); // URL -> clicks from the homepage const inlinks = new Map(); // URL -> set of pages linking to it const queue = [start.href]; while (queue.length && depth.size < 1000) { const from = queue.shift(); const res = await fetch(from, { redirect: "manual" }); if (!res.ok || !res.headers.get("content-type")?.includes("text/html")) continue; const html = await res.text(); for (const [, href] of html.matchAll(/<a [^>]*href="([^"#]+)/gi)) { const to = new URL(href, from); if (to.origin !== start.origin) continue; to.search = ""; if (to.href === from) continue; if (!inlinks.has(to.href)) inlinks.set(to.href, new Set()); inlinks.get(to.href).add(from); if (!depth.has(to.href)) { depth.set(to.href, depth.get(from) + 1); queue.push(to.href); } } } const sitemap = await (await fetch(new URL("/sitemap.xml", start))).text(); for (const [, url] of sitemap.matchAll(/<loc>([^<]+)/g)) { const links = inlinks.get(url)?.size ?? 0; const clicks = depth.get(url) ?? "orphan"; if (url === start.href) continue; if (links < 2 || clicks === "orphan" || clicks > 3) { console.log(String(links).padStart(3), "inlinks", String(clicks).padStart(7), " ", url); } }It reads only the server HTML, as a crawler that doesn't run JavaScript would, so a page it calls an orphan may still be linked from a client-only menu. Treat that as a finding too. If your sitemap uses trailing slashes and your links don't (or the reverse), normalize them first or every page looks orphaned.
A worked example
Say you just published /guides/canonical-tags and the audit shows one inbound link, from the guides hub. Here is the whole job, start to finish.
- Find pages that already talk about the topic
Grep finds six pages mentioning "canonical" with no link to the new guide. Two only mention it in passing; four discuss it in a sentence a reader would want to follow.
- Pick anchors from real queries
Once the guide has some impressions, Search Console shows queries like "canonical tag", "rel canonical next.js" and "google chose different canonical". Until then, use the words people would search. Vary the anchors across source pages.
- Link from the sentence, not a footer list
Illustrative: three contextual links for one new guide Source page Sentence it goes in Anchor Next.js SEO checklist Set a self-referencing canonical on every page. self-referencing canonical Not-indexed guide Check that the canonical points to itself. how canonical tags work Structured data guide Fix crawling, canonicals and content first. canonicals - Check it shipped
Confirm each link is in the server HTML of the source page, then rerun the audit:
Terminal curl -s https://your-site.com/guides/nextjs-seo-checklist \ | grep -o '<a [^>]*href="/guides/canonical-tags"[^>]*>[^<]*' node link-audit.mjs https://your-site.com | grep canonical-tags # no output: fixed
That took four links and ten minutes. On a 50-page site, doing this for every new page is the whole internal linking strategy.
What Search Console shows
The Links report has a table called "Top linked pages" under Internal links. Click a URL to see which of your pages link to it. Two limits: Google says the report is a sample, not a complete list, and tables stop at 1,000 rows. It can also include links Google found in the past that no longer exist. Use it to see what Google has noticed, and your own crawl to see what is there now.
For one page, URL Inspection lists the referring pages Google used to discover it. A page whose only source is your sitemap is a candidate for more links.
Common mistakes
- Nofollow on internal links. Google's guidance reserves
nofollowfor links you don't trust, such as paid or user-generated ones. Your own pages are not that. - Links that only work with JavaScript. A
<div onClick>or a button callingrouter.pushis not a link. Google can reliably extract URLs only from<a>elements with anhref. - Too many identical anchors. The same exact-match phrase on every page looks manufactured and wastes the chance to describe the page in different words.
- Links to redirects and 404s. After renaming a URL, update the links to the final URL. A redirect costs a hop; a 404 is a dead end.
- Image links with no alt text. For a linked image, Google uses the image's
altas the anchor text. A logo or card image withalt=""is a link that says nothing. - Only linking from new to old. When you publish a page, also go back and link to it from two or three older, related pages. That is where the weight is.
Internal links in Next.js
next/link renders a real <a href>, so crawlers can follow it, and it adds client-side navigation and prefetching for users. Use it for every internal link.
import Link from "next/link";
type Guide = { slug: string; title: string; summary: string };
// Server Component: the links are in the HTML Google receives.
export function RelatedGuides({ guides }: { guides: Guide[] }) {
return (
<aside aria-labelledby="related">
<h2 id="related">Related guides</h2>
<ul>
{guides.map((g) => (
<li key={g.slug}>
<Link href={`/guides/${g.slug}`}>{g.title}</Link>
<p>{g.summary}</p>
</li>
))}
</ul>
</aside>
);
}"use client";
import { useRouter } from "next/navigation";
// No href: crawlers can't follow this.
export function Card({ slug }: { slug: string }) {
const router = useRouter();
return <div onClick={() => router.push(`/guides/${slug}`)}>Read the guide</div>;
}Render related-page blocks and breadcrumbs in Server Components so the links are in the server HTML. In MDX content, map Markdown links to Link through your MDX components. For the rest of the Next.js basics, see the Next.js SEO checklist.
## Internal links
- Every new page gets a link from its hub page and from at least two related
existing pages, placed in a relevant sentence, not a "see also" list.
- Anchors describe the target in plain words. Never "click here". Don't reuse
the same anchor on every page.
- Use next/link (renders <a href>). No onClick navigation, no rel="nofollow".
- After renaming a URL, update every internal link to the new URL.
- List the links you added (source file, anchor, target) in your summary.How Serpkin helps
Serpkin's site scan includes these internal linking checks:
- Orphan pages: pages in your sitemap that no crawled page links to.
- Broken internal links: links to URLs that return 4xx or 5xx.
- Links to redirects: internal links that should point at the final URL.
- Weak internal links: top-level pages with only one internal link pointing at them.
- Deep pages: pages more than three clicks from the homepage.
For pages you want to rank, it also suggests links. It compares the text of your crawled pages with the target page and its search queries, picks up to three related pages that don't link to it yet, and gives each suggestion an anchor drawn from those queries and the source file to edit, from your route map. Your agent adds the links, and the mission checks that they exist. You can try it on the free plan.
Questions
How many internal links should a page have?
Google says there is no magical ideal number. Aim for every important page to have at least two or three internal links from related pages, plus a link from its hub. Link where it helps the reader, not to hit a count.
What is an orphan page?
A page with no internal links pointing to it. People and crawlers can only reach it from search, an external link or your sitemap, so it tends to be crawled late and rank poorly. Fix it by linking it from a hub and from related pages.
Should internal links be nofollow?
No. Google recommends nofollow only for links you don't trust, such as paid or user-generated links. Nofollow on your own pages just hides them from the link graph.
Do footer links count for SEO?
Yes, Google can crawl and follow them, and they keep key pages one click from anywhere. But because they are the same on every page, they say little about relevance. Use them for a few key pages and rely on in-content links for the rest.
Sources
- Google: Link best practices (crawlable links, anchor text) (checked October 27, 2026)
- Search Console Help: Links report (checked October 27, 2026)
- Search Console Help: URL Inspection tool (checked October 27, 2026)
- Next.js docs: Link component (checked October 27, 2026)