Key takeaways

  • Write the positioning and a banned-words list before any keyword. A keyword list without them is a wish list.
  • Screen every keyword against the live results page. The test is whether someone with your business model already ranks, not how hard it looks.
  • Each head term, the main query a page targets, maps to one URL in a CSV, and a script rejects any brief that breaks it.
  • Do the schedule arithmetic before you commit: one, two, then three pages a day is 180 pages in three months.
  • Every page ships with two inbound links and a quotable definition, so Google and AI assistants both find it.

An SEO and AI search strategy is a set of files that decides which pages a site should publish, in what order, and how they link: a positioning statement, a screened keyword list, a one-URL-per-term map, a schedule and a brief per page. Claude Code builds the files. You approve the decisions.

You asked an AI for fifty article ideas and got fifty article ideas. Six of them match pages you already have. Ten target results pages owned by software vendors you will never outrank. None of them has a date, a link plan or a reason to exist.

That is the normal way, and it makes a content calendar, not a strategy. I run the other way: Claude Code builds the strategy as files first, and only then does anything get written. This is the method behind the plan for this site, in eight steps, with the real files and scripts you can copy. The writing pipeline that consumes the plan is a separate post: Claude Code writes my blog.

This post helps you build the structure: what to publish, in what order, and how every page links. Want to build each blog post on top of it? The writing pipeline does that part.

Build each post with the pipeline

Why a keyword list is not a strategy, and what to build instead

A keyword list fails in three places. It does not know who you sell to, so it fills with queries from the wrong buyer. It does not know what you already published, so it proposes duplicates. And it does not know who ranks, so it sends you to results pages that belong to someone else.

The fix is a chain where each file constrains the next one:

  1. positioning.md: who you sell to, the themes that are in or out, the words you never say.
  2. inventory.csv: every page you already have, measured.
  3. keywords-raw.csv: every suggestion from the free expansion, with its seed.
  4. keywords.csv: each one marked PASS or FAIL against the live results page, with a reason.
  5. clusters.csv and url-map.csv: groups of related pages, and one URL per head term.
  6. schedule.csv: every page with a date.
  7. briefs/<slug>.json: one contract per article.
  8. Two inbound links and an indexing call per page.

I packaged the chain as a Claude Code skill, which is a folder with a SKILL.md that loads when the work comes up (the format is documented here). You do not need the skill to copy the method. You need the files.

Five tests per theme
Not a trapBuyerNo platform riskOperators rankTrue in a year
Who to hire: agency, freelancer or in-house
passpasspasspasspass
IN
Meta ads for telehealth
passfailpassfailpass
IN, narrow
Best AI marketing tools
failpasspassfailfail
OUT
Positioning decides the keywords, not the other way round. Three passes out of five and a theme is in.

Step 1: Write the positioning and the banned list

Before any keyword, write down who you sell to and what you will not say. My statement, verbatim: “Solo growth operator helping B2B and expert-led businesses turn marketing into a predictable revenue engine. Full stack. Revenue accountable. One person.”

Then score every candidate theme on five yes or no tests. Three passes and the theme is in:

1. Not a transactional trap: can I explain this, or must I BE the thing being bought?
2. Buyer: is the searcher the person I sell to, or their customer?
3. Platform risk: would a platform, processor or regulator object to a page ranking here?
4. Operators rank: is page one made of businesses like mine, not regulators or one vendor?
5. True in a year: will I still stand behind this answer in twelve months?

On the last run I scored 19 themes. Who to hire (agency, freelancer or in-house) passed all five. Telehealth Meta ads passed three, so it is in, narrowly, as a compliance explainer. “Best AI marketing tools” passed two and is out: a commodity results page owned by vendors and listicles.

Scores are evidence, not orders. Coaching and course creators started as OUT, one pass out of five, because I had no coaching clients and the first screen showed no operators ranking. I rescored it after the homepage named that buyer and live results showed solo marketers ranking: five out of five, in. And “growth operator” scored four out of five and I killed it myself, because I decided to move to “AI marketing”.

