fix(landing): rename /comparison route to /comparisons for naming consistency (#5651)

Aligns with the /integrations/[slug] convention (plural category, singular
item). Adds a 301 redirect (both the bare hub page and every competitor
detail page) so previously indexed URLs keep working, and updates every
internal reference: footer nav, sitemap generation, and all import
specifiers/canonical URLs/breadcrumb JSON-LD within the route itself.
This commit is contained in:
Waleed
2026-07-13 16:54:06 -07:00
committed by GitHub
parent 7bcae15154
commit 5b5dd197e2
19 changed files with 47 additions and 28 deletions
@@ -4,10 +4,10 @@ import type { CompetitorProfile } from '@/lib/compare/data'
import { simProfile } from '@/lib/compare/data'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import { COMPARISON_SECTIONS, getFactGroup } from '@/app/(landing)/comparison/comparison-sections'
import { BrandIconTile, SimIconTile } from '@/app/(landing)/comparison/components/brand-icon-tile'
import { ComparisonCards } from '@/app/(landing)/comparison/components/comparison-cards'
import { ComparisonTable } from '@/app/(landing)/comparison/components/comparison-table'
import { COMPARISON_SECTIONS, getFactGroup } from '@/app/(landing)/comparisons/comparison-sections'
import { BrandIconTile, SimIconTile } from '@/app/(landing)/comparisons/components/brand-icon-tile'
import { ComparisonCards } from '@/app/(landing)/comparisons/components/comparison-cards'
import { ComparisonTable } from '@/app/(landing)/comparisons/components/comparison-table'
import {
ALL_COMPETITORS,
buildBottomLine,
@@ -15,7 +15,7 @@ import {
getCompetitorBySlug,
getLatestVerifiedDate,
SIM_LATEST_VERIFIED,
} from '@/app/(landing)/comparison/utils'
} from '@/app/(landing)/comparisons/utils'
import { BackLink } from '@/app/(landing)/components'
import { Cta } from '@/app/(landing)/components/cta/cta'
import { JsonLd } from '@/app/(landing)/components/json-ld'
@@ -57,7 +57,7 @@ export async function generateMetadata({
return buildLandingMetadata({
title: `Sim vs ${competitor.name} | Sim, the AI Workspace`,
description: `Compare Sim, the open-source AI workspace, to ${competitor.name} on platform, AI, integrations, pricing, security, and support. Sourced and dated facts.`,
path: `/comparison/${competitor.id}`,
path: `/comparisons/${competitor.id}`,
keywords: [
`Sim vs ${competitor.name}`,
`${competitor.name} alternative`,
@@ -91,12 +91,12 @@ export default async function ComparisonProviderPage({
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'Home', item: baseUrl },
{ '@type': 'ListItem', position: 2, name: 'Comparison', item: `${baseUrl}/comparison` },
{ '@type': 'ListItem', position: 2, name: 'Comparisons', item: `${baseUrl}/comparisons` },
{
'@type': 'ListItem',
position: 3,
name: `Sim vs ${competitor.name}`,
item: `${baseUrl}/comparison/${competitor.id}`,
item: `${baseUrl}/comparisons/${competitor.id}`,
},
],
}
@@ -110,7 +110,7 @@ export default async function ComparisonProviderPage({
'@type': 'ItemList',
name: `Sim vs ${competitor.name}`,
description: `Feature and pricing comparison between Sim and ${competitor.name}.`,
url: `${baseUrl}/comparison/${competitor.id}`,
url: `${baseUrl}/comparisons/${competitor.id}`,
dateModified: latestVerified.toISOString().slice(0, 10),
numberOfItems: 2,
itemListElement: [
@@ -163,7 +163,7 @@ export default async function ComparisonProviderPage({
<main id='main-content' className='bg-[var(--bg)]'>
<div className='mx-auto w-full max-w-[1446px] px-12 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
<div className='mb-6'>
<BackLink href='/comparison' label='Back to comparisons' />
<BackLink href='/comparisons' label='Back to comparisons' />
</div>
<div className='flex flex-col gap-4'>
@@ -1,5 +1,5 @@
import type { FactSource } from '@/lib/compare/data'
import { SourceLink } from '@/app/(landing)/comparison/components/source-info'
import { SourceLink } from '@/app/(landing)/comparisons/components/source-info'
interface ComparisonCardItem {
title: string
@@ -1,9 +1,9 @@
import type { ReactNode } from 'react'
import { cn } from '@sim/emcn'
import type { CompetitorProfile } from '@/lib/compare/data'
import { COMPARISON_SECTIONS, getFactGroup } from '@/app/(landing)/comparison/comparison-sections'
import { BrandIconTile, SimIconTile } from '@/app/(landing)/comparison/components/brand-icon-tile'
import { FactValue } from '@/app/(landing)/comparison/components/fact-value'
import { COMPARISON_SECTIONS, getFactGroup } from '@/app/(landing)/comparisons/comparison-sections'
import { BrandIconTile, SimIconTile } from '@/app/(landing)/comparisons/components/brand-icon-tile'
import { FactValue } from '@/app/(landing)/comparisons/components/fact-value'
export interface ComparisonTableProps {
sim: CompetitorProfile
@@ -1,7 +1,7 @@
import { Check, X } from '@sim/emcn/icons'
import type { Fact } from '@/lib/compare/data'
import { SourceLink } from '@/app/(landing)/comparison/components/source-info'
import { parseFactValue } from '@/app/(landing)/comparison/fact-status'
import { SourceLink } from '@/app/(landing)/comparisons/components/source-info'
import { parseFactValue } from '@/app/(landing)/comparisons/fact-status'
export interface FactValueProps {
fact: Fact
@@ -18,7 +18,7 @@ export default function ComparisonNotFound() {
<p className='text-[var(--text-muted)] text-lg'>
The comparison you&apos;re looking for doesn&apos;t exist or has been moved.
</p>
<ChipLink variant='primary' href='/comparison' className='mt-3'>
<ChipLink variant='primary' href='/comparisons' className='mt-3'>
Browse comparisons
</ChipLink>
</main>
@@ -3,8 +3,8 @@ import Link from 'next/link'
import { simProfile } from '@/lib/compare/data'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import { BrandIconTile } from '@/app/(landing)/comparison/components/brand-icon-tile'
import { ALL_COMPETITORS, ensurePeriod, lowercaseFirst } from '@/app/(landing)/comparison/utils'
import { BrandIconTile } from '@/app/(landing)/comparisons/components/brand-icon-tile'
import { ALL_COMPETITORS, ensurePeriod, lowercaseFirst } from '@/app/(landing)/comparisons/utils'
import { ChevronArrow } from '@/app/(landing)/components/chevron-arrow'
import { JsonLd } from '@/app/(landing)/components/json-ld'
import { LandingFAQ } from '@/app/(landing)/components/landing-faq'
@@ -47,7 +47,7 @@ export const metadata: Metadata = buildLandingMetadata({
title: 'Sim Comparisons | Sim, the AI Workspace',
description:
'Compare Sim, the open-source AI workspace, to n8n, Zapier, Make, and other workflow automation and AI agent platforms. Sourced, dated, fact-checked.',
path: '/comparison',
path: '/comparisons',
keywords: [
'Sim comparison',
'Sim vs n8n',
@@ -65,7 +65,7 @@ export default function ComparisonHubPage() {
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'Home', item: baseUrl },
{ '@type': 'ListItem', position: 2, name: 'Comparison', item: `${baseUrl}/comparison` },
{ '@type': 'ListItem', position: 2, name: 'Comparisons', item: `${baseUrl}/comparisons` },
],
}
@@ -74,12 +74,12 @@ export default function ComparisonHubPage() {
'@type': 'ItemList',
name: 'Sim Comparisons',
description: 'Directory of Sim comparison pages against AI agent and workflow platforms.',
url: `${baseUrl}/comparison`,
url: `${baseUrl}/comparisons`,
numberOfItems: ALL_COMPETITORS.length,
itemListElement: ALL_COMPETITORS.map((competitor, index) => ({
'@type': 'ListItem',
position: index + 1,
url: `${baseUrl}/comparison/${competitor.id}`,
url: `${baseUrl}/comparisons/${competitor.id}`,
name: `Sim vs ${competitor.name}`,
})),
}
@@ -144,7 +144,7 @@ export default function ComparisonHubPage() {
return (
<div key={competitor.id}>
<Link
href={`/comparison/${competitor.id}`}
href={`/comparisons/${competitor.id}`}
className='group/link flex items-center gap-4 px-6 py-4 transition-colors hover-hover:bg-[var(--surface-hover)]'
aria-label={`Sim vs ${competitor.name} comparison`}
>
@@ -41,7 +41,7 @@ const PRODUCT_LINKS: FooterItem[] = [
const RESOURCES_LINKS: FooterItem[] = [
{ label: 'Blog', href: '/blog' },
{ label: 'Docs', href: 'https://docs.sim.ai', external: true },
{ label: 'Compare', href: '/comparison' },
{ label: 'Compare', href: '/comparisons' },
{ label: 'Careers', href: '/careers' },
{ label: 'Changelog', href: '/changelog' },
{ label: 'Contact', href: '/contact' },
+3 -3
View File
@@ -9,7 +9,7 @@ import {
ALL_COMPETITORS,
getLatestVerifiedDate,
SIM_LATEST_VERIFIED,
} from '@/app/(landing)/comparison/utils'
} from '@/app/(landing)/comparisons/utils'
import { ALL_CATALOG_MODELS, MODEL_PROVIDERS_WITH_CATALOGS } from '@/app/(landing)/models/utils'
/** One sitemap entry per author, timestamped by their most recently updated post. */
@@ -172,9 +172,9 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
: SIM_LATEST_VERIFIED
const comparisonPages: MetadataRoute.Sitemap = [
{ url: `${baseUrl}/comparison`, lastModified: comparisonLastModified },
{ url: `${baseUrl}/comparisons`, lastModified: comparisonLastModified },
...ALL_COMPETITORS.map((competitor) => ({
url: `${baseUrl}/comparison/${competitor.id}`,
url: `${baseUrl}/comparisons/${competitor.id}`,
lastModified: competitorLastModified(competitor),
})),
]
+19
View File
@@ -455,6 +455,25 @@ const nextConfig: NextConfig = {
})
}
/**
* The comparison route was renamed from `/comparison` to `/comparisons`
* for naming consistency with `/integrations/[slug]` (plural category,
* singular item). Preserve previously indexed URLs for the hub page and
* every competitor detail page.
*/
redirects.push(
{
source: '/comparison',
destination: '/comparisons',
permanent: true,
},
{
source: '/comparison/:path*',
destination: '/comparisons/:path*',
permanent: true,
}
)
return redirects
},
async rewrites() {