Add strategy library index and navigation (#479)
* feat(web): add strategy library index * fix(web): align strategy library review feedback * fix(web): reorder resource links
This commit is contained in:
parent
bd402844fa
commit
a1ed6ece4b
@ -96,9 +96,11 @@ function getLibraryPaths(dir = LIBRARY_ROUTES_DIR, segments = []) {
|
||||
const slug = entry.name.replace(/\.tsx$/i, "");
|
||||
const pathSegments = slug === "index" ? segments : [...segments, slug];
|
||||
|
||||
return pathSegments.length > 0
|
||||
? [`/library/${pathSegments.join("/")}`]
|
||||
: [];
|
||||
return [
|
||||
pathSegments.length > 0
|
||||
? `/library/${pathSegments.join("/")}`
|
||||
: "/library",
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { DocsBody } from "fumadocs-ui/page";
|
||||
import { buildBreadcrumbJsonLd } from "@/lib/seo";
|
||||
|
||||
const LIBRARY_PILLAR_PATH = "/library/keyword-research";
|
||||
const KEYWORD_RESEARCH_LIBRARY_PATH = "/library/keyword-research";
|
||||
|
||||
type LibrarySpokePageProps = {
|
||||
title: string;
|
||||
description?: string;
|
||||
crumb: string;
|
||||
path: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
@ -14,20 +16,37 @@ export function LibrarySpokePage({
|
||||
title,
|
||||
description,
|
||||
crumb,
|
||||
path,
|
||||
children,
|
||||
}: LibrarySpokePageProps) {
|
||||
const breadcrumbLd = buildBreadcrumbJsonLd([
|
||||
{ name: "Strategy Library", path: "/library" },
|
||||
{ name: "Keyword Research", path: KEYWORD_RESEARCH_LIBRARY_PATH },
|
||||
{ name: crumb, path },
|
||||
]);
|
||||
|
||||
return (
|
||||
<article className="mx-auto max-w-3xl text-neutral-900">
|
||||
<header className="mb-10 border-b border-[var(--color-border-subtle)] pb-8">
|
||||
<p className="text-sm text-[var(--color-brand-muted)]">
|
||||
<nav
|
||||
aria-label="Breadcrumb"
|
||||
className="text-sm text-[var(--color-brand-muted)]"
|
||||
>
|
||||
<a
|
||||
href={LIBRARY_PILLAR_PATH}
|
||||
href="/library"
|
||||
className="font-medium text-[var(--color-brand-accent)]"
|
||||
>
|
||||
Strategy Library
|
||||
</a>{" "}
|
||||
/{" "}
|
||||
<a
|
||||
href={KEYWORD_RESEARCH_LIBRARY_PATH}
|
||||
className="font-medium text-[var(--color-brand-accent)]"
|
||||
>
|
||||
Keyword Research
|
||||
</a>{" "}
|
||||
/ <span>{crumb}</span>
|
||||
</p>
|
||||
</nav>
|
||||
<h1 className="mt-3 text-4xl font-semibold leading-tight tracking-tight text-neutral-950 md:text-5xl">
|
||||
{title}
|
||||
</h1>
|
||||
@ -43,6 +62,12 @@ export function LibrarySpokePage({
|
||||
</DocsBody>
|
||||
|
||||
<LibrarySpokeCta />
|
||||
|
||||
<script
|
||||
type="application/ld+json"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }}
|
||||
/>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@ -51,11 +76,11 @@ function LibrarySpokeCta() {
|
||||
return (
|
||||
<section className="mt-14 rounded-xl border border-[var(--color-border-subtle)] bg-white p-6">
|
||||
<p className="text-xl font-semibold tracking-tight text-neutral-950">
|
||||
Run every play in this guide
|
||||
Run this strategy in OpenSEO
|
||||
</p>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
OpenSEO connects your Search Console and expands your seeds. Open
|
||||
source, free to try, no credit card.
|
||||
Run the MCP prompt in this guide with OpenSEO. OpenSEO is open source,
|
||||
free to try, and does not require a credit card.
|
||||
</p>
|
||||
<div className="mt-5 flex flex-col gap-3 sm:flex-row">
|
||||
<a
|
||||
@ -68,10 +93,10 @@ function LibrarySpokeCta() {
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_PILLAR_PATH}
|
||||
href={KEYWORD_RESEARCH_LIBRARY_PATH}
|
||||
className="inline-flex h-10 items-center justify-center rounded-lg border border-[var(--color-border-subtle)] bg-white px-4 text-sm font-medium text-neutral-950 transition-colors hover:border-neutral-950"
|
||||
>
|
||||
Back to the Strategy Library
|
||||
Back to Keyword Research
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -15,7 +15,7 @@ export function SiteFooter({ className }: { className?: string }) {
|
||||
OpenSEO
|
||||
</Link>
|
||||
|
||||
<div className="mt-6 grid grid-cols-2 gap-8 md:grid-cols-4">
|
||||
<div className="mt-6 grid grid-cols-2 gap-8 md:grid-cols-[repeat(auto-fit,minmax(9rem,1fr))]">
|
||||
<div>
|
||||
<p className="font-semibold text-neutral-900">Features</p>
|
||||
<div className="mt-2 flex flex-col gap-1.5">
|
||||
@ -41,10 +41,19 @@ export function SiteFooter({ className }: { className?: string }) {
|
||||
<div>
|
||||
<p className="font-semibold text-neutral-900">Resources</p>
|
||||
<div className="mt-2 flex flex-col gap-1.5">
|
||||
<a href="/docs/mcp">MCP</a>
|
||||
<a href="/docs/skills">Skills</a>
|
||||
<Link to="/library">Strategy Library</Link>
|
||||
<Link to="/open-source-seo">Why Open Source?</Link>
|
||||
<Link to="/blogs">Blog</Link>
|
||||
<a href="/docs">Docs</a>
|
||||
<a href="/docs/skills">Skills</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-semibold text-neutral-900">Free Tools</p>
|
||||
<div className="mt-2 flex flex-col gap-1.5">
|
||||
<Link to="/backlink-checker">Backlink Checker</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -112,7 +112,7 @@ export const featurePages = {
|
||||
{
|
||||
question: "Can I use OpenSEO as a free keyword research tool?",
|
||||
answer:
|
||||
"Not unlimited: quality keyword data costs money everywhere, which is why the big SEO suites run $100/month and up. OpenSEO is the most affordable option; you can start for free, and paid plans start at $10/month with usage credits included. It's also open source, so you can self-host with your own DataForSEO account.",
|
||||
"Not unlimited: quality keyword data costs money everywhere, which is why the big SEO suites run $100/month and up. You can start OpenSEO for free; the paid plan is $10/month with usage credits included. It's also open source, so you can self-host with your own DataForSEO account.",
|
||||
},
|
||||
{
|
||||
question: "Does OpenSEO show live search results?",
|
||||
@ -123,7 +123,7 @@ export const featurePages = {
|
||||
guides: {
|
||||
title: "The Keyword Research Strategy Library",
|
||||
description:
|
||||
"Practitioner plays that treat keyword research as demand discovery, not a volume spreadsheet. Each guide is a full walkthrough with the copy-paste MCP prompt that runs it.",
|
||||
"Practitioner strategies for using keyword research to discover demand. Each guide includes a full walkthrough and a copy-paste MCP prompt.",
|
||||
items: [
|
||||
{
|
||||
label: "Seed from conversation, not a volume report",
|
||||
@ -139,8 +139,7 @@ export const featurePages = {
|
||||
},
|
||||
{
|
||||
label: "Search-intent mapping (hot / warm / cold)",
|
||||
description:
|
||||
"Label every keyword by buying temperature before you write.",
|
||||
description: "Sort keywords by buying temperature before you write.",
|
||||
href: "/library/keyword-research/search-intent-mapping",
|
||||
},
|
||||
{
|
||||
@ -151,7 +150,7 @@ export const featurePages = {
|
||||
},
|
||||
],
|
||||
cta: {
|
||||
label: "Browse the full Strategy Library",
|
||||
label: "Browse all keyword research strategies",
|
||||
href: "/library/keyword-research",
|
||||
},
|
||||
},
|
||||
|
||||
@ -19,6 +19,21 @@ export function toCanonicalUrl(path: string): string {
|
||||
return new URL(toCanonicalPath(path), `${SITE_URL}/`).href;
|
||||
}
|
||||
|
||||
export function buildBreadcrumbJsonLd(
|
||||
items: Array<{ name: string; path: string }>,
|
||||
) {
|
||||
return {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: items.map((item, index) => ({
|
||||
"@type": "ListItem",
|
||||
position: index + 1,
|
||||
name: item.name,
|
||||
item: toCanonicalUrl(item.path),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
type BuildSeoParams = {
|
||||
title: string;
|
||||
path: string;
|
||||
|
||||
55
web/src/lib/strategy-libraries.ts
Normal file
55
web/src/lib/strategy-libraries.ts
Normal file
@ -0,0 +1,55 @@
|
||||
export type StrategyLibraryItem = {
|
||||
title: string;
|
||||
description: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
export const keywordResearchStrategies: StrategyLibraryItem[] = [
|
||||
{
|
||||
title: "Seed from conversation, not a volume report",
|
||||
description:
|
||||
"Harvest seed keywords from sales calls and support tickets using the language customers already use.",
|
||||
href: "/library/keyword-research/seed-from-conversation",
|
||||
},
|
||||
{
|
||||
title: "What are long-tail keywords, and how to mine them",
|
||||
description:
|
||||
"Find long-tail keywords in People Also Ask, autocomplete, and Search Console queries where your pages already rank.",
|
||||
href: "/library/keyword-research/long-tail-question-mining",
|
||||
},
|
||||
{
|
||||
title: "Search-intent mapping (hot / warm / cold)",
|
||||
description:
|
||||
"Sort keywords by buying temperature before you write, then build high-intent pages first.",
|
||||
href: "/library/keyword-research/search-intent-mapping",
|
||||
},
|
||||
{
|
||||
title: "Cluster keywords into topical hubs",
|
||||
description:
|
||||
"Group keywords by intent and build topical hubs without creating competing pages.",
|
||||
href: "/library/keyword-research/cluster-topical-hubs",
|
||||
},
|
||||
{
|
||||
title: "Programmatic discovery with Search Console",
|
||||
description:
|
||||
"Use MCP to find Search Console queries and pages with room to gain more clicks.",
|
||||
href: "/library/keyword-research/gsc-programmatic-discovery",
|
||||
},
|
||||
{
|
||||
title: "Opportunity sizing & forecasting",
|
||||
description:
|
||||
"Estimate a cluster's difficulty, traffic range, and payback scenarios before you invest.",
|
||||
href: "/library/keyword-research/opportunity-sizing-forecasting",
|
||||
},
|
||||
{
|
||||
title: "Intent beyond Google (Pinterest, AI, LinkedIn)",
|
||||
description: "Research demand on Pinterest, LinkedIn, and AI assistants.",
|
||||
href: "/library/keyword-research/intent-beyond-google",
|
||||
},
|
||||
{
|
||||
title: "Map positioning to real demand",
|
||||
description:
|
||||
"Check whether your category language matches the terms customers search for.",
|
||||
href: "/library/keyword-research/positioning-to-demand",
|
||||
},
|
||||
];
|
||||
@ -28,6 +28,7 @@ import { Route as MarketingPricingRouteImport } from './routes/_marketing/pricin
|
||||
import { Route as MarketingOpenSourceSeoRouteImport } from './routes/_marketing/open-source-seo'
|
||||
import { Route as MarketingGoogleSearchConsoleMcpRouteImport } from './routes/_marketing/google-search-console-mcp'
|
||||
import { Route as MarketingBacklinkCheckerRouteImport } from './routes/_marketing/backlink-checker'
|
||||
import { Route as MarketingLibraryIndexRouteImport } from './routes/_marketing/library/index'
|
||||
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'
|
||||
@ -145,6 +146,11 @@ const MarketingBacklinkCheckerRoute =
|
||||
path: '/backlink-checker',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryIndexRoute = MarketingLibraryIndexRouteImport.update({
|
||||
id: '/library/',
|
||||
path: '/library/',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingFeaturesIndexRoute = MarketingFeaturesIndexRouteImport.update({
|
||||
id: '/features/',
|
||||
path: '/features/',
|
||||
@ -296,6 +302,7 @@ export interface FileRoutesByFullPath {
|
||||
'/features/saved-keywords': typeof MarketingFeaturesSavedKeywordsRoute
|
||||
'/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
|
||||
'/features/': typeof MarketingFeaturesIndexRoute
|
||||
'/library/': typeof MarketingLibraryIndexRoute
|
||||
'/library/keyword-research/cluster-topical-hubs': typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
'/library/keyword-research/gsc-programmatic-discovery': typeof MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute
|
||||
'/library/keyword-research/intent-beyond-google': typeof MarketingLibraryKeywordResearchIntentBeyondGoogleRoute
|
||||
@ -336,6 +343,7 @@ export interface FileRoutesByTo {
|
||||
'/features/saved-keywords': typeof MarketingFeaturesSavedKeywordsRoute
|
||||
'/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
|
||||
'/features': typeof MarketingFeaturesIndexRoute
|
||||
'/library': typeof MarketingLibraryIndexRoute
|
||||
'/library/keyword-research/cluster-topical-hubs': typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
'/library/keyword-research/gsc-programmatic-discovery': typeof MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute
|
||||
'/library/keyword-research/intent-beyond-google': typeof MarketingLibraryKeywordResearchIntentBeyondGoogleRoute
|
||||
@ -378,6 +386,7 @@ export interface FileRoutesById {
|
||||
'/_marketing/features/saved-keywords': typeof MarketingFeaturesSavedKeywordsRoute
|
||||
'/_marketing/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
|
||||
'/_marketing/features/': typeof MarketingFeaturesIndexRoute
|
||||
'/_marketing/library/': typeof MarketingLibraryIndexRoute
|
||||
'/_marketing/library/keyword-research/cluster-topical-hubs': typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
'/_marketing/library/keyword-research/gsc-programmatic-discovery': typeof MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute
|
||||
'/_marketing/library/keyword-research/intent-beyond-google': typeof MarketingLibraryKeywordResearchIntentBeyondGoogleRoute
|
||||
@ -420,6 +429,7 @@ export interface FileRouteTypes {
|
||||
| '/features/saved-keywords'
|
||||
| '/features/site-audit'
|
||||
| '/features/'
|
||||
| '/library/'
|
||||
| '/library/keyword-research/cluster-topical-hubs'
|
||||
| '/library/keyword-research/gsc-programmatic-discovery'
|
||||
| '/library/keyword-research/intent-beyond-google'
|
||||
@ -460,6 +470,7 @@ export interface FileRouteTypes {
|
||||
| '/features/saved-keywords'
|
||||
| '/features/site-audit'
|
||||
| '/features'
|
||||
| '/library'
|
||||
| '/library/keyword-research/cluster-topical-hubs'
|
||||
| '/library/keyword-research/gsc-programmatic-discovery'
|
||||
| '/library/keyword-research/intent-beyond-google'
|
||||
@ -501,6 +512,7 @@ export interface FileRouteTypes {
|
||||
| '/_marketing/features/saved-keywords'
|
||||
| '/_marketing/features/site-audit'
|
||||
| '/_marketing/features/'
|
||||
| '/_marketing/library/'
|
||||
| '/_marketing/library/keyword-research/cluster-topical-hubs'
|
||||
| '/_marketing/library/keyword-research/gsc-programmatic-discovery'
|
||||
| '/_marketing/library/keyword-research/intent-beyond-google'
|
||||
@ -663,6 +675,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof MarketingBacklinkCheckerRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/': {
|
||||
id: '/_marketing/library/'
|
||||
path: '/library'
|
||||
fullPath: '/library/'
|
||||
preLoaderRoute: typeof MarketingLibraryIndexRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/features/': {
|
||||
id: '/_marketing/features/'
|
||||
path: '/features'
|
||||
@ -824,6 +843,7 @@ interface MarketingRouteChildren {
|
||||
MarketingFeaturesSavedKeywordsRoute: typeof MarketingFeaturesSavedKeywordsRoute
|
||||
MarketingFeaturesSiteAuditRoute: typeof MarketingFeaturesSiteAuditRoute
|
||||
MarketingFeaturesIndexRoute: typeof MarketingFeaturesIndexRoute
|
||||
MarketingLibraryIndexRoute: typeof MarketingLibraryIndexRoute
|
||||
MarketingLibraryKeywordResearchClusterTopicalHubsRoute: typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute: typeof MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute
|
||||
MarketingLibraryKeywordResearchIntentBeyondGoogleRoute: typeof MarketingLibraryKeywordResearchIntentBeyondGoogleRoute
|
||||
@ -854,6 +874,7 @@ const MarketingRouteChildren: MarketingRouteChildren = {
|
||||
MarketingFeaturesSavedKeywordsRoute: MarketingFeaturesSavedKeywordsRoute,
|
||||
MarketingFeaturesSiteAuditRoute: MarketingFeaturesSiteAuditRoute,
|
||||
MarketingFeaturesIndexRoute: MarketingFeaturesIndexRoute,
|
||||
MarketingLibraryIndexRoute: MarketingLibraryIndexRoute,
|
||||
MarketingLibraryKeywordResearchClusterTopicalHubsRoute:
|
||||
MarketingLibraryKeywordResearchClusterTopicalHubsRoute,
|
||||
MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute:
|
||||
|
||||
@ -59,10 +59,11 @@ function getMobileNavItems(githubStarCount: string) {
|
||||
{
|
||||
label: "Resources",
|
||||
links: [
|
||||
{ label: "Blog", href: "/blogs" },
|
||||
{ label: "Docs", href: "/docs" },
|
||||
{ label: "MCP Setup", href: "/docs/mcp" },
|
||||
{ label: "Skills", href: "/docs/skills" },
|
||||
{ label: "Strategy Library", href: "/library" },
|
||||
{ label: "Blog", href: "/blogs" },
|
||||
{ label: "Docs", href: "/docs" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -274,16 +275,6 @@ function MarketingLayout() {
|
||||
|
||||
function ResourcesDropdown() {
|
||||
const resources = [
|
||||
{
|
||||
label: "Blog",
|
||||
href: "/blogs",
|
||||
description: "SEO articles and guides.",
|
||||
},
|
||||
{
|
||||
label: "Docs",
|
||||
href: "/docs",
|
||||
description: "Setup, MCP, skills, and self-hosting guides.",
|
||||
},
|
||||
{
|
||||
label: "MCP",
|
||||
href: "/docs/mcp",
|
||||
@ -294,6 +285,21 @@ function ResourcesDropdown() {
|
||||
href: "/docs/skills",
|
||||
description: "Focused OpenSEO workflows.",
|
||||
},
|
||||
{
|
||||
label: "Strategy Library",
|
||||
href: "/library",
|
||||
description: "Practical SEO strategies grouped by topic.",
|
||||
},
|
||||
{
|
||||
label: "Blog",
|
||||
href: "/blogs",
|
||||
description: "SEO articles and guides.",
|
||||
},
|
||||
{
|
||||
label: "Docs",
|
||||
href: "/docs",
|
||||
description: "Setup, MCP, skills, and self-hosting guides.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
87
web/src/routes/_marketing/library/index.tsx
Normal file
87
web/src/routes/_marketing/library/index.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
import { keywordResearchStrategies } from "@/lib/strategy-libraries";
|
||||
|
||||
const PATH = "/library";
|
||||
const description =
|
||||
"Browse practical SEO strategies for finding search demand, mapping intent, and planning pages.";
|
||||
const featuredStrategies = keywordResearchStrategies.slice(0, 4);
|
||||
|
||||
export const Route = createFileRoute("/_marketing/library/")({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title: "SEO Strategy Library",
|
||||
description,
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO",
|
||||
}),
|
||||
component: StrategyLibraryIndexPage,
|
||||
});
|
||||
|
||||
function StrategyLibraryIndexPage() {
|
||||
return (
|
||||
<article className="mx-auto max-w-5xl">
|
||||
<header className="max-w-3xl">
|
||||
<p className="text-sm font-medium text-[var(--color-brand-accent)]">
|
||||
Resources
|
||||
</p>
|
||||
<h1 className="mt-3 text-4xl font-semibold leading-tight tracking-tight text-neutral-950 md:text-6xl">
|
||||
SEO Strategy Library
|
||||
</h1>
|
||||
<p className="mt-5 text-lg leading-8 text-[var(--color-brand-muted)]">
|
||||
Find search demand and decide which pages to build. Strategies are
|
||||
grouped by topic so you can start with the problem you need to solve.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Browse by topic
|
||||
</h2>
|
||||
<div className="mt-5 grid gap-4">
|
||||
<a
|
||||
href="/library/keyword-research"
|
||||
className="block rounded-lg border border-[var(--color-border-subtle)] bg-white p-6 transition-colors hover:border-neutral-900"
|
||||
>
|
||||
<h3 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Keyword Research
|
||||
</h3>
|
||||
<p className="mt-3 max-w-xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
Start with customer language, expand into long-tail demand, map
|
||||
search intent, and decide which opportunities deserve a page.
|
||||
</p>
|
||||
<p className="mt-5 text-sm font-medium text-neutral-950">
|
||||
View all {keywordResearchStrategies.length} strategies{" "}
|
||||
<span aria-hidden="true">→</span>
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Start with a strategy
|
||||
</h2>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
Go straight to a workflow if you already know what you need to do.
|
||||
</p>
|
||||
<div className="mt-5 grid gap-4 md:grid-cols-2">
|
||||
{featuredStrategies.map((strategy) => (
|
||||
<a
|
||||
key={strategy.href}
|
||||
href={strategy.href}
|
||||
className="rounded-lg border border-[var(--color-border-subtle)] bg-white p-5 transition-colors hover:border-neutral-900"
|
||||
>
|
||||
<h3 className="text-base font-semibold text-neutral-950">
|
||||
{strategy.title} <span aria-hidden="true">→</span>
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
{strategy.description}
|
||||
</p>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@ -6,6 +6,8 @@ import Content, {
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
const PATH = "/library/keyword-research/cluster-topical-hubs";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/cluster-topical-hubs",
|
||||
)({
|
||||
@ -14,7 +16,7 @@ export const Route = createFileRoute(
|
||||
title:
|
||||
"Keyword Clustering: Turn a Keyword List into Topical Hubs (and Fix Cannibalization)",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/cluster-topical-hubs",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
@ -23,6 +25,7 @@ export const Route = createFileRoute(
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Cluster keywords into topical hubs"
|
||||
path={PATH}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
|
||||
@ -6,6 +6,8 @@ import Content, {
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
const PATH = "/library/keyword-research/gsc-programmatic-discovery";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/gsc-programmatic-discovery",
|
||||
)({
|
||||
@ -13,7 +15,7 @@ export const Route = createFileRoute(
|
||||
buildPageSeo({
|
||||
title: "Search Console Keyword Research: Striking-Distance Queries",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/gsc-programmatic-discovery",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
@ -22,6 +24,7 @@ export const Route = createFileRoute(
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Programmatic discovery with Search Console"
|
||||
path={PATH}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
|
||||
@ -1,59 +1,9 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
import { buildBreadcrumbJsonLd, buildPageSeo } from "@/lib/seo";
|
||||
import { keywordResearchStrategies } from "@/lib/strategy-libraries";
|
||||
|
||||
const PATH = "/library/keyword-research";
|
||||
|
||||
const strategies = [
|
||||
{
|
||||
title: "Seed from conversation, not a volume report",
|
||||
description:
|
||||
"Harvest seed keywords from sales calls and support tickets: the phrasings tools never surface.",
|
||||
href: "/library/keyword-research/seed-from-conversation",
|
||||
},
|
||||
{
|
||||
title: "What are long-tail keywords, and how to mine them",
|
||||
description:
|
||||
"PAA fan-out, autocomplete harvesting, and the GSC queries you already half-rank for.",
|
||||
href: "/library/keyword-research/long-tail-question-mining",
|
||||
},
|
||||
{
|
||||
title: "Search-intent mapping (hot / warm / cold)",
|
||||
description:
|
||||
"Label every keyword by buying temperature before you write. Build the hot pages first.",
|
||||
href: "/library/keyword-research/search-intent-mapping",
|
||||
},
|
||||
{
|
||||
title: "Cluster keywords into topical hubs",
|
||||
description:
|
||||
"One page per intent, one hub per topic, plus the fix for keyword cannibalization.",
|
||||
href: "/library/keyword-research/cluster-topical-hubs",
|
||||
},
|
||||
{
|
||||
title: "Programmatic discovery with Search Console",
|
||||
description:
|
||||
"Query-mine your own GSC by MCP: striking-distance keywords, zero-click pages, dark queries.",
|
||||
href: "/library/keyword-research/gsc-programmatic-discovery",
|
||||
},
|
||||
{
|
||||
title: "Opportunity sizing & forecasting",
|
||||
description:
|
||||
"Size a cluster before you invest: difficulty, traffic ceiling, and modeled payback windows.",
|
||||
href: "/library/keyword-research/opportunity-sizing-forecasting",
|
||||
},
|
||||
{
|
||||
title: "Intent beyond Google (Pinterest, AI, LinkedIn)",
|
||||
description:
|
||||
"Where queries happen when they don't happen in a search box, AI assistants included.",
|
||||
href: "/library/keyword-research/intent-beyond-google",
|
||||
},
|
||||
{
|
||||
title: "Map positioning to real demand",
|
||||
description:
|
||||
"Check whether your category language matches what customers actually search.",
|
||||
href: "/library/keyword-research/positioning-to-demand",
|
||||
},
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: "How do you do keyword research for SEO?",
|
||||
@ -63,17 +13,17 @@ const faqs = [
|
||||
{
|
||||
question: "How do you do keyword research for free?",
|
||||
answer:
|
||||
"The discovery half runs on sources you already have: customer conversations, Google's autocomplete and People Also Ask, and your Search Console. Quality SEO data (volume, difficulty, live SERPs) is difficult to get, which is why SaaS tools run $100/month and up. OpenSEO is the most affordable option, starting at $10/month, and you can start for free.",
|
||||
"The discovery half runs on sources you already have: customer conversations, Google's autocomplete and People Also Ask, and your Search Console. Quality SEO data costs money, which is why the big SEO suites run $100/month and up. You can start OpenSEO for free; the paid plan starts at $10/month and includes $10 of usage. If you need more, you can buy top-up credits.",
|
||||
},
|
||||
{
|
||||
question: "Can you do keyword research without Google Keyword Planner?",
|
||||
answer:
|
||||
"Yes, and for SEO you should. Planner groups variants and hides zero-ad-demand queries. Use it to sanity-check commercial value, not to discover topics.",
|
||||
"Yes. Keyword Planner combines close variants and reports approximate search-volume data designed for ad planning. Use it to sanity-check commercial value rather than as your only source for discovering topics.",
|
||||
},
|
||||
{
|
||||
question: "What are the 3 types of keywords?",
|
||||
answer:
|
||||
"By intent: informational, commercial/navigational, transactional. By shape: head, mid-tail, long-tail. The useful planning question is always intent first, shape second.",
|
||||
"There is no universal set of three. OpenSEO uses four intent types: informational, navigational, commercial, and transactional. By shape, terms are often grouped as head, mid-tail, and long-tail.",
|
||||
},
|
||||
{
|
||||
question: "How do you do keyword research for a blog?",
|
||||
@ -92,12 +42,17 @@ const faqLd = {
|
||||
})),
|
||||
};
|
||||
|
||||
const breadcrumbLd = buildBreadcrumbJsonLd([
|
||||
{ name: "Strategy Library", path: "/library" },
|
||||
{ name: "Keyword Research", path: PATH },
|
||||
]);
|
||||
|
||||
export const Route = createFileRoute("/_marketing/library/keyword-research/")({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title: "How to Do Keyword Research: The Strategy Library",
|
||||
description:
|
||||
"Eight demand-discovery strategies drawn from interviews with working SEOs, each with a workflow you can run in OpenSEO.",
|
||||
"Eight demand-discovery strategies drawn from interviews with working SEOs, each with a workflow and an OpenSEO MCP prompt for its data-backed steps.",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO",
|
||||
}),
|
||||
@ -108,76 +63,70 @@ function KeywordResearchLibraryPage() {
|
||||
return (
|
||||
<article className="mx-auto max-w-5xl">
|
||||
<header className="max-w-3xl">
|
||||
<p className="text-sm font-medium text-[var(--color-brand-accent)]">
|
||||
Strategy Library
|
||||
</p>
|
||||
<nav
|
||||
aria-label="Breadcrumb"
|
||||
className="text-sm text-[var(--color-brand-muted)]"
|
||||
>
|
||||
<a
|
||||
href="/library"
|
||||
className="font-medium text-[var(--color-brand-accent)]"
|
||||
>
|
||||
Strategy Library
|
||||
</a>{" "}
|
||||
/ <span>Keyword Research</span>
|
||||
</nav>
|
||||
<h1 className="mt-3 text-4xl font-semibold leading-tight tracking-tight text-neutral-950 md:text-6xl">
|
||||
The Keyword Research Strategy Library
|
||||
</h1>
|
||||
<p className="mt-5 text-lg leading-8 text-[var(--color-brand-muted)]">
|
||||
Eight demand-discovery strategies drawn from interviews with working
|
||||
SEOs, each with a workflow you can run in OpenSEO.
|
||||
SEOs, each with a workflow and an OpenSEO MCP prompt for its
|
||||
data-backed steps.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
How to do keyword research: demand discovery, not a volume spreadsheet
|
||||
How to do keyword research through demand discovery
|
||||
</h2>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
Most guides teach you to export a volume report and sort descending.
|
||||
These strategies start earlier, where demand originates: customer
|
||||
language, question mining, your own Search Console. They end with
|
||||
pages mapped to intent, not keywords stuffed into paragraphs. Each
|
||||
strategy includes a walkthrough and a copy-paste MCP prompt for the
|
||||
workflow.
|
||||
strategy includes a walkthrough and a copy-paste MCP prompt for its
|
||||
data-backed steps.
|
||||
</p>
|
||||
<div className="mt-6 grid gap-4 md:grid-cols-2">
|
||||
{strategies.map((strategy, index) => {
|
||||
{keywordResearchStrategies.map((strategy, index) => {
|
||||
const number = String(index + 1).padStart(2, "0");
|
||||
const body = (
|
||||
<>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<span className="font-mono text-sm tabular-nums text-[var(--color-brand-accent)]">
|
||||
{number}
|
||||
</span>
|
||||
{strategy.href ? null : (
|
||||
<span className="rounded-full border border-[var(--color-border-subtle)] px-2 py-0.5 font-mono text-[10px] uppercase tracking-wider text-[var(--color-brand-muted)]">
|
||||
Next up
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="font-mono text-sm tabular-nums text-[var(--color-brand-accent)]">
|
||||
{number}
|
||||
</span>
|
||||
<h3 className="mt-3 text-base font-semibold text-neutral-950">
|
||||
{strategy.title}
|
||||
{strategy.href ? (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="ml-1 text-[var(--color-brand-accent)]"
|
||||
>
|
||||
→
|
||||
</span>
|
||||
) : null}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="ml-1 text-[var(--color-brand-accent)]"
|
||||
>
|
||||
→
|
||||
</span>
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
{strategy.description}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
return strategy.href ? (
|
||||
return (
|
||||
<a
|
||||
key={strategy.title}
|
||||
key={strategy.href}
|
||||
href={strategy.href}
|
||||
className="rounded-lg border border-[var(--color-border-subtle)] bg-white p-5 transition-colors hover:border-neutral-900"
|
||||
>
|
||||
{body}
|
||||
</a>
|
||||
) : (
|
||||
<div
|
||||
key={strategy.title}
|
||||
className="rounded-lg border border-[var(--color-border-subtle)] bg-white p-5"
|
||||
>
|
||||
{body}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@ -185,47 +134,50 @@ function KeywordResearchLibraryPage() {
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
What Google Keyword Planner hides (and what to use instead)
|
||||
How to supplement Google Keyword Planner
|
||||
</h2>
|
||||
<p className="mt-3 max-w-3xl text-sm leading-6 text-neutral-700">
|
||||
Keyword Planner is an ads tool wearing an SEO costume: it buckets
|
||||
close variants into one number, rounds volumes into bands, and hides
|
||||
everything with no ad demand. That's fine for bidding. It's blinding
|
||||
for content strategy.
|
||||
Keyword Planner is built for planning search-ad campaigns. Its
|
||||
historical metrics combine close variants and report approximate
|
||||
monthly searches, so treat them as directional rather than a complete
|
||||
content-discovery dataset.
|
||||
</p>
|
||||
<p className="mt-3 max-w-3xl text-sm leading-6 text-neutral-700">
|
||||
The strategies in this library replace it with three sources you can
|
||||
inspect: your customers' language (strategy 01), Google's own question
|
||||
surfaces (strategy 02), and your Search Console reality (strategy 05).
|
||||
Volume data still matters, but it's the <em>last</em> filter, not the
|
||||
first.
|
||||
The strategies in this library supplement it with three sources you
|
||||
can inspect: your customers' language (strategy 01), Google's own
|
||||
question surfaces (strategy 02), and your Search Console reality
|
||||
(strategy 05). Volume data still matters, but it is most useful after
|
||||
you understand the customer language and search intent.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Free keyword research tools for every strategy
|
||||
Free sources for keyword discovery
|
||||
</h2>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
Google's free surfaces (autocomplete, People Also Ask) plus your own
|
||||
Search Console do the discovery. You can run each workflow in{" "}
|
||||
Search Console support the discovery. You can run the data-backed
|
||||
parts of these workflows with{" "}
|
||||
<a
|
||||
href="/features/keyword-research"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
OpenSEO's keyword research
|
||||
</a>
|
||||
, connected to your live Search Console. Open source, self-hostable,
|
||||
and scriptable through the{" "}
|
||||
</a>{" "}
|
||||
and your connected Search Console. OpenSEO is open source and
|
||||
self-hostable, and its{" "}
|
||||
<a
|
||||
href="/docs/mcp"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
MCP
|
||||
</a>{" "}
|
||||
so your AI assistant can run the whole workflow. Quality SEO data is
|
||||
why the big suites run $100/month and up; OpenSEO starts at $10/month,
|
||||
and you can start for free.
|
||||
lets a compatible AI assistant query both sources while it works
|
||||
through the workflow. Quality SEO data is why the big suites run
|
||||
$100/month and up; OpenSEO's paid plan starts at $10/month and includes
|
||||
$10 of usage, with top-ups available if you need more. You can start
|
||||
for free.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@ -253,8 +205,8 @@ function KeywordResearchLibraryPage() {
|
||||
The Keyword Research Playbook
|
||||
</h2>
|
||||
<p className="mt-2 max-w-xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
The strategies as a working PDF: checklists, the MCP prompts, and
|
||||
the keyword-map template. Ungated.
|
||||
Eight interview-backed strategies and a seed-to-brief checklist in a
|
||||
working PDF. Ungated.
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
@ -273,6 +225,11 @@ function KeywordResearchLibraryPage() {
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqLd) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }}
|
||||
/>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import Content, {
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
const PATH = "/library/keyword-research/intent-beyond-google";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/intent-beyond-google",
|
||||
)({
|
||||
@ -13,7 +15,7 @@ export const Route = createFileRoute(
|
||||
buildPageSeo({
|
||||
title: "Keyword Research Beyond Google: Pinterest, LinkedIn and AI",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/intent-beyond-google",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
@ -22,6 +24,7 @@ export const Route = createFileRoute(
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Intent beyond Google"
|
||||
path={PATH}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
|
||||
@ -6,6 +6,8 @@ import Content, {
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
const PATH = "/library/keyword-research/long-tail-question-mining";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/long-tail-question-mining",
|
||||
)({
|
||||
@ -13,7 +15,7 @@ export const Route = createFileRoute(
|
||||
buildPageSeo({
|
||||
title: "What Are Long-Tail Keywords? How to Find and Use Them",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/long-tail-question-mining",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
@ -22,6 +24,7 @@ export const Route = createFileRoute(
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Long-tail & question mining"
|
||||
path={PATH}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
|
||||
@ -6,6 +6,8 @@ import Content, {
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
const PATH = "/library/keyword-research/opportunity-sizing-forecasting";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/opportunity-sizing-forecasting",
|
||||
)({
|
||||
@ -13,7 +15,7 @@ export const Route = createFileRoute(
|
||||
buildPageSeo({
|
||||
title: "SEO Forecasting: Size a Keyword Opportunity Before You Build",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/opportunity-sizing-forecasting",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
@ -22,6 +24,7 @@ export const Route = createFileRoute(
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Opportunity sizing & forecasting"
|
||||
path={PATH}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
|
||||
@ -6,6 +6,8 @@ import Content, {
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
const PATH = "/library/keyword-research/positioning-to-demand";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/positioning-to-demand",
|
||||
)({
|
||||
@ -13,7 +15,7 @@ export const Route = createFileRoute(
|
||||
buildPageSeo({
|
||||
title: "Does Your Positioning Have Search Demand Behind It?",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/positioning-to-demand",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
@ -22,6 +24,7 @@ export const Route = createFileRoute(
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Map positioning to real demand"
|
||||
path={PATH}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
|
||||
@ -6,6 +6,8 @@ import Content, {
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
const PATH = "/library/keyword-research/search-intent-mapping";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/search-intent-mapping",
|
||||
)({
|
||||
@ -13,7 +15,7 @@ export const Route = createFileRoute(
|
||||
buildPageSeo({
|
||||
title: "What Is Search Intent? Mapping Keywords Hot, Warm, and Cold",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/search-intent-mapping",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
@ -22,6 +24,7 @@ export const Route = createFileRoute(
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Search-intent mapping"
|
||||
path={PATH}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
|
||||
@ -6,6 +6,8 @@ import Content, {
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
const PATH = "/library/keyword-research/seed-from-conversation";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/seed-from-conversation",
|
||||
)({
|
||||
@ -14,7 +16,7 @@ export const Route = createFileRoute(
|
||||
title:
|
||||
"Seed Keywords from Customer Conversations (Keyword Research Without a Paid Tool)",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/seed-from-conversation",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
@ -23,6 +25,7 @@ export const Route = createFileRoute(
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Seed from conversation"
|
||||
path={PATH}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user