From 4f3734fbb4352b334308ee0af8f0a2c22f89c57d Mon Sep 17 00:00:00 2001 From: hiabhee Date: Sat, 7 Nov 2026 20:15:09 +0530 Subject: [PATCH 1/3] feat(seo): free tools and comparison pages Free tools, one distinct tool per page (near-duplicate keyword pages would be doorway pages): - /free-architecture-diagram-generator: describe a system or paste a GitHub repo; opens the editor generating it. /ai-architecture-diagram- generator redirects here (308) so "free" and "AI" searches share one page. - /free-diagram-generator: Mermaid text to a laid-out diagram through the real pipeline (dry-run preview, nothing stored), PNG/SVG download, "Edit in ArchDraw". SVG shown as so it cannot run script. - /system-design-calculator: QPS, peak, storage and bandwidth estimates (pure, tested lib/site/capacityEstimate.ts), latency and powers-of-two tables, links into the glossary. Comparisons: /alternatives and Eraser, draw.io, Lucidchart pages with an at-a-glance table, "Choose them if" / "Choose ArchDraw if" and FAQ. Claims limited to public facts checked 2026-11-07; prices left out. Sitemap, robots allow and footer links for all of them. --- frontend/app/alternatives/[slug]/page.tsx | 160 +++++++++++++++++ frontend/app/alternatives/page.tsx | 46 +++++ .../page.tsx | 151 ++++++++++++++++ frontend/app/free-diagram-generator/page.tsx | 121 +++++++++++++ frontend/app/robots.ts | 2 +- frontend/app/sitemap.ts | 6 + .../app/system-design-calculator/page.tsx | 123 +++++++++++++ frontend/components/landing/Footer.tsx | 4 + .../components/landing/GeneratorPromptBox.tsx | 45 +++++ .../landing/tools/CapacityCalculator.tsx | 85 +++++++++ .../landing/tools/DiagramFromTextTool.tsx | 169 ++++++++++++++++++ frontend/data/alternatives/index.ts | 165 +++++++++++++++++ .../site/__tests__/capacityEstimate.test.ts | 28 +++ frontend/lib/site/capacityEstimate.ts | 90 ++++++++++ frontend/next.config.ts | 6 + 15 files changed, 1200 insertions(+), 1 deletion(-) create mode 100644 frontend/app/alternatives/[slug]/page.tsx create mode 100644 frontend/app/alternatives/page.tsx create mode 100644 frontend/app/free-architecture-diagram-generator/page.tsx create mode 100644 frontend/app/free-diagram-generator/page.tsx create mode 100644 frontend/app/system-design-calculator/page.tsx create mode 100644 frontend/components/landing/GeneratorPromptBox.tsx create mode 100644 frontend/components/landing/tools/CapacityCalculator.tsx create mode 100644 frontend/components/landing/tools/DiagramFromTextTool.tsx create mode 100644 frontend/data/alternatives/index.ts create mode 100644 frontend/lib/site/__tests__/capacityEstimate.test.ts create mode 100644 frontend/lib/site/capacityEstimate.ts diff --git a/frontend/app/alternatives/[slug]/page.tsx b/frontend/app/alternatives/[slug]/page.tsx new file mode 100644 index 00000000..2ac89a0b --- /dev/null +++ b/frontend/app/alternatives/[slug]/page.tsx @@ -0,0 +1,160 @@ +import type { Metadata } from 'next'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { Check } from 'lucide-react'; +import { LandingNav } from '@/components/landing/LandingNav'; +import { Footer } from '@/components/landing/Footer'; +import { GeneratorPromptBox } from '@/components/landing/GeneratorPromptBox'; +import { ALTERNATIVES, getAlternative } from '@/data/alternatives'; +import { breadcrumbJsonLd, pageMetadata } from '@/lib/site/seo'; + +interface Props { + params: Promise<{ slug: string }>; +} + +export const dynamicParams = false; + +export function generateStaticParams() { + return ALTERNATIVES.map((a) => ({ slug: a.slug })); +} + +export async function generateMetadata({ params }: Props): Promise { + const { slug } = await params; + const alt = getAlternative(slug); + if (!alt) return { title: 'Comparison not found', robots: { index: false } }; + return pageMetadata({ + title: `${alt.name} Alternative: ArchDraw vs ${alt.name}`, + description: alt.summary, + path: `/alternatives/${alt.slug}`, + type: 'article', + keywords: [`${alt.name.toLowerCase()} alternative`, `archdraw vs ${alt.name.toLowerCase()}`, `${alt.name.toLowerCase()} vs`, 'architecture diagram tool'], + }); +} + +const h2 = 'text-xl font-semibold tracking-[-.03em] text-text-primary'; +const li = 'flex gap-2.5 leading-7 text-text-secondary'; + +function Ticks({ items }: { items: string[] }) { + return ( + + ); +} + +export default async function AlternativePage({ params }: Props) { + const { slug } = await params; + const alt = getAlternative(slug); + if (!alt) notFound(); + const path = `/alternatives/${alt.slug}`; + const checked = new Date(`${alt.checkedOn}T00:00:00Z`).toLocaleDateString('en-US', { month: 'long', year: 'numeric', timeZone: 'UTC' }); + + const jsonLd = { + '@context': 'https://schema.org', + '@graph': [ + { '@type': 'FAQPage', mainEntity: alt.faq.map((f) => ({ '@type': 'Question', name: f.question, acceptedAnswer: { '@type': 'Answer', text: f.answer } })) }, + breadcrumbJsonLd([['Alternatives', '/alternatives'], [`ArchDraw vs ${alt.name}`, path]]), + ], + }; + + return ( +
+ +