Add grouped marketing feature pages and SEO-friendly feature hub (#199)

* Refine marketing feature pages and feature navigation

* Add SEO feature page polish

* Share feature slugs with sitemap generation
This commit is contained in:
Ben Senescu 2026-05-17 17:53:46 -04:00 committed by GitHub
parent 7a0ab0de2c
commit a8124a07b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1388 additions and 19 deletions

View File

@ -3,6 +3,7 @@
import { existsSync, readdirSync, statSync, writeFileSync } from "node:fs"; import { existsSync, readdirSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path"; import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { FEATURE_PAGE_SLUGS } from "../src/lib/feature-page-slugs.js";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
@ -19,6 +20,9 @@ const STATIC_PATHS = [
"/privacy", "/privacy",
"/terms-and-conditions", "/terms-and-conditions",
"/guides", "/guides",
"/features",
"/features/mcp",
...Object.values(FEATURE_PAGE_SLUGS).map((slug) => `/features/${slug}`),
]; ];
function getGuideEntries(dir = GUIDE_CONTENT_DIR, segments = []) { function getGuideEntries(dir = GUIDE_CONTENT_DIR, segments = []) {

View File

@ -0,0 +1,196 @@
import type { FeaturePage } from "@/lib/feature-pages";
type FeaturePageProps = {
page: FeaturePage;
};
export function FeaturePageTemplate({ page }: FeaturePageProps) {
return (
<article>
<header>
<p className="text-sm font-medium text-neutral-500">{page.eyebrow}</p>
<h1 className="mt-3 text-3xl font-bold leading-tight tracking-tight">
{page.title}
</h1>
<p className="mt-4 text-neutral-700 leading-relaxed">
{page.description}
</p>
<div className="mt-5">
<a
href="https://app.openseo.so/sign-up"
className="inline-flex h-10 items-center justify-center rounded-md bg-neutral-900 px-5 text-sm font-medium text-white transition-colors hover:bg-neutral-800"
>
{page.ctaLabel ?? "Try OpenSEO"}
</a>
</div>
</header>
<FeatureImage page={page} />
<section className="mt-12">
<h2 className="text-xl font-semibold">What you can do</h2>
<ol className="mt-6 space-y-6">
{page.workflows.map((workflow, index) => (
<li
key={workflow.title}
className="grid grid-cols-[2.25rem_1fr] gap-x-4"
>
<span className="pt-[2px] font-mono text-sm tabular-nums text-neutral-400">
{String(index + 1).padStart(2, "0")}
</span>
<div>
<h3 className="text-sm font-semibold text-neutral-900">
{workflow.title}
</h3>
<p className="mt-1.5 text-sm leading-relaxed text-neutral-600">
{workflow.description}
</p>
</div>
</li>
))}
</ol>
</section>
<section className="mt-12">
<h2 className="text-xl font-semibold">Data you can act on</h2>
<dl className="mt-5 grid grid-cols-2 border-y border-neutral-200 md:grid-cols-4 md:divide-x md:divide-neutral-200">
{page.metrics.map((metric, index) => (
<div
key={metric.label}
className={[
"px-4 py-3",
index % 2 === 1 && "border-l border-neutral-200 md:border-l-0",
index < 2 && "border-b border-neutral-200 md:border-b-0",
]
.filter(Boolean)
.join(" ")}
>
<dt className="text-xs text-neutral-500">{metric.label}</dt>
<dd className="mt-1 text-sm font-semibold text-neutral-900">
{metric.value}
</dd>
</div>
))}
</dl>
</section>
<section className="mt-12">
<h2 className="text-xl font-semibold">Use cases</h2>
<ul className="mt-4 space-y-3">
{page.useCases.map((item) => (
<li key={item} className="flex gap-2.5 text-sm text-neutral-700">
<span className="mt-[2px] text-neutral-400">&mdash;</span>
<span>{item}</span>
</li>
))}
</ul>
</section>
<section className="mt-12">
<h2 className="text-xl font-semibold">Why OpenSEO</h2>
<ul className="mt-4 space-y-3">
{page.differentiators.map((item) => (
<li key={item} className="flex gap-2.5 text-sm text-neutral-700">
<span className="mt-[2px] text-neutral-400">&mdash;</span>
<span>{item}</span>
</li>
))}
</ul>
</section>
<section className="mt-12">
<h2 className="text-xl font-semibold">Related features</h2>
<div className="mt-4 grid gap-3 md:grid-cols-3">
{page.related.map((item) => (
<a
key={item.href}
href={item.href}
className="rounded-lg border border-neutral-200 bg-white p-4 text-sm font-medium text-neutral-900 transition-colors hover:border-neutral-900"
>
{item.label}
<span aria-hidden="true" className="ml-1 text-neutral-500">
&rarr;
</span>
</a>
))}
</div>
</section>
<section className="mt-12">
<h2 className="text-xl font-semibold">FAQ</h2>
<div className="mt-5 divide-y divide-neutral-200 rounded-lg border border-neutral-200 bg-white">
{page.faqs.map((faq) => (
<div key={faq.question} className="p-4">
<h3 className="text-sm font-semibold text-neutral-900">
{faq.question}
</h3>
<p className="mt-1.5 text-sm leading-relaxed text-neutral-600">
{faq.answer}
</p>
</div>
))}
</div>
</section>
<section className="mt-12 rounded-lg border border-neutral-200 bg-neutral-50 p-5">
<h2 className="text-lg font-semibold text-neutral-900">
Try OpenSEO
</h2>
<p className="mt-2 text-sm leading-relaxed text-neutral-600">
The open source alternative to bloated, expensive, legacy SEO tools.
</p>
<div className="mt-4">
<a
href="https://app.openseo.so/sign-up"
className="inline-flex h-9 items-center justify-center rounded-md bg-neutral-900 px-4 text-sm font-medium text-white transition-colors hover:bg-neutral-800"
>
{page.ctaLabel ?? "Try OpenSEO"}
</a>
</div>
</section>
</article>
);
}
function FeatureImage({ page }: FeaturePageProps) {
if (page.imageSrc) {
return (
<figure className="mt-9">
<img
src={page.imageSrc}
alt={page.imageAlt}
width={1600}
height={1000}
loading="eager"
decoding="async"
className="aspect-[16/10] w-full rounded-lg border border-neutral-200 object-cover object-top"
/>
<figcaption className="mt-2 text-[11px] text-neutral-500">
{page.eyebrow} in OpenSEO.
</figcaption>
</figure>
);
}
return (
<figure className="mt-9">
<div
role="img"
aria-label={page.imageAlt}
className="flex aspect-[16/10] w-full items-center justify-center rounded-lg border border-dashed border-neutral-300 bg-neutral-50 px-6 text-center"
>
<div>
<p className="text-sm font-medium text-neutral-900">
{page.imagePlaceholder}
</p>
<p className="mt-1 text-xs text-neutral-500">
Replace with final product screenshot or short demo asset.
</p>
</div>
</div>
<figcaption className="mt-2 text-[11px] text-neutral-500">
Placeholder for final OpenSEO product imagery.
</figcaption>
</figure>
);
}

View File

@ -0,0 +1,10 @@
export const FEATURE_PAGE_SLUGS = {
keywordResearch: "keyword-research",
siteAudit: "site-audit",
backlinkChecker: "backlink-checker",
domainOverview: "domain-overview",
rankTracking: "rank-tracking",
savedKeywords: "saved-keywords",
aiBrandVisibility: "ai-brand-visibility",
aiSearchPrompts: "ai-search-prompts",
};

View File

@ -0,0 +1,646 @@
import { FEATURE_PAGE_SLUGS } from "@/lib/feature-page-slugs";
export type FeaturePage = {
slug: string;
eyebrow: string;
navDescription: string;
title: string;
description: string;
primaryKeyword: string;
secondaryKeywords: string[];
imageAlt: string;
imagePlaceholder: string;
imageSrc?: string;
ctaLabel?: string;
workflows: Array<{
title: string;
description: string;
}>;
metrics: Array<{
label: string;
value: string;
}>;
useCases: string[];
differentiators: string[];
related: Array<{
label: string;
href: string;
}>;
faqs: Array<{
question: string;
answer: string;
}>;
};
export const featurePages = {
keywordResearch: {
slug: FEATURE_PAGE_SLUGS.keywordResearch,
eyebrow: "Keyword Research",
navDescription: "Find keyword ideas and SERPs.",
title: "Keyword research tool for practical SEO planning",
description:
"Find keyword ideas, compare search volume and difficulty, inspect SERP results, and save the opportunities worth building around.",
primaryKeyword: "keyword research tool",
secondaryKeywords: [
"seo keyword research tool",
"free keyword research tool",
"keyword research tools",
],
imageAlt: "OpenSEO keyword research dashboard",
imagePlaceholder: "Keyword research screenshot placeholder",
imageSrc:
"https://imagedelivery.net/ysLOa6bzFaM49Jxok-TAlw/d77077d0-cdf4-4523-0c41-56a7b4861300/public",
workflows: [
{
title: "Research seed topics",
description:
"Start with one or more seeds and expand them into keyword ideas with volume, difficulty, CPC, and intent signals.",
},
{
title: "Inspect the real SERP",
description:
"Open SERP results beside keyword metrics so content decisions are based on the pages ranking for that query.",
},
{
title: "Save and organize opportunities",
description:
"Keep useful keywords in your workspace and tag them for content planning, rank tracking, or AI-agent workflows.",
},
],
metrics: [
{ label: "Search volume", value: "Demand" },
{ label: "Keyword difficulty", value: "Competition" },
{ label: "CPC", value: "Commercial signal" },
{ label: "SERP results", value: "Search context" },
],
useCases: [
"Build a content roadmap from real keyword data.",
"Find lower-competition variants before writing.",
"Group keywords for articles, landing pages, and rank tracking.",
],
differentiators: [
"Open-source SEO workflows you can self-host or run in the managed app.",
"DataForSEO-backed metrics without locking the research process into a black box.",
"MCP access so AI agents can research and save keywords for you.",
],
related: [
{ label: "Rank Tracking", href: "/features/rank-tracking" },
{ label: "Saved Keywords", href: "/features/saved-keywords" },
{ label: "OpenSEO MCP", href: "/features/mcp" },
],
faqs: [
{
question: "What is OpenSEO keyword research best for?",
answer:
"OpenSEO is best for finding SEO keyword ideas, checking demand and difficulty, and turning those ideas into saved keywords you can revisit.",
},
{
question: "Can I use OpenSEO as a free keyword research tool?",
answer:
"OpenSEO is open source and can be self-hosted. The managed app also keeps keyword research tied to transparent usage instead of a large fixed subscription to a closed SEO suite.",
},
{
question: "Does OpenSEO show live search results?",
answer:
"Yes. Keyword research can be paired with SERP inspection so you can see ranking pages alongside the metrics.",
},
],
},
siteAudit: {
slug: FEATURE_PAGE_SLUGS.siteAudit,
eyebrow: "Site Audit",
navDescription: "Audit page-level SEO signals.",
title: "SEO audit tool for finding technical issues fast",
description:
"Crawl a site, collect page-level technical signals, and optionally run Lighthouse checks for performance, SEO, accessibility, and best-practice issues.",
primaryKeyword: "seo audit tool",
secondaryKeywords: [
"seo site audit",
"free seo audit tool",
"seo audit tools",
],
imageAlt: "OpenSEO site audit report",
imagePlaceholder: "Site audit screenshot placeholder",
imageSrc:
"https://imagedelivery.net/ysLOa6bzFaM49Jxok-TAlw/53149e87-0027-4fa8-5d13-bcaab60c7100/public",
workflows: [
{
title: "Run a site crawl",
description:
"Inspect pages for status codes, titles, meta descriptions, headings, indexability signals, image alt coverage, links, response time, and optional Lighthouse findings.",
},
{
title: "Prioritize issues",
description:
"Review crawled pages and optional Lighthouse results so the team can focus on visible page and performance problems.",
},
{
title: "Drill into affected URLs",
description:
"Move into URLs with missing titles, metadata, heading and image-alt signals, status-code issues, response-time data, or optional Lighthouse findings.",
},
],
metrics: [
{ label: "Crawled URLs", value: "Coverage" },
{ label: "Page fields", value: "Checks" },
{ label: "Affected pages", value: "Scope" },
{ label: "Audit history", value: "Progress" },
],
useCases: [
"Audit a new site before publishing SEO work.",
"Find technical issues after a migration or redesign.",
"Export crawled page data and Lighthouse findings for developers and content teams.",
],
differentiators: [
"A practical crawler built into the same workspace as keyword and domain research.",
"Open-source implementation for teams that want to inspect or extend the audit flow.",
"Simple reports that expose page-level signals and optional Lighthouse findings instead of relying only on a generic score.",
],
related: [
{ label: "Domain Overview", href: "/features/domain-overview" },
{ label: "Backlink Checker", href: "/features/backlink-checker" },
{ label: "Keyword Research", href: "/features/keyword-research" },
],
faqs: [
{
question: "What does the OpenSEO site audit tool check?",
answer:
"OpenSEO crawls pages, shows page-level technical signals, and can attach Lighthouse issue details when Lighthouse is enabled.",
},
{
question: "Is OpenSEO a free SEO audit tool?",
answer:
"OpenSEO is open source and can be self-hosted. Managed usage depends on the crawl and data costs behind each workflow.",
},
{
question: "Who should use OpenSEO Site Audit?",
answer:
"It is useful for founders, marketers, agencies, and developers who need a shared crawl report and optional Lighthouse issue export.",
},
],
},
backlinkChecker: {
slug: FEATURE_PAGE_SLUGS.backlinkChecker,
eyebrow: "Backlinks",
navDescription: "Check links and referring domains.",
title: "Backlink checker for understanding a domain's link profile",
description:
"Analyze backlinks, referring domains, and linked pages without separating link research from the rest of your SEO workspace.",
primaryKeyword: "backlink checker",
secondaryKeywords: [
"free backlink checker",
"backlink analysis tool",
"google backlink checker",
],
imageAlt: "OpenSEO backlinks report",
imagePlaceholder: "Backlink checker screenshot placeholder",
imageSrc:
"https://imagedelivery.net/ysLOa6bzFaM49Jxok-TAlw/d97206ed-bd64-447c-2b9e-1b9f07c5ec00/public",
workflows: [
{
title: "Check a domain's backlinks",
description:
"Look up backlinks and referring-domain signals for your site, competitors, or pages you are evaluating.",
},
{
title: "Compare link quality",
description:
"Use backlink rows, referring-domain rows, rank, spam, broken, lost, and nofollow signals to inspect link quality.",
},
{
title: "Filter and export link data",
description:
"Export and filter backlink, referring-domain, and top-page data for your own outreach, competitor research, or cleanup review.",
},
],
metrics: [
{ label: "Backlinks", value: "Links" },
{ label: "Referring domains", value: "Sources" },
{ label: "Target URLs", value: "Distribution" },
{ label: "Rank and spam signals", value: "Quality context" },
],
useCases: [
"See who links to a competitor.",
"Inspect link opportunities for important pages.",
"Understand whether a domain has real authority before investing in content.",
],
differentiators: [
"Backlink analysis sits beside keyword research, domain overview, and audit data.",
"Self-host or adapt backlink reporting for your team's workflow.",
"MCP support lets an AI agent pull backlink context during SEO research.",
],
related: [
{ label: "Domain Overview", href: "/features/domain-overview" },
{ label: "Site Audit", href: "/features/site-audit" },
{ label: "OpenSEO MCP", href: "/features/mcp" },
],
faqs: [
{
question: "What is a backlink checker used for?",
answer:
"A backlink checker helps you understand which sites link to a domain or page, which links have stronger rank, spam, broken, lost, or nofollow signals, and where competitors are earning authority.",
},
{
question: "Can I check competitor backlinks in OpenSEO?",
answer:
"Yes. OpenSEO's backlink workflow is designed for researching your own domain as well as competitor domains.",
},
{
question: "How does backlink research connect to SEO planning?",
answer:
"Backlinks add link-profile context that can inform link-building, digital PR, and competitor research alongside your keyword work.",
},
],
},
domainOverview: {
slug: FEATURE_PAGE_SLUGS.domainOverview,
eyebrow: "Domain Overview",
navDescription: "Analyze competitor visibility.",
title: "Domain analysis tool for competitor SEO research",
description:
"Review a domain's estimated organic traffic, organic keyword count, and ranking keyword and page data before deciding where to compete.",
primaryKeyword: "domain analysis tool",
secondaryKeywords: [
"competitor keyword analysis tool",
"website traffic checker",
"competitor analysis seo tool",
],
imageAlt: "OpenSEO domain overview",
imagePlaceholder: "Domain overview screenshot placeholder",
imageSrc:
"https://imagedelivery.net/ysLOa6bzFaM49Jxok-TAlw/189e22b8-fdf8-46b4-198c-e912beef2300/public",
workflows: [
{
title: "Analyze a domain",
description:
"Start with a domain and get an overview of estimated organic traffic, organic keyword count, top ranking keywords, and top organic pages.",
},
{
title: "Find competitor keywords",
description:
"Inspect keywords a competitor already ranks for and identify topics worth building or defending.",
},
{
title: "Move into deeper research",
description:
"Use domain insights to open keyword research, backlink analysis, or rank tracking without starting over.",
},
],
metrics: [
{ label: "Organic traffic", value: "Visibility" },
{ label: "Organic keywords", value: "Topics" },
{ label: "Top keywords", value: "Rankings" },
{ label: "Top pages", value: "Organic reach" },
],
useCases: [
"Research a competitor before writing a content plan.",
"Estimate a site's organic footprint.",
"Find keyword gaps between your site and the domains already ranking.",
],
differentiators: [
"Domain research connects directly to keyword, backlink, and rank tracking workflows.",
"Built around ranking keywords, estimated traffic, and top pages for practical competitor research.",
"Open-source and self-hostable for teams that want control over their SEO stack.",
],
related: [
{ label: "Keyword Research", href: "/features/keyword-research" },
{ label: "Backlink Checker", href: "/features/backlink-checker" },
{ label: "Rank Tracking", href: "/features/rank-tracking" },
],
faqs: [
{
question: "What does a domain analysis tool show?",
answer:
"It summarizes a domain's organic footprint, including estimated traffic, organic keyword count, ranking keywords, and top organic pages.",
},
{
question: "Can OpenSEO help with competitor keyword analysis?",
answer:
"Yes. Domain Overview is designed to reveal the keywords and topics a competitor is already visible for.",
},
{
question: "Is Domain Overview the same as a traffic checker?",
answer:
"It includes traffic-oriented visibility metrics, but the bigger value is connecting that traffic estimate to ranking keywords and top pages.",
},
],
},
rankTracking: {
slug: FEATURE_PAGE_SLUGS.rankTracking,
eyebrow: "Rank Tracking",
navDescription: "Monitor keyword positions.",
title: "Rank tracker for monitoring keyword positions",
description:
"Track the keywords that matter, optionally compare desktop and mobile results, and keep ranking changes connected to your research workflow.",
primaryKeyword: "rank tracker",
secondaryKeywords: [
"seo rank tracking tool",
"keyword rank tracker",
"google rank tracker",
],
imageAlt: "OpenSEO rank tracking table",
imagePlaceholder: "Rank tracking screenshot placeholder",
imageSrc:
"https://imagedelivery.net/ysLOa6bzFaM49Jxok-TAlw/4a0f8508-1527-46a8-c91c-086456f21c00/public",
workflows: [
{
title: "Add tracked domains",
description:
"Create rank tracking configurations for the domains and locations you care about.",
},
{
title: "Track important keywords",
description:
"Add keywords manually or from ranking suggestions and monitor positions over time.",
},
{
title: "Compare SERP context",
description:
"Review the configured device results, ranking URLs, movement, and available SERP feature signals.",
},
],
metrics: [
{ label: "Desktop rank", value: "When enabled" },
{ label: "Mobile rank", value: "When enabled" },
{ label: "SERP features", value: "Context" },
{ label: "Position change", value: "Movement" },
],
useCases: [
"Monitor target keywords after publishing content.",
"Track launch, migration, and optimization impact.",
"Keep ranking checks close to the keywords your team already researched.",
],
differentiators: [
"Rank tracking is part of the same workspace as discovery, audit, and competitor research.",
"Optional desktop and mobile tracking helps teams avoid one-dimensional rank reports.",
"OpenSEO can expose ranking data to AI agents through MCP.",
],
related: [
{ label: "Keyword Research", href: "/features/keyword-research" },
{ label: "Saved Keywords", href: "/features/saved-keywords" },
{ label: "OpenSEO MCP", href: "/features/mcp" },
],
faqs: [
{
question: "What is a rank tracker?",
answer:
"A rank tracker monitors where a domain appears for selected keywords over time so you can see whether SEO work is improving visibility.",
},
{
question: "Does OpenSEO track mobile and desktop rankings?",
answer:
"OpenSEO rank tracking can be configured for mobile, desktop, or both, so teams can compare devices when both are enabled.",
},
{
question: "How should I choose keywords to track?",
answer:
"Start with keywords tied to important pages, active content work, and competitor opportunities discovered in keyword research.",
},
],
},
savedKeywords: {
slug: FEATURE_PAGE_SLUGS.savedKeywords,
eyebrow: "Saved Keywords",
navDescription: "Organize SEO opportunities.",
title: "Saved keywords for turning SEO research into a plan",
description:
"Keep useful keyword ideas organized so they can inform content planning, rank tracking decisions, and AI-agent workflows.",
primaryKeyword: "saved keywords",
secondaryKeywords: [
"seo keyword list",
"keyword list tool",
"keyword planning",
],
imageAlt: "OpenSEO saved keywords list",
imagePlaceholder: "Saved keywords screenshot placeholder",
imageSrc:
"https://imagedelivery.net/ysLOa6bzFaM49Jxok-TAlw/8938a529-b443-4d4f-9869-c972f3cef900/public",
workflows: [
{
title: "Save promising keywords",
description:
"Collect useful ideas from keyword research instead of losing them after each search.",
},
{
title: "Organize by topic",
description:
"Tag keywords by page, campaign, content cluster, or priority so planning stays readable.",
},
{
title: "Reuse saved keywords across workflows",
description:
"Use saved keywords and tags as a planning reference for rank tracking, content planning, or MCP-powered research.",
},
],
metrics: [
{ label: "Saved ideas", value: "Pipeline" },
{ label: "Tags", value: "Organization" },
{ label: "Volume", value: "Demand" },
{ label: "Difficulty", value: "Priority" },
],
useCases: [
"Tag keyword ideas into topic or page groups from keyword research.",
"Prepare candidate keywords to add to rank tracking.",
"Keep human and AI-agent research in the same workspace.",
],
differentiators: [
"Saved keywords bridge research, tracking, and AI workflows.",
"Saved keywords preserve available metrics like volume, CPC, difficulty, intent, and tags.",
"The workflow stays simple enough for repeated planning sessions.",
],
related: [
{ label: "Keyword Research", href: "/features/keyword-research" },
{ label: "Rank Tracking", href: "/features/rank-tracking" },
{ label: "OpenSEO MCP", href: "/features/mcp" },
],
faqs: [
{
question: "Why save keywords in an SEO tool?",
answer:
"Saved keywords keep research organized so teams can return to the ideas that are worth writing, optimizing, or tracking.",
},
{
question: "Can saved keywords be used with rank tracking?",
answer:
"Yes. Saved keywords are a natural source for deciding which terms should be monitored over time.",
},
{
question: "How do saved keywords fit into SEO planning?",
answer:
"Saved Keywords keeps promising ideas organized so they can inform content planning, rank tracking decisions, and future research.",
},
],
},
aiBrandVisibility: {
slug: FEATURE_PAGE_SLUGS.aiBrandVisibility,
eyebrow: "AI Visibility",
navDescription: "Look up brand mentions in AI search.",
title: "Brand lookup for ChatGPT and Google AI Overview visibility",
description:
"Look up a brand or domain and review ChatGPT and Google AI Overview mentions, cited pages, and related prompts.",
primaryKeyword: "ai visibility tool",
secondaryKeywords: [
"brand visibility ai search",
"ai search visibility",
"answer engine optimization",
],
imageAlt: "OpenSEO AI brand visibility report",
imagePlaceholder: "AI brand visibility screenshot placeholder",
imageSrc:
"https://imagedelivery.net/ysLOa6bzFaM49Jxok-TAlw/cde3e4f8-079f-4890-cb17-371087107400/public",
workflows: [
{
title: "Look up a brand",
description:
"Search for a brand or domain and inspect how ChatGPT and Google AI Overview mention or cite it in available results.",
},
{
title: "Review citations and platforms",
description:
"Review the URLs, domains, and platforms contributing to brand mentions.",
},
{
title: "Find visibility gaps",
description:
"Use cited pages and related prompts as clues for content, reputation, or comparison coverage to investigate.",
},
],
metrics: [
{ label: "Mentions", value: "Presence" },
{ label: "Citations", value: "Sources" },
{ label: "Platforms", value: "Surfaces" },
{ label: "Cited domains", value: "Sources" },
],
useCases: [
"See whether ChatGPT and Google AI Overview data mention or cite your brand or domain.",
"Find pages and domains cited alongside brand mentions.",
"Use cited sources and prompts to plan content experiments for answer-engine visibility.",
],
differentiators: [
"AI visibility sits beside classic SEO research instead of replacing it.",
"The workflow focuses on concrete sources and mentions, not vague AI hype.",
"OpenSEO helps teams connect AI mention and citation research to concrete SEO planning.",
],
related: [
{ label: "AI Search Prompts", href: "/features/ai-search-prompts" },
{ label: "Domain Overview", href: "/features/domain-overview" },
{ label: "OpenSEO MCP", href: "/features/mcp" },
],
faqs: [
{
question: "What is AI brand visibility?",
answer:
"AI brand visibility is how often your brand or domain appears in available ChatGPT and Google AI Overview mention and citation data.",
},
{
question: "How is AI visibility different from traditional SEO?",
answer:
"Traditional SEO focuses on rankings and pages. OpenSEO's AI visibility workflow looks at mentions, cited pages, related prompts, and platform-level metrics from supported AI-search sources.",
},
{
question: "Should AI visibility replace keyword research?",
answer:
"No. It should sit beside keyword, domain, backlink, and audit data so teams can understand both search rankings and answer coverage.",
},
],
},
aiSearchPrompts: {
slug: FEATURE_PAGE_SLUGS.aiSearchPrompts,
eyebrow: "Prompt Explorer",
navDescription: "Compare answers across supported models.",
title: "AI search prompt explorer for visibility research",
description:
"Run the same prompt across supported AI models, compare the answers, and review citations when they are returned.",
primaryKeyword: "ai search visibility",
secondaryKeywords: [
"chatgpt search visibility",
"ai search prompts",
"answer engine optimization tool",
],
imageAlt: "OpenSEO prompt explorer",
imagePlaceholder: "Prompt explorer screenshot placeholder",
imageSrc:
"https://imagedelivery.net/ysLOa6bzFaM49Jxok-TAlw/9f3d38f2-aa97-417c-ca74-ae378654d700/public",
workflows: [
{
title: "Test category prompts",
description:
"Compare answers to the questions your customers might ask AI tools.",
},
{
title: "Inspect web-backed answers",
description:
"When web search is enabled, inspect the pages and domains cited by model responses.",
},
{
title: "Check brand mentions",
description:
"Highlight a brand and see whether each model mentions it in the answer or cited sources.",
},
],
metrics: [
{ label: "Prompts", value: "Questions" },
{ label: "Web context", value: "Sources" },
{ label: "Web search country", value: "Regional context" },
{ label: "Brand mentions", value: "Presence" },
],
useCases: [
"Compare how supported AI models answer the same prompt.",
"Review which pages and domains appear in cited sources.",
"Check whether a brand appears in AI answers and citations.",
],
differentiators: [
"Prompt research lives in the same workspace as domain, keyword, and brand visibility workflows.",
"OpenSEO treats AI search as a research layer, not a replacement for SEO fundamentals.",
"OpenSEO MCP exposes keyword, SERP, domain, backlink, saved keyword, and rank-tracking tools to AI agents.",
],
related: [
{ label: "AI Brand Visibility", href: "/features/ai-brand-visibility" },
{ label: "Keyword Research", href: "/features/keyword-research" },
{ label: "OpenSEO MCP", href: "/features/mcp" },
],
faqs: [
{
question: "What is an AI search prompt explorer?",
answer:
"It lets teams run the same prompt across supported AI models, compare answers, and inspect citation URLs returned with supported model responses.",
},
{
question: "Why does prompt research matter for SEO?",
answer:
"Prompts reveal comparison, problem, and buying questions that can inform pages, guides, and the pages or domains that appear in returned citations.",
},
{
question: "Can this help with answer engine optimization?",
answer:
"Yes. Prompt Explorer is a starting point for mapping prompt responses and returned citations back to source pages and possible SEO follow-up work.",
},
],
},
} satisfies Record<string, FeaturePage>;
export const featureGroups = [
{
label: "Keyword workflows",
description: "Find, organize, and monitor the keywords that matter.",
pages: [
featurePages.keywordResearch,
featurePages.savedKeywords,
featurePages.rankTracking,
],
},
{
label: "Domain research",
description: "Understand competitors, backlinks, and technical health.",
pages: [
featurePages.domainOverview,
featurePages.backlinkChecker,
featurePages.siteAudit,
],
},
{
label: "AI visibility",
description: "Research AI search prompts, citations, and brand visibility.",
pages: [featurePages.aiBrandVisibility, featurePages.aiSearchPrompts],
},
] as const;

View File

@ -19,7 +19,16 @@ import { Route as GuidesSplatRouteImport } from './routes/guides/$'
import { Route as ApiSubscribeRouteImport } from './routes/api/subscribe' import { Route as ApiSubscribeRouteImport } from './routes/api/subscribe'
import { Route as ApiEventRouteImport } from './routes/api/event' import { Route as ApiEventRouteImport } from './routes/api/event'
import { Route as MarketingPricingRouteImport } from './routes/_marketing/pricing' import { Route as MarketingPricingRouteImport } from './routes/_marketing/pricing'
import { Route as MarketingFeaturesIndexRouteImport } from './routes/_marketing/features/index'
import { Route as MarketingFeaturesSiteAuditRouteImport } from './routes/_marketing/features/site-audit'
import { Route as MarketingFeaturesSavedKeywordsRouteImport } from './routes/_marketing/features/saved-keywords'
import { Route as MarketingFeaturesRankTrackingRouteImport } from './routes/_marketing/features/rank-tracking'
import { Route as MarketingFeaturesMcpRouteImport } from './routes/_marketing/features/mcp' import { Route as MarketingFeaturesMcpRouteImport } from './routes/_marketing/features/mcp'
import { Route as MarketingFeaturesKeywordResearchRouteImport } from './routes/_marketing/features/keyword-research'
import { Route as MarketingFeaturesDomainOverviewRouteImport } from './routes/_marketing/features/domain-overview'
import { Route as MarketingFeaturesBacklinkCheckerRouteImport } from './routes/_marketing/features/backlink-checker'
import { Route as MarketingFeaturesAiSearchPromptsRouteImport } from './routes/_marketing/features/ai-search-prompts'
import { Route as MarketingFeaturesAiBrandVisibilityRouteImport } from './routes/_marketing/features/ai-brand-visibility'
const TermsAndConditionsRoute = TermsAndConditionsRouteImport.update({ const TermsAndConditionsRoute = TermsAndConditionsRouteImport.update({
id: '/terms-and-conditions', id: '/terms-and-conditions',
@ -70,11 +79,64 @@ const MarketingPricingRoute = MarketingPricingRouteImport.update({
path: '/pricing', path: '/pricing',
getParentRoute: () => MarketingRoute, getParentRoute: () => MarketingRoute,
} as any) } as any)
const MarketingFeaturesIndexRoute = MarketingFeaturesIndexRouteImport.update({
id: '/features/',
path: '/features/',
getParentRoute: () => MarketingRoute,
} as any)
const MarketingFeaturesSiteAuditRoute =
MarketingFeaturesSiteAuditRouteImport.update({
id: '/features/site-audit',
path: '/features/site-audit',
getParentRoute: () => MarketingRoute,
} as any)
const MarketingFeaturesSavedKeywordsRoute =
MarketingFeaturesSavedKeywordsRouteImport.update({
id: '/features/saved-keywords',
path: '/features/saved-keywords',
getParentRoute: () => MarketingRoute,
} as any)
const MarketingFeaturesRankTrackingRoute =
MarketingFeaturesRankTrackingRouteImport.update({
id: '/features/rank-tracking',
path: '/features/rank-tracking',
getParentRoute: () => MarketingRoute,
} as any)
const MarketingFeaturesMcpRoute = MarketingFeaturesMcpRouteImport.update({ const MarketingFeaturesMcpRoute = MarketingFeaturesMcpRouteImport.update({
id: '/features/mcp', id: '/features/mcp',
path: '/features/mcp', path: '/features/mcp',
getParentRoute: () => MarketingRoute, getParentRoute: () => MarketingRoute,
} as any) } as any)
const MarketingFeaturesKeywordResearchRoute =
MarketingFeaturesKeywordResearchRouteImport.update({
id: '/features/keyword-research',
path: '/features/keyword-research',
getParentRoute: () => MarketingRoute,
} as any)
const MarketingFeaturesDomainOverviewRoute =
MarketingFeaturesDomainOverviewRouteImport.update({
id: '/features/domain-overview',
path: '/features/domain-overview',
getParentRoute: () => MarketingRoute,
} as any)
const MarketingFeaturesBacklinkCheckerRoute =
MarketingFeaturesBacklinkCheckerRouteImport.update({
id: '/features/backlink-checker',
path: '/features/backlink-checker',
getParentRoute: () => MarketingRoute,
} as any)
const MarketingFeaturesAiSearchPromptsRoute =
MarketingFeaturesAiSearchPromptsRouteImport.update({
id: '/features/ai-search-prompts',
path: '/features/ai-search-prompts',
getParentRoute: () => MarketingRoute,
} as any)
const MarketingFeaturesAiBrandVisibilityRoute =
MarketingFeaturesAiBrandVisibilityRouteImport.update({
id: '/features/ai-brand-visibility',
path: '/features/ai-brand-visibility',
getParentRoute: () => MarketingRoute,
} as any)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof MarketingIndexRoute '/': typeof MarketingIndexRoute
@ -86,7 +148,16 @@ export interface FileRoutesByFullPath {
'/guides/$': typeof GuidesSplatRoute '/guides/$': typeof GuidesSplatRoute
'/js/script.js': typeof JsScriptDotjsRoute '/js/script.js': typeof JsScriptDotjsRoute
'/guides/': typeof GuidesIndexRoute '/guides/': typeof GuidesIndexRoute
'/features/ai-brand-visibility': typeof MarketingFeaturesAiBrandVisibilityRoute
'/features/ai-search-prompts': typeof MarketingFeaturesAiSearchPromptsRoute
'/features/backlink-checker': typeof MarketingFeaturesBacklinkCheckerRoute
'/features/domain-overview': typeof MarketingFeaturesDomainOverviewRoute
'/features/keyword-research': typeof MarketingFeaturesKeywordResearchRoute
'/features/mcp': typeof MarketingFeaturesMcpRoute '/features/mcp': typeof MarketingFeaturesMcpRoute
'/features/rank-tracking': typeof MarketingFeaturesRankTrackingRoute
'/features/saved-keywords': typeof MarketingFeaturesSavedKeywordsRoute
'/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
'/features/': typeof MarketingFeaturesIndexRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/privacy': typeof PrivacyRoute '/privacy': typeof PrivacyRoute
@ -98,7 +169,16 @@ export interface FileRoutesByTo {
'/js/script.js': typeof JsScriptDotjsRoute '/js/script.js': typeof JsScriptDotjsRoute
'/': typeof MarketingIndexRoute '/': typeof MarketingIndexRoute
'/guides': typeof GuidesIndexRoute '/guides': typeof GuidesIndexRoute
'/features/ai-brand-visibility': typeof MarketingFeaturesAiBrandVisibilityRoute
'/features/ai-search-prompts': typeof MarketingFeaturesAiSearchPromptsRoute
'/features/backlink-checker': typeof MarketingFeaturesBacklinkCheckerRoute
'/features/domain-overview': typeof MarketingFeaturesDomainOverviewRoute
'/features/keyword-research': typeof MarketingFeaturesKeywordResearchRoute
'/features/mcp': typeof MarketingFeaturesMcpRoute '/features/mcp': typeof MarketingFeaturesMcpRoute
'/features/rank-tracking': typeof MarketingFeaturesRankTrackingRoute
'/features/saved-keywords': typeof MarketingFeaturesSavedKeywordsRoute
'/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
'/features': typeof MarketingFeaturesIndexRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
@ -112,7 +192,16 @@ export interface FileRoutesById {
'/js/script.js': typeof JsScriptDotjsRoute '/js/script.js': typeof JsScriptDotjsRoute
'/_marketing/': typeof MarketingIndexRoute '/_marketing/': typeof MarketingIndexRoute
'/guides/': typeof GuidesIndexRoute '/guides/': typeof GuidesIndexRoute
'/_marketing/features/ai-brand-visibility': typeof MarketingFeaturesAiBrandVisibilityRoute
'/_marketing/features/ai-search-prompts': typeof MarketingFeaturesAiSearchPromptsRoute
'/_marketing/features/backlink-checker': typeof MarketingFeaturesBacklinkCheckerRoute
'/_marketing/features/domain-overview': typeof MarketingFeaturesDomainOverviewRoute
'/_marketing/features/keyword-research': typeof MarketingFeaturesKeywordResearchRoute
'/_marketing/features/mcp': typeof MarketingFeaturesMcpRoute '/_marketing/features/mcp': typeof MarketingFeaturesMcpRoute
'/_marketing/features/rank-tracking': typeof MarketingFeaturesRankTrackingRoute
'/_marketing/features/saved-keywords': typeof MarketingFeaturesSavedKeywordsRoute
'/_marketing/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
'/_marketing/features/': typeof MarketingFeaturesIndexRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
@ -126,7 +215,16 @@ export interface FileRouteTypes {
| '/guides/$' | '/guides/$'
| '/js/script.js' | '/js/script.js'
| '/guides/' | '/guides/'
| '/features/ai-brand-visibility'
| '/features/ai-search-prompts'
| '/features/backlink-checker'
| '/features/domain-overview'
| '/features/keyword-research'
| '/features/mcp' | '/features/mcp'
| '/features/rank-tracking'
| '/features/saved-keywords'
| '/features/site-audit'
| '/features/'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: to:
| '/privacy' | '/privacy'
@ -138,7 +236,16 @@ export interface FileRouteTypes {
| '/js/script.js' | '/js/script.js'
| '/' | '/'
| '/guides' | '/guides'
| '/features/ai-brand-visibility'
| '/features/ai-search-prompts'
| '/features/backlink-checker'
| '/features/domain-overview'
| '/features/keyword-research'
| '/features/mcp' | '/features/mcp'
| '/features/rank-tracking'
| '/features/saved-keywords'
| '/features/site-audit'
| '/features'
id: id:
| '__root__' | '__root__'
| '/_marketing' | '/_marketing'
@ -151,7 +258,16 @@ export interface FileRouteTypes {
| '/js/script.js' | '/js/script.js'
| '/_marketing/' | '/_marketing/'
| '/guides/' | '/guides/'
| '/_marketing/features/ai-brand-visibility'
| '/_marketing/features/ai-search-prompts'
| '/_marketing/features/backlink-checker'
| '/_marketing/features/domain-overview'
| '/_marketing/features/keyword-research'
| '/_marketing/features/mcp' | '/_marketing/features/mcp'
| '/_marketing/features/rank-tracking'
| '/_marketing/features/saved-keywords'
| '/_marketing/features/site-audit'
| '/_marketing/features/'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
@ -237,6 +353,34 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MarketingPricingRouteImport preLoaderRoute: typeof MarketingPricingRouteImport
parentRoute: typeof MarketingRoute parentRoute: typeof MarketingRoute
} }
'/_marketing/features/': {
id: '/_marketing/features/'
path: '/features'
fullPath: '/features/'
preLoaderRoute: typeof MarketingFeaturesIndexRouteImport
parentRoute: typeof MarketingRoute
}
'/_marketing/features/site-audit': {
id: '/_marketing/features/site-audit'
path: '/features/site-audit'
fullPath: '/features/site-audit'
preLoaderRoute: typeof MarketingFeaturesSiteAuditRouteImport
parentRoute: typeof MarketingRoute
}
'/_marketing/features/saved-keywords': {
id: '/_marketing/features/saved-keywords'
path: '/features/saved-keywords'
fullPath: '/features/saved-keywords'
preLoaderRoute: typeof MarketingFeaturesSavedKeywordsRouteImport
parentRoute: typeof MarketingRoute
}
'/_marketing/features/rank-tracking': {
id: '/_marketing/features/rank-tracking'
path: '/features/rank-tracking'
fullPath: '/features/rank-tracking'
preLoaderRoute: typeof MarketingFeaturesRankTrackingRouteImport
parentRoute: typeof MarketingRoute
}
'/_marketing/features/mcp': { '/_marketing/features/mcp': {
id: '/_marketing/features/mcp' id: '/_marketing/features/mcp'
path: '/features/mcp' path: '/features/mcp'
@ -244,19 +388,73 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MarketingFeaturesMcpRouteImport preLoaderRoute: typeof MarketingFeaturesMcpRouteImport
parentRoute: typeof MarketingRoute parentRoute: typeof MarketingRoute
} }
'/_marketing/features/keyword-research': {
id: '/_marketing/features/keyword-research'
path: '/features/keyword-research'
fullPath: '/features/keyword-research'
preLoaderRoute: typeof MarketingFeaturesKeywordResearchRouteImport
parentRoute: typeof MarketingRoute
}
'/_marketing/features/domain-overview': {
id: '/_marketing/features/domain-overview'
path: '/features/domain-overview'
fullPath: '/features/domain-overview'
preLoaderRoute: typeof MarketingFeaturesDomainOverviewRouteImport
parentRoute: typeof MarketingRoute
}
'/_marketing/features/backlink-checker': {
id: '/_marketing/features/backlink-checker'
path: '/features/backlink-checker'
fullPath: '/features/backlink-checker'
preLoaderRoute: typeof MarketingFeaturesBacklinkCheckerRouteImport
parentRoute: typeof MarketingRoute
}
'/_marketing/features/ai-search-prompts': {
id: '/_marketing/features/ai-search-prompts'
path: '/features/ai-search-prompts'
fullPath: '/features/ai-search-prompts'
preLoaderRoute: typeof MarketingFeaturesAiSearchPromptsRouteImport
parentRoute: typeof MarketingRoute
}
'/_marketing/features/ai-brand-visibility': {
id: '/_marketing/features/ai-brand-visibility'
path: '/features/ai-brand-visibility'
fullPath: '/features/ai-brand-visibility'
preLoaderRoute: typeof MarketingFeaturesAiBrandVisibilityRouteImport
parentRoute: typeof MarketingRoute
}
} }
} }
interface MarketingRouteChildren { interface MarketingRouteChildren {
MarketingPricingRoute: typeof MarketingPricingRoute MarketingPricingRoute: typeof MarketingPricingRoute
MarketingIndexRoute: typeof MarketingIndexRoute MarketingIndexRoute: typeof MarketingIndexRoute
MarketingFeaturesAiBrandVisibilityRoute: typeof MarketingFeaturesAiBrandVisibilityRoute
MarketingFeaturesAiSearchPromptsRoute: typeof MarketingFeaturesAiSearchPromptsRoute
MarketingFeaturesBacklinkCheckerRoute: typeof MarketingFeaturesBacklinkCheckerRoute
MarketingFeaturesDomainOverviewRoute: typeof MarketingFeaturesDomainOverviewRoute
MarketingFeaturesKeywordResearchRoute: typeof MarketingFeaturesKeywordResearchRoute
MarketingFeaturesMcpRoute: typeof MarketingFeaturesMcpRoute MarketingFeaturesMcpRoute: typeof MarketingFeaturesMcpRoute
MarketingFeaturesRankTrackingRoute: typeof MarketingFeaturesRankTrackingRoute
MarketingFeaturesSavedKeywordsRoute: typeof MarketingFeaturesSavedKeywordsRoute
MarketingFeaturesSiteAuditRoute: typeof MarketingFeaturesSiteAuditRoute
MarketingFeaturesIndexRoute: typeof MarketingFeaturesIndexRoute
} }
const MarketingRouteChildren: MarketingRouteChildren = { const MarketingRouteChildren: MarketingRouteChildren = {
MarketingPricingRoute: MarketingPricingRoute, MarketingPricingRoute: MarketingPricingRoute,
MarketingIndexRoute: MarketingIndexRoute, MarketingIndexRoute: MarketingIndexRoute,
MarketingFeaturesAiBrandVisibilityRoute:
MarketingFeaturesAiBrandVisibilityRoute,
MarketingFeaturesAiSearchPromptsRoute: MarketingFeaturesAiSearchPromptsRoute,
MarketingFeaturesBacklinkCheckerRoute: MarketingFeaturesBacklinkCheckerRoute,
MarketingFeaturesDomainOverviewRoute: MarketingFeaturesDomainOverviewRoute,
MarketingFeaturesKeywordResearchRoute: MarketingFeaturesKeywordResearchRoute,
MarketingFeaturesMcpRoute: MarketingFeaturesMcpRoute, MarketingFeaturesMcpRoute: MarketingFeaturesMcpRoute,
MarketingFeaturesRankTrackingRoute: MarketingFeaturesRankTrackingRoute,
MarketingFeaturesSavedKeywordsRoute: MarketingFeaturesSavedKeywordsRoute,
MarketingFeaturesSiteAuditRoute: MarketingFeaturesSiteAuditRoute,
MarketingFeaturesIndexRoute: MarketingFeaturesIndexRoute,
} }
const MarketingRouteWithChildren = MarketingRoute._addFileChildren( const MarketingRouteWithChildren = MarketingRoute._addFileChildren(

View File

@ -1,6 +1,7 @@
import { createFileRoute, Link, Outlet } from "@tanstack/react-router"; import { createFileRoute, Link, Outlet } from "@tanstack/react-router";
import { useState } from "react"; import { useState } from "react";
import { SiteFooter } from "@/components/site-footer"; import { SiteFooter } from "@/components/site-footer";
import { featureGroups } from "@/lib/feature-pages";
function GitHubIcon({ size = 18 }: { size?: number }) { function GitHubIcon({ size = 18 }: { size?: number }) {
return ( return (
@ -31,12 +32,7 @@ function MarketingLayout() {
OpenSEO OpenSEO
</Link> </Link>
<div className="flex items-center gap-4 sm:gap-5"> <div className="flex items-center gap-4 sm:gap-5">
<Link <FeatureDropdown />
to="/guides"
className="text-sm text-neutral-600 hover:text-neutral-900 transition-colors"
>
Guides
</Link>
<Link <Link
to="/pricing" to="/pricing"
className="text-sm text-neutral-600 hover:text-neutral-900 transition-colors" className="text-sm text-neutral-600 hover:text-neutral-900 transition-colors"
@ -68,6 +64,78 @@ function MarketingLayout() {
); );
} }
function FeatureDropdown() {
return (
<div className="relative group">
<Link
to="/features"
className="text-sm text-neutral-600 transition-colors hover:text-neutral-900 md:hidden"
>
Features
</Link>
<button
type="button"
className="hidden text-sm text-neutral-600 transition-colors hover:text-neutral-900 md:inline-flex"
>
Features
</button>
<div className="pointer-events-none absolute left-1/2 top-full z-20 hidden w-[560px] -translate-x-1/2 pt-4 opacity-0 transition md:block group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100">
<div className="rounded-lg border border-neutral-200 bg-white p-5 shadow-xl shadow-neutral-900/10">
<div className="grid grid-cols-2 gap-x-8 gap-y-6">
{featureGroups.map((group) => (
<div key={group.label}>
<p className="text-xs font-semibold uppercase tracking-wide text-neutral-500">
{group.label}
</p>
<div className="mt-3 space-y-1">
{group.pages.map((page) => (
<a
key={page.slug}
href={`/features/${page.slug}`}
className="block rounded-md px-2 py-1.5 transition-colors hover:bg-neutral-50"
>
<span className="block text-sm font-semibold text-neutral-900">
{page.eyebrow}
</span>
<span className="mt-0.5 block text-xs leading-relaxed text-neutral-600">
{page.navDescription}
</span>
</a>
))}
</div>
</div>
))}
<div>
<p className="text-xs font-semibold uppercase tracking-wide text-neutral-500">
AI agents
</p>
<div className="mt-3 space-y-2">
<a
href="/features/mcp"
className="block rounded-md p-2 transition-colors hover:bg-neutral-50"
>
<span className="text-sm font-semibold text-neutral-900">
OpenSEO MCP
</span>
<span className="mt-0.5 block text-xs leading-relaxed text-neutral-600">
Connect Claude, Codex, and agents.
</span>
</a>
<a
href="/features"
className="block rounded-md border border-neutral-200 bg-neutral-50 px-2 py-1.5 text-sm font-medium text-neutral-900 transition-colors hover:border-neutral-900"
>
View all features <span aria-hidden="true">&rarr;</span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
function useNewsletter() { function useNewsletter() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [status, setStatus] = useState< const [status, setStatus] = useState<

View File

@ -0,0 +1,18 @@
import { createFileRoute } from "@tanstack/react-router";
import { FeaturePageTemplate } from "@/components/feature-page";
import { featurePages } from "@/lib/feature-pages";
import { buildPageSeo } from "@/lib/seo";
const page = featurePages.aiBrandVisibility;
export const Route = createFileRoute("/_marketing/features/ai-brand-visibility")({
head: () =>
buildPageSeo({
title: "AI Brand Visibility Tool",
description: page.description,
path: "/features/ai-brand-visibility",
titleSuffix: "OpenSEO",
imageAlt: page.imageAlt,
}),
component: () => <FeaturePageTemplate page={page} />,
});

View File

@ -0,0 +1,18 @@
import { createFileRoute } from "@tanstack/react-router";
import { FeaturePageTemplate } from "@/components/feature-page";
import { featurePages } from "@/lib/feature-pages";
import { buildPageSeo } from "@/lib/seo";
const page = featurePages.aiSearchPrompts;
export const Route = createFileRoute("/_marketing/features/ai-search-prompts")({
head: () =>
buildPageSeo({
title: "AI Search Prompt Explorer",
description: page.description,
path: "/features/ai-search-prompts",
titleSuffix: "OpenSEO",
imageAlt: page.imageAlt,
}),
component: () => <FeaturePageTemplate page={page} />,
});

View File

@ -0,0 +1,18 @@
import { createFileRoute } from "@tanstack/react-router";
import { FeaturePageTemplate } from "@/components/feature-page";
import { featurePages } from "@/lib/feature-pages";
import { buildPageSeo } from "@/lib/seo";
const page = featurePages.backlinkChecker;
export const Route = createFileRoute("/_marketing/features/backlink-checker")({
head: () =>
buildPageSeo({
title: "Backlink Checker",
description: page.description,
path: "/features/backlink-checker",
titleSuffix: "OpenSEO",
imageAlt: page.imageAlt,
}),
component: () => <FeaturePageTemplate page={page} />,
});

View File

@ -0,0 +1,18 @@
import { createFileRoute } from "@tanstack/react-router";
import { FeaturePageTemplate } from "@/components/feature-page";
import { featurePages } from "@/lib/feature-pages";
import { buildPageSeo } from "@/lib/seo";
const page = featurePages.domainOverview;
export const Route = createFileRoute("/_marketing/features/domain-overview")({
head: () =>
buildPageSeo({
title: "Domain Analysis Tool",
description: page.description,
path: "/features/domain-overview",
titleSuffix: "OpenSEO",
imageAlt: page.imageAlt,
}),
component: () => <FeaturePageTemplate page={page} />,
});

View File

@ -0,0 +1,100 @@
import { createFileRoute } from "@tanstack/react-router";
import { featureGroups } from "@/lib/feature-pages";
import { buildPageSeo } from "@/lib/seo";
const featuresDescription =
"Explore OpenSEO's open-source SEO tools for AI-agent workflows, keyword research, rank tracking, backlinks, site audits, competitor analysis, and AI visibility.";
export const Route = createFileRoute("/_marketing/features/")({
head: () =>
buildPageSeo({
title: "OpenSEO Features",
description: featuresDescription,
path: "/features",
titleSuffix: "OpenSEO",
}),
component: FeaturesIndex,
});
function FeaturesIndex() {
return (
<>
<p className="text-sm font-medium text-neutral-500">
Open-source SEO tools
</p>
<h1 className="mt-3 text-3xl font-bold leading-tight tracking-tight">
A delightful suite of SEO tools
</h1>
<p className="mt-4 leading-relaxed text-neutral-700">
Research keywords, track rankings, audit sites, and understand your AI
visibility from one modern platform.
</p>
<div className="mt-10 space-y-12">
<section>
<div className="border-b border-neutral-200 pb-3">
<h2 className="text-xl font-semibold text-neutral-900">
AI agent workflows
</h2>
<p className="mt-1 text-sm leading-relaxed text-neutral-600">
Let supported MCP clients research keywords, SERPs, domains, and
backlinks through OpenSEO.
</p>
</div>
<a
href="/features/mcp"
className="mt-5 block rounded-lg border border-neutral-200 bg-white p-5 transition-colors hover:border-neutral-900"
>
<p className="text-xs font-medium text-neutral-500">OpenSEO MCP</p>
<h3 className="mt-2 text-lg font-semibold text-neutral-900">
OpenSEO MCP for your AI agent
</h3>
<p className="mt-2 text-sm leading-relaxed text-neutral-600">
Connect OpenSEO to Claude, Codex, and supported MCP clients so
agents can call OpenSEO research tools with authorized project
context.
</p>
<p className="mt-3 text-sm font-medium text-neutral-900">
Explore MCP <span aria-hidden="true">&rarr;</span>
</p>
</a>
</section>
{featureGroups.map((group) => (
<section key={group.label}>
<div className="border-b border-neutral-200 pb-3">
<h2 className="text-xl font-semibold text-neutral-900">
{group.label}
</h2>
<p className="mt-1 text-sm leading-relaxed text-neutral-600">
{group.description}
</p>
</div>
<div className="mt-5 grid gap-4 md:grid-cols-2">
{group.pages.map((page) => (
<a
key={page.slug}
href={`/features/${page.slug}`}
className="rounded-lg border border-neutral-200 bg-white p-5 transition-colors hover:border-neutral-900"
>
<p className="text-xs font-medium text-neutral-500">
{page.eyebrow}
</p>
<h3 className="mt-2 text-lg font-semibold text-neutral-900">
{page.title}
</h3>
<p className="mt-2 text-sm leading-relaxed text-neutral-600">
{page.description}
</p>
<p className="mt-3 text-sm font-medium text-neutral-900">
Explore feature <span aria-hidden="true">&rarr;</span>
</p>
</a>
))}
</div>
</section>
))}
</div>
</>
);
}

View File

@ -0,0 +1,18 @@
import { createFileRoute } from "@tanstack/react-router";
import { FeaturePageTemplate } from "@/components/feature-page";
import { featurePages } from "@/lib/feature-pages";
import { buildPageSeo } from "@/lib/seo";
const page = featurePages.keywordResearch;
export const Route = createFileRoute("/_marketing/features/keyword-research")({
head: () =>
buildPageSeo({
title: "Keyword Research Tool",
description: page.description,
path: "/features/keyword-research",
titleSuffix: "OpenSEO",
imageAlt: page.imageAlt,
}),
component: () => <FeaturePageTemplate page={page} />,
});

View File

@ -4,7 +4,7 @@ import { useState } from "react";
import { buildPageSeo } from "@/lib/seo"; import { buildPageSeo } from "@/lib/seo";
const mcpDescription = const mcpDescription =
"Connect OpenSEO MCP to your AI agent so it can research keywords, SERPs, domains, backlinks, and rank tracking data."; "Connect OpenSEO MCP so compatible AI clients can call keyword, SERP, domain, backlink, saved keyword, and rank-tracking tools.";
const toolCategories = [ const toolCategories = [
{ {
@ -16,7 +16,7 @@ const toolCategories = [
}, },
{ {
title: "Get SERP results", title: "Get SERP results",
description: "See live Google results for a keyword.", description: "See SERP results for a keyword.",
}, },
{ {
title: "Get rank tracking positions", title: "Get rank tracking positions",
@ -24,7 +24,7 @@ const toolCategories = [
}, },
{ {
title: "Get saved keywords", title: "Get saved keywords",
description: "Pull your saved keyword lists.", description: "Pull saved keywords from OpenSEO.",
}, },
{ {
title: "Save keywords", title: "Save keywords",
@ -70,8 +70,10 @@ function McpPage() {
OpenSEO MCP for your AI agent OpenSEO MCP for your AI agent
</h1> </h1>
<p className="mt-4 text-neutral-700 leading-relaxed"> <p className="mt-4 text-neutral-700 leading-relaxed">
Connect OpenSEO to your AI agent so that it can research keywords, Connect OpenSEO to your AI agent so your MCP client can call OpenSEO
SERPs, domains, backlinks, and rank tracking data for you. tools for keyword research, SERP results, domain overview, domain
keywords, backlink overview, saved keywords, and rank-tracking
positions.
</p> </p>
<a <a
@ -85,10 +87,10 @@ function McpPage() {
<section className="mt-12"> <section className="mt-12">
<h2 className="text-xl font-semibold">What it does</h2> <h2 className="text-xl font-semibold">What it does</h2>
<p className="mt-2 text-sm leading-relaxed text-neutral-600"> <p className="mt-2 text-sm leading-relaxed text-neutral-600">
MCP lets your AI do the most tedious parts of your job faster. Connect MCP lets compatible AI clients call OpenSEO research tools directly.
OpenSEO once, then ask your agent to research keywords, inspect search After connecting OpenSEO through a supported MCP client, you can ask
results, compare domains, and save useful ideas back to your your agent to research keywords, inspect search results, compare
workspace. domains, and save useful ideas back to your workspace.
</p> </p>
</section> </section>
@ -121,8 +123,8 @@ function McpPage() {
<h2 className="text-xl font-semibold">Set up OpenSEO MCP</h2> <h2 className="text-xl font-semibold">Set up OpenSEO MCP</h2>
<p className="mt-2 text-sm leading-relaxed text-neutral-600"> <p className="mt-2 text-sm leading-relaxed text-neutral-600">
The first connection sends you through OpenSEO login. After The first connection sends you through OpenSEO login. After
authorization, your AI client can call OpenSEO tools using your authorization, your MCP client can call OpenSEO tools using the
workspace permissions. The MCP server URL is{" "} authorized OpenSEO project context and scopes. The MCP server URL is{" "}
<code>https://app.openseo.so/mcp</code>. For the most current setup UI <code>https://app.openseo.so/mcp</code>. For the most current setup UI
and a copyable endpoint, open{" "} and a copyable endpoint, open{" "}
<a <a
@ -145,8 +147,9 @@ function McpPage() {
</div> </div>
<p className="mt-5 text-sm leading-relaxed text-neutral-600"> <p className="mt-5 text-sm leading-relaxed text-neutral-600">
Follow the setup steps for Claude, Claude Code, Codex, or any Follow the setup steps for Claude, Claude Code, Codex, or MCP clients
MCP-compatible AI tool. that support remote HTTP MCP servers and the required authorization
flow.
</p> </p>
<div className="mt-5 divide-y divide-neutral-200 overflow-hidden rounded-lg border border-neutral-200 bg-neutral-50"> <div className="mt-5 divide-y divide-neutral-200 overflow-hidden rounded-lg border border-neutral-200 bg-neutral-50">

View File

@ -0,0 +1,18 @@
import { createFileRoute } from "@tanstack/react-router";
import { FeaturePageTemplate } from "@/components/feature-page";
import { featurePages } from "@/lib/feature-pages";
import { buildPageSeo } from "@/lib/seo";
const page = featurePages.rankTracking;
export const Route = createFileRoute("/_marketing/features/rank-tracking")({
head: () =>
buildPageSeo({
title: "Rank Tracker",
description: page.description,
path: "/features/rank-tracking",
titleSuffix: "OpenSEO",
imageAlt: page.imageAlt,
}),
component: () => <FeaturePageTemplate page={page} />,
});

View File

@ -0,0 +1,18 @@
import { createFileRoute } from "@tanstack/react-router";
import { FeaturePageTemplate } from "@/components/feature-page";
import { featurePages } from "@/lib/feature-pages";
import { buildPageSeo } from "@/lib/seo";
const page = featurePages.savedKeywords;
export const Route = createFileRoute("/_marketing/features/saved-keywords")({
head: () =>
buildPageSeo({
title: "Saved Keyword Lists",
description: page.description,
path: "/features/saved-keywords",
titleSuffix: "OpenSEO",
imageAlt: page.imageAlt,
}),
component: () => <FeaturePageTemplate page={page} />,
});

View File

@ -0,0 +1,18 @@
import { createFileRoute } from "@tanstack/react-router";
import { FeaturePageTemplate } from "@/components/feature-page";
import { featurePages } from "@/lib/feature-pages";
import { buildPageSeo } from "@/lib/seo";
const page = featurePages.siteAudit;
export const Route = createFileRoute("/_marketing/features/site-audit")({
head: () =>
buildPageSeo({
title: "SEO Audit Tool",
description: page.description,
path: "/features/site-audit",
titleSuffix: "OpenSEO",
imageAlt: page.imageAlt,
}),
component: () => <FeaturePageTemplate page={page} />,
});