What it refuses to build
Every check the library performs fails the build, instead of shipping a defect that is invisible in a browser and expensive weeks later in search.
Everything below is a defect that ships silently and costs weeks to find. The library refuses it at build time instead, which is the only moment it is cheap.
Routes
| Refused | Because |
|---|---|
| A path no route declares | the page would render a canonical and be absent from the sitemap |
| Two routes on one path | the head and the sitemap would describe different routes under one URL |
| A locale a route does not serve | the canonical would name a page that was never built |
| A collection entry in a locale the site omits | the content and the declaration contradict each other, and neither wins |
A path that does not start with /, or ends with one | both double into every canonical and every alternate on the site |
path: "" on a route with a fixed locale | the empty path is the home page under /[locale]; outside that segment it is /, a path like any other |
Site
| Refused | Because |
|---|---|
baseUrl with a path, or a relative one | it prefixes every absolute URL the site emits |
baseUrl that is not http or https | same, and the scheme reaches the sitemap |
| A site serving no locale | there is no cluster to build and no x-default to point anywhere |
| Duplicate locales | the alternates would list the same page twice |
| A malformed language tag | locale.invalid, which exists because these tables get copied wrong |
| A locale ICU has no likely region for | og:locale is language_TERRITORY and there is nothing to derive from |
A sitemap priority outside 0.0 to 1.0 | the value is silently dropped by crawlers rather than corrected |
A locale with no territory is not refused: pt becomes pt_BR and en becomes
en_US from ICU's likely subtags, which is the same table both sides used to
hand-copy. Only a tag ICU gives no likely region for needs localeTags.
Two things it gets right that hand-written versions usually do not
Your URL keeps the case you wrote, and the tag takes its canonical form.
Declare pt-br and you get /pt-br/ in the canonical, hreflang="pt-BR" in
the cluster, lang="pt-BR" on the document and pt_BR in og:locale. BCP 47
makes case insignificant, so those are one tag and not two, and emitting the
canonical form is what keeps lang and hreflang from answering the same
question two different ways in one document.
This is worth spelling out because the library did the opposite for a while. It
used to echo the string you declared, to work around a crawler that compared the
URL segment against the tag byte for byte and reported a translation hole for a
language already served. That was the wrong layer to fix it in: goflag now folds
the two to one identity, as BCP 47 says they are, and the library went back to
emitting the canonical form in 0.2.0.
x-default is in its own cluster. A x-default that points outside the set
of alternates is a cluster that does not close, and reciprocity checks fail on
it.
The tests are the rules
The library's own suite does not invent its assertions. Each one is named after
the goflag rule it covers: hreflang reciprocity, x-default inside its own
cluster, sitemap and <head> in agreement, og:locale territorialised.
That file is still provisional, but no longer for the reason it used to give.
goflag rules already prints the catalogue as JSON — no crawl, no network — and
rules.json ships inside the package. What the library still cannot do is import
it: the invariant that keeps the two products independently useful forbids
packages/next reaching into the CLI, so the assertions stay hand-written until
the catalogue moves into a package both may depend on.
The loop worth having is unchanged: a rule added to the catalogue should break the library when the library does not satisfy it, rather than be discovered later as an accident to correct.
What it deliberately does not do
- It will not write
og:image:altfor you. A library cannot describe a card it did not draw, and the only value always available — the page title — is the one the protocol excludes: "a description of what is in the image (not a caption)". PassimageAltbesideimage, or the tag is omitted and an auditor tells you so. Synthesising it would satisfy the check that looks for it, which is worse than failing: the defect would never be reported again. - No
next-intlwrapper. Forty lines with nothing added. - No React components. They drag a design system in with them.
- No
process.env. See the registry. - No JSON-LD yet. The catalogue does not judge structured data beyond its presence, and producing what nobody can check is how signals get collected and never read.
- No Pages Router. App Router only.