Skip to content

Link validator

Checks every link your site published: pages, assets, anchors, and the ones that leave the site when you ask for it.

What it adds

The markdown plugin already resolves the links you write, and fails the build when one points at no page. This plugin checks something else: the site as it was published. It takes every href and src in every rendered page - from markdown, a layout, a web component, a raw HTML block - and checks it against the files the build actually wrote.

That catches:

  • an asset a theme names but never ships
  • a link written in HTML, which no markdown pass ever looked at
  • an anchor that moved when a heading was reworded
  • a relative link, whose meaning depends on the page it is read from
  • a link that leaves the site and has since died

Add it

Terminal
dotnet add package Nacara.Plugin.LinkValidator --prerelease
Site.create "My library"
|> Markdown.register
|> Theme.register theme
|> LinkValidator.register

It runs after everything is written, so it reads the output directory. You configure nothing for the checks that need no network:

Terminal
✗ content/guide/deploy.md(1,1): error link-validator/target-missing: '/assets/diagram.png' points at nothing this build wrote
    hint: The build would have to write 'assets/diagram.png'

These are checked too, and a failure is a warning rather than an error, since an external link dies on someone else's schedule.

Answers are cached under ~/.cache/nacara for a week, so most builds send no requests at all. A check sends HEAD first and retries a refusal as a GET, since plenty of sites answer HEAD with 405 while serving the page.

Turn it off where you have no network, and turn failures into errors where you want the build to stop:

|> LinkValidator.registerWith (fun options ->
    { options with
        CheckExternal = false   // a build behind a firewall
        FailOnExternal = true   // or: a dead link is a broken site
    }
)

To ask only where a failure means something - in a weekly workflow, say - decide from the environment:

|> LinkValidator.registerWith (fun options ->
    { options with
        CheckExternal = System.Environment.GetEnvironmentVariable "NACARA_CHECK_LINKS" = "1"
    }
)

Options

OptionDefaultEffect
CheckExternaltrueAsk servers about links that leave the site
CheckWhileWatchingfalseCheck during watch builds too
FailOnExternalfalseAn unreachable external link fails the build rather than warning
Timeout10Seconds to wait for a server
Concurrency8How many external links to ask about at once
Ignore[]Regular expressions matched against the whole url
AllowStatusCodes[]Extra status codes to accept - 403 and 429 are common for bots
CacheHours168How long an answer stays good

Use Ignore for sites that refuse anything that is not a browser:

|> LinkValidator.registerWith (fun options ->
    { options with
        CheckExternal = true
        Ignore = [ @"^https://www\.linkedin\.com/"; @"localhost" ]
        AllowStatusCodes = [ 403 ]
    }
)

Versioned sites

Each version is its own build, so each one checks its own output. A link from /2.0/ into /3.0/ is reported as link-validator/outside-site, and rightly so: that page belongs to a build this one cannot see, and nothing here can promise it exists.

Reference

Every function and option of it, signature by signature: LinkValidator.

Edit this page