← back to Open Seo

badseo/README.md

140 lines

# badseo.dev

**A test site full of SEO mistakes.**

badseo.dev is a set of open-source web pages. Each page breaks one common
technical-SEO rule: a missing `<title>`, a redirect loop, a page nothing links
to, thin content. Point an SEO crawler at it and check what the crawler catches.

It is also the end-to-end test fixture for the
[OpenSEO](https://openseo.so) site audit. Every page lists the audit issues it
should trigger, and a harness runs the real audit engine against a running copy
to check that it does.

Maintained by the team behind [OpenSEO](https://openseo.so), an open-source SEO
tool.

---

## What's covered

Every issue type in the OpenSEO audit engine is exercised by at least one page
(the harness enforces this). Pages are grouped by category:

| Category                     | Pages                                                                                                         |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **Head tags & headings**     | missing title, title too long/short, missing meta, meta too long, missing H1, multiple H1, heading-level skip |
| **Content quality**          | thin content, images missing alt, duplicate content, duplicate title, duplicate meta description              |
| **Indexability & canonical** | noindex (meta + `X-Robots-Tag` header), canonicalized to another URL, conflicting canonicals                  |
| **HTTP status & links**      | 404, 500, 403 (blocked), broken internal link                                                                 |
| **Redirects**                | redirect chain, redirect loop, trailing-slash canonical (redirect-cycle trap)                                 |
| **Performance**              | slow server response (TTFB)                                                                                   |
| **Site structure**           | orphan page, deep click-path                                                                                  |
| **Kitchen sink**             | one page that breaks six ways at once                                                                         |

Browse them all on the homepage at `/#issues`.

## How it's built

badseo.dev is a TanStack Start app deployed to a Cloudflare Worker, following
the same Vite and Cloudflare setup as the repository's `web/` app.

TanStack React routes render the healthy homepage and privacy policy.
A TanStack catch-all server route keeps the deliberate fixtures as raw
responses with byte-level control over status codes, redirects, headers
(`X-Robots-Tag`, `Link: …; rel=canonical`), timing, and the malformed `<head>`
states the audit needs to observe.

- `src/routes/` — TanStack pages plus raw server routes for fixtures,
  `robots.txt`, and `sitemap.xml`.
- `src/server/badseo.ts` — fixture dispatch and crawler-discovery responses.
- `src/lib.ts` — raw fixture HTML rendering. Its shared chrome is deliberately
  **SEO-neutral**: it emits no `<h1>`–`<h6>` and no `<img>`.
- `src/fixtures/*.ts` — the fixtures, one file per category.

## Analytics

Plausible Analytics loads on every page using the site-specific script supplied
for badseo.dev. It provides the cookieless aggregate baseline without changing
the Google Analytics consent choice.

Google Analytics uses measurement ID `G-7MXV9FH7SS`. The small consent script in
`public/analytics.js` is shared by TanStack pages and raw fixture documents. It
does not request Google's tag or set analytics cookies until a visitor accepts.
The visitor can reject analytics or revisit the choice from **Cookie settings**
in the footer. The choice is stored only in the visitor's browser.

## Run it locally

```bash
# from the badseo/ directory
npm run dev                 # serves on http://localhost:8787
```

## Run the end-to-end audit

The harness drives the **real** OpenSEO crawl + issue-detection functions
(imported straight from `../src`) against a running badseo.dev, then asserts every
fixture triggers exactly the issues it declares — and that the homepage,
privacy policy, and support pages come back clean.

```bash
# with `npm run dev` running in another terminal:
npm run audit -- http://localhost:8787
```

It prints a per-page pass/fail matrix and an issue-type coverage line, and exits
non-zero on any mismatch — so it works as a CI gate for the audit engine.

## Add a fixture

Contributions are welcome — a new fixture _is_ a new regression test. Each is a
small object:

```ts
const myFixture: Fixture = {
  path: "/category/my-mistake",
  category: "Content quality",
  name: "My SEO mistake",
  summary: "One-line description shown in the on-page test panel.",
  lesson: "Why it matters / how to fix it.",
  expectedIssues: ["thin-content"], // the audit issue ids this page must trigger
  handler: () =>
    htmlResponse(
      renderPage({
        fixture: myFixture,
        title: "…",
        metaDescription: "…",
        bodyHtml: "…",
      }),
    ),
};
```

Then add it to its category's exported array. `expectedIssues` is type-checked
against the real audit registry, and the harness will hold you to it.

Guidelines:

- **Isolate one issue per page.** A themed page should be healthy in every way
  _except_ the defect it demonstrates, so the audit result is unambiguous. (The
  kitchen-sink page is the deliberate exception.)
- **Keep titles and meta descriptions unique** across the site, or you'll create
  accidental duplicate-title / duplicate-meta groups. The exceptions are the
  intentional duplicate pairs.
- **Keep the copy plain.** Say what the page does and why the mistake matters.
  No hype.

## Deploy

Vite builds the TanStack Start client and Worker bundles, then TypeScript checks
the project before Wrangler deploys it:

```bash
npm run build                          # Vite build + typecheck
npm run deploy                         # build + wrangler deploy → badseo.dev
```

The custom-domain routes for `badseo.dev` and `www.badseo.dev` live in
`wrangler.jsonc`, alongside the TanStack server entry and built asset directory.