getzola/zola · warning
Anchor `#{}` not found on page
Error message
Anchor `#{}` not found on page What it means
The link checker, when validating in-page anchors, fetches the page body and searches for a matching id/name via `has_anchor_id`. If the fragment identifier after '#' is not found anywhere in the page, `check_page_for_anchor` returns this error, marking the internal link as broken.
Source
Thrown at components/link_checker/src/lib.rs:117
fn has_anchor(url: &str) -> bool {
match url.find('#') {
Some(index) => match url.get(index..=index + 1) {
Some("#/") | Some("#!") | None => false,
Some(_) => true,
},
None => false,
}
}
fn check_page_for_anchor(url: &str, body: String) -> errors::Result<()> {
let index = url.find('#').unwrap();
let anchor = url.get(index + 1..).unwrap();
if has_anchor_id(&body, anchor) {
Ok(())
} else {
Err(anyhow!("Anchor `#{}` not found on page", anchor))
}
}
#[cfg(test)]
mod tests {
use super::{
LINKS, LinkChecker, check_page_for_anchor, check_url, has_anchor, is_valid, message,
};
use reqwest::StatusCode;
// NOTE: HTTP mock paths below are randomly generated to avoid name
// collisions. Mocks with the same path can sometimes bleed between tests
// and cause them to randomly pass/fail. Please make sure to use unique
// paths when adding or modifying tests that use Mockito.
#[test]
fn can_validate_ok_links() {
let mut server = mockito::Server::new();View on GitHub (pinned to 61d3082821)
Solutions
- Open the target page and find the actual rendered id of the section; update the link's fragment to match
- Slugify the heading text the same way Zola does (lowercase, spaces to hyphens, punctuation stripped) and use that as the anchor
- If the target truly should not have an anchor, remove the '#fragment' from the link
- Add an explicit anchor/id to the target element
Example fix
// before [docs](./guide.md#Quick-Start) // after [docs](./guide.md#quick-start)
Defensive patterns
Strategy: validation
Validate before calling
import fs from 'fs';
function anchorsInFile(path) {
const html = fs.readFileSync(path, 'utf8');
return new Set([...html.matchAll(/(?:id|name)="([^"]+)"/g)].map((m) => m[1]));
}
function linkIsValid(pageAnchors, url) {
const i = url.indexOf('#');
if (i === -1) return true;
return pageAnchors.has(url.slice(i + 1));
} Type guard
function hasAnchor(html, fragment) {
return typeof fragment === 'string' &&
new RegExp(`(?:id|name)=["']${CSS.escape(fragment)}["']`).test(html);
} Try / catch
match check_internal_link(url) {
Ok(()) => {},
Err(e) if e.to_string().starts_with("Anchor `#") => {
log::warn!("dead anchor, fix link: {} ({e})", url);
}
Err(e) => return Err(e),
} Prevention
- Slugify heading text the same way the site generator does when hand-writing anchors
- Run the link checker in CI and treat anchor failures as warnings to fix before merge
- When renaming/deleting headings, grep the content directory for links to the old fragment
- Prefer linking to the page without a fragment unless the section anchor is verified
When it happens
Trigger: A markdown/HTML link like `[text](./page.md#section)` where page.md exists but no heading or element defines `id="section"` (or a matching `name` attribute) — detected by `check_page_for_anchor`, invoked from `check_url`.
Common situations: Renaming or deleting a heading without updating links to its anchor; anchors with special characters that Zola slugifies differently (e.g. 'C++' -> 'c'); case mismatches; anchor pointing at a heading rendered by shortcodes not present at check time.
Related errors
- Found {} broken internal anchor link(s) {}
- could not parse domain `{}` from link
- could not parse domain `{}` from link: `{}`
- Found {} broken external link(s) {}
AI-assisted analysis of getzola/zola@61d3082821 (2026-09-03).
Data as JSON: /api/errors/702b03ea08ace190.
Report an issue: GitHub.