Manage Cross-Site Links
When your project hosts more than one Docusaurus instance under the same
domain (for example a User area at /docs/ and a Developer area at
/developers/), links that cross instance boundaries must be absolute.
Docusaurus does not resolve relative paths across plugin boundaries — and
neither does Zenzic's link validator.
By default, Zenzic's Z105 ABSOLUTE_PATH rule rejects any absolute link
(/foo/bar) because absolute paths break when a site is hosted in a
subdirectory. This guide shows you how to declare the cross-instance
prefixes your project legitimately owns, so the validator stops flagging
them — without weakening Z105 elsewhere.
TL;DR — Which tool, when?
| Situation | Use this | Don't use |
|---|---|---|
Many cross-plugin links share a prefix (/developers/, /api/) | [link_validation] absolute_path_allowlist in zenzic.toml | inline ignores |
| One isolated line in one file legitimately matches a rule | <!-- zenzic:ignore Zxxx --> (or {/* zenzic:ignore Zxxx */} for MDX) | allowlist |
| Two or more files would need the same inline ignore | Promote to allowlist / config | inline ignore in each file |
The decision rule: if it is a property of your project, it belongs in config; if it is a property of one line, it belongs inline.
Allowlist a cross-instance prefix
Add the prefixes you trust to zenzic.toml:
# zenzic.toml
[link_validation]
absolute_path_allowlist = [
"/developers/",
"/api/",
]
Now any link starting with /developers/ or /api/ is accepted by Z105.
Everything else still fails — a typo like /developres/foo will be
caught.
Rules to remember:
- Entries must start with
/. The check uses simplestartswithmatching — no regex, no globs. - The match is on the URL path only —
?queryand#anchorare stripped before comparison. - The validator does not check that the target actually exists in the sibling instance. The allowlist is a trust contract, not a resolver.
When to use an inline ignore instead
Inline ignores are surgical. Reach for them when:
- A single line in a single file legitimately triggers a rule (e.g. a documentation example that looks like a credential but is fake).
- The exception is local context, not a project-wide truth.
<!-- zenzic:ignore Z201 -->
api_key = "sk_test_PLACEHOLDER_FOR_DOCS"
{/* zenzic:ignore Z105 */}
[Hard link example](/legacy/path)
The inline form leaves an audit trail at the exact line — visible in PR
diffs, traceable in git blame.
Anti-pattern: inline-ignoring cross-plugin links
Do not sprinkle <!-- zenzic:ignore Z105 --> over every cross-plugin
link. This:
- Hides the cross-instance dependency from anyone reading
zenzic.toml. - Forces every contributor to rediscover the same rule.
- Implies the link is "broken and accepted" when in reality it is correct by design.
If two or more files need the same Z105 ignore for the same prefix, that
prefix is a systemic truth of your project — promote it to
absolute_path_allowlist.
Reverting
Remove an entry from absolute_path_allowlist and Z105 enforcement
returns immediately on that prefix. The protection is cheap to enable
and cheap to remove — there is no migration cost either way.
Managing Virtual Identity: Slug-Based Routes
Docusaurus lets any page declare a custom slug: in its frontmatter that
completely overrides the filename-derived URL. Once a slug is declared,
only the slug URL exists in Docusaurus's routing table — the original
filename path is gone.
Zenzic enforces this identity at validation time. When the Docusaurus adapter
is active, set_slug_map() reads frontmatter slugs before building the
Virtual Site Map (VSM). The VSM then contains only the canonical slug URL —
not the filename URL.
Identifying the correct route
Given a post with the following frontmatter:
---
slug: amazing-post
title: An Amazing Post
---
| Link | Result |
|---|---|
[Post](/blog/2026-05-05-post) | ❌ Z104 — filename-derived path absent from the VSM |
[Post](/blog/amazing-post) | ✅ Validated — slug matches the VSM entry |
The validator raises Z104 FILE_NOT_FOUND (not Z105) because /blog/ is a
project-owned prefix. The prefix is trusted; the exact route must still exist.
Finding the canonical slug
Run zenzic inspect routes --kind physical to list every route in the VSM
alongside its source file and frontmatter slug:
$ zenzic inspect routes --kind physical
/blog/amazing-post/ ← blog/2026-05-05-post.md [slug: amazing-post]
Update any link that points to the old filename-derived path to use the slug URL shown in the output.
Engine agnosticism: the hasattr guard
This feature is adapter-selective. The validator calls set_slug_map() only
when the active adapter exposes that method — detected at runtime via
hasattr. Simpler adapters (Standalone, MkDocs) never receive a slug-map
call and their validation path is unchanged.
Discovery → set_slug_map (Docusaurus only) → build_vsm → Validate
↕
hasattr guard — no-op for MkDocs / Standalone
This follows the Quartz Maturity principle: evolve the complex adapter without burdening the simpler ones.
Related
- Configuration Reference —
[link_validation] - Suppression Policy — full inline- ignore syntax and scope rules.
- For the full design rationale, see ADR 011: Cross-Instance Allowlist.