The second output of this step is banned_terms.txt, a list of 12 words I never use, loaded as a hard filter by every later brief. The claim rules live in positioning.md and the publish gate enforces them: no price that is not on the vendor’s own page today, no competitor capability claim (“lacks”, “doesn’t support”), and no guarantee wording other than the real offer.

Step 2: Inventory what you already have

The audit comes before the plan. Skip it and you build a page you already own.

Give Claude Code your sitemap and this script. It needs nothing beyond Python’s standard library, and it measures what the plan needs: status, headings, structured data blocks, links out and links in.

import csv, re, sys, urllib.request
from html.parser import HTMLParser

def get(url):
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 inventory-sketch"})
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            return r.status, r.read().decode("utf-8", "replace")
    except Exception:
        return 0, ""

class Page(HTMLParser):
    def __init__(self):
        super().__init__()
        self.h1 = self.h2 = self.schema = 0
        self.links = set()
    def handle_starttag(self, tag, attrs):
        a = dict(attrs)
        if tag == "h1": self.h1 += 1
        if tag == "h2": self.h2 += 1
        if tag == "a" and a.get("href", "").startswith("/"):
            self.links.add(a["href"].split("#")[0].rstrip("/") or "/")
        if tag == "script" and a.get("type") == "application/ld+json": self.schema += 1

_, sitemap = get(sys.argv[1])
urls = re.findall(r"<loc>(.*?)</loc>", sitemap)
rows, inbound = [], {}
for u in urls:
    status, html = get(u)
    p = Page(); p.feed(html)
    for link in p.links: inbound[link] = inbound.get(link, 0) + 1
    rows.append([u, status, p.h1, p.h2, p.schema, len(p.links)])
