:::contentbit
Guides

Internal linking

Build and validate a frontmatter-authored internal-link graph with contentbit links.

Internal links are content metadata, not renderer behavior. Author each page's place in the graph in frontmatter, let contentbit derive backlinks, and fail CI when a slug drifts after a rename.

---
slug: beginner-pizza-dough
linksTo:
  - cold-fermentation-pizza
  - detroit-pizza-dough
aliases:
  - intro-pizza-dough
keywords:
  primary: how to make pizza dough
  secondary: [easy pizza dough recipe, homemade pizza dough]
---

Only slug, linksTo, aliases, and keywords belong in source files. linkedFrom is derived and written only to .contentbit/link-index.json.

Build the index

Point links at the same content glob you validate:

contentbit links "content/**/*.md"

The command reads frontmatter, resolves aliases, derives backlinks, writes .contentbit/link-index.json, and prints a summary:

12 page(s), 34 link(s), 1 orphan(s): 0 errors, 1 warnings
index written to /site/.contentbit/link-index.json

The JSON artifact is intentionally small and stable, so an LLM agent or pipeline can load it before writing a page:

{
  "pages": [
    {
      "slug": "beginner-pizza-dough",
      "path": "/repo/content/beginner.md",
      "title": "Beginner Pizza Dough",
      "linksTo": ["cold-fermentation-pizza"],
      "linkedFrom": ["pizza-flour-guide"],
      "aliases": ["intro-pizza-dough"],
      "keywords": {
        "primary": "how to make pizza dough",
        "secondary": ["easy pizza dough recipe"]
      }
    }
  ],
  "aliases": {
    "intro-pizza-dough": "beginner-pizza-dough"
  }
}

contentbit validate runs the cross-file link checks automatically whenever any matched file declares a slug:

contentbit validate "content/**/*.md" --registry ./blocks/registry.ts

Use the full content glob for linked documents. Validating only one linked file does not give the checker the rest of the graph.

The link diagnostics use the same file:line:col severity CODE message shape as block validation:

  • CB_LINK_UNRESOLVED for a linksTo target that is not a slug or alias.
  • CB_SLUG_DUPLICATE for two pages with the same slug.
  • CB_ALIAS_CONFLICT for aliases that collide with another alias or real slug.
  • CB_LINK_LOCALE_MISSING for a target that exists in another locale but not the current locale.
  • CB_LINK_CROSS_LOCALE for an explicit object target that intentionally points across locales.
  • CB_LINK_SELF for a page linking to itself.
  • CB_LINK_ORPHAN for a page with no inbound links.

Multilingual projects

Use the resolver mode that matches your URL model:

# Same translated slug in every locale: /en/blog/espresso and /fr/blog/espresso
contentbit links "content/**/*.md" --link-resolve same-locale-slug

# Translated slugs with stable content keys:
contentbit links "content/**/*.md" --link-resolve same-locale-key

# Prefer stable keys, but allow same-locale slug links for simple projects:
contentbit links "content/**/*.md" --link-resolve prefer-same-locale-key-fallback-slug

For translated slugs, author a stable key and a locale-specific slug:

---
locale: fr
slug: regler-espresso
key: espresso-dial-in
linksTo:
  - espresso-grinder-settings
---

The matching English page can use the same key with its own slug:

---
locale: en
slug: dialing-in-espresso
key: espresso-dial-in
linksTo:
  - espresso-grinder-settings
---

The generated index keeps locale, slug, and key together so an app can build URLs with its own route helper, such as /{locale}/blog/{slug}. To point across locales intentionally, use an object target:

linksTo:
  - locale: en
    slug: dialing-in-espresso

That emits a warning instead of silently hiding the cross-locale edge.

Heal renames

When a page changes slug, keep the old slug in aliases on the destination page:

---
slug: cold-fermentation-pizza
aliases:
  - overnight-pizza-dough
---

Then let --fix rewrite stale outbound references:

contentbit links "content/**/*.md" --fix

--fix is deliberately narrow. It only rewrites values inside linksTo that point at known aliases. It does not remove aliases, invent related pages, change body Markdown, or write linkedFrom to source files.

Agent workflow

contentbit agents teaches installed skills and AGENTS.md to use the link graph when it exists:

  1. Run contentbit links <content glob> before writing to read current slugs, aliases, keywords, and backlinks.
  2. Pick linksTo values from existing slugs; never guess a slug from a title.
  3. For new linked pages, include keywords.primary and keywords.secondary with search-intent phrases that help future LLM agents choose related pages.
  4. Run contentbit links <content glob> --fix after edits when aliases are present.
  5. Run contentbit validate <content glob> --registry <path> until clean.

For projects without generated LLM-agent files, add the same loop to your own skill or rules file. The CLI stays the source of truth.

On this page