You have hundreds of published posts, and you already know they barely link to each other. The job feels enormous, so it keeps sliding down the list. Here's the good news: it isn't enormous, it's just badly shaped. This guide gives you a way to retrofit internal links across an old catalog in small batches, using tiny edits instead of rewrites, so you can start today and finish a cluster this week.
Two things are assumed. You've already run the audit that found the gaps, and you already know which pages deserve the links. What follows is the execution lane: how to actually add internal links to old posts at speed, without touching the substance of a single article.
Step 1: Batch the work by cluster, not by post
The reason a retrofit stalls is almost never effort. It's shape. A flat list of 300 posts has no natural stopping point, so every session feels like the first one.
So don't work from a list of posts. Work from one topic cluster at a time. A cluster is a broad pillar page plus the narrower pages that support it. Supporting pages link back to the pillar when it helps the reader. Related supporting pages link to each other when the connection is real. That's the whole structure.
Pick one cluster. Build a small queue for it, one row per proposed link, with these columns:
- source URL (the page you'll edit)
- target URL (the page you'll link to)
- cluster or pillar
- the target's plain-language subject
- the candidate phrase already sitting in the source
- proposed anchor text
- exact paragraph or location
- editor, status, date, verification result
- notes, including why you rejected a row
That last column matters more than it looks. Rejected rows are how you avoid re-reviewing the same bad idea three batches from now. Write the reason down: irrelevant context, duplicate destination, phrase already linked, sentence would get awkward.
Then decide what "done" means for the batch before you start. Something like: every accepted pair has one live contextual link, a destination that resolves, a recorded anchor, a QA result, and a post-publish check.
What "done" should not mean is a link quota. Google is explicit that there's no magical ideal number of links on a page. If a page feels crowded, it is crowded. Judge by usefulness and readability, not by a number you picked in a spreadsheet.
Common mistake: treating an internal linking back catalog job as one flat list of 300 posts. It produces random links, the same destination over and over, and no cluster shape at the end. Batching by cluster gives you a coherent result and a real finish line.
Done when: the batch is small enough to review in one sitting, every row belongs to a named cluster, and your queue can tell proposed, accepted, implemented, failed and verified apart.
Step 2: Build a page and phrase map for each target
Before you go looking for places to link, get clear on what you're linking to.
For every target page, write a short destination card. Four lines is plenty:
- The page title.
- The problem or question it answers.
- The words a reader would naturally use for that thing.
- The canonical URL, exactly as your CMS serves it.
Line three is the one that does the work. You're collecting concepts and natural wording, not a keyword list to force into prose. Include the plural and the singular, the abbreviation, and the phrasing your site actually uses, which is often not the phrasing the industry uses.
Now build a working table that carries a candidate from idea to decision:
target | target topic | candidate phrase | source URL | already linked? | proposed anchor | placement | decision
The manual version of the next step is simple. Search your own site for the target's subject, collect the pages where that concept comes up, and read the sentence rather than trusting the match. On a small catalog, that's genuinely fine. On a large one you'll want a crawler, which is Step 3.
This is also where a current picture of your own site saves you real hours. DeepSmith's Content Map crawls your sitemap, enriches every page, and classifies it onto a granular topic and a funnel stage, so the cluster you're working on is something you can look at instead of something you rebuild in a spreadsheet. It rechecks sitemaps every 24 hours, so newly published pages fold in on their own. It won't edit your old posts for you, and it isn't meant to. It gives you the map you're editing against.

Where people go wrong: treating a shared word as proof of relevance. A phrase can match perfectly while the destination answers a completely different question. Reject it unless the link improves the reader's next step.
Done when: every row names both a source and a destination, the destination is a known canonical page, and the phrase sits in a sentence where a link would actually help.
Step 3: Find the unlinked mentions at scale
This is the step that turns a month of work into an afternoon. You're looking for one specific thing: pages where your target concept already appears in the body text, and that don't already link to the target.
Both halves matter. Search for the phrase alone and you get a noisy pile that includes every page that's already linked.
Option A: crawler custom search
If you know your targets and your phrases, a crawler's custom search does this in one pass. In Screaming Frog, the flow looks like this:
- Run a complete crawl first. Under Configuration > Spider > Limits, leave crawl-depth and folder-depth limits unset so pages aren't quietly excluded. On a very large site, split the crawl into segments instead.
- Open Configuration > Custom > Custom Search and add your candidate phrases, one per line. The documented limit is 100 lines, which is far more than one cluster needs.
- Set the search type to Page Text No Anchors. That surfaces the phrase where it sits in plain text, not where it's already inside a link.
- Add one more line containing the target's URL path. Leave the type as HTML and switch Contains to Does Not Contain. That's your exclusion: pages that already point at the destination drop out.
- Run the crawl, open the Custom Search tab, and review the Does Not Contain results by hand.
Two settings are worth knowing. Searches are case-insensitive by default, and you only need case sensitivity when it genuinely matters. Navigation and footer text is excluded by default, so you're searching body content, which is what you want. If your theme puts body text in unusual classes, adjust Configuration > Content > Content Area.

Option B: work from the inlinks export
The other route is Bulk Export > Links > All Inlinks. The CSV gives you source URL, destination URL, anchor text, follow status and link position. That's enough to answer three questions fast: is this already linked under a different phrase, is the existing link in body content or in a template, and would my new link just be a duplicate?
While you're in there, sort unique inlinks from low to high. The pages at the top of that sort have the weakest internal support, and they're usually where a single link changes the most. The crawler's non-descriptive-anchor report is worth a look too, since vague anchors you already have are cheap to improve while the file is open anyway.
Pro tip: keep the phrase search and the destination-path exclusion together in the same crawl. Running them separately is how a 40-row list becomes a 400-row list you never review.
One hard limit on this step: it produces candidates, not approvals. Read the surrounding paragraph every time. Do not run automated text replacement across the site. Bulk internal linking without a human relevance check is how you end up with 200 links you have to unpick later, and unpicking is much slower than placing.
Done when: every accepted opportunity has a source paragraph, a destination, a phrase not already linked to that destination, and a one-line reason a reader would click it.
Step 4: Make the smallest edit that works
Here's the part that keeps this from becoming a rewrite. You're editing a sentence, not an article.
Link a phrase that's already there
Anchor text is just the visible text of a link, and Google's guidance on it is short: make it descriptive, reasonably concise, relevant to both pages, and natural where it sits. Skip "click here," "read more," and "this article." Don't stuff every related term into it either.
Semrush suggests five words or fewer as a rule of thumb for brief anchors. Treat that as a publisher's recommendation, not a rule from Google. A longer phrase is fine when it's the clearest description of the destination.
The order of operations, cheapest first:
- Link an existing phrase that already describes the target accurately.
- Make a light phrase-level edit so that it does.
- Add one short bridging sentence, but only when the reader genuinely needs it.
- Leave the paragraph alone when the link would be forced.
Most of your rows should stop at option one. That's the point. When you add internal links to old posts this way, the article you publish back is the article you opened, plus one anchor.
Give the link room to breathe
The words around the anchor are what tell the reader where they're going. Put the link where the concept actually comes up, in the explanatory body, not in a bolted-on list of related links at the bottom. And don't chain links side by side. Two anchors touching each other lose the context that made either one useful.
Ask what the link does for the reader at that exact moment. Does it expand the point, define a term, show the thing, or continue the thread? If it doesn't do one of those, it's decoration.
Don't turn a link job into a refresh job
Retrofitting a link is not a reason to update every statistic, change the publish date, restructure the headings, or rewrite the intro. Those are separate decisions with separate risks. Keeping the batch to link edits only is what makes internal linking existing content a task you can finish rather than a project that swallows the quarter.
Common mistake: linking every occurrence of a term. One well-placed contextual link is clearer than three in the same paragraph. There's no universal count to hit, so read the page and stop when it feels crowded.
Done when: the paragraph would still read fine with the link removed, the anchor describes the destination, the link is useful at that exact point, and you haven't introduced a single new factual claim.
Step 5: Apply the batch safely in your CMS
Before you touch anything live, make sure you can get back. A backup, or a confirmed revision history that can restore the previous content. If your setup has a staging environment, use it.
Then start small. Push a pilot batch of a handful of pages, open the rendered pages, and look at them. Only then run the same documented procedure across the rest of the cluster.
If you're on WordPress and doing this through the API, the REST API documents updating an existing post with POST /wp/v2/posts/<id>, and the post schema includes a content field. Authentication and permissions are required, and your installed version and custom post types are the real source of truth. On any other CMS, use its editor, its import workflow, or its authenticated content API. There is no universal endpoint here, and anyone promising one is guessing.
Whichever route you take, keep an update record for every page:
- post ID and URL
- old content hash or revision reference
- the new content or patch
- links added
- editor or job ID
- timestamp and response status
- how to roll it back
One rule protects you more than the rest combined. Anchor every replacement to a known source phrase or a specific piece of structured HTML, never a global string replace. Confirm the replacement count before you save. If the count comes back zero, or greater than one, stop the job for that page and look at it yourself. That single check is the difference between bulk internal linking and a bulk incident.
WordPress revisions let an editor view and restore an earlier version, which is a real safety net. It is not a backup, a staging test, or a change log. And don't update a page while a teammate is editing it.
Done when: the pilot renders correctly, the old version is recoverable, every update has an audit record, and the job halted safely on any mismatch.
Step 6: Crawl and QA the pages you touched
You changed live pages. Go look at them, with the same crawl settings you used for the baseline.
Run each changed source and destination through this list:
- The link is a real HTML anchor with an
hrefthat resolves. Google can crawl those. Script-event links and anchors without an href are not reliable for discovery. - The destination is live, is the page you meant, and is the preferred canonical URL.
- Both URLs are on the intended host and protocol. No staging domains, no tracking parameters, no duplicate variants.
- The anchor is descriptive and fits the sentence, and it isn't generic or stuffed.
- The link is in body content, not only in a nav or footer template.
- The paragraph still reads naturally, with no adjacent link chain.
- The link isn't broken, isn't redirecting for no reason, and isn't pointing at a removed page. Swap a broken destination only for a page that's genuinely equivalent.
- Pages that matter have at least one internal link pointing in. A page that only appears in your sitemap is still orphaned in practice.
- The batch didn't create excessive outlinks, or duplicate links to the same destination.
- No link got marked nofollow by accident when your policy expects an ordinary followed link.
- The destination's canonical, redirect and robots state is compatible with the relationship you just built.
In Screaming Frog, sort Links > Crawl Depth high to low and look at any important page sitting above the general 1 to 3 link-depth guideline. That's a practical crawler recommendation, not a ranking cutoff, and redirects add a level to the reported depth.
If you saved a baseline crawl before the batch, and you should, Crawl Analysis > Crawl Comparison in database storage mode will diff it against the new one. Link counts, Link Score, crawl depth, anchor text, link position, error states. Link Score is a relative 0 to 100 crawler indicator, useful for comparing your own pages to each other and nothing more.
QA is also what keeps internal linking existing content honest. You are editing pages that already work. The bar is that they still work afterwards, not that they changed.
Done when: every changed URL passes both the rendered-page and the crawl checks, and your change log ties each edit to its QA result.
Step 7: Measure the batch, then keep the queue alive
Record a baseline before you edit anything: crawl date, link counts on sources and targets, crawl depth, and whatever traffic and conversion numbers your team already trusts for those landing pages.
Then be patient with the SEO half. Screaming Frog's testing guidance suggests a second crawl 2 to 4 weeks later with identical settings, and tracking user behaviour and search indicators over 3 to 6 months, because internal link changes often show their full effect over months rather than weeks. For low-traffic pages it suggests at least 200 clicks before and after before you read anything into a change. None of that is a search-engine rule, and a short-term move is not proof of causation.
The operational numbers come back faster, and they're the ones that tell you whether the process is working:
- accepted, implemented, rejected and failed rows
- median editing time per page, and pages finished per batch
- broken or redirected destinations you found on the way
- pages still carrying no incoming internal links
- duplicate or excessive links removed
- change in crawl depth and inlink spread
Now the part that decides whether you ever have to do this again. Keep the queue. It stops being a project tracker and becomes your maintenance ledger. Every time you publish something new, add it to its cluster and check the existing pages where its topic already comes up. That's fifteen minutes on publication day instead of another retrofit in two years.
This is where tooling earns its place, on the prevention side rather than the cleanup side. DeepSmith's Content Map keeps that page and topic inventory current on a 24-hour recheck, so a new post is part of the map before you've thought about it. Its writing pipeline scans that enriched sitemap and places up to five internal links into each article during generation, so new pieces arrive connected instead of arriving orphaned. Deep IQ keeps your product, persona and voice context consistent across everything the system writes. None of that reaches back and rewrites your historical posts, and you shouldn't expect it to. It stops the backlog rebuilding behind you while you clear it.

Done when: the batch has a written result, the queue survives as a living document, and there's a named step in your publishing checklist that links every new article both ways.
What to do next
Pick your densest cluster, the one with a real pillar and five or six supporting posts, and run the whole loop on it this week. One cluster teaches you more about your own catalog than a month of planning does, and it hands you a time-per-page number you can multiply.
Then do the next one. Cluster by cluster, small edits instead of rewrites, and a linking step in every new brief. That is the entire strategy for an internal linking back catalog, and it's genuinely lighter than it looks from the outside. You do not need a quarter to retrofit internal links. You need one cluster and a Friday.
If you'd rather the new work arrived already linked, start a free DeepSmith trial and see what your topic map and a generated, internally linked article look like on your own site.



