AI Search Optimization, Part 2: Front-Loaded Answers & Drift-Proof Schema

Part 1 was the theory: answer engines retrieve passages, not pages, and they cite whatever chunk most directly answers the query. This post is the hands-on half. It's the actual code I shipped across lans.cloud to make our tool pages quotable — front-loaded answers, drift-proof FAQ and HowTo schema, and honest freshness dates.

None of this is exotic. It's mostly discipline: write the answer where the retriever looks, and never let your visible content and your structured data come from two different places.

Front-load the answer

Here's the tension. Our tool pages lead with the interactive tool — the calculator, the generator, the converter. That's correct for humans: they came to do the thing, not read about it. But it's wrong for retrieval. Perplexity and Google's AI Overviews judge relevance heavily on the opening content, and an interactive widget is mostly <input> and <button> elements with no prose to quote. The actual answer gets buried under the fold, or worse, only exists after a click.

The fix is a compact, highlighted "Quick answer" block that leads the article prose, right after the tool. It's a plain server-rendered component — no client JS — so the text lands in the initial HTML that crawlers and retrievers see:

export function DirectAnswer({ children, label = 'Quick answer' }: DirectAnswerProps) {
  return (
    <div className="border-primary/30 bg-primary/5 mb-8 rounded-xl border p-5 sm:p-6">
      <p className="text-primary mb-1 text-sm font-semibold tracking-wide uppercase">{label}</p>
      <div className="text-foreground text-lg leading-relaxed">{children}</div>
    </div>
  );
}

The component is trivial. The copy inside it is where the work is. The rule I follow: write it so it reads correctly if an AI quotes it alone, stripped of all surrounding context. No "as shown above", no "use the tool below". Here's what sits on the test grade calculator:

Quick answer — 8 questions wrong out of 40 is 80% — a B−. The grade is (total − wrong) ÷ total × 100, so (40 − 8) ÷ 40 × 100 = 80%. Every wrong answer on a 40-question test costs 2.5 percentage points.

That's self-contained. If Perplexity lifts it verbatim, it still makes sense, still names the tool's domain, and still shows the formula. We put these on 11 calculator and converter pages — the ones where a genuine numeric or factual answer exists.

The honest trade-off: the block pushes the tool itself down slightly on mobile, which is real friction for the human who just wants to compute something. So we keep it to 1–3 sentences, and we only add it where a direct answer actually helps. A spin-the-wheel tool doesn't get one; the Roman numeral converter does, because "what is 49 in Roman numerals" has a crisp answer worth quoting (XLIX).

FAQPage schema

If front-loading is the highest-leverage prose change, FAQPage schema is the highest-leverage structured one for GEO. Each question/answer pair is a discrete, self-contained citation candidate — exactly the shape retrievers love. An answer engine can grab one Q&A without needing the rest of the page.

But there's a discipline that trips people up: Google requires the JSON-LD FAQ content to match the visible FAQ on the page. Mismatched schema is a manual-action risk, not just a missed opportunity. The naive implementation — hand-write the visible <details> list, then hand-write a parallel JSON-LD object — is a drift machine. Someone edits an answer for clarity, forgets the schema copy, and now your structured data lies.

So we generate both from one array:

const faqs = [
  { q: 'How do I calculate a test grade from wrong answers?',
    a: 'Subtract the number wrong from the total, divide by the total, and multiply by 100. For 8 wrong out of 40: (40 − 8) ÷ 40 × 100 = 80%.' },
  { q: 'What letter grade is 80%?',
    a: 'On a standard US scale, 80% is a B−. The B range runs 80–89%.' },
];

// Visible FAQ
{faqs.map(({ q, a }) => (
  <details key={q}>
    <summary>{q}</summary>
    <p>{a}</p>
  </details>
))}

// Structured data, same source
const faqLd = {
  '@context': 'https://schema.org',
  '@type': 'FAQPage',
  mainEntity: faqs.map(({ q, a }) => ({
    '@type': 'Question',
    name: q,
    acceptedAnswer: { '@type': 'Answer', text: a },
  })),
};

One faqs array, two consumers. They cannot drift because there is nothing to keep in sync — edit the answer once and both the rendered <details> and the JSON-LD update together.

HowTo schema for procedural tools

Some tools answer a "how do I…" query, and those earn HowTo schema instead of (or alongside) FAQ. The Minecraft circle generator is the canonical case: people search "how to build a circle in Minecraft", and the answer is a sequence of steps.

Same drift-safe pattern — one steps array drives the visible numbered list and the HowTo JSON-LD:

const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'HowTo',
  name: title,
  step: steps.map((step, i) => ({ '@type': 'HowToStep', position: i + 1, name: step.name, text: step.text })),
};

The visible component maps the exact same steps array into an <ol>. Position numbers come from the array index, so they never disagree with what the reader sees. Renumber the list by reordering the array, and the schema renumbers itself.

Freshness dates

Retrieval engines favor content with visible, recent dates — Perplexity especially will surface "updated 3 days ago" content over an undated page on the same query. But there's a trap: fake or hardcoded dates. If you slap dateModified: today on every render, you're lying, and it's the kind of lie that eventually gets caught and discounted.

We derive dates from the tool registry, which carries git-history-backed added and updated fields, and emit them on each tool's WebApplication JSON-LD:

datePublished: tool.added,
dateModified: tool.updated ?? tool.added,

updated falls back to added when a tool has never been revised — so a page that genuinely hasn't changed reports its original date rather than pretending to be fresh. The dates are true because they come from the same registry that governs when we actually touched the page.

The one principle underneath all of it

Notice the shape repeating across all four sections. FAQ visible list and FAQ JSON-LD: one array. HowTo steps and HowTo JSON-LD: one array. Freshness date on the page and in the schema: one registry field.

That's the whole discipline: any time visible content and its structured-data twin come from two places, they will diverge. Not might — will. Someone edits one and forgets the other, and now your schema misrepresents your page, which is worse than having no schema at all. Derive both from a single source and drift becomes structurally impossible, not just something you promise to watch for.

If you want the deeper mechanics of JSON-LD for classic search — rich results, breadcrumb and social cards, the validation workflow — I wrote that up separately in structured data for classic SEO. This post is the AI-answer angle layered on top of it: same schema types, but chosen and written so an answer engine can lift a clean, correct passage without ever seeing the rest of the page.

Next up, Part 3: llms.txt, and what it actually means to publish for agents that read your site the way a program reads an API.