How to Build a Jev Internal-Linking and Cannibalization Workflow

Key takeaways

A runnable pipeline that turns a crawl export into a reviewed internal-link and merge worklist. Two-stage candidate selection, exact Jev question schemas, confidence routing, and the checks that keep you from shipping bad links at scale.

Most internal linking advice stops at "add more links." That is not the hard part. The hard part is that a 1,000-page site has roughly half a million possible page pairs, and almost none of them should link to each other. Adding links indiscriminately dilutes the value of the links that actually matter.

The same problem shows up in cannibalization. Two pages competing for the same query is easy to spot when there are two. It gets messy when there are forty pages across a topic cluster, all with overlapping intent, and the right answer is sometimes merge, sometimes differentiate, and sometimes leave both alone.

Both jobs are decision problems with a fixed set of possible answers. That is exactly what Jev is for. This guide walks through a pipeline you can run this week: crawl export in, reviewed worklist out.

What you will finish with

By the end of this workflow you will have:

  • A deduplicated list of internal-link proposals, each with a source page, a target page, a confidence score, and an owner.
  • A cannibalization worklist that sorts competing URLs into keep, merge, differentiate, or no conflict.
  • A confidence threshold you set before looking at results, not after.
  • A decision log you can review, and a rollback path if the proposals turn out to be noise.

Who this is for: an SEO or content lead working on a site with a few hundred to a few thousand URLs, who has crawl data and can run a script.

Prerequisites:

  • A crawl export containing, at minimum: URL, title, H1, and the main body text of each page.
  • A Jev API key (TypeSafe direct, or through a gateway).
  • A way to run a script, or an agent with the TypeSafe skill installed.
  • Edit access to your CMS or template layer for the humans who will act on the worklist.

Honest time estimate: two to four hours for setup and a first run on a mid-size site. The analysis itself is fast. The review is the work.

Definition of done: every proposed link and every merge decision carries a confidence score, and nothing has been auto-applied above your risk tolerance without a verification pass.

Before you start: the one rule that keeps this safe

Internal links are cheap to add and annoying to clean up. If you push 3,000 auto-generated links into a live site and they turn out to be mediocre, you now have a manual removal project.

So the rule for this workflow is: the first run produces a worklist, not a deployment. You are generating candidates with confidence scores and reviewing them. Automate the application step only after you have seen a full run and agree with the output.

Two other constraints worth stating up front:

  • Jev has no live web access. Everything it judges has to be in the data you send it. It cannot fetch a page or check a SERP.
  • Jev cannot say "I don't know." A choice question forces a pick from the options you define. If you do not give it an escape hatch, it will choose the least-wrong option and return it with whatever confidence it has. Always include a "none" or "no action" option.

Shrink the candidate set before Jev sees it

This is the step people skip, and it is the step that makes the whole thing affordable and accurate.

Do not ask Jev to compare every page against every other page. Use a cheap similarity pass first to get a shortlist, then let Jev make the semantic judgment on that shortlist.

The practical shape:

  1. Chunk your pages. Split each page's body text into passages of a few hundred words. Internal links work at the passage level, not the page level. A 3,000-word guide has several places where a link makes sense, and they point to different targets.
  2. Embed the passages and the candidate pages. Any embedding model works. You are not looking for precision here, just a reasonable shortlist.
  3. Retrieve the top candidates per passage. Around fifteen is a workable number. Fewer and you risk missing the right target; many more and you are paying for candidates that were never plausible.
  4. Skip pairs that already link. No point proposing a link that exists.

Now you have, for each passage, a list of roughly fifteen candidate targets. That is a size Jev can reason about precisely, and it is a size where a choice question is meaningful.

A reported benchmark from a real run gives you the order of magnitude: rebuilding an internal link map across 586 pages took about 45 seconds and cost roughly $0.21. Treat that as a reported figure rather than a guarantee, but it tells you the approach scales to sites far larger than most teams manage manually. For a teardown of a real tool that implements this pattern end to end, see how one team built a Jev internal-link analyzer.

Pipeline diagram showing pages chunked into passages, embedded, narrowed to fifteen candidates, then judged by Jev.

Cheap similarity search first, precise semantic judgment second. That order is what makes this affordable.

This is where most implementations go wrong. People write a vague instruction like "pick the best page to link to," and then wonder why the output is inconsistent.

Jev needs exact criteria for each option. The option list should be generated in your code, and every option should describe what it actually is.

Here is the schema. The passage is the state; the question is a choice over the shortlist.