with open(sys.argv[2], "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["url", "status", "h1", "h2", "schema_blocks", "links_out", "links_in"])
    for r in rows:
        w.writerow(r + [inbound.get("/" + r[0].split("/", 3)[-1].rstrip("/"), 0)])

Run it as python inventory.py https://yoursite.com/sitemap-0.xml inventory.csv. A sitemap index lists other sitemaps, so point it at a child file.

My run found 33 URLs, all returning 200, two orphans (pages nothing links to), eleven case studies with no structured data, and nine pages that skip a heading level. The step also writes cannibalization.csv: every head term that more than one URL targets. Consolidate those before you add anything. Volume on top of duplicates makes the duplicates worse.

One trap. My first inventory script reported zero H1 tags on every blog post. The H1 was there, I checked with curl. The script skipped everything inside <header>, and my site puts the article title in a header. Test your measuring tool on one page you know before you believe its report.

Step 3: Expand the keywords for free

Seeds come from real questions: objections prospects raised on calls, your services, the categories you compete in. I used 40 seeds in six groups: the role, the outcome, the full stack, the expert-led buyer, the objections and the topics I was carrying forward.

Google and Bing both expose their autocomplete over plain HTTP. These endpoints are unofficial and undocumented, so expect them to change and space your requests out.

curl -s "https://suggestqueries.google.com/complete/search?client=firefox&hl=en&gl=us&q=fractional%20cmo"
curl -s "https://api.bing.com/osjson.aspx?query=fractional%20cmo"

Each seed fans out through modifiers, so one seed becomes 44 probes: the bare seed, ten suffixes (cost, vs, best, pricing and so on), seven prefixes (what is, how to, why, how much and so on) and the alphabet a to z. Write every suggestion to keywords-raw.csv with its seed and source, dedupe, then have Claude Code flag junk with a reason: wrong buyer, wrong country, job listings, student definitions.

My run took 40 seeds to 18,753 raw suggestions and 5,712 unique ones. Cost: zero dollars.

Step 4: Pass every keyword through the live results page

This is the step that rejects most of what you found. For each candidate, fetch the top ten results and classify each slot: solo operator, agency, software vendor, forum, publisher, regulator, other.

The rule is not “low difficulty”. It is this:

PASS  Someone with my business model already ranks on page one.
PASS  A forum thread sits in the top three: the query wants a human answer, so a first-person page can win.
FAIL  Page one is vendors, platforms, regulators, schools or affiliate roundups, whatever the volume.
Read the top ten, slot by slot
fractional cmoPASS
123456789
Solo and small operators rank. A page like mine can too.
pipeline generationFAIL
12345678910
Only sales-software vendors. Skip it.
solo operatoragencyforumsoftware vendorother
The gate is not difficulty. It asks whether someone with your business model already ranks.

I screened 78 queries in two rounds. 64 passed and 14 failed. The fails are instructive. “Pipeline generation” is six sales-software vendors and nobody else. “How to sell online courses” belongs to the course platforms, and the honest answer is a sign-up page. “Meta conversions API” is vendor documentation and integration tools.

A table of screened queries with the pattern of who owns page one, the final PASS or FAIL verdict and the reason, including rows where a PASS was reread by hand and flipped to FAIL
The screen as a table. Every row has a verdict and a reason. Where my heuristic was wrong, the reread is on the row.

Do not trust the classifier blindly. I reread 26 verdicts by hand and five flipped. “Best AI marketing tools” passed the heuristic because two domains looked like operators. One was a publisher and the other a training body. The page was vendors and listicles, so it failed.

A FAIL does not delete a page you already own. My Meta conversions API tutorial stays, because it answers an outcome, not the API. The flagship agentic marketing post stays as support, and I plan no new pages there until an operator ranks.

Two limits, stated in the files. Google blocked my fetches behind a JavaScript gate, so I used Brave’s results from a UK address with the country set to the US. That is directional, not a rank measurement. And 18 planned long tails I only read for intent, so they carry the label “PASS, unverified”. Never print a verdict you did not measure.

Step 5: Build the clusters and the URL map

A cluster is a group of pages built around one head term. It holds a main page that answers that query, pages for the narrower questions people ask around it (the long tails: longer, more specific queries), a page that leads to your offer, and a proof page. The pages link to each other so they support one another instead of competing. Every page in the plan should be there for a reason you can state in one sentence.

My plan has 13 clusters. Two get no new pages at all: existing posts I keep and link to but do not extend. Then the constraint that makes the rest safe, url-map.csv:

head_term,url,cluster
fractional cmo,/what-a-fractional-cmo-does-and-costs,fractional-cmo
ai marketing agency,/what-an-ai-marketing-agency-actually-does,ai-marketing
revenue engine,/what-is-a-revenue-engine-in-marketing,revenue-engine
marketing for coaches,/marketing-for-coaches-and-course-creators,coaches-and-courses

One head term, one URL. A short script enforces it:

import csv

def check(head_term, path="url-map.csv"):
    for row in csv.DictReader(open(path, encoding="utf-8")):
        if row["head_term"] == head_term.lower().strip():
            return f"REJECT: already mapped to {row['url']}"
    return "OK"

print(check("revenue engine"))
url-map.csv
fractional cmoto/what-a-fractional-cmo-does-and-costs
ai marketing agencyto/what-an-ai-marketing-agency-actually-does
revenue engineto/what-is-a-revenue-engine-in-marketing
marketing for coachesto/marketing-for-coaches-and-course-creators
new brief: core_keyword = "revenue engine"
REJECT: already mapped to /what-is-a-revenue-engine-in-marketing
One head term, one URL, enforced by a script. The reviewer never has to remember what already exists.

Every brief runs this before anything else. If the head term is taken, the brief is rejected and nobody argues. The article that term maps to, what a revenue engine is, is the proof.

A table with three columns, head term, URL and cluster, listing fourteen real head terms each mapped to its own page
The hard constraint. Fourteen rows from the real map. No head term appears twice.

Page types have bands too, and you should measure yours. A guide that ranks in your space has a typical word count, heading count and link count. Measure three pages that rank where you want to, write the numbers in the rulebook, and have a script enforce them.

Step 6: Schedule with a throttle

The order is fixed. Consolidation fixes first, then the main page of each cluster, then the support pages with the strongest PASS, then the rest. schedule.csv has seven columns: date, slug, cluster, type, keyword and priority.

Then the arithmetic, before you commit to a pace. A new or low-authority domain gets a ramp: one page a day in month one, two in month two, three in month three. That is 30, then 90, then 180 pages cumulative. I use the ramp as a default and label it a judgment, because the point is not to look like a content farm overnight.

The default throttle, cumulative pages
0
Month 11 a day
0
Month 22 a day
0
Month 33 a day
This site's plan: 1 a day, then 2 a day. 59 pages, the last one on 9 December.
Do the arithmetic before you pick a pace. One, two, then three pages a day adds up to 180 pages in three months.

My plan used one a day, then two a day, starting Monday 26 October. It holds 59 new pages and the last is dated 9 December. Before the plan my measured pace was 14 posts in seven weeks, about two a week, which would have stretched the original 48 pages to around April 2027.

The schedule also encodes a weekly rhythm:

  • Monday: review the orphan report from last week and plan the week.
  • Tuesday and Wednesday: publish.
  • Thursday: update two existing winners, and build links to the main page of each cluster and to proof pages only.
  • Friday: check that every sitemap URL returns 200, validate structured data, decide what to prune.

Step 7: Write a brief per page, and build the answer layer in

A brief is one JSON contract per article. The fields that matter most:

{
  "core_keyword": "revenue engine",
  "page_type": "blog",
  "cluster": "revenue-engine",
  "what_top_results_miss": "the domains that rank now and the one thing they all fail to do",
  "definition": "One or two sentences that answer the title and can be quoted alone",
  "takeaways": ["three to five one-line claims"],
  "h2": ["question-shaped headings", "Frequently Asked Questions", "Next step"],
  "prompt_set": ["five questions a person would type into an assistant"],
  "internal_links": [{"url": "/cluster-main-page", "anchor": "varied, never a bare URL", "role": "cluster-main"}],
  "inbound_edits": [{"url": "/related-page", "anchor": "", "where": "closing section"}]
}

what_top_results_miss is the field that decides the quality. A vague answer such as “be more thorough” is not allowed. For the revenue engine brief it says the pages ranking use the term for revenue operations software, none starts where a small business starts with an offer and an ad, and none shows an engine’s numbers end to end. Then it says what my page does instead.

The answer layer is built into the contract, not added later. The definition sits above the body, so an assistant can quote it alone. The prompt_set becomes the FAQ, five buyer objections rather than restated keywords. Google’s documentation on AI features says there are no additional requirements to appear in AI Overviews or AI Mode, and no special markup. So I do not run a second strategy. It is the same page, with the answer stated early.

I keep the FAQ pairs anyway. Google retired the FAQ rich result on 7 May 2026, so I do not count the block as a search feature. It stays because a question with a short answer is the cleanest passage for an assistant to lift. That is a judgment, and I label it as one. The rulebook says the definition sentence is reused word for word on every other surface, a video description or a LinkedIn post, so the claim corroborates itself.

Every brief then faces a publish gate with 20 checks and no override flag. In brief mode it checks the head term against the URL map, the description length, the slug, the definition, the FAQ count and the banned terms. Ten briefs ran through it. All ten passed after one fix, a description at 159 characters.

Publishing runs both ways. Each brief names two existing pages to edit so that they point at the new one, and those edits ship in the same change as the page. That is how you avoid the orphan problem my inventory found.

Then tell the engines, automatically:

  • Google: resubmit the sitemap through the Search Console API. It needs a service account with access to the property. Until you set that up, that half skips and Google finds pages through the sitemap.
  • Everyone else: one IndexNow POST with only the changed URLs. Bing, Yandex and others read it. Google does not take part.

Two things not to do. The old sitemap ping is gone: Google deprecated it in 2023 and code that still calls it does nothing. And the Indexing API is limited to job posting and broadcast event markup, so a blog post does not belong there. The Request indexing button has no API at all. That click stays manual.

Decide what changed from your own frontmatter dates, never from the sitemap’s lastmod. My first version compared lastmod, my CI checkout restamped every file, and the first deploy submitted 26 URLs instead of one.

Then measure on a fixed weekly report: pages published, pages updated, inbound edits shipped and orphans left, index coverage, clicks, impressions and position per cluster, which pages the assistants cite for which question in your prompt_set, and at least one metric that got worse.

What you get

The output is a set of files: positioning, inventory, screened keywords, clusters, a URL map, a dated schedule and a brief per page, plus a rulebook the writing step obeys. For this site that is 13 clusters, 59 new pages and ten briefs that passed the gate, approved on 22 October. The first page was dated for 26 October.

I will not claim rankings from a plan that is days old. What it removes is the avoidable waste: duplicate pages, unwinnable results, orphans and a calendar you cannot defend.

Hand it to Claude Code

Open Claude Code in an empty folder for your site’s plan and paste this.

Read https://rodrigonask.com/claude-code-seo-strategy-for-ai-search.md in full. It describes an eight-step SEO and AI search strategy method.

Run it for my site. First ask me, in one message: my domain, one sentence on what I sell and to whom, my conversion page, my proof pages (case studies, benchmarks), words I never use, and my publishing pace.

Then work in order, writing each file before starting the next, and stop to show me the result of steps 1 and 4:
1. positioning.md with the five yes or no tests per theme and banned_terms.txt.
2. inventory.csv from my sitemap with a standard-library Python script, plus cannibalization.csv.
3. keywords-raw.csv from Google and Bing autocomplete, with delays between requests and a flagged junk column.
4. keywords.csv: classify the top ten results for each candidate. PASS only when a business like mine ranks. Mark anything you could not fetch as UNSCREENED, never as PASS. Reread ambiguous verdicts and write the reason on the row.
5. clusters.csv and url-map.csv, one head term per URL, plus a script that rejects a duplicate head term.
6. schedule.csv with the throttle I chose. Print the arithmetic of pages per month.
7. One JSON brief per page for the first ten pages, each with what the top results miss, a definition, five FAQ questions and two inbound edits.

Use only free sources. Do not publish, send, spend or change my site. Stop at the plan and show it to me.

When not to do this

If your site will hold fewer than 20 pages, skip the clusters and keep the positioning, the live results gate and the URL map. They cost an afternoon and prevent most of the waste.

If you are a local business, add the city to the model. This method has no place for service-plus-city pages.

If you cannot publish at the pace the schedule sets, shrink the plan. A calendar you miss is worse than a short one. And if a paid tool gives you real volume and difficulty, use it, then keep the live results gate anyway. A number does not tell you who owns page one.

The plan is half the system. The review page I use to approve it, the goal wrapper that keeps a long run honest and the context diet that keeps these rules cheap to load are the other parts. The operating model underneath is agentic marketing. If you want this running on your ads, see the service tiers or book a working session with me. Real accounts are in the case studies, and every other build is on the blog.

I break down builds like this one, Claude Code skills, content pipelines and Meta ads with AI, inside my community, Claude For Marketing.

Join Claude For MarketingWork with me 1-on-1

Do I need a paid SEO tool to run this?

No. Every step here runs on free autocomplete endpoints, live results pages and your own sitemap. What you give up is search volume and difficulty, so the order of pages inside a topic cluster is judgment until Search Console shows impressions. If you already pay for a keyword tool, the same steps run with its numbers added.

Is optimizing for AI answers different from SEO?

Google’s documentation says there are no extra requirements to appear in its AI features and no special markup to add. So I do not run a second strategy. I keep one page per question and put a quotable definition at the top. Other assistants pick passages the same way: a clear answer, stated early, on a page that earns links.

How many pages should the plan hold?

As many as you can publish well, and say the arithmetic out loud. One page a day for a month is 30. One, then two, then three a day is 180 in three months. My plan holds 59 new pages across 13 clusters, at one then two a day. A 20 page plan with every page earning its place beats 200 thin ones.

Does this guarantee rankings?

No, and nothing honest does. The method removes the avoidable failures: targeting results pages you cannot win, two pages competing for one query, and pages nothing links to. Whether a page ranks still depends on the page and the competition. The plan stops you wasting a month finding that out one article at a time.

How do I get a new page into Google fast?

Resubmit the sitemap through the Search Console API when a page goes live, and make sure two existing pages link to it. IndexNow reaches Bing and others but not Google. The Request indexing button in Search Console has no API, so that one stays a manual click, and Google limits how many you can send in a day.