diff --git a/apps/web/src/app/docs/[[...slug]]/page.tsx b/apps/web/src/app/docs/[[...slug]]/page.tsx index 56b82c4..160797a 100644 --- a/apps/web/src/app/docs/[[...slug]]/page.tsx +++ b/apps/web/src/app/docs/[[...slug]]/page.tsx @@ -51,9 +51,17 @@ export async function generateMetadata(props: { if (!page) notFound() const data = page.data as unknown as ExtendedPageData + const slug = params.slug?.join('/') || '' return { title: `${data.title} | RepliMap Docs`, description: data.description, + openGraph: { + title: data.title, + description: data.description, + url: `https://replimap.com/docs/${slug}`, + siteName: 'RepliMap', + type: 'article', + }, } } diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 67afbcb..79c532f 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import localFont from "next/font/local"; import { ClerkProviderWrapper } from "@/components/clerk-provider"; import { ThemeProvider } from "@/components/theme-provider"; +import { generateSiteSchema } from "@/lib/schema"; import "./globals.css"; const geistSans = localFont({ @@ -22,6 +23,8 @@ const geistMono = localFont({ // SEO Metadata export const metadata: Metadata = { + metadataBase: new URL('https://replimap.com'), + title: { default: "RepliMap - AWS Infrastructure Intelligence", template: "%s | RepliMap", @@ -38,17 +41,25 @@ export const metadata: Metadata = { "DevOps", "SRE", "infrastructure audit", + "AWS to Terraform", + "reverse engineer AWS", + "Terraform import", ], authors: [{ name: "RepliMap" }], creator: "RepliMap", - // Icons from v0 (enhanced) + publisher: "RepliMap", + + // CRITICAL FIX: './' instead of '/' + // './' = relative to current path, each page gets correct canonical + // '/' = absolute path, all pages point to homepage (fatal SEO error!) + alternates: { + canonical: './', + }, + icons: { - icon: [ - { url: "/favicon.ico" }, - { url: "/icon.svg", type: "image/svg+xml" }, - ], - apple: "/apple-icon.png", + icon: [{ url: "/favicon.ico" }], }, + openGraph: { title: "RepliMap - AWS Infrastructure Intelligence", description: "Reverse-engineer your AWS infrastructure into Terraform", @@ -65,32 +76,28 @@ export const metadata: Metadata = { locale: "en_US", type: "website", }, + twitter: { card: "summary_large_image", title: "RepliMap - AWS Infrastructure Intelligence", description: "Reverse-engineer your AWS infrastructure into Terraform", images: ["/og-image.png"], }, + robots: { index: true, follow: true, + googleBot: { + index: true, + follow: true, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, + }, }, -}; -// JSON-LD Structured Data for SEO -const jsonLd = { - "@context": "https://schema.org", - "@type": "SoftwareApplication", - name: "RepliMap", - applicationCategory: "DeveloperApplication", - operatingSystem: "Linux, macOS, Windows", - description: - "AWS Infrastructure Intelligence Engine - Reverse-engineer infrastructure into Terraform, detect drift, generate IAM policies", - offers: [ - { "@type": "Offer", price: "0", priceCurrency: "USD", name: "Free" }, - { "@type": "Offer", price: "49", priceCurrency: "USD", name: "Solo" }, - { "@type": "Offer", price: "99", priceCurrency: "USD", name: "Pro" }, - ], + category: 'Technology', + applicationName: 'RepliMap', }; export default function RootLayout({ @@ -98,6 +105,8 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { + const jsonLd = generateSiteSchema(); + return ( diff --git a/apps/web/src/app/opengraph-image.tsx b/apps/web/src/app/opengraph-image.tsx new file mode 100644 index 0000000..0a69cde --- /dev/null +++ b/apps/web/src/app/opengraph-image.tsx @@ -0,0 +1,62 @@ +import { ImageResponse } from 'next/og' + +export const runtime = 'edge' +export const alt = 'RepliMap - AWS Infrastructure Intelligence' +export const size = { width: 1200, height: 630 } +export const contentType = 'image/png' + +export default async function Image() { + return new ImageResponse( + ( +
+
+ R +
+
+ RepliMap +
+
+ AWS Infrastructure Intelligence +
+
+ Reverse-engineer AWS into Terraform • Detect Drift • Generate IAM Policies +
+
+
+ ), + { ...size } + ) +} diff --git a/apps/web/src/app/robots.ts b/apps/web/src/app/robots.ts new file mode 100644 index 0000000..5671f9a --- /dev/null +++ b/apps/web/src/app/robots.ts @@ -0,0 +1,47 @@ +import { MetadataRoute } from 'next' + +export default function robots(): MetadataRoute.Robots { + const baseUrl = 'https://replimap.com' + + return { + rules: [ + { + userAgent: '*', + allow: '/', + disallow: ['/dashboard/', '/api/', '/sign-in/', '/sign-up/', '/_next/'], + }, + // Explicitly welcome AI crawlers + { + userAgent: 'GPTBot', + allow: '/', + disallow: ['/dashboard/', '/api/', '/sign-in/', '/sign-up/'], + }, + { + userAgent: 'PerplexityBot', + allow: '/', + disallow: ['/dashboard/', '/api/', '/sign-in/', '/sign-up/'], + }, + { + userAgent: 'ClaudeBot', + allow: '/', + disallow: ['/dashboard/', '/api/', '/sign-in/', '/sign-up/'], + }, + { + userAgent: 'Google-Extended', + allow: '/', + disallow: ['/dashboard/', '/api/', '/sign-in/', '/sign-up/'], + }, + { + userAgent: 'Amazonbot', + allow: '/', + disallow: ['/dashboard/', '/api/', '/sign-in/', '/sign-up/'], + }, + // Block low-value crawlers + { userAgent: 'CCBot', disallow: '/' }, + { userAgent: 'AhrefsBot', disallow: '/' }, + { userAgent: 'SemrushBot', disallow: '/' }, + ], + sitemap: `${baseUrl}/sitemap.xml`, + host: baseUrl, + } +} diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts new file mode 100644 index 0000000..b22541a --- /dev/null +++ b/apps/web/src/app/sitemap.ts @@ -0,0 +1,54 @@ +import { MetadataRoute } from 'next' +import fs from 'fs' +import path from 'path' + +function getDocSlugs(): string[] { + const docsDir = path.join(process.cwd(), 'content/docs') + try { + return fs + .readdirSync(docsDir) + .filter((file) => file.endsWith('.mdx')) + .map((file) => file.replace('.mdx', '')) + .filter((slug) => slug !== 'index') + } catch { + return [] + } +} + +export default function sitemap(): MetadataRoute.Sitemap { + const baseUrl = 'https://replimap.com' + const now = new Date() + + // Core pages + const coreRoutes: MetadataRoute.Sitemap = [ + { url: baseUrl, lastModified: now, changeFrequency: 'weekly', priority: 1.0 }, + { url: `${baseUrl}/docs`, lastModified: now, changeFrequency: 'weekly', priority: 0.9 }, + ] + + // Dynamic documentation pages + const docSlugs = getDocSlugs() + const docRoutes: MetadataRoute.Sitemap = docSlugs.map((slug) => ({ + url: `${baseUrl}/docs/${slug}`, + lastModified: now, + changeFrequency: 'monthly' as const, + priority: slug === 'quick-start' ? 0.9 : 0.7, + })) + + // Legal pages + const legalRoutes: MetadataRoute.Sitemap = [ + { + url: `${baseUrl}/privacy`, + lastModified: new Date('2026-01-01'), + changeFrequency: 'yearly', + priority: 0.3, + }, + { + url: `${baseUrl}/terms`, + lastModified: new Date('2026-01-01'), + changeFrequency: 'yearly', + priority: 0.3, + }, + ] + + return [...coreRoutes, ...docRoutes, ...legalRoutes] +} diff --git a/apps/web/src/lib/schema.ts b/apps/web/src/lib/schema.ts new file mode 100644 index 0000000..28fc359 --- /dev/null +++ b/apps/web/src/lib/schema.ts @@ -0,0 +1,71 @@ +import { PLANS, type PlanName } from './pricing' + +/** + * Generate JSON-LD structured data for SEO + * Single Source of Truth: prices from PLANS config + */ +export function generateSiteSchema() { + const displayPlans: PlanName[] = ['community', 'pro', 'team'] + + const offers = displayPlans.map((planKey) => { + const plan = PLANS[planKey] + return { + '@type': 'Offer', + name: plan.name, + price: String(plan.price.monthly), + priceCurrency: 'USD', + description: plan.tagline, + } + }) + + return { + '@context': 'https://schema.org', + '@graph': [ + { + '@type': 'SoftwareApplication', + '@id': 'https://replimap.com/#software', + name: 'RepliMap', + applicationCategory: 'DeveloperApplication', + applicationSubCategory: 'Infrastructure as Code Tool', + operatingSystem: 'Linux, macOS, Windows', + description: + 'AWS Infrastructure Intelligence Engine - Reverse-engineer infrastructure into Terraform, detect drift, generate IAM policies', + url: 'https://replimap.com', + downloadUrl: 'https://replimap.com/docs/installation', + softwareVersion: '1.0.0', + releaseNotes: 'https://replimap.com/docs/changelog', + screenshot: 'https://replimap.com/og-image.png', + featureList: [ + 'Reverse-engineer AWS to Terraform', + 'Infrastructure drift detection', + 'Least-privilege IAM policy generation', + 'Multi-region scanning', + 'Offline/air-gapped support', + ], + offers, + author: { '@id': 'https://replimap.com/#organization' }, + }, + { + '@type': 'Organization', + '@id': 'https://replimap.com/#organization', + name: 'RepliMap', + url: 'https://replimap.com', + logo: { '@type': 'ImageObject', url: 'https://replimap.com/og-image.png' }, + sameAs: ['https://github.com/RepliMap/replimap-mono'], + contactPoint: { + '@type': 'ContactPoint', + email: 'hello@replimap.com', + contactType: 'customer support', + }, + }, + { + '@type': 'WebSite', + '@id': 'https://replimap.com/#website', + url: 'https://replimap.com', + name: 'RepliMap', + description: 'AWS Infrastructure Intelligence', + publisher: { '@id': 'https://replimap.com/#organization' }, + }, + ], + } +}