Link checking
The site is link-checked with Lychee, backed by a committed cache of external-link results (see Link cache).
bot can update the link cache for you. To run checks locally,
install Lychee; CI installs its own pinned copy (see the
.github/actions/install-lychee action), so keep your local version
reasonably close to it.
Check links
To check links locally, run:
npm run check:links
Common commands
| Command | Checking scope |
|---|---|
check:links | Whole site |
check:links:internal | Whole site, offline (no external links) |
check:links:diff | Changed files only |
fix:link-cache | Alias 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; seecontent/en/blog/_index.md. Start a pattern with^(../)?to have it cover every locale: the optional../matches a two-letter locale path segment such asja/.drifted_from_default— drifted localized pages, statustrue(EN counterpart changed) orfile 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:
| Workflow | Link-check command |
|---|---|
| Refcache refresh | fix: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.