Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/web/src/app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
}
}
51 changes: 30 additions & 21 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -65,39 +76,37 @@ 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({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const jsonLd = generateSiteSchema();

return (
<ClerkProviderWrapper>
<html lang="en" suppressHydrationWarning className="dark">
Expand Down
62 changes: 62 additions & 0 deletions apps/web/src/app/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -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(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#030712',
backgroundImage: 'linear-gradient(to bottom right, #030712, #0a1628)',
}}
>
<div
style={{
width: '120px',
height: '120px',
backgroundColor: '#10b981',
borderRadius: '24px',
marginBottom: '40px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<span style={{ fontSize: '60px', color: 'white', fontWeight: 700 }}>R</span>
</div>
<div style={{ fontSize: '72px', fontWeight: 700, color: '#f1f5f9', marginBottom: '20px' }}>
RepliMap
</div>
<div style={{ fontSize: '32px', color: '#10b981', marginBottom: '16px' }}>
AWS Infrastructure Intelligence
</div>
<div
style={{ fontSize: '24px', color: '#94a3b8', textAlign: 'center', maxWidth: '800px' }}
>
Reverse-engineer AWS into Terraform • Detect Drift • Generate IAM Policies
</div>
<div
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: '6px',
background: 'linear-gradient(to right, #10b981, #06b6d4)',
}}
/>
</div>
),
{ ...size }
)
}
47 changes: 47 additions & 0 deletions apps/web/src/app/robots.ts
Original file line number Diff line number Diff line change
@@ -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,
}
}
54 changes: 54 additions & 0 deletions apps/web/src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -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]
}
71 changes: 71 additions & 0 deletions apps/web/src/lib/schema.ts
Original file line number Diff line number Diff line change
@@ -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' },
},
],
}
}
Loading