Link checking

How the site’s links are checked, locally and in CI.

The site is link-checked with Lychee, backed by a committed cache of external-link results (see Link cache).

To check links locally, run:

npm run check:links

Common commands

CommandChecking scope
check:linksWhole site
check:links:internalWhole site, offline (no external links)
check:links:diffChanged files only
fix:link-cacheAlias of check:links; use it to refresh the link cache

The check:links and check:links:internal scripts run over a build of BUILD_KIND; check:links:diff checks files from the existing public/ build. For details, see Build kinds: full and lean.

Configuration

Lychee runs over the built site (public/) using the generated, git-ignored lychee.toml. The generate:config:links script derives it from lychee.base.toml plus an exclude_path block computed from page front matter, which has two sources:

  • link_check_exclude_path — a list of site-relative path regexes for pages the link checker must skip, such as blog pagination and old blog posts; see content/en/blog/_index.md. Start a pattern with ^(../)? to have it cover every locale: the optional ../ matches a two-letter locale path segment such as ja/.
  • drifted_from_defaultdrifted localized pages, status true (EN counterpart changed) or file not found (EN counterpart deleted). Links from such a page aren’t checked, since they may be stale, but the page remains a valid link target: inbound links from in-sync pages, including fragments, are still validated.

Stored drift statuses are only as fresh as the last nightly Housekeeping status sync (as merged, so the window can exceed a day), so the generator also skips drift-pending pages: locale copies of English pages changed (or deleted) since the drift-status baseline, the main-branch commit recorded in data/l10n-drift.yaml by tree-wide status syncs (npm run fix:i18n). A copy that itself changed since the baseline stays checked: someone is working on it. Config generation fails when the baseline is missing or can’t be resolved; in CI, the CHECK LINKS job first deepens its shallow clone to the baseline commit; locally, fetch the missing history (git fetch upstream main) or override the baseline: DRIFT_BASELINE=HEAD npm run check:links empties the overlay (stored-status skips still apply).

Link cache

External-link check results are cached in .lycheecache, which is under version control so that checks only fetch URLs that are new or whose cache entries have expired. Lychee caches successful results only, so failures are retried on every run.

If you add or change external links, run npm run check:links before submitting your PR — the site build dominates the run time — and commit the updated .lycheecache along with your content changes. Otherwise the CACHE updates committed? check will fail; for recovery steps, see CACHE updates committed?.

Cache refresh and housekeeping workflows

The following workflows are scheduled daily and run a link checking command over a full build:

WorkflowLink-check command
Refcache refreshfix:link-cache (after pruning)
Housekeeping (fix-and-test:all)fix:link-cache

Refcache refresh prunes the oldest cache entries (the count is a workflow input) and re-runs the link check, which refreshes the cache entries for the pruned URLs that are still used in the site.

In CI

The check-links.yml workflow builds the site once (lean) and shares that artifact with the CHECK LINKS job, so local runs and CI check the same build. That job fails if any link check fails, and hands the cache it refreshed to the CACHE updates committed? job, which fails if the run left the committed .lycheecache stale.