zudo-doc
GitHub repository

Type to search...

to open search from anywhere

Link Checker

Created Mar 26, 2026Updated Sep 13, 2026Takeshi Takatsudo

Post-build broken link checker that validates internal links in built HTML and MDX source files.

Overview

zudo-doc includes a built-in link checker (scripts/check-links.js) that runs two types of validation after the site is built.

What It Checks

Mode 1: Built HTML scan

Scans all .html files in dist/ for internal <a href="..."> links and verifies each target exists on disk. It handles:

  • Base path stripping (e.g., /my-docs/docs/foodocs/foo)

  • Trailing slash resolution (docs/foo/docs/foo/index.html)

  • Extension resolution (docs/foodocs/foo/index.html or docs/foo.html)

  • Query string and fragment stripping before resolution

  • Relative link resolution against the file's directory

  • Caching resolved paths for performance

Links that are skipped (not checked):

  • External URLs (https://, http://)

  • Anchor-only links (#section)

  • mailto:, javascript:, data:, tel: URIs

  • Versioned docs links (/v/*/) — version content may be incomplete

Mode 2: MDX source scan

Scans all .mdx and .md files in the configured content directories for absolute links that bypass the base path. This catches links like:

<!-- These are flagged — they bypass the base path -->
[guide](/docs/guides/foo)
<a href="/ja/docs/guides/foo">link</a>

<!-- These are fine — they use relative paths -->
[guide](./foo.mdx)
[guide](../other/bar.mdx)

When a base path is configured (e.g., /my-docs/), absolute links starting with /docs/ or /ja/docs/ will break because they're missing the base path prefix. The MDX source scan catches these before they become broken links in production.

Links inside fenced code blocks are ignored.

Image Checker

zudo-doc also includes a strict post-build image and asset checker (zudo-doc check images). It scans every rendered .html file under the build output and checks local references supplied by these elements and attributes:

  • HTML img[src] and each valid img[srcset] candidate

  • HTML source[src] and each valid source[srcset] candidate (including <picture> sources)

  • HTML video[src] and video[poster]

  • HTML audio[src], track[src], and input[type="image"][src]

  • SVG image[href] and image[xlink:href]

Both site-absolute URLs (/images/diagram.png) and relative URLs (./diagram.png, ../images/diagram.png, or images/diagram.png) are resolved with URL semantics from the page that contains them. The configured base path and the page's <base href> are honored. Query strings and fragments do not affect the file lookup. A reference is valid only when it resolves to a file inside the build output; directories and symlinks that point outside the output are reported.

The checker skips external URLs (including URLs with a scheme such as http: or https:), data: URLs, protocol-relative URLs (//cdn.example/...), and fragment-only references (#diagram).

Build-time plugin and strict CLI

The package's build-time img-src-check plugin runs after the build and uses the existing onBrokenMarkdownLinks setting. Its default is warn, which reports broken local references without failing the build; error fails the build, and ignore disables this scan. This setting also controls broken Markdown links, so use the CLI below when the image check itself must be a strict, independent gate.

The strict CLI exits nonzero when any broken local reference remains after allowlisting:

pnpm build && pnpm check:images

Its options are:

  • --dist <dir> — build output directory (default: dist)

  • --base <path> — public URL base (default: /)

  • --allowlist <file> — exact broken-reference exceptions

  • --help — show the current command help

For example, a site deployed below /docs/ can pass its base explicitly:

pnpm check:images -- --dist dist --base /docs/ --allowlist=.check-images-allowlist

The allowlist contains one exact <page>:<url> key per line, where <page> is the path relative to the build output and <url> is the original URL value. Blank lines and lines beginning with # are ignored:

# A generated page still points at an intentionally absent asset
docs/generated/index.html:/images/legacy.png

Keep exceptions narrow and remove each line when its underlying asset is fixed. A generated project should add the strict check to CI after its build (and after any link check), so broken local assets cannot be published:

- name: Build site
  run: pnpm build
- name: Check images
  run: pnpm run check:images

Build the site first, then run the checker:

pnpm build && pnpm check:links

By default, the checker exits with code 0 even when issues are found. Select the categories that should fail the command with the explicit --strict-broken, --strict-absolute, and --strict-trailing flags. The b4push and CI workflows fail on broken links and absolute MDX links while leaving trailing-slash findings informational:

pnpm check:links -- --strict-broken --strict-absolute --allowlist=.check-links-allowlist

Run pnpm check:links -- --help to see the current CLI options.

--allowlist=PATH excludes exact <file>:<line>:<href> entries from the strict-mode failure counts — for issues that cannot be fixed at the source, such as a JA page that intentionally links to an EN-only sibling, or an absolute href that resolves to a runtime-generated route with no MDX file. The repo ships a real allowlist at .check-links-allowlist: one <file>:<line>:<href> entry per line, with # comments and blank lines ignored. Each entry must match the printed report verbatim — file path relative to the repo root, 1-based line number, and href as it appeared in the source or built HTML. Delete an entry once its underlying issue is fixed, so the strict gate catches future regressions of the same shape.

The link checker runs as step 28 of 32 in the pre-push validation (pnpm b4push). The strict image checker follows it as step 29, before HTML validation:

26. Build
27. Content-fallback check
28. **Link check**
29. **Image check**
30. HTML validation
31. Preview smoke
32. Manual smoke

The canonical, authoritative b4push step list lives in the header comment of scripts/run-b4push.sh — treat that script as the single source of truth for the full 32-step sequence.

The link checker runs in the build-site job of the PR checks workflow (pr-checks.yml), immediately after pnpm build; the strict image checker runs immediately after the link checker. The production and preview deployment workflows run the same image gate before archiving their build output.

Output

When no issues are found:

Checking links (base: /my-docs/)...

✓ No broken links or absolute path issues found

When issues are found:

Checking links (base: /my-docs/)...

=== Broken Links in Built HTML ===
  dist/docs/page/index.html:42  /my-docs/docs/missing-page

=== Absolute Links Bypassing Base Path (MDX Source) ===
  src/content/docs/guides/test.mdx:15  /docs/guides/foo

✗ Found 1 broken link and 1 absolute path warning

Each entry shows the file path, line number, and the problematic href.

Configuration

The link checker reads its configuration from the resolved config (the fields you pass to zudoDoc({...}) in zfb.config.ts):

  • base — The site's base path, used to strip prefixes when resolving links

  • docsDir — Primary content directory for MDX source scanning

  • locales — Locale configurations with dir fields pointing to additional content directories for MDX source scanning (e.g., locales: { ja: { dir: "src/content/docs-ja" } })

Revision History

Takeshi TakatsudoCreated: 2026-03-26T23:19:35+09:00Updated: 2026-09-13T16:19:44+09:00

AI Assistant

Ask a question about the documentation.

Preview theme

Loading theme previews…