json
{
  "model": "jev-latest",
  "state": {
    "source_page": {
      "url": "/blog/crm-migration-guide",
      "title": "How to Migrate Your CRM Without Losing Data",
      "passage": "Before you export anything, audit which custom fields are actually in use. Most teams migrate fields nobody has touched in two years."
    },
    "candidate_pages": [
      { "id": "p1", "url": "/blog/crm-data-cleanup", "title": "CRM Data Cleanup: A Practical Checklist", "summary": "How to find and remove unused fields and duplicate records before a migration." },
      { "id": "p2", "url": "/blog/crm-implementation-timeline", "title": "How Long Does a CRM Implementation Take?", "summary": "Timeline expectations and common delays during CRM rollouts." },
      { "id": "p3", "url": "/pricing", "title": "Pricing", "summary": "Plan comparison and per-seat pricing." }
    ]
  },
  "questions": {
    "link_target": {
      "type": "choice",
      "instructions": "Which candidate page, if any, should this passage link to? Choose based on whether a reader finishing this passage would genuinely benefit from visiting that page next.",
      "criteria": {
        "p1": "Directly continues the passage's argument about auditing fields before migration",
        "p2": "Related to migration but addresses scheduling, not data preparation",
        "p3": "Commercial page with no informational continuation of the passage",
        "none": "No candidate offers a genuine next step for the reader"
      }
    },
    "anchor_intent": {
      "type": "choice",
      "instructions": "If a link is added, what should the anchor text describe?",
      "criteria": {
        "data_cleanup": "The cleanup or audit of CRM data before migration",
        "timeline": "How long implementation takes",
        "pricing": "Cost or plan information",
        "not_applicable": "No link should be added"
      }
    }
  }
}

Three things to notice.

The "none" option is not optional. Without it, Jev is forced to pick a link even when the honest answer is that no link belongs there. You will get confident, useless links.

The criteria describe why each option fits or does not fit. "p2: related but addresses scheduling" is far more useful to the model than just listing the page title. You are writing a rubric, not a menu.

The second question rides along for free. Questions in one request run in parallel, so asking for anchor intent at the same time costs you almost nothing in latency. This is the single biggest efficiency gain available, and most teams miss it.

Write the cannibalization question

Cannibalization is a different decision with a different answer set. Do not try to reuse the link schema.

First, group pages that plausibly compete. Cluster by target query, by embedding similarity, or by your keyword mapping. Then ask Jev to sort each cluster.

json
{
  "model": "jev-latest",
  "state": {
    "cluster_query": "best project management software for small teams",
    "pages": [
      { "id": "a", "url": "/blog/best-pm-tools-small-teams", "title": "Best Project Management Tools for Small Teams", "intent_signal": "Comparison list with pricing and feature tables" },
      { "id": "b", "url": "/blog/project-management-software-guide", "title": "How to Choose Project Management Software", "intent_signal": "Buying-guide narrative with evaluation criteria" },
      { "id": "c", "url": "/blog/what-is-project-management", "title": "What Is Project Management?", "intent_signal": "Definitional explainer for beginners" }
    ]
  },
  "questions": {
    "disposition": {
      "type": "choice",
      "instructions": "What is the correct disposition for this cluster?",
      "criteria": {
        "keep_all": "Pages serve genuinely distinct intents and should all remain",
        "merge": "Two or more pages target the same intent and should be consolidated",
        "differentiate": "Pages overlap but can be separated by retargeting one to a distinct intent",
        "no_conflict": "These pages do not actually compete"
      }
    },
    "merge_target": {
      "type": "choice",
      "instructions": "If a merge is recommended, which page should be the surviving canonical target?",
      "criteria": {
        "a": "The comparison list page",
        "b": "The buying guide page",
        "c": "The definitional explainer",
        "not_applicable": "No merge recommended"
      }
    },
    "severity": {
      "type": "score",
      "instructions": "How much risk does this overlap pose to search performance?",
      "criteria": ["No measurable risk", "Minor overlap", "Clear competition", "Direct conflict on the same query"]
    }
  }
}

The score question gives you a prioritization axis. A cluster with a merge recommendation and a severity of 3 gets fixed this sprint. A differentiate at severity 1 goes on a backlog. Without the severity score, every flagged cluster looks equally urgent, which is the same as having no priority at all.

Side-by-side comparison of the internal link decision schema and the cannibalization decision schema.

Link decisions and cannibalization decisions need different answer sets. Do not reuse one schema for both.

Run it in batches and read the probabilities

Now execute. A few practical notes from running this kind of pipeline:

Batch by shared context. If several questions use the same state, send them in one request. If you are processing 500 passages, that is 500 requests, not 5,000.

Log the full distribution, not just the pick. For a choice question you get the selected option plus the probability of every option. A pick at 0.51 across four options is a coin flip wearing a label. A pick at 0.94 is a decision. If your pipeline only stores the label, you cannot tell those apart later.

