feat(web): bias Google Sitelinks toward Extension, Cloud, Docs, Pricing

- Add /extension canonical page with metadata and BreadcrumbList JSON-LD

- Header: change 'Extension' to link /extension (desktop & mobile)

- Home hero: add above-the-fold Docs/Pricing links

- Footer: add Cloud and Pricing under Product

- Sitemap: prioritize /extension (0.9), /cloud & /pricing (0.8); fix additionalPaths; reduce /enterprise

- Robots: publish roocode.com and docs.roocode.com sitemaps

- Structured data: add SiteNavigationElement; per-page BreadcrumbList

- Ensure explicit metadata on Cloud/Pricing/Extension; fix rel for target=_blank
This commit is contained in:
Hannes Rudolph 2025-10-17 13:35:04 -06:00
parent f30caf277c
commit f0802f1bec
9 changed files with 197 additions and 29 deletions

View file

@ -33,9 +33,18 @@ module.exports = {
if (path === '/') {
priority = 1.0;
changefreq = 'yearly';
} else if (path === '/enterprise' || path === '/evals') {
} else if (path === '/extension') {
priority = 0.9;
changefreq = 'monthly';
} else if (path === '/cloud' || path === '/pricing') {
priority = 0.8;
changefreq = 'monthly';
} else if (path === '/enterprise') {
priority = 0.5;
changefreq = 'yearly';
} else if (path === '/evals') {
priority = 0.6;
changefreq = 'monthly';
} else if (path === '/privacy' || path === '/terms') {
priority = 0.5;
changefreq = 'yearly';
@ -50,24 +59,20 @@ module.exports = {
};
},
additionalPaths: async (config) => {
// Add any additional paths that might not be automatically discovered
// This is useful for dynamic routes or API-generated pages
// Add the /evals page since it's a dynamic route
return [{
loc: '/evals',
changefreq: 'monthly',
priority: 0.8,
lastmod: new Date().toISOString(),
}];
// Add the /evals page since it's a dynamic route
result.push({
loc: '/evals',
changefreq: 'monthly',
priority: 0.8,
lastmod: new Date().toISOString(),
});
return result;
// Explicitly include dynamic or non-file-system paths
return [
{
loc: '/evals',
changefreq: 'monthly',
priority: 0.6,
lastmod: new Date().toISOString(),
},
{
loc: '/extension',
changefreq: 'monthly',
priority: 0.9,
lastmod: new Date().toISOString(),
},
];
},
};

View file

@ -119,8 +119,17 @@ const cloudFeatures: Feature[] = [
import screenshotDark from "/public/heroes/cloud-screen.png"
export default function CloudPage() {
const breadcrumbLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "Home", item: SEO.url },
{ "@type": "ListItem", position: 2, name: "Cloud", item: `${SEO.url}${PATH}` },
],
}
return (
<>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }} />
<section className="relative flex md:h-[calc(80vh-theme(spacing.12))] items-center overflow-hidden">
<AnimatedBackground />
<div className="container relative flex items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">

View file

@ -0,0 +1,102 @@
import { ArrowRight } from "lucide-react"
import type { Metadata } from "next"
import Link from "next/link"
import { Button } from "@/components/ui"
import { AnimatedBackground } from "@/components/homepage"
import { SEO } from "@/lib/seo"
import { EXTERNAL_LINKS } from "@/lib/constants"
const TITLE = "Roo Code Extension"
const DESCRIPTION = "Open-source VS Code extension that turns your editor into an AI dev team. Works with any model."
const PATH = "/extension"
const OG_IMAGE = SEO.ogImage
export const metadata: Metadata = {
title: TITLE,
description: DESCRIPTION,
alternates: { canonical: `${SEO.url}${PATH}` },
openGraph: {
title: TITLE,
description: DESCRIPTION,
url: `${SEO.url}${PATH}`,
siteName: SEO.name,
images: [{ url: OG_IMAGE.url, width: OG_IMAGE.width, height: OG_IMAGE.height, alt: OG_IMAGE.alt }],
locale: SEO.locale,
type: "website",
},
twitter: {
card: SEO.twitterCard,
title: TITLE,
description: DESCRIPTION,
images: [OG_IMAGE.url],
},
keywords: [...SEO.keywords, "VS Code extension", "Roo Code extension"],
}
export default function ExtensionPage() {
const breadcrumbLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "Home", item: SEO.url },
{ "@type": "ListItem", position: 2, name: "Extension", item: `${SEO.url}${PATH}` },
],
}
return (
<>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }} />
<section className="relative flex md:h-[calc(80vh-theme(spacing.12))] items-center overflow-hidden">
<AnimatedBackground />
<div className="container relative flex items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid h-full relative gap-8 lg:grid-cols-2">
<div className="flex flex-col px-4 justify-center space-y-6 sm:space-y-8">
<div>
<h1 className="text-4xl font-bold tracking-tight mt-8 text-center md:text-left md:text-4xl lg:text-5xl lg:mt-0">
Roo Code Extension
</h1>
<p className="mt-4 max-w-md text-lg text-muted-foreground text-center md:text-left sm:mt-6">
Open-source AI coding agent that lives in VS Code. Multi-step, projectwide context,
and works with any model.
</p>
</div>
<div className="flex flex-col space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
<Button
size="lg"
className="w-full hover:bg-gray-200 dark:bg-white dark:text-black sm:w-auto">
<a
href={EXTERNAL_LINKS.MARKETPLACE}
target="_blank"
rel="noreferrer"
className="flex w-full items-center justify-center">
Install VS Code Extension
<ArrowRight className="ml-2" />
</a>
</Button>
<Button
variant="outline"
size="lg"
className="w-full sm:w-auto bg-white/20 dark:bg-white/10 backdrop-blur-sm border border-black/40 dark:border-white/30 hover:border-blue-400 hover:bg-white/30 dark:hover:bg-white/20 hover:shadow-[0_0_20px_rgba(59,130,246,0.5)] transition-all duration-300">
<a
href={EXTERNAL_LINKS.DOCUMENTATION}
target="_blank"
rel="noreferrer"
className="flex w-full items-center justify-center">
Docs
<ArrowRight className="ml-2" />
</a>
</Button>
</div>
<div className="text-sm text-muted-foreground text-center md:text-left">
<Link href="/pricing" className="underline underline-offset-4 hover:text-foreground">
Pricing
</Link>
</div>
</div>
</div>
</div>
</section>
</>
)
}

