Skip to content

Commit a68fc67

Browse files
committed
improvement(comparisons): keep the hero summary, section ids, and better-than question in the page's FAQ data
Restores the sr-only hero summary for pages that lead with a question, gives every new section an id, includes the 'Is Sim better than' answer in the page's question list so FAQ data matches the page, and asserts every answer and at-a-glance value in the landing SEO E2E.
1 parent 919e975 commit a68fc67

6 files changed

Lines changed: 47 additions & 15 deletions

File tree

‎apps/sim/app/(landing)/comparisons/[provider]/page.tsx‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { QuestionSections } from '@/app/(landing)/comparisons/components/questio
1818
import { getLibraryPostsForCompetitor } from '@/app/(landing)/comparisons/library-links'
1919
import {
2020
ALL_COMPETITORS,
21+
betterThanQuestion,
2122
buildBottomLine,
2223
buildComparisonFaqs,
2324
getCompetitorBySlug,
@@ -200,7 +201,11 @@ export default async function ComparisonProviderPage({
200201
Sim vs {competitor.name}
201202
</h1>
202203
{competitor.leadQuestion && competitor.leadAnswer ? (
203-
<section aria-labelledby='lead-question-heading' className='flex flex-col gap-3'>
204+
<section
205+
id='lead-question'
206+
aria-labelledby='lead-question-heading'
207+
className='flex flex-col gap-3'
208+
>
204209
<h2
205210
id='lead-question-heading'
206211
className='max-w-[720px] text-balance text-[var(--text-primary)] text-xl leading-[120%] tracking-[-0.02em] lg:text-2xl'
@@ -228,14 +233,14 @@ export default async function ComparisonProviderPage({
228233
pricing, security, and support. Every fact below is sourced and dated, last
229234
verified {verifiedDate}.
230235
</p>
231-
<p className='sr-only'>
232-
Sim is an open-source AI workspace for building, deploying, and managing AI
233-
agents. This page compares Sim to {competitor.name} across platform architecture,
234-
AI capabilities, integrations, pricing, security and compliance, observability,
235-
and support, using sourced, dated facts for buyers evaluating both platforms.
236-
</p>
237236
</>
238237
)}
238+
<p className='sr-only'>
239+
Sim is an open-source AI workspace for building, deploying, and managing AI agents.
240+
This page compares Sim to {competitor.name} across platform architecture, AI
241+
capabilities, integrations, pricing, security and compliance, observability, and
242+
support, using sourced, dated facts for buyers evaluating both platforms.
243+
</p>
239244
</div>
240245
</div>
241246

@@ -256,7 +261,7 @@ export default async function ComparisonProviderPage({
256261
id='better-than-heading'
257262
className='mb-4 text-[var(--text-primary)] text-xl leading-[100%] tracking-[-0.02em] lg:text-2xl'
258263
>
259-
Is Sim better than {competitor.name}?
264+
{betterThanQuestion(competitor)}
260265
</h2>
261266
<p className='max-w-[720px] text-[var(--text-body)] text-small leading-[150%]'>
262267
<ProseText prose={competitor.betterThanAnswer} />

‎apps/sim/app/(landing)/comparisons/components/glance-table/glance-table.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const STACKED_LABEL_CLASS = 'block font-medium text-[var(--text-muted)] text-cap
2121
export function GlanceTable({ rows, competitorName }: GlanceTableProps) {
2222
const heading = `Sim vs ${competitorName}: at a glance`
2323
return (
24-
<section aria-labelledby='glance-heading' className='px-6 py-10'>
24+
<section id='glance' aria-labelledby='glance-heading' className='px-6 py-10'>
2525
<h2
2626
id='glance-heading'
2727
className='mb-4 text-[var(--text-primary)] text-xl leading-[100%] tracking-[-0.02em] lg:text-2xl'

‎apps/sim/app/(landing)/comparisons/components/question-sections/question-sections.tsx‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ProseText } from '@/app/(landing)/comparisons/components/prose-text'
44

55
interface QuestionSectionsProps {
66
questions: ComparisonQuestion[]
7-
/** Prefix for each section's heading id, unique on the page. */
7+
/** Prefix for each section's id, unique on the page. */
88
idPrefix: string
99
}
1010

@@ -15,10 +15,11 @@ interface QuestionSectionsProps {
1515
*/
1616
export function QuestionSections({ questions, idPrefix }: QuestionSectionsProps) {
1717
return questions.map(({ question, answer }, index) => {
18-
const headingId = `${idPrefix}-${index}-heading`
18+
const sectionId = `${idPrefix}-${index}`
19+
const headingId = `${sectionId}-heading`
1920
return (
2021
<Fragment key={question}>
21-
<section aria-labelledby={headingId} className='px-6 py-10'>
22+
<section id={sectionId} aria-labelledby={headingId} className='px-6 py-10'>
2223
<h2
2324
id={headingId}
2425
className='mb-4 text-[var(--text-primary)] text-xl leading-[100%] tracking-[-0.02em] lg:text-2xl'

‎apps/sim/app/(landing)/comparisons/utils.ts‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,22 @@ export function buildComparisonFaqs(competitor: CompetitorProfile): ComparisonFa
178178
return faqs
179179
}
180180

181+
/** Heading of the section answering {@link CompetitorProfile.betterThanAnswer}. */
182+
export function betterThanQuestion(competitor: CompetitorProfile): string {
183+
return `Is Sim better than ${competitor.name}?`
184+
}
185+
181186
/**
182187
* Every buyer question the page answers in its own section, in page order: the lead question,
183-
* then the intro, glance, and closing questions.
188+
* the "better than" question, then the intro, glance, and closing questions.
184189
*/
185190
export function getQuestionSections(competitor: CompetitorProfile): ComparisonQuestion[] {
186-
const { leadQuestion, leadAnswer, questions } = competitor
191+
const { leadQuestion, leadAnswer, betterThanAnswer, questions } = competitor
187192
return [
188193
...(leadQuestion && leadAnswer ? [{ question: leadQuestion, answer: leadAnswer }] : []),
194+
...(betterThanAnswer
195+
? [{ question: betterThanQuestion(competitor), answer: betterThanAnswer }]
196+
: []),
189197
...(questions?.intro ?? []),
190198
...(questions?.glance ?? []),
191199
...(questions?.closing ?? []),

‎apps/sim/app/llms-full.txt/route.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ function comparisonMarkdown(competitor: CompetitorProfile): string {
100100
`URL: ${toSiteUrl(`/comparisons/${competitor.id}`)} · Facts last verified ${isoDate(verified)}`,
101101
`${competitor.name}: ${competitor.oneLiner}`,
102102
competitor.leadAnswer && !competitor.leadQuestion ? proseToMarkdown(competitor.leadAnswer) : '',
103-
competitor.betterThanAnswer ? proseToMarkdown(competitor.betterThanAnswer) : '',
104103
...getQuestionSections(competitor).map(
105104
({ question, answer }) => `**${question}**\n\n${proseToMarkdown(answer)}`
106105
),

‎apps/sim/scripts/test-landing-seo-e2e.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
n8nProfile,
1818
openaiAgentkitProfile,
1919
openClawProfile,
20+
type Prose,
2021
pipedreamProfile,
2122
vellumProfile,
2223
} from '@/lib/compare/data'
@@ -510,6 +511,24 @@ async function run() {
510511
assert(text.includes(`${row.label}</th>`), `at-a-glance row missing: ${row.label}`)
511512
}
512513

514+
const visibleText = decodeHtml(
515+
body
516+
.replace(/<script\b[\s\S]*?<\/script>/g, '')
517+
.replace(/<!--[\s\S]*?-->/g, '')
518+
.replace(/<[^>]+>/g, '')
519+
)
520+
const proseText = (prose: Prose) =>
521+
prose.map((part) => (typeof part === 'string' ? part : part.text)).join('')
522+
const expectedProse = [
523+
...(profile.leadAnswer ? [profile.leadAnswer] : []),
524+
...[...intro, ...glance, ...closing].map((q) => q.answer),
525+
...(profile.glance ?? []).flatMap((row) => [row.sim, row.competitor]),
526+
]
527+
for (const prose of expectedProse) {
528+
const expected = proseText(prose)
529+
assert(visibleText.includes(expected), `page text missing: ${expected.slice(0, 80)}`)
530+
}
531+
513532
const relatedStart = text.indexOf('Related reading</h2>')
514533
const relatedEnd = text.indexOf('More comparisons</h2>')
515534
assert(relatedStart >= 0 && relatedEnd > relatedStart, 'related reading section missing')

0 commit comments

Comments
 (0)