Fixing SEO Issues With Claude Code: A Practical Workflow
The handoff from audit report to shipped fix is where SEO work dies. How to turn findings into instructions a coding agent can actually execute.
An audit that finds forty issues and ships zero fixes is worth precisely nothing. The scarce resource in technical SEO has never been detection — crawlers have been good at finding missing meta descriptions since roughly 2011. The scarce resource is the trip from "the report says this" to "the deployed HTML no longer says this." Coding agents are unusually good at exactly that trip, and unusually bad at the parts either side of it. This is a workflow for using them where they are strong and stopping them where they are not.
Where the traditional workflow breaks
Trace a single finding through a normal process. A crawler reports that /pricing has a 71-character <title> that duplicates /pricing/enterprise. That row now enters a pipeline:
- Someone exports the report and reads 200 rows, most of which are variations of eight underlying problems.
- They group rows into tickets. The row knew the URL and the exact violated constraint. The ticket says "Fix duplicate title tags on pricing pages."
- The ticket is estimated by a developer who has no idea whether titles come from a CMS field, a shared React component, or a hardcoded string.
- The developer opens the repo, discovers titles are generated by a shared template that also feeds four other route groups, and asks a question in Slack that takes two days to answer.
- The fix ships in the sprint after next, by which point the audit is stale.
Three distinct losses happen here. Translation loss: the report knew the precise defect and precise URL; the ticket retains neither. Context split: the person who understands why duplicate titles matter is not the person who knows where the template lives. Batching cost: a change that takes ninety seconds to make waits behind a sprint boundary, because the overhead of routing it exceeds the work itself.
None of those are knowledge problems. The developer knows how to write a <meta name="description">. What they lack is location — where in this repository does this string come from — and location is a search problem over a codebase. That is what an agent with file access solves in seconds.
The division of labour that actually works
An agent should own: locating the code responsible for a finding, applying a mechanical change in the framework's idiomatic style, and reporting what it touched.
A human should own: deciding what the copy should say, judging whether the change altered meaning, and deciding whether a page should exist at all.
Everything below follows from that split. The prompt exists to give the agent enough precision that the mechanical part is unambiguous, and enough constraint that it does not stray into the judgement part.
What a good fix prompt contains
Four components. Drop any one and the failure mode is predictable.
1. The finding, with evidence and location. Not a category label — the observed fact. GET /pricing returned 200 with no <meta name="description"> in <head> is actionable. "Missing meta description" is a category that the agent will resolve by guessing which pages you meant.
2. The required end state, as an assertion about output. Describe what the rendered HTML must contain, not what code to write. This lets the agent choose the right mechanism for the codebase, and it gives you a check you can run afterwards.
3. The framework-native mechanism. If you skip this, the agent writes whatever pattern dominated its training data — a raw <meta> tag dropped into a component body, or react-helmet in an app that has a first-class metadata API. Naming the mechanism ("use the App Router generateMetadata export") collapses a hundred plausible implementations into one correct one. Framework specifics matter enormously here; see Next.js SEO problems for how much the right answer varies by version.
4. The constraints. The single most important one: do not change any visible copy. An agent asked to fix a heading hierarchy will cheerfully rewrite the heading text while it is in there, because rewriting text is a thing it is good at. Add the boring ones too: no dependency changes, no reformatting of files you did not otherwise edit, no changes to robots.txt or any noindex directive.
Here is the difference in practice.
# Weak
Fix the SEO issues on our pricing page.
# Workable
Finding: GET https://example.com/pricing returns 200 with no
<meta name="description"> element in <head>. Confirmed by curl.
Required end state: the rendered <head> for /pricing contains exactly one
<meta name="description"> whose content is between 120 and 158 characters
and describes the pricing tiers.
Mechanism: this is a Next.js App Router app. Add or extend the exported
`metadata` object in the page's server component. Do not add a raw <meta>
tag to JSX and do not add a metadata library.
Constraints: change no visible page copy. Touch only the files needed for
this finding. Do not modify robots.txt, sitemap generation, or any
existing robots directives.
Report: list each file you changed and the one-line reason.
Three findings, converted
Missing meta description
The prompt above is the whole job for a single page. It gets more interesting at scale, because the honest answer to "300 pages have no description" is usually not "write 300 descriptions."
Finding: 312 of 340 routes under app/docs/ render no meta description.
Required end state: every route under app/docs/ emits exactly one
<meta name="description">. Where the MDX frontmatter has a `summary`
field, use it verbatim. Where it does not, emit no description rather
than generating one — leave those routes for manual authoring and list
them for me at the end.
Mechanism: generateMetadata in app/docs/[...slug]/page.tsx, reading the
same frontmatter the page body already reads. Reuse the existing loader;
do not add a second read of the file.
Constraints: do not invent description text. Do not truncate body copy
into a description.
The "do not invent" clause is doing real work. Descriptions assembled from the first 155 characters of body text are worse than no description at all, because Google will otherwise synthesise a snippet from the query-relevant part of the page — which is frequently better than a truncated intro. A description is worth writing when you have something to say that the body copy does not already say better; otherwise, skipping it is a legitimate choice.
No structured data
This is where agents most need a leash, because structured data is the one SEO change with an actual penalty attached.
Finding: article pages emit no schema.org structured data.
Required end state: each page under app/blog/[slug] contains one
<script type="application/ld+json"> with a BlogPosting object whose
headline, datePublished, dateModified, author.name and description
fields are read from existing post frontmatter.
Mechanism: render the script tag inside the server component using
JSON.stringify on a plain object, and replace every `<` in the result
with \u003c before injecting it. Do not add schema-dts or any JSON-LD package.
Constraints: every field must come from data that already exists in the
frontmatter and is already visible on the rendered page. If a field has
no source, omit the field — do not supply a placeholder, a default date,
or an inferred author. List omitted fields at the end.
That last constraint is the entire point. Structured data describing things not present on the page is a documented Google spam policy violation, and an agent's instinct when a required field is missing is to fill it with something reasonable. "author": {"name": "Admin"} is reasonable and also a lie. The escaping clause is not paranoia either: JSON.stringify does not sanitise, so a post title containing </script> breaks straight out of the tag.
Broken heading hierarchy
The instructive one, because the naive fix breaks the design.
Finding: app/(marketing)/features/page.tsx renders <h1> followed by four
<h3> elements with no intervening <h2>. Section headings skip a level.
Required end state: heading levels descend without gaps. The four section
headings become <h2>. Exactly one <h1> remains on the page.
Mechanism: change the element tag only. These headings are styled by
Tailwind utility classes, not by element selectors — carry the existing
classes across unchanged so rendered size, weight and spacing are
byte-identical.
Constraints: do not change heading text. Do not add, merge or reorder
headings. Do not restyle.
Without the mechanism clause, an agent that swaps <h3> to <h2> in a codebase using typographic base styles has just made four headings visibly larger, and you have shipped a layout regression to fix a semantic one. The heading structure rules article covers what heading order does and does not affect — the short version is that it matters far more for assistive technology and machine parsing than for rankings, which is a reason to fix it cheaply rather than a reason to fix it dramatically.
Make the agent report its own diff
Always end the prompt with a reporting instruction, and ask for the mapping, not just the list:
When done, output a table: | Finding | Files changed | What changed |
How I verified |. One row per finding. If you could not fix a finding,
say so and why, rather than fixing something adjacent.
This is not bookkeeping. It is the artefact that makes review possible in minutes instead of an hour. A diff alone tells you what changed; the table tells you what each change was for, which is the only way to spot the specific failure where an agent fixed something real that you did not ask about and did not evaluate. It also surfaces scope creep immediately: a finding scoped to one route that produced changes in a shared layout is a blast-radius question you want to ask before merge, not after.
The "how I verified" column forces the agent to state a check you can re-run. "Ran curl -s http://localhost:3000/pricing | grep -c 'name=\"description\"' and got 1" is a claim you can falsify in two seconds.
The review discipline
An agent's SEO fix is a code change and needs the review any code change needs, plus three checks specific to this class of work.
| Check | Why | How |
|---|---|---|
| Visible copy unchanged | The most common silent regression: text "improved" while fixing markup | Read every hunk that touches a text node; diff rendered text, not just source |
| Blast radius matches scope | One-page findings fixed in shared components affect every page | git diff --stat; question any shared layout or component in the list |
| No new crawl directives | A one-line noindex or Disallow: / can deindex a site |
git diff filtered for robots, noindex, canonical, Disallow |
The visible-copy check is the one people skip and the one that costs. If an agent rewrote an <h2> from "Pricing" to "Our Pricing Plans" while fixing the heading level, nothing will fail, no test will go red, and you have quietly changed what the page is about. Reading the diff is the control. There is no automated substitute, because "did the meaning change" is exactly the judgement you kept for yourself when you set up this division of labour.
One hard rule worth adopting: never let an agent modify robots.txt or robots meta directives as part of a batch. Those changes have unbounded downside and take one minute to make by hand. Keep them out of scope explicitly, every time — the constraint costs you one line in the prompt. Robots.txt versus meta robots explains why these two mechanisms fail in different directions and why an agent conflating them is a plausible disaster.
Where this workflow does not apply
The workflow is good at mechanical changes with a verifiable output assertion. It is bad — sometimes dangerously bad, because it will comply confidently — at everything else.
Editorial judgement. What a title tag should say is ad copy competing in a results page against nine other results. An agent can draft twenty candidates; a human picks. Do not automate the pick.
Canonical decisions between near-duplicates. Which of two overlapping pages should represent the cluster is a business question about which one you want to rank and which one converts. The agent can implement the decision perfectly and cannot make it. See canonical tag mistakes for the failure modes when this is decided carelessly.
Content strategy and information architecture. What to write, what to consolidate, what to delete, how URLs should be organised. These have redirect consequences and traffic consequences that no amount of file access informs.
Link building and digital PR. Relationship work. Not a code problem.
Anything where the correct fix is "this page should not exist." An agent asked to improve a thin page will improve it. It will not tell you the page should be merged into another one, because you did not ask, and asking would require it to understand your business.
The pattern across all five: the agent optimises what you point it at. Pointing it at the wrong thing produces a well-executed wrong thing, faster than before.
The compile step
The workflow has one manual bottleneck left: turning audit rows into the four-part prompts above. Done by hand it is fifteen minutes of copying URLs and restating findings — better than the sprint-boundary alternative, and still friction.
That step is what SEO Fix Agent's Prompt Studio does. It takes the findings from a site audit and compiles them into a severity-ordered prompt with the finding, required end state, and constraints already written out, ready to paste into Claude Code, Cursor or ChatGPT. The judgement calls above stay yours; the transcription does not need to be.
Audit your page, then ship the fix
SEO Fix Agent runs 30+ technical, content and AI-search checks on a page, then compiles every finding into a severity-ordered prompt your coding agent can execute. 75 free credits, no card.
Start free →