Creative Brain Inc. (this site) · Day 26 · May 26, 2026 · 7 min read

Batch D — Builder Attribution, Service FAQs, Comparison Pages, and the Brampton Pillar

Shipping seven new routes in one batch: Trade Book Central builder-attribution page, FAQPage schema across all 11 service pages, four AI-buyer comparison pages, and a Brampton SaaS Agency pillar. Notes on why these pages exist, how the schema is wired, and what to do next.

By Andy D — Founder, Creative Brain Inc. — Brampton, Ontario

Future authors: this file is the canonical example for content/sprint-logs/*.mdx. Copy the frontmatter shape exactly. The lib/mdx.ts parser keys on these field names — rename one and the page silently drops it.

Trade Book Central dashboard — the SaaS product we built and operate, now wired to the new /work/trade-book-central builder-attribution page on this site.

Hero pattern for Sprint Logs. Drop your screenshot at /public/images/sprint-logs/2026-05-26-aeo-pillar-and-comparison-pages/tbc-image.png, then reference it from this <figure> block. The .prose-sprint CSS scope already styles <figure> + <figcaption> with a hairline brand-blue rail and muted caption text — no per-log styling needed.

TL;DR

We shipped seven new routes in one batch:

  • /work/trade-book-central — first builder-attribution page (SoftwareApplication schema with creator: Creative Brain Inc.).
  • /services/[slug] × 11 — added FAQPage JSON-LD plus a visible FAQ block, sourced from a single lib/service-faqs.ts map (44 Q&A pairs total).
  • /compare + /compare/[slug] × 4 — agency vs freelancer, Sprint vs retainer, no-code vs custom, OpenAI vs Anthropic vs Google. Each emits Article + BreadcrumbList + FAQPage schema.
  • /brampton-saas-agency — local pillar page with ProfessionalService schema, areaServed, and six FAQs.

Wiring: all routes added to app/sitemap.ts and proxy.ts's isPublicRoute matcher. No DB migrations. No new env vars.

Why we shipped this batch

The May 5 audit gave us SEO 64 / AEO 1 (out of 100 each). The May 26 re-audit after Sprints A–C showed SEO 70 / AEO 4 — real movement on technical fixes, but AEO was still close to zero. The diagnostic was simple: AEO doesn't move from schema fixes alone. It moves from citable surfaces — pages with a clear declarative answer that an LLM crawler can quote without inferring anything.

Batch D is four such surfaces:

  1. Builder attribution — most AEO crawls couldn't tie Trade Book Central back to us. The new page is the canonical citation: H1 says "built by Creative Brain Inc.", SoftwareApplication schema names us as creator.
  2. Service FAQsFAQPage is one of the few schemas that still gets rich-result treatment in 2026. Every service page now has 4 FAQs answered in 1–3 sentences each (the citation length LLMs prefer).
  3. Comparison pages — "AI agency vs freelancer" is a real query potential clients run before they email us. If we don't answer it on our domain with our verdict, somebody else's answer gets cited.
  4. Brampton pillar — fixes the local-entity story. We're a Brampton agency; we want exactly one canonical URL Google and the LLM crawlers can land on for that fact.

Files added or modified

New routes (7)

  • app/work/trade-book-central/page.tsx
  • app/services/[slug]/page.tsx (FAQ JSON-LD added to existing route)
  • app/services/[slug]/ServiceDetailClient.tsx (visible FAQ block added)
  • app/compare/page.tsx
  • app/compare/[slug]/page.tsx
  • app/brampton-saas-agency/page.tsx

New data layer (2)

  • lib/service-faqs.ts — single source of truth for service FAQs. The server page reads it for JSON-LD; the client renderer reads it for visible HTML. Google FAQ rich results require the two to match exactly, so we render both from the same {q, a} map.
  • lib/comparisons-data.ts — typed Comparison[] with axes, when-to-choose buckets, verdict, FAQs, and related-page links. One entry per /compare/[slug].

Plumbing (3)

  • app/sitemap.ts — added workPages, compareIndex, comparePages, /brampton-saas-agency, /contact.
  • proxy.ts — added /work(.*), /compare(.*), /brampton-saas-agency to isPublicRoute.
  • components/sections/entity-intro.tsx — anchor switched from tradebookcentral.com to /work/trade-book-central. Internal link, our domain, our schema.

Design rationale (the bits worth copying for future logs)

Single source of truth for FAQ text

lib/service-faqs.ts is the only place service FAQ copy lives. The server page maps it to FAQPage JSON-LD; ServiceDetailClient maps it to a <dl>. We did this because Google's spec says the visible answer text and the schema text field must match — if you fork them into two files, they drift in two weeks and you lose the rich result.

// lib/service-faqs.ts (excerpt)
export const getServiceFAQs = (slug: string): ServiceFAQ[] => serviceFAQs[slug] ?? []
// app/services/[slug]/page.tsx (JSON-LD branch)
const faqs = getServiceFAQs(service.slug)
const faqSchema = faqs.length > 0
  ? { '@type': 'FAQPage', mainEntity: faqs.map(/* ...Question shape... */) }
  : null
