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. Thelib/mdx.tsparser keys on these field names — rename one and the page silently drops it.

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 (SoftwareApplicationschema withcreator: Creative Brain Inc.)./services/[slug]× 11 — addedFAQPageJSON-LD plus a visible FAQ block, sourced from a singlelib/service-faqs.tsmap (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 emitsArticle+BreadcrumbList+FAQPageschema./brampton-saas-agency— local pillar page withProfessionalServiceschema,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:
- 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.",
SoftwareApplicationschema names us ascreator. - Service FAQs —
FAQPageis 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). - 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.
- 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.tsxapp/services/[slug]/page.tsx(FAQ JSON-LD added to existing route)app/services/[slug]/ServiceDetailClient.tsx(visible FAQ block added)app/compare/page.tsxapp/compare/[slug]/page.tsxapp/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— typedComparison[]with axes, when-to-choose buckets, verdict, FAQs, and related-page links. One entry per/compare/[slug].
Plumbing (3)
app/sitemap.ts— addedworkPages,compareIndex,comparePages,/brampton-saas-agency,/contact.proxy.ts— added/work(.*),/compare(.*),/brampton-saas-agencytoisPublicRoute.components/sections/entity-intro.tsx— anchor switched fromtradebookcentral.comto/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)
-
OG image +
generateStaticParams+ edge runtime is a runtime crash in Next 16.app/services/[slug]/opengraph-image.tsxhadexport const runtime = 'edge'andgenerateStaticParams. 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 theruntime = 'edge'line. Node runtime is the default and works fine forImageResponse. -
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, restartnpm run devbefore debugging further. -
proxy.ts(notmiddleware.ts). This repo renamed Clerk middleware toproxy.ts. Every public route has to be added toisPublicRouteor it redirects to sign-in. -
Don't fabricate
aggregateRatinginSoftwareApplicationschema. Google's spam policies treat this as deceptive. The/work/trade-book-centralschema 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, andCollectionPagetypes 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-centralpage 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
- Copy this file to a new
content/sprint-logs/<kebab-slug>.mdx. The filename becomes the URL slug. - Fill in all frontmatter fields.
lib/mdx.tskeys on these field names — typos silently drop the value. publishedAtandupdatedAtare ISO date strings (YYYY-MM-DD). Convert relative dates like "yesterday" before saving.- 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.
- Use fenced code blocks with the language tag (
```tsx,```bash) so the syntax highlighter renders. Comments inside MDX use{/* … */}(JSX-style), not<!-- -->. - Drop the file in
content/sprint-logs/. The route, sitemap entry, RSS item, and adjacent-log nav all auto-generate from there. - 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 markdownso you get the captioned brand-blue rail. The.prose-sprint figure+figcaptionCSS handles styling; no per-log overrides needed. Always write a realalt— screen readers and image-search both rely on it.