All case studies
Case 02, SEO MakerPublished4 min read

Grounding AI recommendations in real page data

The engineering questionHow do you let an AI explain SEO issues and prioritise fixes without inventing problems the site doesn’t have?

System flow in SEO Maker
  1. 01Crawl
  2. 02Store
  3. 03Check with rules
  4. 04Explain with AI
  5. 05Re-crawl
Diagram: crawled page data, rule findings and site context feed an evidence pack with IDs E1 to E4. An LLM behind a provider adapter returns structured JSON that cites those IDs. A validator checks the JSON, the schema, the evidence IDs and the rule IDs, and the answer is either stored and shown or rejected. A re-crawl then confirms the finding is gone. A planned knowledge base separates authoritative and heuristic guidance.
Figure 02The AI layer builds an evidence pack from measured facts, asks for a structured answer and rejects anything that cites a fact it wasn’t given. A re-crawl then checks the fix.

01 Context

The problem

SEO tools are good at listing problems and bad at explaining which ones matter for a particular site. LLMs are the opposite: they explain well, but given a page they will happily invent issues, quote rules that don’t apply and guess at numbers they don’t have. SEO Maker crawls a site, stores the page data and runs deterministic checks. My part is the AI backend: turning those facts into explanations and prioritised fixes that people can trust.

  • Every claim the AI makes must trace back to a measured fact.
  • The AI may explain and prioritise findings, but never create new ones.
  • The model provider must be replaceable without rewriting the system.
  • Recommendations must be checkable afterwards, by crawling again.

02 Design

The architecture and decisions

Deterministic analysis and AI stay separate. The crawler and the rule engine decide what is true about the site. The AI layer receives an evidence pack built from those facts plus the site’s context, such as its goal and key pages, and must answer in a fixed structure that cites the evidence by ID. Nothing reaches the user until that structure and every citation have been checked.

  • Next.js and TypeScript for the app and APIs
  • Crawlee for crawling, with BullMQ and Redis for background jobs
  • A provider adapter in front of the LLM, with Groq used during development
  • Structured outputs validated before they are stored
ChoseRules find the facts, the AI explains them
OverAsking the AI to audit raw HTML
Because

Rules are repeatable and testable. The AI adds judgement and language on top of facts instead of replacing them.

ChoseEvidence IDs the model must cite
OverFree-form explanations
Because

A citation can be checked mechanically. A paragraph of advice can’t.

ChoseA small provider adapter
OverCalling one vendor’s SDK everywhere
Because

Prompts, schemas and evidence stay vendor-neutral, so a cheaper model during development and a stronger one later is a config change.

The only shape the backend accepts (simplified)ts
type Recommendation = {
  page: string;
  ruleIds: RuleId[];          // must come from the rule engine
  priority: "high" | "medium" | "low";
  summary: string;
  why: string;
  evidence: EvidenceId[];     // must exist in this page's pack
  fix?: { title?: string; metaDescription?: string };
  needsData?: string[];       // what it would need, stated, not guessed
};

03 Implementation

The difficult parts

Building the evidence pack

For each page and finding, the backend selects only the facts that matter: the failing values, related pages and the site context. Each fact gets a short ID such as E1 or E2. Keeping the pack small keeps the prompt cheap and gives the model less room to wander.

Validating every answer

A response is parsed as JSON, checked against the schema, and then every evidence ID and rule ID is looked up. If the model cites a fact that isn’t in the pack, the recommendation is rejected and logged, and the user never sees it.

Reject anything that cites missing evidence (simplified)ts
const rec = schema.parse(JSON.parse(raw));
const missing = rec.evidence.filter(id => !pack.has(id));
const unknownRules = rec.ruleIds.filter(id => !findings.has(id));
if (missing.length || unknownRules.length) {
  return reject(rec, { missing, unknownRules });
}
return store(rec);

Prioritising with site context

The same missing meta description matters more on a pricing page than on an old blog post. The site context tells the model which pages drive the business goal, and the model has to explain its ranking with evidence like any other claim.

Saying “I don’t know” on purpose

When a useful answer depends on data the system doesn’t have, such as traffic, the schema has a place to say so. The model states what it would need instead of estimating it.

04 Validation

How I check it

The strongest check isn’t a model score. It’s whether the site actually changes. Around that, the AI layer has checks that must always hold.

  • Every stored recommendation has evidence and rule IDs that resolve, and tests feed in answers that cite missing facts to make sure they are rejected.
  • Saved crawls of sample sites are re-run after prompt or provider changes, to catch regressions in what the AI says.
  • After a fix ships, a re-crawl measures whether the finding is gone, which is the real test of a recommendation.
  • Next step: tracking how many recommendations are accepted and fixed, so quality can be reported with numbers.

05 Reflection

What I learned and what comes next

Keep the AI on a short leash of facts

Most of the quality came from what the model was given and what it was allowed to say, not from prompt wording.

Design for the provider to change

A low-cost provider during development kept iteration cheap, and the adapter means that choice isn’t permanent.

What comes next

A reference knowledge base split into authoritative and heuristic guidance, with retrieval on top, followed by competitive intelligence and generative engine optimisation (GEO) analysis.