Skip to content

Commit 349d2f3

Browse files
committed
docs: programmatic compare landing pages, brand avatars, SEO cleanup
- Add /alternatives and /switch-from routes with shared ComparisonPageView - Programmatic titles/descriptions in programmatic-comparison-seo.ts; sitemap entries - Competitor Simple Icons avatars, Amplitude mark, Rybbit frog asset; align brand hex in comparison-config - Remove brand color from price/titles in cards; SKILL note on marketing copy tone
1 parent c5a164c commit 349d2f3

18 files changed

Lines changed: 1000 additions & 228 deletions

File tree

.agents/skills/databuddy/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ Read [codebase-map.md](./references/codebase-map.md) when you need deeper routin
118118
- **`applyAuthWideEvent`** in `apps/api/src/index.ts` runs a session DB lookup on every request including anonymous `/public/` routes. Skip it for public endpoints via URL check in `onBeforeHandle`.
119119
- **Flags API local dev** requires `dotenv -e .env` from repo root to pick up `REDIS_URL`, `DATABASE_URL`, etc.
120120
- **Detail page stats**: Use compact inline `flex` bars at `min-h-10`/`py-2.5` (40px) — not `<dl>` grids with large padding. Heights must be multiples of 10px to align with sidebar item sizing. Status uses a colored dot + text, not `Badge`.
121+
- **`apps/docs` marketing copy:** Do not explain pages as “keyword-focused,” “programmatic,” “intent,” or “meta” in UI—users care about tasks (compare tools, replace X, migrate). Keep internal SEO rationale out of hero and body copy.
121122