View file

@ -14,6 +14,7 @@ import {
} from "@/components/homepage"
import { EXTERNAL_LINKS } from "@/lib/constants"
import { ArrowRight } from "lucide-react"
import Link from "next/link"
import { StructuredData } from "@/components/structured-data"
// Invalidate cache when a request comes in, at most once every hour.
@ -64,6 +65,19 @@ export default async function Home() {
</a>
</Button>
</div>
<div className="text-sm text-muted-foreground text-center md:text-left">
<a
href={EXTERNAL_LINKS.DOCUMENTATION}
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-4 hover:text-foreground">
Docs
</a>
<span className="mx-2"></span>
<Link href="/pricing" className="underline underline-offset-4 hover:text-foreground">
Pricing
</Link>
</div>
<CompanyLogos />
</div>
<div className="relative flex items-center mx-auto h-full mt-8 lg:mt-0">

View file

@ -132,9 +132,18 @@ const pricingTiers: PricingTier[] = [
]
export default function PricingPage() {
const breadcrumbLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "Home", item: SEO.url },
{ "@type": "ListItem", position: 2, name: "Pricing", item: `${SEO.url}${PATH}` },
],
}
return (
<>
<AnimatedBackground />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }} />
{/* Hero Section */}
<section className="relative overflow-hidden pt-16 pb-12">

View file

@ -7,7 +7,7 @@ export default function robots(): MetadataRoute.Robots {
userAgent: "*",
allow: "/",
},
sitemap: `${SEO.url}/sitemap.xml`,
sitemap: [`${SEO.url}/sitemap.xml`, "https://docs.roocode.com/sitemap.xml"],
host: SEO.url,
}
}

View file

@ -78,6 +78,20 @@ export function Footer() {
Docs
</a>
</li>
<li>
<Link
href="/cloud"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Cloud
</Link>
</li>
<li>
<Link
href="/pricing"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Pricing
</Link>
</li>
<li>
<a
href={EXTERNAL_LINKS.CHANGELOG}

View file

@ -35,11 +35,11 @@ export function NavBar({ stars, downloads }: NavBarProps) {
{/* Desktop Navigation */}
<nav className="grow ml-6 hidden text-sm font-medium md:flex md:items-center">
<ScrollButton
targetId="product"
<Link
href="/extension"
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground max-lg:hidden">
Extension
</ScrollButton>
</Link>
<Link
href="/cloud"
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground">
@ -141,12 +141,12 @@ export function NavBar({ stars, downloads }: NavBarProps) {
<nav className="flex flex-col justify-between h-full pb-16 overflow-y-auto bg-background pointer-events-auto">
{/* Main navigation items */}
<div className="grow-1 py-4 font-semibold text-lg">
<ScrollButton
targetId="product"
<Link
href="/extension"
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
onClick={() => setIsMenuOpen(false)}>
Extension
</ScrollButton>
</Link>
<Link
href="/cloud"
className="block w-full p-5 text-left text-foreground active:opacity-50"

View file

@ -47,9 +47,17 @@ interface SoftwareApplication {
publisher: { "@id": string }
}
interface SiteNavigationElement {
"@type": "SiteNavigationElement"
name: string
url: string
}
type GraphNode = Organization | WebSite | SoftwareApplication | SiteNavigationElement
interface StructuredDataGraph {
"@context": "https://schema.org"
"@graph": [Organization, WebSite, SoftwareApplication]
"@graph": GraphNode[]
}
/**
@ -115,9 +123,16 @@ export function getStructuredData(): StructuredDataGraph {
publisher: { "@id": orgId },
}
const siteNavigation: SiteNavigationElement[] = [
{ "@type": "SiteNavigationElement", name: "Extension", url: `${SEO.url}/extension` },
{ "@type": "SiteNavigationElement", name: "Cloud", url: `${SEO.url}/cloud` },
{ "@type": "SiteNavigationElement", name: "Docs", url: EXTERNAL_LINKS.DOCUMENTATION },
{ "@type": "SiteNavigationElement", name: "Pricing", url: `${SEO.url}/pricing` },
]
return {
"@context": "https://schema.org",
"@graph": [organization, website, softwareApplication],
"@graph": [organization, website, softwareApplication, ...siteNavigation],
}
}