# Run an SEO Audit on an Astro Site

Yes. An agent builds your Astro site in a sandbox, crawls the output for titles, canonicals, robots rules and structured data, and maps each issue to its file.

[Run this on Rush](https://cloud.getrush.ai/run/seo-audit/astro/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own sandbox, installs with the lockfile, and builds the site, because an SEO audit of an Astro project is an audit of the HTML the build emits, not of the source alone. In a site built on arthelokyo/astrowind, `astro.config.ts` sets `output: 'static'` with the `sitemap`, `mdx`, `icon` and `compress` integrations, `.nvmrc` pins Node 22, and the `build` script is `astro build`. The agent runs `npm ci`, `npm run build`, serves `dist/` with `astro preview`, and crawls every page the sitemap lists, keeping the response for each.

Then it reads where the metadata comes from, so each finding points at a file and not at a URL. In AstroWind that is `src/config.yaml`, which sets `site`, `base`, `trailingSlash: false`, the default `title` template `%s — AstroWind`, the description, `robots.index` and `follow`, the Open Graph image at 1200 by 628 and the Twitter card type; `src/components/common/Metadata.astro`, which merges those defaults with each page's props and renders them through `astro-seo`, computing the canonical from `getCanonical` in `src/utils/permalinks.ts`; and `src/layouts/Layout.astro`, which sets `<html lang>` from the `i18n` block and places `Metadata`, `SiteVerification`, `Analytics` and the `ClientRouter` in the head. The blog post page under `src/pages/[...blog]/index.astro` adds `StructuredData` and `Breadcrumbs`, and `src/pages/rss.xml.ts` builds the feed from the content collection.

The crawl checks each page against what those files promise. It compares every `<title>` and description for length and duplication across pages, confirms the canonical matches the final URL including the `trailingSlash` rule, checks that `robots` meta on tag pages reflects `index: false` from the `blog.tag` block of `config.yaml` while category pages stay indexable, reads `public/robots.txt`, which allows everything and names no sitemap, and verifies `sitemap-index.xml` lists the pages that exist and none that return 404. It validates the JSON-LD the blog pages emit, checks every image for `alt` and the Open Graph image for an absolute URL, and reads the `<html lang>` value. Headings are checked for one `h1` per page, and internal links for redirects and dead targets.

What it finds is written against the source. A description over the length that search results show is traced to the `metadata.description` string in `config.yaml` or to the page's own `metadata` prop; a missing `Sitemap:` line to `public/robots.txt`; a tag page that is indexable when the config says otherwise to `blogPostRobots` in `src/utils/blog.ts`. It changes no file and opens no pull request.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Audit this Astro site for technical SEO and write seo-audit.md. Install with the lockfile, run the production build, serve the output and crawl every page the generated sitemap lists, keeping each response. Read the site config, the metadata component, the layouts and the robots and sitemap sources so every finding names the file and line that produces it. Check titles and descriptions for length and duplicates, canonical URLs against the trailing-slash rule, robots meta and robots.txt, the sitemap for missing or dead entries, structured data validity, Open Graph and Twitter tags with absolute image URLs, html lang, one h1 per page, image alt text, and internal links that redirect or 404. For each issue write the pages affected, the file and line, and the fix, ordered by impact. Do not invent issues the crawl did not show. Change no code and open no pull request.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

`seo-audit.md` comes back as an artifact on the session, with a download link good for an hour that reading the session again renews. It opens with how the site was built and crawled: the Node version, the install and build commands, the number of pages the sitemap listed and the number crawled, so the reader knows the scope. Then come the findings, ordered by impact, each with the pages affected, the `file:line` that renders the tag, the value the crawl saw, and the fix.

A second section lists what was checked and found correct, with the file that makes it so, such as canonicals that match the `trailingSlash: false` rule from `config.yaml` or the JSON-LD on blog posts that validated. A third lists the checks that need a live domain and were not run, such as crawl budget, Core Web Vitals field data, or whether the deployed `robots.txt` differs from the one in `public/`.

A reviewer opens the file beside the report, confirms the line produces the tag the crawl saw, and fixes the ones that matter. The clone is clean: no commit, no pull request, no edited file. The transcript keeps the build output and every crawled response.

## Run it on every weekly build

In the console, add an automation to the Project with this prompt and a `schedule` trigger, with `cron` set to a weekly hour in your `timezone`; each run builds the current default branch and returns a fresh `seo-audit.md` as its artifact. A GitHub trigger on `pull_request.opened` does the same per pull request so a change to `config.yaml` or a layout gets audited before it merges, with the event added after the prompt.

From your own server, `POST /o/{org}/sessions` with this prompt and the repository in `resources` starts one on demand, and a `webhook_url` on the key POSTs `run.completed` with the `session_url` to fetch the report from.

## Why run it on Rush?

An SEO checklist applied to source files guesses what the build will emit; Astro's metadata goes through a config file, a merge in a component and a layout before it becomes a tag. The sandbox builds the site, serves it and crawls the real HTML, so every finding is something a search engine would see, and every finding names the line that caused it.

The result is a file with citations, not a tool's dashboard. A developer fixes `config.yaml` or `Metadata.astro` with the report open, and the next scheduled run shows the finding gone. Nothing in the repository changes unless someone asks for a pull request in a follow-up.

## Frequently asked questions

### Will it edit the metadata or open a pull request?

No. The prompt forbids changing code, and the report is a session artifact. If you want fixes, reply to the same session with a follow-up asking for a pull request, and the agent applies the report's fixes in the same sandbox with the build still there.

### Does it need my production domain?

No. The crawl runs against the build served in the sandbox, with the `site` value from the config used for canonicals and the sitemap. Checks that only a live domain can answer, such as field performance data or what the deployed `robots.txt` serves, are listed as not run.

### My site uses server rendering or a hybrid output. Does the crawl still work?

Yes. The agent reads `output` in `astro.config` and, for `server` or hybrid, starts the adapter's server in the sandbox and crawls the same routes. The `sitemap` integration still emits the list of static routes; dynamic routes are crawled by following internal links.

### Does it check the RSS feed and structured data?

Yes. It requests `rss.xml`, checks it parses and that its links resolve, and validates every JSON-LD block the pages emit. In AstroWind the feed comes from `src/pages/rss.xml.ts` and the structured data from the `StructuredData` component on blog posts, and the report names both.
