Skip to content

Your page is perfect. Google no longer sees it.

goflag is a CLI that reads your site the way a crawler does, and fails a CI step before an invisible defect ships.

npx @goflag/cli https://example.com
commit

A tag changes.

day 0

Without goflag

shipped

It ships.

day 0
unnoticed

The page leaves the index.

weeks later
too late

Someone asks why traffic fell.

months later

With goflag

caught in CI

CI raises the flag.

4 min later
fixed

A developer or an agent fixes it.

minutes later
shipped clean

It ships clean.

still day 0

What a browser cannot show you

Each of these renders perfectly. The defect is in the part of the page nobody looks at, and the cost is paid in a search index you never see.

What you see

A page that renders exactly as designed.

What a crawler sees

<link rel="canonical" href="/another-page">

Consequence

The page asks to be dropped in favour of another. It will be.

What you see

Four locales, every one of them reachable.

What a crawler sees

no <link rel="alternate" hreflang> anywhere

Consequence

Each translation competes with the other three instead of consolidating with them.

What you see

A live production site, serving traffic.

What a crawler sees

robots.txt → User-agent: * Disallow: /

Consequence

No page is ever fetched, so the index directive on every page is never read.

The whole interface

The JSON report is the interface: pipe it into scripts, diff it between runs, gate CI on it, or hand it to an AI agent. The terminal output is a render of it.

$ npx @goflag/cli https://example.com

goflag https://example.com/
RED FLAG  128 pages crawled, 128 scanned

3 broken links   2 missing translations   14 SEO issues   1 site issue   0 unreachable pages

Broken links
  https://example.com/blog/hreflang-basics
    [404] https://example.com/guides/canonical-tags
    [blocked 403 forbidden] https://api.partner.example/status

Missing translations
  /blog/hreflang-basics — missing de (have en, fr)
  missing-back-link https://example.com/fr/blog/hreflang-basics
    /fr/blog/hreflang-basics declares an alternate to /de/... but the peer does not link back.

SEO issues
  https://example.com/blog/hreflang-basics
    warn  canonical.missing  Page is missing <link rel="canonical">.
    warn  description.length  Description is 31 characters — short of the recommended 50–160 window.
  https://example.com/pricing
    error robots.conflict  Page asks to be indexed while robots.txt disallows it.
    warn  og.image.missing  Page has no og:image.

Site-wide issues
  locales: en, fr, de (via sitemap)
  https://example.com/
    error hreflang.missing  Site serves 3 locales but declares no hreflang alternates.

note: 1 link returned 403 from an anti-bot filter, not counted as broken.

Real output; only the spacing is adjusted for the page. Progress goes to stderr so stdout stays clean for the report.

Gate on regressions, not on perfection

A run that fails on any finding is unusable on a site that is not clean yet, so it gets switched off, or quietly ignored. Three flags make the gate tenable, and one of them makes the backlog actually shrink.

  1. Capture a baseline

    goflag https://example.com \
      --baseline .goflag/baseline.json --update-baseline

    Freeze today’s findings as known, so the gate can ask whether this change made things worse instead of whether the site is perfect. The output never says clean while findings are outstanding; it says how many are being let through.

  2. Gate on what is new

    goflag http://localhost:3000 --no-external \
      --baseline .goflag/baseline.json --regressions-only

    Findings are matched by fingerprint, and page URLs are normalised to origin-independent routes, so a baseline captured against production compares cleanly with a run against localhost.

  3. Lower the ratchet

    goflag … --baseline .goflag/baseline.json --max-debt 13

    Fix a finding, drop --max-debt by one, commit both. Without this, a baseline fossilises behind a build that passes.

In CI

Two moments are worth auditing, and they answer different questions.

WhenAgainstAnswers
On the merge requestThe branch’s own build, booted by --startDoes this change regress?
After deployingThe running environmentIs what is actually serving correct?

The flags worth knowing (--no-external, version pinning, exit codes) are three short sections in the docs.

See the CI recipes

Built by auditing my own sites

goflag exists because four sites I run needed it, this one included: goflag.tech is gated by its own audit in CI, and a merge that breaks it does not land. Every number below is measured, not estimated.

sites gated in CI
4sites gated in CI
pages audited in 4 min
456pages audited in 4 min
tests in the CLI
516tests in the CLI

What goflag is not

One command, a JSON report, an exit code. If what you want is a number to optimise, this is the wrong tool: a score invites optimising the score.

If you want a desktop crawler with a UI, Screaming Frog exists; if you only want dead links, lychee is faster. goflag is for gating a build on what a crawler reads.

Before you install

No. It needs a URL. goflag reads what a crawler reads, which is also why it catches things a local build cannot show you: the real base URL, whatever the proxy serves for robots.txt, the redirects.

Point it at your site.

The quickstart takes you from a red first run to a gate you trust.

npx @goflag/cli https://example.com