// app/services/[slug]/ServiceDetailClient.tsx (visible HTML branch)
{(() => {
  const faqs = getServiceFAQs(service.slug)
  if (faqs.length === 0) return null
  return <dl>{faqs.map(/* ...same q/a... */)}</dl>
})()}

Comparison pages: TLDR up top, axis table for receipts

LLMs cite short declarative paragraphs. The tldr field in lib/comparisons-data.ts is written specifically to be the citation target — 1 to 3 sentences, both options named, decision rule explicit. Everything else on the page (the axis table, the verdict, the FAQs) is for the human who arrives via that citation.

Honest verdicts beat self-serving ones

Each comparison includes a verdict field where we name our bias and then say which option actually wins for the reader. The agency-vs-freelancer page literally tells the reader to hire a freelancer for small scoped tasks. This costs us nothing — that reader was never going to buy a Sprint anyway — and it earns the citation when the broader audience reads "this agency is willing to recommend against itself."

Auto-flip pattern (carried from earlier batches)

/about and /anti-portfolio use a isXReady() helper that returns false while content is empty. The route returns noindex and the sitemap omits it. When content lands in the content module, the same helper flips to true, the page becomes indexable, and the sitemap picks it up. No metadata edit, no sitemap edit, no JSON-LD edit. Future content-driven pages should follow this pattern.

Gotchas we hit (so the next log doesn't repeat them)

  1. OG image + generateStaticParams + edge runtime is a runtime crash in Next 16. app/services/[slug]/opengraph-image.tsx had export const runtime = 'edge' and generateStaticParams. Next 16 disallows the combination — boot fails with an unhandled rejection, and the symptom is that every new top-level route 404s because the manifest never registers. Fix: drop the runtime = 'edge' line. Node runtime is the default and works fine for ImageResponse.

  2. New top-level app/ folders need a dev-server restart. Turbopack's route manifest doesn't always pick up brand-new top-level folders during HMR. If a route 404s and the file definitely exists, restart npm run dev before debugging further.

  3. proxy.ts (not middleware.ts). This repo renamed Clerk middleware to proxy.ts. Every public route has to be added to isPublicRoute or it redirects to sign-in.

  4. Don't fabricate aggregateRating in SoftwareApplication schema. Google's spam policies treat this as deceptive. The /work/trade-book-central schema deliberately omits ratings — we'll add them when we have real ones from a third party (G2, Capterra, App Store).

Verifying the work

npm run dev               # restart if it was already running
# then:
curl -sI http://localhost:3000/work/trade-book-central | head -1   # expect 200
curl -sI http://localhost:3000/compare                              | head -1
curl -sI http://localhost:3000/brampton-saas-agency                 | head -1

Schema validation:

  • Paste rendered HTML into Schema.org Validator.
  • Expect: SoftwareApplication, Article, FAQPage, BreadcrumbList, ProfessionalService, and CollectionPage types to surface depending on which route you check.

What's next

  • Production re-audit. All seven routes need to be re-scored by the same SEO/AEO tools we ran on May 5 and May 26 — but against the production deploy, not localhost, so HSTS, canonical, and trailing-slash artifacts disappear from the report.
  • Comparison page expansion. Four templates is a beachhead; the long tail of buyer-side comparisons ("React vs Svelte for AI products", "Webflow vs Framer", "Supabase vs Firebase for client SaaS") is the actual AEO play.
  • Trade Book Central case-study depth. The current /work/trade-book-central page attributes the builder. The next pass adds a real before/after with metrics — that's what earns the citation in "best trading journal SaaS" queries.

How to use this file as a template

  1. Copy this file to a new content/sprint-logs/<kebab-slug>.mdx. The filename becomes the URL slug.
  2. Fill in all frontmatter fields. lib/mdx.ts keys on these field names — typos silently drop the value.
  3. publishedAt and updatedAt are ISO date strings (YYYY-MM-DD). Convert relative dates like "yesterday" before saving.
  4. Section structure for future logs: TL;DR → Why → Files → Design rationale → Gotchas → Verification → What's next. Keep TL;DR under 6 bullets and Design rationale honest — explain trade-offs, not just decisions.
  5. Use fenced code blocks with the language tag (```tsx, ```bash) so the syntax highlighter renders. Comments inside MDX use {/* … */} (JSX-style), not <!-- -->.
  6. Drop the file in content/sprint-logs/. The route, sitemap entry, RSS item, and adjacent-log nav all auto-generate from there.
  7. Images and screenshots — drop assets at /public/images/sprint-logs/<log-slug>/<descriptive-name>.png (one folder per log keeps the public/ tree tidy as logs accumulate). Embed with the <figure> pattern shown near the top of this file rather than plain markdown ![alt](src) so you get the captioned brand-blue rail. The .prose-sprint figure + figcaption CSS handles styling; no per-log overrides needed. Always write a real alt — screen readers and image-search both rely on it.