Watch for flat distributions. One of the reported failure modes of early Jev usage is flat, unhelpful probabilities on some tasks. If you see a high volume of near-uniform distributions, the problem is usually your question or your input, not the model. Rewrite the criteria before you rewrite the threshold.

Do not trust the top pick when the top two are close. If the top two options are within a few points of each other, treat it as ambiguous regardless of which one won.

Route by confidence and log every decision

Set your thresholds before you look at the output. Here is a workable starting point for internal linking, where a bad link is annoying but not catastrophic:

Confidence

Route

What happens

0.85 and above

Auto-queue

Added to the apply list, spot-checked weekly

0.70 to 0.85

Human review

A reviewer approves or rejects

0.55 to 0.70

Low priority

Held, and the candidate list is re-examined

Below 0.55

Drop

Do not act; the input or options are probably wrong

For cannibalization, raise the bar. Merging pages destroys URLs and redirects traffic, so nothing below 0.90 should be auto-actioned, and merges should require a human sign-off regardless of score.

Every log row should carry: the model version, the state, the question, the decision, the confidence, the full distribution, the route taken, and — once you know it — the outcome. That last column is what turns a log into evidence. Without it you are collecting opinions.

One more design note that matters more than it sounds: keep thresholds in a version-controlled config file, not scattered through your code. When you change a threshold, you want a diff and a reviewer, not a silent behavior change.

Verify the finished result before anything ships

Do not skip this. The pipeline produces proposals, and proposals need checking.

Run these five checks on the first full batch:

  1. Spot-check twenty auto-queued links by hand. Open the source passage and the target page. Would a reader actually benefit? If you disagree with more than two or three, your criteria need rewriting.
  2. Check for loops and reciprocity. Page A linking to B and B linking back to A in the same passage context is usually a sign the model is hedging, not that both links are valuable.
  3. Confirm the target pages are indexable. Linking to a noindexed or redirected page wastes the link. This is a code check, not a model check.
  4. Verify anchor text matches the destination. If the anchor intent question says "data cleanup" but the target is a pricing page, something is wrong upstream.
  5. Compare against a manual sample. Take twenty pairs you would have chosen yourself. If Jev disagrees on more than a quarter of them, do not scale up yet.

There is a real caveat here worth stating plainly. Adding internal links is not automatically good. More links to a page do not always help, and a link that does not serve the reader dilutes the ones that do. Jev can tell you which link is most contextually relevant. It cannot tell you whether the link is worth adding at all. That judgment stays with you.

Maintain it

Once the first run is done, the maintenance loop is light:

  • Monthly: re-run on pages published or updated since the last run.
  • Quarterly: re-run the cannibalization pass across your full cluster map, since intent drift shows up slowly.
  • After any site migration: re-run the link pass, because redirects break existing internal links and create new gaps.
  • Ongoing: review the auto-queue spot-check and adjust thresholds if your disagreement rate moves.

Keep the decision log from every run. Six months of logs will tell you more about what your site actually needs than any audit template.

FAQ

Do I need embeddings, or can I send every page pair to Jev? You can, but you should not. Comparing every pair on a large site is wasteful and produces noisier results. A similarity shortlist of around fifteen candidates per passage is the practical sweet spot.

What if Jev keeps recommending the same page for everything? That usually means your candidate summaries are too generic, or one page genuinely is the best match for a whole topic. Check the summaries first. If the page really is the hub for that topic, the pattern is correct.

Can I auto-apply the links? After you have run a full batch, reviewed the output, and set a threshold you trust. Not on the first run. Start with a worklist.

How do I handle pages in multiple languages? Run each language separately. Do not mix languages in one candidate set, and do not reuse a confidence threshold from one language for another without re-validating.

What about links to pages I am about to delete? Exclude them from the candidate set. Jev has no knowledge of your publishing roadmap, so it cannot know a page is scheduled for removal.

Is this replacing my manual internal linking process? It replaces the candidate-finding and first-pass sorting. The judgment about whether a link serves the reader is still yours, at least until you have enough logged outcomes to trust a threshold.

What to do next

Pull your crawl export. Chunk the pages. Embed them. Retrieve fifteen candidates per passage. Write the two questions above with real criteria. Run it on a hundred passages before you run it on ten thousand.

Then read the log, and let the disagreement rate tell you whether to scale.

Read the rest of the series

This article is part of a six-part series on using Jev for SEO and GEO work.

Author: Julian Mercer, 14-Year Technical SEO Practitioner at Auspia. Julian writes about crawlability, site architecture, internal linking, and the technical foundations that make content readable to both search engines and AI systems.

Explore this topic

Keep following the same growth thread