# 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 explicitly provisional. When `goflag rules --json` ships the
catalogue as data, it is replaced by a harness that renders the library's output
and evaluates the real rules against it. A rule added to the catalogue then
breaks the library if the library does not satisfy it, which is the loop worth
having rather than an accident to correct.

## What it deliberately does not do

- **It will not write `og:image:alt` for 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)". Pass `imageAlt` beside `image`, 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-intl` wrapper.** Forty lines with nothing added.
- **No React components.** They drag a design system in with them.
- **No `process.env`.** See [the registry](/docs/next/routes#it-reads-no-environment-variable).
- **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.
