Keyword Research Strategy Library + feature-page guides section + Search Everywhere interview (#128)
This commit is contained in:
parent
633876c0ef
commit
a4ede67ebb
@ -24,9 +24,11 @@ When explaining traffic growth, Sam should frame OpenSEO as a tool for making be
|
||||
|
||||
## Hosted plan and credits
|
||||
|
||||
The managed OpenSEO app costs $10/month.
|
||||
Hosted OpenSEO is free to try. Signing up requires no credit card, and new accounts include $0.50 of trial credits to test credit-using features before subscribing.
|
||||
|
||||
The managed plan includes:
|
||||
The paid managed plan costs $10/month.
|
||||
|
||||
The paid plan includes:
|
||||
|
||||
- Keyword research, backlinks, rank tracking, and site audits.
|
||||
- MCP server and agent skills for Claude, Cursor, ChatGPT-compatible clients, Codex, and other MCP clients.
|
||||
@ -36,9 +38,9 @@ The managed plan includes:
|
||||
|
||||
OpenSEO uses usage credits for features that query paid SEO data providers, especially DataForSEO. Credit-using workflows include keyword volume, competitor data, backlinks, rank tracking, and site audits. Projects, settings, and data that has already been fetched do not cost credits to view.
|
||||
|
||||
Top-up credits can be purchased if monthly credits run out. Top-up credits roll over and do not expire. Monthly included credits reset each billing cycle.
|
||||
Subscribers can purchase top-up credits if monthly credits run out. Top-up credits roll over and do not expire. Monthly included credits reset each billing cycle. Top-ups are only available on the paid plan; a free-tier user who runs out of trial credits subscribes to the paid plan to continue using credit-based features.
|
||||
|
||||
Hosted users need an active subscription to use OpenSEO. If credits run out, OpenSEO should not create unexpected bills; users can buy more credits.
|
||||
Running out of credits never creates unexpected bills. Credit-using features stop working until the user has credits again.
|
||||
|
||||
## Why OpenSEO for SEO consultants and agencies
|
||||
|
||||
|
||||
73
web/content/blogs/search-everywhere-optimization.md
Normal file
73
web/content/blogs/search-everywhere-optimization.md
Normal file
@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "Search Everywhere: Why Finder's GM Killed His SEO Team"
|
||||
description: "Zak Ali took SEO from the terminal to the executive suite. His verdict: optimize for every surface people find answers on, and measure retention instead of rankings."
|
||||
author: "Jeremy Rivera"
|
||||
date: "2026-07-22"
|
||||
---
|
||||
|
||||
Very few people have run SEO as a line item _and_ signed off on the budget that funds it. Zak Ali has done both. He started in search in 2015, co-founding the news blog Rant and growing it to roughly 100,000 readers on rapid-response articles and long-form think pieces. In 2018 he joined [Finder](https://www.finder.com/), the editorially independent financial comparison site, as a publisher, then head of growth, and now General Manager of Finder US, where he owns organic, paid, email, CRM, and sales.
|
||||
|
||||
That vantage point has made him unusually blunt about what SEO has become. On a recent [Unscripted SEO conversation](https://unscriptedseo.com/zak-ali-finder-search-everywhere-optimization/), he argued that the job most SEOs are defending no longer exists, and the one that replaced it is bigger.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [SEO didn't shrink. It got absorbed.](#seo-didnt-shrink-it-got-absorbed)
|
||||
- [He dissolved the SEO team on purpose](#he-dissolved-the-seo-team-on-purpose)
|
||||
- [Retention is the KPI nobody puts on the dashboard](#retention-is-the-kpi-nobody-puts-on-the-dashboard)
|
||||
- [AI is putting the customer first again](#ai-is-putting-the-customer-first-again)
|
||||
- [The terminal-native research stack](#the-terminal-native-research-stack)
|
||||
- [Keyword research didn't die, it got directional](#keyword-research-didnt-die-it-got-directional)
|
||||
|
||||
## SEO didn't shrink. It got absorbed.
|
||||
|
||||
The framing that's caught on with practitioners, including AJ Kohn's "[surface optimization](https://www.blindfiveyearold.com/)", is that SEO now sits inside a much larger discipline. Large language models didn't carve out a slice next to search; they wrapped a shell over the top of it that also reaches social, video, and every other place a person can find an answer.
|
||||
|
||||
> LLM really is what took it from search engine optimization to search everywhere optimization. Now it's really about being on every discoverable surface.
|
||||
|
||||
In practice, you can't coast off one surface anymore. Finder now runs what Zak calls "content waterfall" techniques: a written piece becomes a YouTube video, which gets cut into shorts and reels. The written word is the first domino now, not the finished product.
|
||||
|
||||
## He dissolved the SEO team on purpose
|
||||
|
||||
Last May, he shut down Finder US's dedicated SEO team and folded it into a generalist growth org. It wasn't easy; those people had spent years building the discipline. But the logic was simple.
|
||||
|
||||
> There are two types of SEOs that exist today. There are the technical SEOs, and there are SEOs who haven't realized yet that their job is digital PR and branding.
|
||||
|
||||
Editors, in his model, stop being editors of written content and become spokespeople, "niche messengers." A publisher is a marketer whose job is to be wherever the customer is, not to work Google for traffic in isolation.
|
||||
|
||||
## Retention is the KPI nobody puts on the dashboard
|
||||
|
||||
If the surface count is exploding, the economics of chasing each new one get worse. Zak's warning here reframes what an SEO team is for.
|
||||
|
||||
> The traffic you have today is as cheap as it's going to be. It's only going to get more expensive. Retention should be a core KPI of every SEO.
|
||||
|
||||
He's watched Google Discover and News traffic behave like a firehose that shuts off without warning, "very ephemeral." The teams winning there capture the visit into something durable instead of chasing raw impressions. He points to publishers gating even free content behind an email so an ephemeral moment becomes a relationship. For any SEO, that makes the job audience acquisition and cultivation, not just rankings, whether you sell brokerage accounts or, in his example, concrete walls in Florida.
|
||||
|
||||
## AI is putting the customer first again
|
||||
|
||||
Zak pushes back on the idea that "[slop](https://rightthing.agency/articles/your-ai-slop-has-a-fingerprint)" arrived with LLMs. In his read, homogeneous content is what SEO _already_ incentivized: everyone reverse-engineering the P1 result until every article looked the same. The 2023–24 [Helpful Content updates](https://developers.google.com/search/updates/helpful-content-update) were the correction, and the canary was searchers appending "reddit" to escape the sameness.
|
||||
|
||||
> What AI is doing is actually allowing us to put the customer first again. It's forcing companies to deliver more — and the customer actually wins.
|
||||
|
||||
For a YMYL business like Finder, that customer-first bar is non-negotiable anyway: ratings backed by a documented methodology, editorial independence stated in your face, and legally-mandated ordering (a lender comparison can't be sorted by who pays the most). Those "baked-in trust signals" are exactly the kind of thing AI systems are learning to reward.
|
||||
|
||||
## The terminal-native research stack
|
||||
|
||||
Where Zak gets most animated is workflow. Finder US has moved the team into [Claude Code](https://claude.com/claude-code), calling their SEO tooling and Google Workspace over MCP so keyword research and large-scale analysis happen without leaving the terminal. He's blunt that the tooling has flattened:
|
||||
|
||||
> You don't need to pay thousands of dollars for Profound or Otterly. You can build your own LLM tracker with a free DataForSEO tier and AI. Use these tools for direction — then think like your customer.
|
||||
|
||||
His weekly team trainings are now about building a "second brain" in [Obsidian](https://obsidian.md/): dumping your entire SEO process, decision log, and context into markdown so any model can run your playbook. From there he's built a "publisher" agent that does keyword research, customer profiling, the brief, competitor and deep research, internal linking, and the push into the CMS end-to-end.
|
||||
|
||||
This is the shift OpenSEO was built for. The terminal-native workflow works because the data stopped being locked inside thousand-dollar suites, the same flattening Zak is describing. OpenSEO is open source and usage-based, and it ships an [MCP server](/docs/mcp) so an agent can run [keyword research](/features/keyword-research), inspect SERPs, and read your [Search Console](/google-search-console-mcp) data from inside Claude Code, with no dashboard and no context-switching.
|
||||
|
||||
## Keyword research didn't die, it got directional
|
||||
|
||||
None of this means the fundamentals evaporated. Asked which pre-LLM strategies survived, Zak's answer was measured:
|
||||
|
||||
> Keyword research still exists as a directionally helpful tool. People don't really search in keywords anymore — they ask their question directly. But knowing your ICP, technical SEO, schema, speed — all of that still matters a lot.
|
||||
|
||||
The inputs haven't changed; you're attacking them from more angles. That's the premise behind our [Keyword Research Strategy Library](/library/keyword-research): seed demand from real conversations, then validate and expand it in the tool, rather than starting from a volume report.
|
||||
|
||||
---
|
||||
|
||||
_Zak Ali is General Manager of Finder US. From co-founding the news site Rant to running growth and now the U.S. business at [Finder](https://www.finder.com/). Hear the full conversation on the [Unscripted SEO podcast](https://unscriptedseo.com/zak-ali-finder-search-everywhere-optimization/)._
|
||||
49
web/content/marketing/library/cluster-topical-hubs.mdx
Normal file
49
web/content/marketing/library/cluster-topical-hubs.mdx
Normal file
@ -0,0 +1,49 @@
|
||||
---
|
||||
title: "One page per intent, one hub per topic"
|
||||
description: "A 500-row keyword list isn't a strategy. Keyword clustering turns it into a site structure and fixes the cannibalization that's splitting your rankings."
|
||||
---
|
||||
|
||||
## What is keyword clustering?
|
||||
|
||||
Keyword clustering is grouping keywords that deserve the _same page_ because they share one intent. "Keyword clustering", "keyword grouping", and "cluster keywords for seo" are one cluster, so they get one page. "Best keyword clustering tool" is a different intent, so it gets a different page. The unit of SEO is the cluster, not the individual keyword.
|
||||
|
||||
## How to cluster keywords into topical hubs (without a $99/mo tool)
|
||||
|
||||
1. **Start from your expanded list.** [Seeds](/library/keyword-research/seed-from-conversation) → [expansion](/library/keyword-research/long-tail-question-mining) → [intent labels](/library/keyword-research/search-intent-mapping). You want 100+ rows before clustering is worth it.
|
||||
2. **Group by SERP overlap, not word similarity.** Two keywords belong together when Google already ranks the same pages for both. Word-match clustering ("keyword tool" + "keyword tool io") produces false groups.
|
||||
3. **Name each cluster by its intent.** The name becomes the page's working title. If you can't name the intent in one line, the cluster is two.
|
||||
4. **Arrange clusters into a hub.** The head-term cluster is the pillar page; each subtopic cluster is a spoke that links up. Internal links follow intent adjacency, not chronology.
|
||||
5. **Map every cluster to one URL, and record it.** The keyword map (a simple sheet: cluster → URL → status) is the artifact that prevents future cannibalization.
|
||||
|
||||

|
||||
|
||||
## How to fix keyword cannibalization
|
||||
|
||||
Cannibalization is the opposite problem: two of your pages compete for one intent, Google alternates between them, and both rank worse than either would alone.
|
||||
|
||||
- **Find it:** in [Search Console](/google-search-console-mcp), group by query + page. Any query with 2+ of your URLs swapping impressions is a candidate.
|
||||
- **Fix it:** merge the weaker page into the stronger (301), or re-point the weaker page at a genuinely different intent and re-title it.
|
||||
- **Prevent it:** the keyword map from step 5. No new page ships without checking which cluster it claims.
|
||||
|
||||
Run this play with the [OpenSEO MCP](/docs/mcp):
|
||||
|
||||
```text
|
||||
Using the OpenSEO MCP + Search Console: pull my queries grouped by
|
||||
query and page, flag queries where two of my URLs alternate, and
|
||||
propose merge/redirect fixes. Then cluster my researched keywords
|
||||
by shared intent and output a cluster → URL keyword map.
|
||||
```
|
||||
|
||||
## Keyword clustering FAQ
|
||||
|
||||
### What's the best keyword clustering tool?
|
||||
|
||||
For SERP-overlap clustering at scale, paid tools exist, but for most sites, OpenSEO's research + an intent-grouping pass (the MCP prompt above) covers it. Judge tools by whether they cluster on SERP overlap; word-similarity clustering is a toy.
|
||||
|
||||
### Is there a free keyword clustering tool?
|
||||
|
||||
This workflow is the closest thing: the clustering pass runs through the MCP on keywords you've already researched, so there's no separate clustering tool to buy. OpenSEO itself is open source.
|
||||
|
||||
### What is a keyword mapping template?
|
||||
|
||||
A sheet with one row per cluster: primary keyword, supporting keywords, intent, target URL, status. The keyword map above is the working example; copy the structure.
|
||||
48
web/content/marketing/library/long-tail-question-mining.mdx
Normal file
48
web/content/marketing/library/long-tail-question-mining.mdx
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
title: "The queries your competitors can't see"
|
||||
description: "Long-tail keywords are where small sites win. What they are, how to find them without paying for a suite, and how to mine the question queries Google hands you for free."
|
||||
---
|
||||
|
||||
## What are long-tail keywords?
|
||||
|
||||
Long-tail keywords are longer, more specific search queries, usually three or more words, that individually get fewer searches but collectively make up most of Google's traffic. "Keyword research" is a head term; "how to do keyword research for a local restaurant" is long-tail. They bring lower volume, much lower competition, and clearer intent.
|
||||
|
||||
## Long-tail vs short-tail keywords: why the tail converts
|
||||
|
||||
A short-tail query is an audience; a long-tail query is a person with a problem. Someone searching "crm" is browsing. Someone searching "crm for solo real estate agents under $20" is buying. The tail trades volume for intent, and intent is what converts.
|
||||
|
||||
## Long-tail keyword examples
|
||||
|
||||
- **Head:** running shoes → **Tail:** best running shoes for flat feet and plantar fasciitis
|
||||
- **Head:** keyword research → **Tail:** how to do keyword research without a paid tool
|
||||
- **Head:** email marketing → **Tail:** email marketing laws for cold outreach in canada
|
||||
|
||||
## How to find long-tail keywords (3 free methods)
|
||||
|
||||
1. **Mine People Also Ask + autocomplete fan-out.** Type your head term, harvest every PAA question, then re-search each question and harvest again. Two levels deep gives you 30–50 real questions people ask.
|
||||
2. **Pull your own Search Console queries.** Your GSC data is a long-tail generator that no tool can match. Filter [queries with impressions but no clicks](/blogs/dark-queries): those are tails you already half-rank for.
|
||||
3. **Expand seeds in OpenSEO.** Feed your [seed list](/library/keyword-research/seed-from-conversation) into [keyword research](/features/keyword-research) and keep the specific, question-form results with clear intent regardless of volume. "0 volume" queries are measured, not dead.
|
||||
|
||||

|
||||
|
||||
Run this play with the [OpenSEO MCP](/docs/mcp):
|
||||
|
||||
```text
|
||||
Using the OpenSEO MCP: research keywords for the seed "[your topic]",
|
||||
then return only queries with 4+ words or question form (how/what/why).
|
||||
Group them by intent and suggest one page per group.
|
||||
```
|
||||
|
||||
## Long-tail keyword FAQ
|
||||
|
||||
### What are long-tail keywords in SEO?
|
||||
|
||||
Specific multi-word queries with lower individual volume but higher combined traffic and clearer intent than head terms. They're the fastest way for a newer site to rank, because competition concentrates on head terms.
|
||||
|
||||
### How do I use long-tail keywords in content?
|
||||
|
||||
One intent per page. Make the long-tail query the H2 (or H1) verbatim where natural, answer it in the first paragraph, then earn depth below. Don't scatter twenty tails across one page; [cluster related tails](/library/keyword-research/cluster-topical-hubs), then split by [intent](/library/keyword-research/search-intent-mapping).
|
||||
|
||||
### Is there a free long-tail keyword generator?
|
||||
|
||||
Google gives you two: autocomplete and People Also Ask. Your Search Console is the third and best; it's your site's actual tail. OpenSEO connects your Search Console and expands what you find into full keyword lists: open source and free to try.
|
||||
47
web/content/marketing/library/search-intent-mapping.mdx
Normal file
47
web/content/marketing/library/search-intent-mapping.mdx
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: "Rank for the searches that are ready to buy"
|
||||
description: 'Search intent is the "why" behind a query. Map every keyword hot, warm, or cold before you write a word, and you''ll stop producing content that ranks but never converts.'
|
||||
---
|
||||
|
||||
## What is search intent in SEO?
|
||||
|
||||
Search intent is the goal a searcher has when they type a query: to learn, to compare, to find a site, or to buy. Google ranks the page that best satisfies that intent, regardless of who "deserves" it. Optimizing a page against the wrong intent is the most common reason content ranks nowhere.
|
||||
|
||||
## The 4 types of search intent
|
||||
|
||||
- **Informational:** "[what are seed keywords](/library/keyword-research/seed-from-conversation)" → wants an answer
|
||||
- **Commercial:** "[best keyword clustering tool](/library/keyword-research/cluster-topical-hubs)" → wants a comparison
|
||||
- **Transactional:** "openseo pricing" → wants to act
|
||||
- **Navigational:** "google search console login" → wants a specific place
|
||||
|
||||
## Hot, warm, cold: the practitioner's intent map
|
||||
|
||||
The 4-type taxonomy is fine for textbooks. For planning a content calendar, temperature is faster:
|
||||
|
||||
- **Hot: ready now.** Transactional and high-commercial queries. Buyer intent keywords like "X vs Y", "X pricing", "best X for [niche]". Build these pages first: smallest traffic, largest revenue.
|
||||
- **Warm: comparing.** Commercial-investigation queries. They know the problem, they're shortlisting solutions. Comparison pages, use-case pages, honest alternative pages.
|
||||
- **Cold: learning.** Informational queries. Highest volume, longest payback. Their job is trust and retargeting, not conversion. Measure them accordingly.
|
||||
|
||||

|
||||
|
||||
Run this play with the [OpenSEO MCP](/docs/mcp):
|
||||
|
||||
```text
|
||||
Using the OpenSEO MCP: research keywords for "[your topic]" and group
|
||||
results into hot (transactional/commercial), warm (comparison), and
|
||||
cold (informational). Recommend which 5 hot pages to build first.
|
||||
```
|
||||
|
||||
## Search intent FAQ
|
||||
|
||||
### Why is search intent important for SEO?
|
||||
|
||||
Because Google ranks pages that satisfy intent, not pages that mention keywords. A perfectly optimized page against the wrong intent can't win. Read the SERP and you'll see the intent Google has decided the query carries.
|
||||
|
||||
### What are buyer intent keywords?
|
||||
|
||||
Queries that signal purchase readiness: "pricing", "vs", "alternative", "best X for Y", "discount". They're low volume and high competition per click, but still usually your best ROI, because the searcher arrives pre-sold.
|
||||
|
||||
### How do I check the search intent of a keyword?
|
||||
|
||||
Search it. The current top 10 is Google's answer: if it's all listicles, the intent is commercial comparison; all docs and definitions, informational. OpenSEO also auto-labels intent on every [researched keyword](/features/keyword-research).
|
||||
44
web/content/marketing/library/seed-from-conversation.mdx
Normal file
44
web/content/marketing/library/seed-from-conversation.mdx
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "Seed from conversation, not a volume report"
|
||||
description: 'Every keyword tool starts with a box that says "enter a keyword", and that first word decides everything downstream. Get it from your customers'' mouths, not from a tool''s suggestions.'
|
||||
---
|
||||
|
||||
## What is a seed keyword?
|
||||
|
||||
A seed keyword is the starting term you feed a research tool to generate the full set of related queries. Weak seeds (industry jargon) produce weak results. Strong seeds, the words real customers use when they describe their problem, surface keywords your competitors' tools never see.
|
||||
|
||||
## Why customer conversations beat keyword tools for seed keywords
|
||||
|
||||
Tools recycle each other's databases. Your sales calls, support tickets, and podcast interviews contain phrasings that have never been typed into a tool but get typed into Google every day. The searcher who says "my website doesn't show up when I google my own company" is not searching "seo services." Seed from the first phrasing and you own a lane; seed from the second and you're bidding against everyone.
|
||||
|
||||
## How to build a seed keyword list without a paid tool (5 steps)
|
||||
|
||||
1. **Harvest verbatims.** Pull the exact problem-phrases from your last 10 sales calls, support threads, or reviews. Copy the words, not your summary of them.
|
||||
2. **Strip to the seed.** "We can't tell if our blog is doing anything" → seeds: "is my blog working", "blog roi", "measure blog traffic".
|
||||
3. **Add the jargon translation.** For every customer phrase, note the industry term too. You'll need both sides of the vocabulary to catch both audiences.
|
||||
4. **Validate in OpenSEO.** Feed each seed into [keyword research](/features/keyword-research). You want _any_ measured volume plus clear intent, not big numbers. Zero-volume seeds with real intent still convert.
|
||||
5. **Keep the losers list.** Seeds with nothing behind them go in a "watch" list. Customer language often precedes search demand by months.
|
||||
|
||||

|
||||
|
||||
Run this play with the [OpenSEO MCP](/docs/mcp):
|
||||
|
||||
```text
|
||||
Using the OpenSEO MCP: here are 8 phrases my customers actually said:
|
||||
[paste verbatims]. Extract seed keywords from each, research them,
|
||||
and tell me which have measured demand vs. which go on the watch list.
|
||||
```
|
||||
|
||||
## Seed keyword FAQ
|
||||
|
||||
### How do I do keyword research for free?
|
||||
|
||||
Conversations for seeds (this page), Google autocomplete + People Also Ask for expansion, Search Console for validation. OpenSEO validates and expands what those surface: open source, no card required.
|
||||
|
||||
### How do I find LSI keywords?
|
||||
|
||||
"LSI keywords" is tool-industry vocabulary for related phrasings. The fastest free sources are the People Also Ask box and the "related searches" footer. Better still: your customers' own synonyms, which is exactly what conversation seeding harvests.
|
||||
|
||||
### How many seed keywords do I need?
|
||||
|
||||
5–15 strong seeds per topic. Past that you're expanding, not seeding. Feed them into [the long-tail mining play](/library/keyword-research/long-tail-question-mining) next.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 187 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
@ -63,6 +63,8 @@ export function FeaturePageTemplate({ page }: FeaturePageProps) {
|
||||
<ListSection title="Why OpenSEO" items={page.differentiators} />
|
||||
</div>
|
||||
|
||||
{page.guides ? <GuidesSection guides={page.guides} /> : null}
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Related features
|
||||
@ -181,6 +183,109 @@ function MetricsSection({ page }: FeaturePageProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function GuidesSection({
|
||||
guides,
|
||||
}: {
|
||||
guides: NonNullable<FeaturePage["guides"]>;
|
||||
}) {
|
||||
return (
|
||||
<section className="mt-12">
|
||||
<div className="rounded-[20px] bg-neutral-950 p-7 text-white md:p-12">
|
||||
<p className="text-sm font-medium text-[#c9c4bd]">{guides.eyebrow}</p>
|
||||
<h2 className="mt-3 text-2xl font-semibold tracking-tight text-white">
|
||||
{guides.title}
|
||||
</h2>
|
||||
<p className="mt-3 max-w-2xl text-sm leading-6 text-[#c9c4bd]">
|
||||
{guides.description}
|
||||
</p>
|
||||
<div className="mt-8 grid gap-3 md:grid-cols-2">
|
||||
{guides.items.map((item) => {
|
||||
const body = (
|
||||
<>
|
||||
<div className="flex items-start justify-between gap-2 text-[15px] font-semibold text-white">
|
||||
<span>{item.label}</span>
|
||||
{item.href ? (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="text-[var(--color-brand-accent)]"
|
||||
>
|
||||
→
|
||||
</span>
|
||||
) : (
|
||||
<span className="shrink-0 rounded-full border border-[#2e2e2e] px-2 py-0.5 font-mono text-[9px] uppercase tracking-wider text-[#8f8a83]">
|
||||
Next up
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-1.5 font-mono text-[11px] text-[#8f8a83]">
|
||||
{item.by}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
return item.href ? (
|
||||
<a
|
||||
key={item.label}
|
||||
href={item.href}
|
||||
className="block rounded-[10px] border border-[#2e2e2e] bg-[#1c1c1c] px-[18px] py-3.5 transition-colors hover:border-[var(--color-brand-accent)] hover:bg-[#212121]"
|
||||
>
|
||||
{body}
|
||||
</a>
|
||||
) : (
|
||||
<div
|
||||
key={item.label}
|
||||
className="rounded-[10px] border border-[#2e2e2e] bg-[#1c1c1c] px-[18px] py-3.5"
|
||||
>
|
||||
{body}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<a
|
||||
href={guides.cta.href}
|
||||
className="mt-8 inline-flex h-11 items-center rounded-lg border border-[#444] px-5 text-sm font-medium text-white transition-colors hover:border-[var(--color-brand-accent)]"
|
||||
>
|
||||
{guides.cta.label}
|
||||
<span aria-hidden="true" className="ml-2">
|
||||
→
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-wrap items-center gap-8 rounded-xl border border-[var(--color-border-subtle)] bg-white p-7">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="relative h-[90px] w-[72px] shrink-0 rounded-md border border-[var(--color-border-subtle)] bg-gradient-to-br from-white to-[#f5f1ec]"
|
||||
>
|
||||
<span className="absolute left-[9px] right-[9px] top-[9px] h-[3px] bg-[var(--color-brand-accent)] shadow-[0_7px_0_#e7e0d7,0_14px_0_#e7e0d7,0_21px_0_#e7e0d7]" />
|
||||
<span className="absolute bottom-[7px] left-[7px] rounded-[3px] bg-[var(--color-brand-accent)] px-[5px] py-[2px] font-mono text-[9px] text-white">
|
||||
PDF
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-[240px] flex-1">
|
||||
<p className="text-sm font-medium text-[var(--color-brand-accent)]">
|
||||
{guides.download.eyebrow}
|
||||
</p>
|
||||
<h3 className="mt-2 text-[19px] font-semibold text-neutral-950">
|
||||
{guides.download.title}
|
||||
</h3>
|
||||
<p className="mt-1.5 text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
{guides.download.description}
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href={guides.download.href}
|
||||
className="inline-flex h-11 items-center rounded-lg bg-neutral-950 px-5 text-sm font-medium text-white transition-colors hover:bg-neutral-800"
|
||||
>
|
||||
{guides.download.label}
|
||||
<span aria-hidden="true" className="ml-2">
|
||||
→
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function ListSection({ title, items }: { title: string; items: string[] }) {
|
||||
return (
|
||||
<section className="rounded-lg border border-[var(--color-border-subtle)] bg-white p-5">
|
||||
|
||||
79
web/src/components/library-page.tsx
Normal file
79
web/src/components/library-page.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { DocsBody } from "fumadocs-ui/page";
|
||||
|
||||
const LIBRARY_PILLAR_PATH = "/library/keyword-research";
|
||||
|
||||
type LibrarySpokePageProps = {
|
||||
title: string;
|
||||
description?: string;
|
||||
crumb: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export function LibrarySpokePage({
|
||||
title,
|
||||
description,
|
||||
crumb,
|
||||
children,
|
||||
}: LibrarySpokePageProps) {
|
||||
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)]">
|
||||
<a
|
||||
href={LIBRARY_PILLAR_PATH}
|
||||
className="font-medium text-[var(--color-brand-accent)]"
|
||||
>
|
||||
Strategy Library
|
||||
</a>{" "}
|
||||
/ <span>{crumb}</span>
|
||||
</p>
|
||||
<h1 className="mt-3 text-4xl font-semibold leading-tight tracking-tight text-neutral-950 md:text-5xl">
|
||||
{title}
|
||||
</h1>
|
||||
{description ? (
|
||||
<p className="mt-5 max-w-2xl text-lg leading-8 text-[var(--color-brand-muted)]">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
</header>
|
||||
|
||||
<DocsBody className="min-w-0 text-neutral-800 [&_a]:!text-neutral-950 [&_h2]:!text-neutral-950 [&_h2_a]:!no-underline [&_h3]:!text-neutral-950 [&_h3_a]:!no-underline [&_li]:!text-neutral-700 [&_li_a]:font-medium [&_li_a]:underline [&_li_a]:decoration-[var(--color-brand-accent)] [&_li_a]:underline-offset-4 [&_li_a:hover]:!text-neutral-700 [&_p]:!text-neutral-700 [&_p_a]:font-medium [&_p_a]:underline [&_p_a]:decoration-[var(--color-brand-accent)] [&_p_a]:underline-offset-4 [&_p_a:hover]:!text-neutral-700 [&_strong]:!text-neutral-950">
|
||||
{children}
|
||||
</DocsBody>
|
||||
|
||||
<LibrarySpokeCta />
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
</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.
|
||||
</p>
|
||||
<div className="mt-5 flex flex-col gap-3 sm:flex-row">
|
||||
<a
|
||||
href="https://app.openseo.so/sign-up"
|
||||
className="inline-flex h-10 items-center justify-center rounded-lg bg-neutral-950 px-4 text-sm font-medium text-white transition-colors hover:bg-neutral-800"
|
||||
>
|
||||
Start with OpenSEO
|
||||
<span className="ml-2" aria-hidden="true">
|
||||
→
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_PILLAR_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
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -29,6 +29,27 @@ export type FeaturePage = {
|
||||
question: string;
|
||||
answer: string;
|
||||
}>;
|
||||
guides?: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
items: Array<{
|
||||
label: string;
|
||||
by: string;
|
||||
href?: string;
|
||||
}>;
|
||||
cta: {
|
||||
label: string;
|
||||
href: string;
|
||||
};
|
||||
download: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
label: string;
|
||||
href: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const featurePages = {
|
||||
@ -107,6 +128,62 @@ export const featurePages = {
|
||||
"Yes. Keyword research can be paired with SERP inspection so you can see ranking pages alongside the metrics.",
|
||||
},
|
||||
],
|
||||
guides: {
|
||||
eyebrow: "The practitioner playbook",
|
||||
title: "The Keyword Research Strategy Library",
|
||||
description:
|
||||
"Eight field-tested plays for finding demand that converts, each drawn from a working SEO on the Unscripted podcast, with the workflow and who endorses it. Free, and built to be run inside OpenSEO.",
|
||||
items: [
|
||||
{
|
||||
label: "Seed from conversation, not a volume report",
|
||||
by: "Slaymaker · Bajayo · Digneo",
|
||||
href: "/library/keyword-research/seed-from-conversation",
|
||||
},
|
||||
{
|
||||
label: "Long-tail & question mining (PAA, query fan-out)",
|
||||
by: "Baterina · Moser · Barnard",
|
||||
href: "/library/keyword-research/long-tail-question-mining",
|
||||
},
|
||||
{
|
||||
label: "Search-intent mapping (hot / warm / cold)",
|
||||
by: "Merrilees · Ashford",
|
||||
href: "/library/keyword-research/search-intent-mapping",
|
||||
},
|
||||
{
|
||||
label: "Opportunity sizing & forecasting",
|
||||
by: "Rivera · Berkowitz · Baterina",
|
||||
},
|
||||
{
|
||||
label: "Programmatic & data-driven discovery (GSC)",
|
||||
by: "Rivera · Simmons",
|
||||
},
|
||||
{
|
||||
label: "Cluster keywords into topical hubs",
|
||||
by: "Simmons · Homer",
|
||||
href: "/library/keyword-research/cluster-topical-hubs",
|
||||
},
|
||||
{
|
||||
label: "Intent beyond Google (Pinterest, AI, LinkedIn)",
|
||||
by: "Bocchese · Alfon · Popp",
|
||||
},
|
||||
{
|
||||
label: "Make positioning map to real demand",
|
||||
by: "Little · Popp · Homer",
|
||||
},
|
||||
],
|
||||
cta: {
|
||||
label: "Open the full library",
|
||||
href: "/library/keyword-research",
|
||||
},
|
||||
download: {
|
||||
eyebrow: "Free download",
|
||||
title: "The Keyword Research Playbook",
|
||||
description:
|
||||
"All 8 plays in one designed PDF: workflows, the practitioner quotes behind them, and a seed-to-brief checklist.",
|
||||
label: "Download the PDF",
|
||||
href: "/library/keyword-research/keyword-research-playbook.pdf",
|
||||
},
|
||||
},
|
||||
},
|
||||
siteAudit: {
|
||||
slug: FEATURE_PAGE_SLUGS.siteAudit,
|
||||
|
||||
@ -35,6 +35,11 @@ import { Route as MarketingFeaturesDomainOverviewRouteImport } from './routes/_m
|
||||
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'
|
||||
import { Route as MarketingLibraryKeywordResearchIndexRouteImport } from './routes/_marketing/library/keyword-research/index'
|
||||
import { Route as MarketingLibraryKeywordResearchSeedFromConversationRouteImport } from './routes/_marketing/library/keyword-research/seed-from-conversation'
|
||||
import { Route as MarketingLibraryKeywordResearchSearchIntentMappingRouteImport } from './routes/_marketing/library/keyword-research/search-intent-mapping'
|
||||
import { Route as MarketingLibraryKeywordResearchLongTailQuestionMiningRouteImport } from './routes/_marketing/library/keyword-research/long-tail-question-mining'
|
||||
import { Route as MarketingLibraryKeywordResearchClusterTopicalHubsRouteImport } from './routes/_marketing/library/keyword-research/cluster-topical-hubs'
|
||||
|
||||
const TermsAndConditionsRoute = TermsAndConditionsRouteImport.update({
|
||||
id: '/terms-and-conditions',
|
||||
@ -174,6 +179,36 @@ const MarketingFeaturesAiBrandVisibilityRoute =
|
||||
path: '/features/ai-brand-visibility',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryKeywordResearchIndexRoute =
|
||||
MarketingLibraryKeywordResearchIndexRouteImport.update({
|
||||
id: '/library/keyword-research/',
|
||||
path: '/library/keyword-research/',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryKeywordResearchSeedFromConversationRoute =
|
||||
MarketingLibraryKeywordResearchSeedFromConversationRouteImport.update({
|
||||
id: '/library/keyword-research/seed-from-conversation',
|
||||
path: '/library/keyword-research/seed-from-conversation',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryKeywordResearchSearchIntentMappingRoute =
|
||||
MarketingLibraryKeywordResearchSearchIntentMappingRouteImport.update({
|
||||
id: '/library/keyword-research/search-intent-mapping',
|
||||
path: '/library/keyword-research/search-intent-mapping',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryKeywordResearchLongTailQuestionMiningRoute =
|
||||
MarketingLibraryKeywordResearchLongTailQuestionMiningRouteImport.update({
|
||||
id: '/library/keyword-research/long-tail-question-mining',
|
||||
path: '/library/keyword-research/long-tail-question-mining',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryKeywordResearchClusterTopicalHubsRoute =
|
||||
MarketingLibraryKeywordResearchClusterTopicalHubsRouteImport.update({
|
||||
id: '/library/keyword-research/cluster-topical-hubs',
|
||||
path: '/library/keyword-research/cluster-topical-hubs',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof MarketingIndexRoute
|
||||
@ -201,6 +236,11 @@ export interface FileRoutesByFullPath {
|
||||
'/features/saved-keywords': typeof MarketingFeaturesSavedKeywordsRoute
|
||||
'/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
|
||||
'/features/': typeof MarketingFeaturesIndexRoute
|
||||
'/library/keyword-research/cluster-topical-hubs': typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
'/library/keyword-research/long-tail-question-mining': typeof MarketingLibraryKeywordResearchLongTailQuestionMiningRoute
|
||||
'/library/keyword-research/search-intent-mapping': typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute
|
||||
'/library/keyword-research/seed-from-conversation': typeof MarketingLibraryKeywordResearchSeedFromConversationRoute
|
||||
'/library/keyword-research/': typeof MarketingLibraryKeywordResearchIndexRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/privacy': typeof PrivacyRoute
|
||||
@ -228,6 +268,11 @@ export interface FileRoutesByTo {
|
||||
'/features/saved-keywords': typeof MarketingFeaturesSavedKeywordsRoute
|
||||
'/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
|
||||
'/features': typeof MarketingFeaturesIndexRoute
|
||||
'/library/keyword-research/cluster-topical-hubs': typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
'/library/keyword-research/long-tail-question-mining': typeof MarketingLibraryKeywordResearchLongTailQuestionMiningRoute
|
||||
'/library/keyword-research/search-intent-mapping': typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute
|
||||
'/library/keyword-research/seed-from-conversation': typeof MarketingLibraryKeywordResearchSeedFromConversationRoute
|
||||
'/library/keyword-research': typeof MarketingLibraryKeywordResearchIndexRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
@ -257,6 +302,11 @@ export interface FileRoutesById {
|
||||
'/_marketing/features/saved-keywords': typeof MarketingFeaturesSavedKeywordsRoute
|
||||
'/_marketing/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
|
||||
'/_marketing/features/': typeof MarketingFeaturesIndexRoute
|
||||
'/_marketing/library/keyword-research/cluster-topical-hubs': typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
'/_marketing/library/keyword-research/long-tail-question-mining': typeof MarketingLibraryKeywordResearchLongTailQuestionMiningRoute
|
||||
'/_marketing/library/keyword-research/search-intent-mapping': typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute
|
||||
'/_marketing/library/keyword-research/seed-from-conversation': typeof MarketingLibraryKeywordResearchSeedFromConversationRoute
|
||||
'/_marketing/library/keyword-research/': typeof MarketingLibraryKeywordResearchIndexRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
@ -286,6 +336,11 @@ export interface FileRouteTypes {
|
||||
| '/features/saved-keywords'
|
||||
| '/features/site-audit'
|
||||
| '/features/'
|
||||
| '/library/keyword-research/cluster-topical-hubs'
|
||||
| '/library/keyword-research/long-tail-question-mining'
|
||||
| '/library/keyword-research/search-intent-mapping'
|
||||
| '/library/keyword-research/seed-from-conversation'
|
||||
| '/library/keyword-research/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
| '/privacy'
|
||||
@ -313,6 +368,11 @@ export interface FileRouteTypes {
|
||||
| '/features/saved-keywords'
|
||||
| '/features/site-audit'
|
||||
| '/features'
|
||||
| '/library/keyword-research/cluster-topical-hubs'
|
||||
| '/library/keyword-research/long-tail-question-mining'
|
||||
| '/library/keyword-research/search-intent-mapping'
|
||||
| '/library/keyword-research/seed-from-conversation'
|
||||
| '/library/keyword-research'
|
||||
id:
|
||||
| '__root__'
|
||||
| '/_marketing'
|
||||
@ -341,6 +401,11 @@ export interface FileRouteTypes {
|
||||
| '/_marketing/features/saved-keywords'
|
||||
| '/_marketing/features/site-audit'
|
||||
| '/_marketing/features/'
|
||||
| '/_marketing/library/keyword-research/cluster-topical-hubs'
|
||||
| '/_marketing/library/keyword-research/long-tail-question-mining'
|
||||
| '/_marketing/library/keyword-research/search-intent-mapping'
|
||||
| '/_marketing/library/keyword-research/seed-from-conversation'
|
||||
| '/_marketing/library/keyword-research/'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
@ -542,6 +607,41 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof MarketingFeaturesAiBrandVisibilityRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/keyword-research/': {
|
||||
id: '/_marketing/library/keyword-research/'
|
||||
path: '/library/keyword-research'
|
||||
fullPath: '/library/keyword-research/'
|
||||
preLoaderRoute: typeof MarketingLibraryKeywordResearchIndexRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/keyword-research/seed-from-conversation': {
|
||||
id: '/_marketing/library/keyword-research/seed-from-conversation'
|
||||
path: '/library/keyword-research/seed-from-conversation'
|
||||
fullPath: '/library/keyword-research/seed-from-conversation'
|
||||
preLoaderRoute: typeof MarketingLibraryKeywordResearchSeedFromConversationRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/keyword-research/search-intent-mapping': {
|
||||
id: '/_marketing/library/keyword-research/search-intent-mapping'
|
||||
path: '/library/keyword-research/search-intent-mapping'
|
||||
fullPath: '/library/keyword-research/search-intent-mapping'
|
||||
preLoaderRoute: typeof MarketingLibraryKeywordResearchSearchIntentMappingRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/keyword-research/long-tail-question-mining': {
|
||||
id: '/_marketing/library/keyword-research/long-tail-question-mining'
|
||||
path: '/library/keyword-research/long-tail-question-mining'
|
||||
fullPath: '/library/keyword-research/long-tail-question-mining'
|
||||
preLoaderRoute: typeof MarketingLibraryKeywordResearchLongTailQuestionMiningRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/keyword-research/cluster-topical-hubs': {
|
||||
id: '/_marketing/library/keyword-research/cluster-topical-hubs'
|
||||
path: '/library/keyword-research/cluster-topical-hubs'
|
||||
fullPath: '/library/keyword-research/cluster-topical-hubs'
|
||||
preLoaderRoute: typeof MarketingLibraryKeywordResearchClusterTopicalHubsRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -560,6 +660,11 @@ interface MarketingRouteChildren {
|
||||
MarketingFeaturesSavedKeywordsRoute: typeof MarketingFeaturesSavedKeywordsRoute
|
||||
MarketingFeaturesSiteAuditRoute: typeof MarketingFeaturesSiteAuditRoute
|
||||
MarketingFeaturesIndexRoute: typeof MarketingFeaturesIndexRoute
|
||||
MarketingLibraryKeywordResearchClusterTopicalHubsRoute: typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
MarketingLibraryKeywordResearchLongTailQuestionMiningRoute: typeof MarketingLibraryKeywordResearchLongTailQuestionMiningRoute
|
||||
MarketingLibraryKeywordResearchSearchIntentMappingRoute: typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute
|
||||
MarketingLibraryKeywordResearchSeedFromConversationRoute: typeof MarketingLibraryKeywordResearchSeedFromConversationRoute
|
||||
MarketingLibraryKeywordResearchIndexRoute: typeof MarketingLibraryKeywordResearchIndexRoute
|
||||
}
|
||||
|
||||
const MarketingRouteChildren: MarketingRouteChildren = {
|
||||
@ -578,6 +683,16 @@ const MarketingRouteChildren: MarketingRouteChildren = {
|
||||
MarketingFeaturesSavedKeywordsRoute: MarketingFeaturesSavedKeywordsRoute,
|
||||
MarketingFeaturesSiteAuditRoute: MarketingFeaturesSiteAuditRoute,
|
||||
MarketingFeaturesIndexRoute: MarketingFeaturesIndexRoute,
|
||||
MarketingLibraryKeywordResearchClusterTopicalHubsRoute:
|
||||
MarketingLibraryKeywordResearchClusterTopicalHubsRoute,
|
||||
MarketingLibraryKeywordResearchLongTailQuestionMiningRoute:
|
||||
MarketingLibraryKeywordResearchLongTailQuestionMiningRoute,
|
||||
MarketingLibraryKeywordResearchSearchIntentMappingRoute:
|
||||
MarketingLibraryKeywordResearchSearchIntentMappingRoute,
|
||||
MarketingLibraryKeywordResearchSeedFromConversationRoute:
|
||||
MarketingLibraryKeywordResearchSeedFromConversationRoute,
|
||||
MarketingLibraryKeywordResearchIndexRoute:
|
||||
MarketingLibraryKeywordResearchIndexRoute,
|
||||
}
|
||||
|
||||
const MarketingRouteWithChildren = MarketingRoute._addFileChildren(
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||
import Content, {
|
||||
frontmatter,
|
||||
} from "../../../../../content/marketing/library/cluster-topical-hubs.mdx";
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/cluster-topical-hubs",
|
||||
)({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title:
|
||||
"Keyword Clustering: Turn a Keyword List into Topical Hubs (and Fix Cannibalization)",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/cluster-topical-hubs",
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
component: () => (
|
||||
<LibrarySpokePage
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Cluster keywords into topical hubs"
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
),
|
||||
});
|
||||
271
web/src/routes/_marketing/library/keyword-research/index.tsx
Normal file
271
web/src/routes/_marketing/library/keyword-research/index.tsx
Normal file
@ -0,0 +1,271 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
const PATH = "/library/keyword-research";
|
||||
|
||||
const plays = [
|
||||
{
|
||||
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.",
|
||||
},
|
||||
{
|
||||
title: "Opportunity sizing & forecasting",
|
||||
description:
|
||||
"Size a cluster before you invest: difficulty, traffic ceiling, and honest payback windows.",
|
||||
},
|
||||
{
|
||||
title: "Intent beyond Google (Pinterest, AI, LinkedIn)",
|
||||
description:
|
||||
"Where queries happen when they don't happen in a search box, AI assistants included.",
|
||||
},
|
||||
{
|
||||
title: "Map positioning to real demand",
|
||||
description:
|
||||
"Competitor keyword gaps as a positioning instrument, not a copying exercise.",
|
||||
},
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: "How do you do keyword research for SEO?",
|
||||
answer:
|
||||
"Seed from customer language, expand into long-tails and questions, label by intent, cluster into one-page-per-intent hubs, then validate against Search Console. Volume is the final filter, not the starting point.",
|
||||
},
|
||||
{
|
||||
question: "How do you do keyword research for free?",
|
||||
answer:
|
||||
"The entire workflow runs on free surfaces: conversations, autocomplete, People Also Ask, Search Console. OpenSEO itself is open source and free to try.",
|
||||
},
|
||||
{
|
||||
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.",
|
||||
},
|
||||
{
|
||||
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.",
|
||||
},
|
||||
{
|
||||
question: "How do you do keyword research for a blog?",
|
||||
answer:
|
||||
"Blogs win in the tail: mine questions, cluster them into topical hubs, and let each post own one question-intent completely rather than skimming ten.",
|
||||
},
|
||||
];
|
||||
|
||||
const faqLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
mainEntity: faqs.map((faq) => ({
|
||||
"@type": "Question",
|
||||
name: faq.question,
|
||||
acceptedAnswer: { "@type": "Answer", text: faq.answer },
|
||||
})),
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/_marketing/library/keyword-research/")({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title: "How to Do Keyword Research: The Strategy Library",
|
||||
description:
|
||||
"Eight practitioner plays that treat keyword research as demand discovery, sourced from real interviews with working SEOs, executable inside OpenSEO.",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO",
|
||||
}),
|
||||
component: KeywordResearchLibraryPage,
|
||||
});
|
||||
|
||||
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>
|
||||
<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 practitioner plays that treat keyword research as demand
|
||||
discovery, sourced from real interviews with working SEOs, executable
|
||||
inside OpenSEO.
|
||||
</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
|
||||
</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 plays 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 play is a full
|
||||
walkthrough with the copy-paste MCP prompt that runs it.
|
||||
</p>
|
||||
<div className="mt-6 grid gap-4 md:grid-cols-2">
|
||||
{plays.map((play, 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>
|
||||
{play.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>
|
||||
<h3 className="mt-3 text-base font-semibold text-neutral-950">
|
||||
{play.title}
|
||||
{play.href ? (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="ml-1 text-[var(--color-brand-accent)]"
|
||||
>
|
||||
→
|
||||
</span>
|
||||
) : null}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
{play.description}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
return play.href ? (
|
||||
<a
|
||||
key={play.title}
|
||||
href={play.href}
|
||||
className="rounded-lg border border-[var(--color-border-subtle)] bg-white p-5 transition-colors hover:border-neutral-900"
|
||||
>
|
||||
{body}
|
||||
</a>
|
||||
) : (
|
||||
<div
|
||||
key={play.title}
|
||||
className="rounded-lg border border-[var(--color-border-subtle)] bg-white p-5"
|
||||
>
|
||||
{body}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-12 rounded-xl border border-[var(--color-border-subtle)] bg-white p-6 md:p-8">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
What Google Keyword Planner hides (and what to use instead)
|
||||
</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.
|
||||
</p>
|
||||
<p className="mt-3 max-w-3xl text-sm leading-6 text-neutral-700">
|
||||
The plays in this library replace it with three honest sources: your
|
||||
customers' language (play 01), Google's own question surfaces (play
|
||||
02), and your Search Console reality (play 05). Volume data still
|
||||
matters, but it's the <em>last</em> filter, not the first.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Free keyword research tools for every play
|
||||
</h2>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
The free surfaces (autocomplete, People Also Ask, your Search Console)
|
||||
do the discovery. Every play then runs in{" "}
|
||||
<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, free to try,
|
||||
self-hostable, and scriptable through the{" "}
|
||||
<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. No trial clocks.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Keyword research FAQ
|
||||
</h2>
|
||||
<div className="mt-5 divide-y divide-[var(--color-border-subtle)] rounded-lg border border-[var(--color-border-subtle)] bg-white">
|
||||
{faqs.map((faq) => (
|
||||
<div key={faq.question} className="p-5">
|
||||
<h3 className="text-sm font-semibold text-neutral-900">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<p className="mt-1.5 text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
{faq.answer}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-12 flex flex-col items-start justify-between gap-4 rounded-xl border border-[var(--color-border-subtle)] bg-white p-6 sm:flex-row sm:items-center md:p-8">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
The Keyword Research Playbook
|
||||
</h2>
|
||||
<p className="mt-2 max-w-xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
The plays as a working PDF: checklists, the MCP prompts, and the
|
||||
keyword-map template. Ungated.
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="/library/keyword-research/keyword-research-playbook.pdf"
|
||||
className="inline-flex h-11 shrink-0 items-center justify-center rounded-lg bg-neutral-950 px-5 text-sm font-medium text-white transition-colors hover:bg-neutral-800"
|
||||
>
|
||||
Download the playbook
|
||||
<span aria-hidden="true" className="ml-2">
|
||||
→
|
||||
</span>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<script
|
||||
type="application/ld+json"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqLd) }}
|
||||
/>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||
import Content, {
|
||||
frontmatter,
|
||||
} from "../../../../../content/marketing/library/long-tail-question-mining.mdx";
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/long-tail-question-mining",
|
||||
)({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title: "What Are Long-Tail Keywords? How to Find and Use Them",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/long-tail-question-mining",
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
component: () => (
|
||||
<LibrarySpokePage
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Long-tail & question mining"
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
),
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||
import Content, {
|
||||
frontmatter,
|
||||
} from "../../../../../content/marketing/library/search-intent-mapping.mdx";
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/search-intent-mapping",
|
||||
)({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title: "What Is Search Intent? Mapping Keywords Hot, Warm, and Cold",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/search-intent-mapping",
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
component: () => (
|
||||
<LibrarySpokePage
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Search-intent mapping"
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
),
|
||||
});
|
||||
@ -0,0 +1,30 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||
import Content, {
|
||||
frontmatter,
|
||||
} from "../../../../../content/marketing/library/seed-from-conversation.mdx";
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/keyword-research/seed-from-conversation",
|
||||
)({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title:
|
||||
"Seed Keywords from Customer Conversations (Keyword Research Without a Paid Tool)",
|
||||
description: frontmatter.description,
|
||||
path: "/library/keyword-research/seed-from-conversation",
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
component: () => (
|
||||
<LibrarySpokePage
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Seed from conversation"
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
),
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user