122123
## Search Hints
123124

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import type { Metadata } from "next";
2+
import { notFound } from "next/navigation";
3+
import { SITE_URL } from "@/app/util/constants";
4+
import { ComparisonPageView } from "@/components/compare/comparison-page-view";
5+
import {
6+
getAllCompetitorSlugs,
7+
getComparisonData,
8+
} from "@/lib/comparison-config";
9+
import {
10+
getProgrammaticComparisonSeo,
11+
getProgrammaticIntroText,
12+
} from "@/lib/programmatic-comparison-seo";
13+
14+
interface PageProps {
15+
params: Promise<{
16+
slug: string;
17+
}>;
18+
}
19+
20+
export function generateStaticParams() {
21+
return getAllCompetitorSlugs().map((slug) => ({ slug }));
22+
}
23+
24+
export async function generateMetadata({
25+
params,
26+
}: PageProps): Promise<Metadata> {
27+
const { slug } = await params;
28+
const data = getComparisonData(slug);
29+
30+
if (!data) {
31+
return { title: "Not Found | Databuddy" };
32+
}
33+
34+
const { title, description } = getProgrammaticComparisonSeo("alternative", data);
35+
const pageUrl = `${SITE_URL}/alternatives/${slug}`;
36+
37+
return {
38+
title,
39+
description,
40+
openGraph: {
41+
title,
42+
description,
43+
url: pageUrl,
44+
},
45+
alternates: { canonical: pageUrl },
46+
};
47+
}
48+
49+
export default async function AlternativeToPage({ params }: PageProps) {
50+
const { slug } = await params;
51+
const data = getComparisonData(slug);
52+
53+
if (!data) {
54+
notFound();
55+
}
56+
57+
const { competitor, features, hero, faqs, pricingTiers, migrationSection } =
58+
data;
59+
const featuresWin = features.filter(
60+
(f) => f.databuddy && !f.competitor,
61+
).length;
62+
63+
const { title, description } = getProgrammaticComparisonSeo("alternative", data);
64+
const pageUrl = `${SITE_URL}/alternatives/${slug}`;
65+
const introText = getProgrammaticIntroText("alternative", data);
66+
67+
return (
68+
<ComparisonPageView
69+
breadcrumbTrail={[
70+
{ name: "Home", url: `${SITE_URL}/` },
71+
{ name: "Alternatives", url: `${SITE_URL}/alternatives` },
72+
{
73+
name: competitor.name,
74+
url: pageUrl,
75+
},
76+
]}
77+
competitor={competitor}
78+
faqs={faqs}
79+
features={features}
80+
featuresWin={featuresWin}
81+
featureSectionSubtitle={`Databuddy vs ${competitor.name} — same comparison as our main review, tuned for “alternative to ${competitor.name}” searches`}
82+
heroCta={hero.cta}
83+
heroDescription={hero.description}
84+
heroHeading={
85+
<>
86+
Alternative to{" "}
87+
<span className="text-muted-foreground">{competitor.name}</span>
88+
</>
89+
}
90+
introText={introText}
91+
migrationSection={migrationSection}
92+
pageUrl={pageUrl}
93+
pricingTiers={pricingTiers}
94+
structuredDescription={description}
95+
structuredTitle={title}
96+
/>
97+
);
98+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { ArrowRightIcon } from "@phosphor-icons/react/ssr";
2+
import type { Metadata } from "next";
3+
import Link from "next/link";
4+
import { SITE_URL } from "@/app/util/constants";
5+
import { CompetitorCard } from "@/components/compare/competitor-card";
6+
import { SciFiButton } from "@/components/landing/scifi-btn";
7+
import Section from "@/components/landing/section";
8+
import { Spotlight } from "@/components/landing/spotlight";
9+
import { StructuredData } from "@/components/structured-data";
10+
import { competitors } from "@/lib/comparison-config";
11+
12+
const alternativesTitle =
13+
"Alternative to Google Analytics, Plausible & More (2026) | Databuddy";
14+
const alternativesDescription =
15+
"Find a privacy-first analytics alternative for your stack. Compare pricing, features, and migration for every major platform — one page per tool.";
16+
const alternativesUrl = `${SITE_URL}/alternatives`;
17+
18+
export const metadata: Metadata = {
19+
title: alternativesTitle,
20+
description: alternativesDescription,
21+
openGraph: {
22+
title: alternativesTitle,
23+
description: alternativesDescription,
24+
url: alternativesUrl,
25+
},
26+
alternates: { canonical: alternativesUrl },
27+
};
28+
29+
export default function AlternativesHubPage() {
30+
const entries = Object.entries(competitors);
31+
32+
return (
33+
<div className="overflow-hidden">
34+
<StructuredData
35+
page={{
36+
title: alternativesTitle,
37+
description: alternativesDescription,
38+
url: alternativesUrl,
39+
}}
40+
/>
41+
<Spotlight transform="translateX(-60%) translateY(-50%)" />
42+
43+
<div className="container mx-auto px-4 pt-8">
44+
<div className="flex items-center gap-2 text-muted-foreground text-sm">
45+
<Link
46+
className="transition-colors hover:text-foreground"
47+
href="/"
48+
>
49+
Home
50+
</Link>
51+
<span>/</span>
52+
<span className="text-foreground">Alternatives</span>
53+
</div>
54+
</div>
55+
56+
<Section className="overflow-hidden" customPaddings id="alternatives-hero">
57+
<section className="relative w-full pt-12 pb-12 sm:pt-16 sm:pb-16 lg:pt-20 lg:pb-20">
58+
<div className="mx-auto w-full max-w-5xl px-4 sm:px-6 lg:px-8">
59+
<div className="text-center">
60+
<h1 className="mb-4 text-balance font-semibold text-3xl leading-tight tracking-tight sm:text-4xl md:text-5xl lg:text-6xl">
61+
Analytics{" "}
62+
<span className="text-muted-foreground">alternatives</span>
63+
</h1>
64+
<p className="mx-auto max-w-2xl text-balance text-muted-foreground text-sm leading-relaxed sm:text-base">
65+
Replacing a specific tool? Pick it below for pricing, features,
66+
and setup. The same deep comparison is also available as{" "}
67+
<Link
68+
className="font-medium text-foreground underline-offset-4 hover:underline"
69+
href="/compare"
70+
>
71+
standard Databuddy vs pages
72+
</Link>{" "}
73+
or with a{" "}
74+
<Link
75+
className="font-medium text-foreground underline-offset-4 hover:underline"
76+
href="/switch-from"
77+
>
78+
migration-focused view
79+
</Link>
80+
.
81+
</p>
82+
</div>
83+
</div>
84+
</section>
85+
</Section>
86+
87+
<Section
88+
className="border-border border-t border-b bg-background/50"
89+
id="alternatives-grid"
90+
>
91+
<div className="mx-auto w-full max-w-5xl px-4 sm:px-6 lg:px-8">
92+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
93+
{entries.map(([slug, data]) => (
94+
<CompetitorCard
95+
ctaLabel="View alternative page"
96+
data={data}
97+
headline={`Alternative to ${data.competitor.name}`}
98+
href={`/alternatives/${slug}`}
99+
key={slug}
100+
/>
101+
))}
102+
</div>
103+
104+
<div className="mt-12 rounded border border-border bg-card/30 p-6 text-center backdrop-blur-sm sm:p-8">
105+
<h3 className="mb-2 font-semibold text-foreground text-lg">
106+
Ready to try Databuddy?
107+
</h3>
108+
<p className="mb-5 text-pretty text-muted-foreground text-sm">
109+
Start free with 10K monthly pageviews — no credit card.
110+
</p>
111+
<div className="flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
112+
<SciFiButton asChild>
113+
<Link
114+
href="https://app.databuddy.cc/login"
115+
rel="noopener noreferrer"
116+
target="_blank"
117+
>
118+
Start Free — No Credit Card
119+
</Link>
120+
</SciFiButton>
121+
<Link
122+
className="group inline-flex items-center justify-center gap-2 rounded border border-border bg-foreground/5 px-5 py-2 font-medium text-foreground text-sm backdrop-blur-sm transition-colors hover:bg-foreground/10 active:scale-[0.98]"
123+
href="/demo"
124+
>
125+
View Live Demo
126+
<ArrowRightIcon
127+
className="size-3.5 transition-transform group-hover:translate-x-0.5"
128+
weight="fill"
129+
/>
130+
</Link>
131+
</div>
132+
</div>
133+
</div>
134+
</Section>
135+
</div>
136+
);
137+
}

0 commit comments

Comments
 (0)