From 5fc113f0339e566d908ca8c2e1dcc41a03d9ae5d Mon Sep 17 00:00:00 2001 From: Jeremy Rivera Date: Tue, 15 Sep 2026 14:47:05 -0500 Subject: [PATCH 1/5] Rank Tracking strategy library: pillar + 4 strategies Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01R5c56XeUKSKJSaVjPU2Jpc --- .../library/keyword-ranking-report.mdx | 125 +++++++ .../marketing/library/local-rank-tracking.mdx | 105 ++++++ .../search-console-vs-rank-tracker.mdx | 121 +++++++ .../library/which-keywords-to-track.mdx | 119 +++++++ web/src/lib/feature-pages.ts | 34 ++ web/src/lib/strategy-libraries.ts | 32 ++ web/src/routeTree.gen.ts | 115 +++++++ web/src/routes/_marketing/library/index.tsx | 21 +- .../library/rank-tracking/index.tsx | 306 ++++++++++++++++++ .../rank-tracking/keyword-ranking-report.tsx | 77 +++++ .../rank-tracking/local-rank-tracking.tsx | 80 +++++ .../search-console-vs-rank-tracker.tsx | 77 +++++ .../rank-tracking/which-keywords-to-track.tsx | 77 +++++ 13 files changed, 1288 insertions(+), 1 deletion(-) create mode 100644 web/content/marketing/library/keyword-ranking-report.mdx create mode 100644 web/content/marketing/library/local-rank-tracking.mdx create mode 100644 web/content/marketing/library/search-console-vs-rank-tracker.mdx create mode 100644 web/content/marketing/library/which-keywords-to-track.mdx create mode 100644 web/src/routes/_marketing/library/rank-tracking/index.tsx create mode 100644 web/src/routes/_marketing/library/rank-tracking/keyword-ranking-report.tsx create mode 100644 web/src/routes/_marketing/library/rank-tracking/local-rank-tracking.tsx create mode 100644 web/src/routes/_marketing/library/rank-tracking/search-console-vs-rank-tracker.tsx create mode 100644 web/src/routes/_marketing/library/rank-tracking/which-keywords-to-track.tsx diff --git a/web/content/marketing/library/keyword-ranking-report.mdx b/web/content/marketing/library/keyword-ranking-report.mdx new file mode 100644 index 000000000..571a0c315 --- /dev/null +++ b/web/content/marketing/library/keyword-ranking-report.mdx @@ -0,0 +1,125 @@ +--- +title: "The keyword ranking report your CEO will read" +description: "A table of positions is not a report. Lead with the number the business tracks, group the movement into four buckets, explain the three rows that caused it, and say what happens next. One page, every week." +--- + +## What should a keyword ranking report show? + +The number the business already cares about, first, then rankings as the explanation for it. Most ranking reports do this backwards. They open with a table of keywords and positions, sometimes with green and red arrows, and leave the reader to work out whether anything in it matters. The reader is usually someone who does not do SEO, has ten minutes, and needs to decide whether to keep paying for this. A table does not help them decide. + +Grant Simmons, who ran search at Homes.com and now works with Fiat Growth, drew the line when we were talking about a well-liked Search Console tool: + +> it's still not reporting. It's insights. + +Insights are what a tool gives you. A report is what you give someone who has to make a decision with it. His full conversation on measurement is [on the Unscripted SEO podcast](https://unscriptedseo.com/grant-simmons-on-entity-seo-llms-and-the-future-of-search-visibility/). The structure below is built to be the second thing. + +## The structure + +### 1. One number, and what it did + +Open with the metric the business tracks, for the pages the tracker covers, against the previous period. Clicks from Search Console if that is what you have; leads or revenue on those pages if analytics is connected. One sentence: "Organic clicks on the twelve service pages we track were 1,240 this month, up from 980." That sentence is the report. Everything after it is support. + +Do not open with average position across all tracked keywords. It is a mean of unrelated things, it moves when you add a keyword, and nobody outside SEO knows whether 11.4 is good. + +### 2. Movement in four buckets + +Rankings come next, as counts rather than rows. Of the keywords tracked this period: + +- how many entered the top three +- how many entered page one +- how many dropped off page one +- how many did not move + +Four numbers. A reader can hold them, and they say whether the month was good before anyone looks at a keyword. In OpenSEO every tracked keyword carries its previous position, so the buckets are a sort, not a spreadsheet exercise. + +### 3. The three rows that explain the number + +Now the table, cut to three rows: the biggest gain, the biggest loss, and the row where the position held but the clicks changed. Show keyword, position, previous position, ranking URL, and what is on the page. + +The third row is the one that earns the report its credibility. When a term holds position 4 and loses half its clicks, the reason is almost always something Google put above it: an AI overview, a local pack, a video block. A tracker that records SERP features shows it in a column, and a report that names it turns "clicks fell, not sure why" into "clicks fell because Google added an answer box, and here is what we do about it". + +### 4. Brand and non-brand, separately + +Split the tracked list and the clicks into brand and non-brand before you draw any conclusion. Brand terms rise when the company does marketing of any kind, and a report that counts them as SEO wins will get found out at the first hard question. Charlie Birch, founder of Humanize Collective, on what the number is supposed to be for: + +> the result that you ultimately want is not search volume, it's conversion and retention + +The non-brand line is the one SEO can claim. Report it on its own, even when it is the smaller number. Especially then. Her episode is [here](https://unscriptedseo.com/charlie-birch-on-where-brand-strategy-meets-seo-what-every-founder-needs-to-know/). + +### 5. AI answers, with the caveat attached + +If you report visibility in AI assistants, say what the number is. Brittany Trafis, founder of Soarion Digital, on the tools that monitor it: + +> It is a monitoring tool. It is directional. do not think this data is Google Search Console level data. It is nowhere close + +Put the row in, label it directional, and do not let it sit next to the click count as if it were the same kind of fact. Her episode on treating AI search as its own channel is [on the podcast](https://unscriptedseo.com/brittany-trafis-ai-search-is-not-seo-2-0-unscripted-seo-podcast/). + +### 6. What we did, what we will do + +Two short lists. The work that shipped this period, and the work planned for next, each tied to a row in section 3 or a bucket in section 2. This is the section that gets the budget renewed, because it connects the numbers to decisions someone made. + +## What to leave out + +The full keyword table goes in an attachment, not the body. Anyone who wants it can open it; putting it in front of the reader costs the attention you need for section 1. + +Year-on-year comparisons need a warning label now. Google's results pages changed enough between 2024 and 2026 that a query's clicks last year and this year are often not the same kind of number, because the page they came from had a different shape. Compare periods, name what changed on the SERP in between, and resist the chart that makes it look continuous. + +And leave out any score you cannot explain in a sentence. A "visibility index" invites a conversation about how it is calculated instead of a conversation about what to do. + +## Run this with the OpenSEO MCP + +Connect Search Console to the project and the [OpenSEO MCP](/docs/mcp) to your assistant. The tracker read and the Search Console read use no credits, so the report itself is free to produce once the tracker has run. + +```text +Using the OpenSEO MCP on project [name], write this month's ranking +report for [company]. + +1. Call get_rank_tracker and pull every tracked keyword with + position, previous position, and ranking URL. + +2. Pull Search Console performance for the last 28 days and the 28 + days before, by page, filtered to the URLs from step 1. Give me + total clicks for both periods. That is the first sentence. + +3. Bucket the keywords: entered top 3, entered page 1, left page 1, + unchanged. Four counts. + +4. Show me three rows: the largest position gain, the largest loss, + and the keyword where position moved by 2 or less but Search + Console clicks on its URL changed by more than 30%. Include SERP + features for that third row. + +5. Split everything above into brand (queries containing "[brand]") + and non-brand, and report the non-brand clicks on their own line. + +6. Do not compute an average position across keywords. Do not + invent a score. Put the full table in a separate block at the end. +``` + +Step 4 is the only step that needs judgement, and the prompt hands it a rule instead. Adjust the thresholds to your site, but keep the rule written down so the report is consistent month to month. + +## A report is a request for a decision + +Every ranking report is asking the reader to do something: keep funding this, approve that fix, accept that a term is lost. Write it so the request is visible in the first sentence and supported by the fourth section, and the tracker underneath it becomes the most useful thing in your stack rather than the most ignored. If you have not built the tracker yet, [which keywords to track](/library/rank-tracking/which-keywords-to-track) is where to start. + +## Ranking report FAQ + +### What should an SEO ranking report include? + +The business metric for the tracked pages against the prior period, ranking movement as four counts (entered top 3, entered page 1, left page 1, unchanged), the three rows that explain the movement with SERP features noted, a brand and non-brand split, and a short list of work done and planned. The full keyword table goes in an attachment. + +### How often should I send a ranking report? + +Monthly for most businesses, weekly if a launch or migration is in progress. The tracker can check daily; the report should not, because week-to-week ranking noise is real and reporting it trains the reader to ignore the report. + +### Should a ranking report show average position? + +Not across all keywords. It is a mean of unrelated queries, changes whenever the list changes, and means nothing to a non-SEO reader. Report positions per keyword for the few rows that matter and movement counts for the rest. + +### How do I explain a ranking drop to a client or manager? + +Name the keyword, the previous and current position, the URL, and what is now on the results page, then say what you are doing about it. If the position held and clicks fell, show the SERP feature that took the click. A drop with a cause and a plan is a normal report; a drop with neither is a problem. + +### Can OpenSEO generate a ranking report? + +It provides the pieces: tracked positions with previous positions and SERP features, Search Console clicks by page and query at no credit cost, and an MCP so an assistant can assemble the report from a prompt like the one above. It does not produce a composite score, on purpose. diff --git a/web/content/marketing/library/local-rank-tracking.mdx b/web/content/marketing/library/local-rank-tracking.mdx new file mode 100644 index 000000000..5dc0b50c3 --- /dev/null +++ b/web/content/marketing/library/local-rank-tracking.mdx @@ -0,0 +1,105 @@ +--- +title: "Local rank tracking: your position depends on where the searcher is standing" +description: "For a local business there is no single rank, only a rank at a point on the map. A nine-point grid around one Tennessee service company showed three different businesses holding number one, and the company itself nowhere in the set." +--- + +## Why a local business cannot have one rank + +Ask Google for a plumber from the north side of a town and from the south side, three kilometres apart, and you get two different map packs. Neither is wrong. Google weights proximity heavily for local queries, so the answer moves with the searcher, and a rank tracker that checks from one fixed point reports one of the many answers as if it were the answer. + +Melissa Popp, content strategy director at Rickety Roo, states the rule most local SEOs work from: + +> we know proximity and relevance are two of the most important factors for how you show up in map pack as a local business + +Her episode on fixing your Google Business Profile before anything else is [on the Unscripted SEO podcast](https://unscriptedseo.com/melissa-popp-go-fix-your-google-business-profile/). The implication for tracking is that a local business needs its position measured from several places, not one, or the number will describe the tracker's location rather than the customer's. + +## What a nine-point grid showed + +Here is the shape of the problem on a real business: an HVAC and plumbing company in a mid-sized Tennessee town. I ran a three-by-three grid, nine points three kilometres apart, centred on the town, on mobile, for two queries. + +For `plumber cookeville tn`, the number one result was a different business depending on the point. One plumber held the top spot across the north-west of the grid, a larger heating-and-plumbing company held it across the east and south, and at one point in the south a third business took it. Every point returned a full set of 20 results. + +For `hvac repair cookeville`, the split was cleaner: one company held number one at seven of nine points and a second company took two of the three points along the southern edge. + +So over a six-kilometre square, a searcher could be shown four different businesses at number one depending on which query they typed and where they typed it. A single-point tracker reporting "position 1" or "position 4" for this town would be reporting one of those cells and calling it the market. + +The company I was checking on appeared at none of the eighteen points. The grid's name-match returned nothing, and a listings search within 15 kilometres found no business under the name the company uses on its website. That is a finding in its own right, and it comes before any question of rank: the first thing a local grid tells you is whether you are in the set at all. If the profile exists under another name, that name needs to go into the tracker; if it does not exist, no amount of rank tracking will find it. + +## Storefront or service area + +The other thing that changes the map is what kind of business Google thinks you are. A storefront ranks from its address. A service-area business, one that travels to the customer, has no fixed point to rank from, and Melissa is direct about the consequence: + +> it can be a lot easier to rank an actual brick and mortar location than it can be a service area page because you open up the competitiveness + +Two decisions follow from it. Do not register a storefront as a service area to "reach further", because you are trading a strong signal at one point for a weak one everywhere. And if you are a service-area business, build location pages for the neighbourhoods and towns you serve, so you show up in the organic results where the map pack is not going to favour you. [Map positioning to real demand](/library/keyword-research/positioning-to-demand) covers finding which of those towns search for it. + +## Which points to track + +A grid is a survey, not a schedule. Run one to learn the shape of your visibility, then set up ongoing tracking at the points that matter, which are the ones your customers search from. Melissa's method for finding them: + +> there's also testing that needs to be involved there to figure out, know, like what areas, you know, looking in GA4 and other analytics tools, you know, where are people searching from down to at that city level + +That gives you two to five named places. In OpenSEO each tracker has a location, so a business serving three towns runs three small trackers rather than one tracker guessing from the middle. Twenty keywords in three locations is 60 checks a week, which on the queued schedule is about $0.60 a month. + +The grid itself is priced per point: a 3x3 grid is nine Maps searches, a 5x5 is twenty-five, and the app shows the cost before it runs. Use the 3x3 for a quarterly survey and the 5x5 when you are deciding whether to open or list a second location. + +## What to do with a bad grid + +If you rank well at the centre and fall off two kilometres out, that is proximity doing what proximity does, and the fixes are the ones Melissa puts first: a complete, accurate profile in the right category, reviews you answer, and hours and contact details that are correct. If you rank badly everywhere, check the category and the name before anything else. And if you are absent, as the company above was, the tracker has done its job on the first run by telling you the problem is upstream of rankings. + +This is also a budget conversation. Melissa on the size of budget she can work with: + +> I can work with $500. It doesn't mean that we are going to have all the results we want in six months or a year. But I can move the needle with $500 + +A local grid and three small trackers are a few dollars of that, and they are what stops the other $490 being spent on the wrong town. + +## Run this with the OpenSEO MCP + +Connect the [OpenSEO MCP](/docs/mcp) and use the local SEO tools together: the grid to survey, the tracker to follow up. The [local-seo](/docs/skills/local-seo) skill packages this workflow. + +```text +Using the OpenSEO MCP on project [name] for [business name] at +[address or coordinates]: + +1. Search local businesses within 10 km for "[business name]" and + confirm the listing exists. Give me its exact title, its cid, and + its primary category. If nothing matches, stop and tell me. + +2. Run a 3x3 local rank grid, 2 km spacing, on mobile, for + "[main service] [town]", using the cid from step 1 as the target. + Show me the rank at each point and the business holding #1 at + each point. + +3. Repeat for "[second service] [town]". + +4. Summarise: at how many of the 9 points am I in the top 3, how + many in the top 10, and which competitors hold #1 where I do not. + +5. Create one rank tracker per location I name, weekly, mobile, + depth 20, and estimate the cost with 20 keywords each before + adding anything. +``` + +Step 1 is the one that would have saved the company above a quarter of guessing. Match on the cid, not the name, once you have it. + +## Local rank tracking FAQ + +### Why do my local rankings look different on my phone and my colleague's? + +Because you are standing in different places, or Google thinks you are. Proximity is one of the strongest signals for local queries, so two searchers a few kilometres apart routinely see different map packs. A tracker that checks from one point reports one of those views. + +### What is a local rank grid? + +A set of searches run from points on a grid around a location, usually 3x3 or 5x5, reporting where a business ranks at each point. It shows how far a business's visibility reaches and where competitors take over, which a single rank check cannot. + +### How many points should a local rank grid have? + +Nine, spaced to cover the area you serve, is enough to see the shape. Use twenty-five when you are making a location decision. Beyond that you are paying for resolution you will not act on. + +### Should a service-area business set up a service area or an address on Google? + +Whichever is true. A storefront that lists a service area dilutes the proximity signal it would have had at its address. A business that travels to customers has to use a service area, and should build location pages on its site for the places it serves. + +### Does OpenSEO do local rank tracking? + +Yes. Each rank tracker takes a location, so you can track the same keywords from several towns, and the MCP includes a local rank grid tool that runs one Maps search per grid point. Grid searches and rank checks use credits, and the app shows the cost before either runs; on the hosted app they need the $10/month plan, which includes $10 of credits. diff --git a/web/content/marketing/library/search-console-vs-rank-tracker.mdx b/web/content/marketing/library/search-console-vs-rank-tracker.mdx new file mode 100644 index 000000000..9caf173da --- /dev/null +++ b/web/content/marketing/library/search-console-vs-rank-tracker.mdx @@ -0,0 +1,121 @@ +--- +title: "Is Search Console a rank tracker? Where the free data stops" +description: "Search Console tells you an average position across every searcher, device, and country, three days late, for the queries it chooses to show you. That is enough for a lot of sites. Here is how to tell whether yours is one of them." +--- + +## Can you check keyword rankings for free? + +Yes, for your own site, in Google Search Console. It costs nothing, it is Google's own record rather than a third party's guess, and it reports the metric a rank tracker cannot: clicks. For a site that wants to know whether SEO is working, Search Console is the first answer and sometimes the only one needed. + +What it does not give you is a rank. It gives you an average position, and the difference matters more than the name suggests. + +## What Search Console's position is + +Every time your site appears in a result set, Google records the position of your highest result. The number in the report is the mean of those positions across every impression in the date range, every device, every country, and every query that rolls up into the row you are looking at. + +So a query at "position 8.4" might be position 3 on desktop in one state and position 14 on mobile everywhere else. A page at "position 12" might be at 5 for one query and 30 for another. The average is true and it hides the shape. + +Three other properties of the data shape what you can do with it: + +- **It runs two to three days behind.** Yesterday's results are not there yet, and the most recent days can be revised. +- **It keeps 16 months.** Grant Simmons, who ran search at Homes.com and now works with Fiat Growth, points out that this is the part people waste: "generally you get a lot more information when you look at the 16 months of Google search console data to have an understanding of if the article has been around for that long all the queries that have mapped not just the most recent queries I think that's often forgotten." His episode on measurement is [on the Unscripted SEO podcast](https://unscriptedseo.com/grant-simmons-on-entity-seo-llms-and-the-future-of-search-visibility/). +- **It does not show every query.** A share of your clicks arrive on queries Search Console never reports, for privacy reasons. I have a post that earns about a hundred clicks a month, and the query report explains twelve of them. [The Dark Query Problem](/blogs/dark-queries) is about what to do with the rest. + +There is one more thing worth knowing if you compare Search Console across time. In the autumn of 2025, Google stopped serving 100 results on a single page. Rank trackers had been fetching that page for years to record deep positions, and every fetch counted as an impression for every site on it. When the page went away, desktop impressions fell across the industry at once. If your impressions chart has a cliff that month, that is the cliff, and it was never people. + +## What a rank tracker adds + +A tracker asks Google a specific question on a schedule you set: for this keyword, from this location, on this device, where does this domain appear, and what else is on the page? It records the answer as a number, not an average. + +That gets you five things Search Console will not give you: + +1. **A single position** for one keyword on one device in one place, comparable week to week. +2. **The ranking URL.** Which of your pages Google chose, which tells you when two pages are competing. +3. **The previous position**, so movement is a number in a column rather than a chart you squint at. +4. **SERP features** on the page: an AI overview, a local pack, a video carousel, a shopping block. When your position holds and your clicks fall, this column is usually the reason. +5. **Competitors' positions**, for terms you do not rank for at all. Search Console has nothing to say about a query you never appeared for. + +![OpenSEO rank tracker results for seoarcade.com listing tracked keywords with mobile position, previous position, ranking URL, and SERP features after a live check](/library/rank-tracking/rank-tracker-positions-openseo.png) + +The cost is that every one of those answers is a paid request. In OpenSEO, scheduled checks run through a queue that prices out at a fraction of a cent per keyword per device, so a 25-keyword weekly tracker costs about $0.25 a month; a one-off live check costs around three and a half times more per keyword. On the hosted app, rank checks need the $10 plan, which includes $10 of credits each month. Search Console reads in OpenSEO cost nothing at all. + +## When Search Console alone is enough + +More often than tool vendors admit. Search Console is enough when all of the following are true: + +- You have one location and one language, so the average is not blending markets. +- You can read direction from a chart and do not need a position to the decimal. +- Your questions are about your own pages, not a competitor's. +- The pages you care about are ones Google already shows, so the query rows exist. + +A ten-page consultancy site, a regional service business with one office, a founder checking whether the launch post is gaining: Search Console, filtered to the right page and device, answers the question. Adding a tracker adds a bill and a second dashboard without adding a decision. + +## When you need the tracker + +Four situations, and one of them is usually what tips it. + +- **You are local.** Position depends on where the searcher is standing, and Search Console's country filter is far too coarse. [Local rank tracking](/library/rank-tracking/local-rank-tracking) covers this. +- **You report upward.** "Average position improved from 14.2 to 13.1" is a sentence nobody can act on. "Nine of the twenty terms tied to the pricing page moved onto page one" is. Search Console cannot produce the second sentence without a lot of manual work each week. +- **You need to know why clicks fell while position held.** That is the SERP features column. +- **You want a competitor in the same table.** Search Console is a first-party tool and will never report someone else's site. + +## Use both, in the right order + +The workable pattern is Search Console first and the tracker second. Search Console picks the keywords, because it knows which queries already show your site and how far down; [which keywords to track](/library/rank-tracking/which-keywords-to-track) is that process. The tracker then follows those terms with the precision Search Console lacks. Then Search Console comes back at the end, because clicks are the number the tracker cannot produce, and a report that shows positions moving without showing what the clicks did is half a report. + +## Run this with the OpenSEO MCP + +Connect Search Console to the project, then connect the [OpenSEO MCP](/docs/mcp). The first three steps use no credits. + +```text +Using the OpenSEO MCP on project [name]: + +1. Pull Search Console performance for the last 28 days by query and + device. Show me the 20 queries with the most impressions, with + position on mobile and on desktop side by side. Flag any query + where the two positions differ by more than 5. + +2. Pull the same 20 queries by query and page. For any query that + returns two of my URLs, list both with their impressions. + +3. Pull the last 16 months by date for the whole property and tell + me whether impressions dropped by more than 30% in any single + week, with the week. + +4. Call get_rank_tracker for this project and list the tracked + keywords with their latest mobile position and previous position. + For each one that is also in the Search Console list, put the two + positions in one row: tracker position, Search Console average. + +5. Tell me which keywords I am tracking that Search Console shows no + impressions for, and which Search Console queries with more than + 500 impressions I am not tracking. +``` + +Step 4 is where the difference shows. The tracker position and the Search Console average for the same term will not match, and the gap between them is the blending this page is about. Step 5 keeps the two lists honest with each other over time. + +## The free data is the ground truth for clicks + +Everything a tracker reports is a description of a results page. Search Console is a record of what people did on it. When the two disagree about whether things are going well, the clicks are right and the positions are the explanation. Set them up in that relationship from the start and you will read both correctly. + +## Search Console and rank tracking FAQ + +### Is Google Search Console a rank tracker? + +Not in the usual sense. It reports an average position per query or page, blended across devices, countries, and dates, with a two to three day delay, for your own verified site only. A rank tracker records one position per keyword, device, and location on a schedule, and can include sites you do not own. + +### Why does my Search Console position not match the rank tracker? + +Because they measure different things. Search Console averages your best position across every impression in the period, so a term that is 3 in one state and 15 elsewhere reports as something in between. A tracker reports the position from one place on one device on one day. A gap of several positions between them is normal. + +### How accurate is Search Console average position? + +It is Google's own count of where your result appeared, so the impressions and clicks are as accurate as any data you will get. The position is accurate as an average; it is not a rank. Filter by device and country and the average becomes much closer to what a searcher sees. + +### How do I check keyword rankings for free? + +Open Search Console, go to Performance, filter to the page or query you care about, and switch on the average position metric. Filter to a single device and country to make the number meaningful. That covers your own site; for a competitor's rankings you need a tool that fetches results, which costs money wherever you do it. + +### Does OpenSEO use credits to read Search Console? + +No. Search Console and URL inspection reads are free in OpenSEO, in the app and through the MCP. Rank tracking checks use credits because they fetch live results, and on the hosted app they require the $10/month plan, which includes $10 of credits. diff --git a/web/content/marketing/library/which-keywords-to-track.mdx b/web/content/marketing/library/which-keywords-to-track.mdx new file mode 100644 index 000000000..b700781c3 --- /dev/null +++ b/web/content/marketing/library/which-keywords-to-track.mdx @@ -0,0 +1,119 @@ +--- +title: "Which keywords to track, and how many" +description: "A tracker with 500 keywords in it gets ignored by the second month. Pick 20 to 50 from Search Console, tie each one to a page that earns money, and price the list before you add it." +--- + +## How many keywords should you track? + +Fewer than you want to. A rank tracker is a report you have to read every week, and the number of rows you will read is the number you should track. For most sites that is 20 to 50 keywords. An agency running many clients might track a few hundred per site. Nobody reads 500. + +The instinct to track everything comes from a reasonable place: if the tool can do it, why not? Two reasons. Tracking costs money on every check, so 500 keywords on a daily schedule is a real line item. And a list that long hides the ten terms that matter inside 490 that do not, which is how a tracker ends up as a dashboard nobody opens. + +Charlie Birch, founder of Humanize Collective, makes the underlying point about metrics in general: + +> you can always find a big number to point at, you know, out of context or in a sliver, and be like, yay, big numbers, but like big numbers of what + +A 500-row tracker is a big number of what. Her conversation on where brand strategy meets SEO is on [the Unscripted SEO podcast](https://unscriptedseo.com/charlie-birch-on-where-brand-strategy-meets-seo-what-every-founder-needs-to-know/). + +## Where the list comes from + +Not from a brainstorm, and not from the keyword research tool's volume column. The list comes from Search Console, because Search Console already knows which queries Google shows your site for and how far down. Those are the only terms where a rank change can be read against a click change, which is the point of tracking. + +Here is what that looked like on a real site. Three months of Search Console data for seoarcade.com, a small SEO consultancy site, came back with 76 clicks across the top 400 queries, 52 of them on the brand name. That is not much, and the impressions told a different story: + +- `seo for home services`: 4,558 impressions, average position 17.3, two clicks +- `keyword forecasting`: 3,694 impressions, position 20.3, one click +- `white label keyword research`: 3,131 impressions, position 1.8, one click +- `local seo for home services`: 2,861 impressions, position 14.3, two clicks +- `seo sops`: 2,503 impressions, position 11.9, two clicks +- `authority link building`: 1,984 impressions, position 13.1, no clicks +- `international seo keyword research`: 1,917 impressions, position 10.1, one click + +Those are the terms worth tracking. Each one has demand Google is already willing to show the site for, sits within reach of page one, and maps to a page that exists. A position change on any of them will show up in clicks within weeks, which makes the tracker readable: this moved, and here is what it did. + +The third row is the odd one. Position 1.8 on 3,131 impressions and one click means the page is ranking for a query it does not satisfy, or the SERP has a feature above it that takes the click. Search Console cannot tell you which. A tracker that records SERP features per keyword can, and that is a reason to track a term you already rank for. + +## The four buckets + +Build the list in four groups and cap each one. + +**Money pages.** Five to fifteen terms, one or two per page that produces leads or revenue. These are the rows the business cares about, whether or not they move this month. + +**Striking distance.** Ten to twenty terms from Search Console at positions 4 to 20 with meaningful impressions. This is where movement happens and where the tracker earns its keep. Re-pull this bucket quarterly, because the terms change. + +**Brand, as a control.** One or two brand terms. They should sit at position 1. If they move, something is wrong with the site or the listing, and you want to know before anything else. + +**A competitor's term.** One or two terms you do not rank for yet but a named competitor does. They cost the same to track and they turn the report from "how are we doing" into "how are we doing against them". + +That is 20 to 40 rows, and every one of them means something to the person reading the report. + +## Clean the list before it goes in + +Search Console's query list carries junk, and it is worth knowing what it looks like so you do not track it. The same seoarcade.com pull included queries like `12371: multiple keyword serps report` and several that were the full text of an AI agent's prompt, complete with markdown headers. Those are tools and agents hitting Google, not people. They will show impressions and never a click. Leave them out. + +Also decide, per keyword, whether case matters. `OpenSEO` and `openseo` return the same results; `Apple` and `apple` may not. OpenSEO lowercases tracked keywords by default and tracks a cased keyword separately if you ask, which is right for brand names and wrong for almost everything else. + +## Price the list before you add it + +OpenSEO estimates cost before any check runs. On the seoarcade.com tracker, 25 keywords, mobile only, checking the top 40 results, came back as 225 credits for a one-off live check and 63 credits per scheduled weekly check, which is 252 credits or about $0.25 a month. The $10 plan includes $10 of credits, so a list of this size uses under 3% of them. Doubling the depth to 100 results, adding desktop, or moving to daily checks each raise the cost in proportion, and the estimate updates before you commit. + +![OpenSEO rank tracker cost estimate for seoarcade.com showing 25 keywords on mobile at depth 40: 225 credits for one live check and 63 credits per weekly scheduled check, about $0.25 a month](/library/rank-tracking/rank-tracker-cost-estimate-openseo.png) + +## Run this with the OpenSEO MCP + +Connect Search Console to the project first, because the whole workflow reads from it, and connect the [OpenSEO MCP](/docs/mcp) so an agent can do the pull, the filter, and the add in one pass. + +```text +Using the OpenSEO MCP on project [name]: + +1. Pull Search Console performance for the last 3 months by query, + all rows. Exclude queries containing the brand name, queries + longer than 80 characters, and anything that looks like a tool + ID or a prompt rather than a search. + +2. Keep queries with average position between 4 and 20 and at least + 100 impressions. Sort by impressions. Show me the top 30 with + position, impressions, and clicks. + +3. Pull the same period by query and page, and for each of the 30 + tell me which URL Google is showing. Flag any query where two + URLs both appear. + +4. Call get_rank_tracker for this project. If a tracker exists for + the site's domain, use it; otherwise create one with schedule + weekly, devices mobile, depth 40. + +5. Call estimate_rank_tracker_cost with additionalKeywordCount set + to the number of terms I approve, and show me the per-check and + monthly numbers before adding anything. +``` + +Step 3 is the one people skip. If two of your URLs both show for a query, the tracker will report whichever one Google picked that day, and the row will look volatile when the site is competing with itself. [Cluster keywords into topical hubs](/library/keyword-research/cluster-topical-hubs) covers fixing that. + +## The list is a decision, not a setting + +Every keyword in the tracker is a claim that someone will act on its movement. If you cannot say what you would do differently when a row goes from 14 to 8, or from 8 to 14, it does not belong in the list. That test cuts most first drafts in half and makes what is left worth reading. + +Once the list is in, the next question is what to do with the numbers each week. [The keyword ranking report your CEO will read](/library/rank-tracking/keyword-ranking-report) covers that. + +## Tracking keywords FAQ + +### How many keywords should I track for SEO? + +Twenty to fifty for a typical small or mid-size site: the terms tied to pages that earn money, the striking-distance queries from Search Console, one or two brand terms as a control, and a couple of competitor terms. Track more only if someone will read the extra rows. + +### Which keywords should I track first? + +The ones Search Console already shows your site for at positions 4 to 20 with real impressions. They have demand, they are within reach of page one, and a position change will show up in clicks quickly enough to learn from. + +### Should I track keywords I already rank number one for? + +A few. Brand terms belong in the tracker as an early-warning control, and a term where you rank near the top with a low click-through rate is worth tracking for its SERP features, since a feature above your result explains the missing clicks better than the position does. + +### Does it cost more to track more keywords? + +Yes, in proportion. In OpenSEO the cost of a check is the number of keywords times the number of devices times the depth of results inspected, and scheduled checks run through a cheaper queue than one-off live checks. The app shows an estimate before you add keywords or start a run. On the hosted app, rank checks need the $10/month plan, which includes $10 of credits. + +### How often should I re-pick the keywords? + +Quarterly for the striking-distance bucket, since those queries shift as pages move. The money-page bucket changes only when the business does. diff --git a/web/src/lib/feature-pages.ts b/web/src/lib/feature-pages.ts index ad752586a..1fde83c23 100644 --- a/web/src/lib/feature-pages.ts +++ b/web/src/lib/feature-pages.ts @@ -535,6 +535,40 @@ export const featurePages = { "Start with keywords tied to important pages, active content work, and competitor opportunities discovered in keyword research.", }, ], + guides: { + title: "The Rank Tracking Strategy Library", + description: + "Practitioner strategies for tracking what matters and reporting it so it gets read. Each guide includes a full walkthrough and a copy-paste MCP prompt.", + items: [ + { + label: "Which keywords to track, and how many", + description: + "Twenty to fifty terms from Search Console, priced before they go in.", + href: "/library/rank-tracking/which-keywords-to-track", + }, + { + label: "Is Search Console a rank tracker?", + description: + "What the free average position hides, and when it is enough.", + href: "/library/rank-tracking/search-console-vs-rank-tracker", + }, + { + label: "Local rank tracking", + description: "Why a local business needs a grid before a tracker.", + href: "/library/rank-tracking/local-rank-tracking", + }, + { + label: "The keyword ranking report your CEO will read", + description: + "Lead with the business number and use rankings to explain it.", + href: "/library/rank-tracking/keyword-ranking-report", + }, + ], + cta: { + label: "Browse all rank tracking strategies", + href: "/library/rank-tracking", + }, + }, }, savedKeywords: { slug: FEATURE_PAGE_SLUGS.savedKeywords, diff --git a/web/src/lib/strategy-libraries.ts b/web/src/lib/strategy-libraries.ts index 303bdf1c1..4057925fd 100644 --- a/web/src/lib/strategy-libraries.ts +++ b/web/src/lib/strategy-libraries.ts @@ -111,3 +111,35 @@ export const siteAuditStrategies: StrategyLibraryItem[] = [ href: "/library/site-audit/index-bloat", }, ]; + +export const RANK_TRACKING_LIBRARY = { + name: "Rank Tracking", + path: "/library/rank-tracking", +}; + +export const rankTrackingStrategies: StrategyLibraryItem[] = [ + { + title: "Which keywords to track, and how many", + description: + "Twenty to fifty terms from Search Console, tied to pages that earn money, priced before they go in. A 500-row tracker is a report nobody reads.", + href: "/library/rank-tracking/which-keywords-to-track", + }, + { + title: "Is Search Console a rank tracker? Where the free data stops", + description: + "Search Console gives an average across every searcher and device, three days late. Enough for many sites. How to tell whether yours needs more.", + href: "/library/rank-tracking/search-console-vs-rank-tracker", + }, + { + title: "Local rank tracking: position depends on where the searcher stands", + description: + "Nine points three kilometres apart, three different businesses at number one. Why a local business needs a grid before a tracker.", + href: "/library/rank-tracking/local-rank-tracking", + }, + { + title: "The keyword ranking report your CEO will read", + description: + "Lead with the business number, group movement into four counts, explain three rows, say what happens next. One page, every month.", + href: "/library/rank-tracking/keyword-ranking-report", + }, +]; diff --git a/web/src/routeTree.gen.ts b/web/src/routeTree.gen.ts index 7940c2b89..e825f0893 100644 --- a/web/src/routeTree.gen.ts +++ b/web/src/routeTree.gen.ts @@ -42,11 +42,16 @@ import { Route as MarketingFeaturesBacklinkCheckerRouteImport } from './routes/_ 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 MarketingLibrarySiteAuditIndexRouteImport } from './routes/_marketing/library/site-audit/index' +import { Route as MarketingLibraryRankTrackingIndexRouteImport } from './routes/_marketing/library/rank-tracking/index' import { Route as MarketingLibraryKeywordResearchIndexRouteImport } from './routes/_marketing/library/keyword-research/index' import { Route as MarketingLibraryCompetitiveAnalysisIndexRouteImport } from './routes/_marketing/library/competitive-analysis/index' import { Route as MarketingLibrarySiteAuditTechnicalSeoAuditChecklistRouteImport } from './routes/_marketing/library/site-audit/technical-seo-audit-checklist' import { Route as MarketingLibrarySiteAuditSeoAuditReportTemplateRouteImport } from './routes/_marketing/library/site-audit/seo-audit-report-template' import { Route as MarketingLibrarySiteAuditIndexBloatRouteImport } from './routes/_marketing/library/site-audit/index-bloat' +import { Route as MarketingLibraryRankTrackingWhichKeywordsToTrackRouteImport } from './routes/_marketing/library/rank-tracking/which-keywords-to-track' +import { Route as MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRouteImport } from './routes/_marketing/library/rank-tracking/search-console-vs-rank-tracker' +import { Route as MarketingLibraryRankTrackingLocalRankTrackingRouteImport } from './routes/_marketing/library/rank-tracking/local-rank-tracking' +import { Route as MarketingLibraryRankTrackingKeywordRankingReportRouteImport } from './routes/_marketing/library/rank-tracking/keyword-ranking-report' 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 MarketingLibraryKeywordResearchPositioningToDemandRouteImport } from './routes/_marketing/library/keyword-research/positioning-to-demand' @@ -236,6 +241,12 @@ const MarketingLibrarySiteAuditIndexRoute = path: '/library/site-audit/', getParentRoute: () => MarketingRoute, } as any) +const MarketingLibraryRankTrackingIndexRoute = + MarketingLibraryRankTrackingIndexRouteImport.update({ + id: '/library/rank-tracking/', + path: '/library/rank-tracking/', + getParentRoute: () => MarketingRoute, + } as any) const MarketingLibraryKeywordResearchIndexRoute = MarketingLibraryKeywordResearchIndexRouteImport.update({ id: '/library/keyword-research/', @@ -266,6 +277,30 @@ const MarketingLibrarySiteAuditIndexBloatRoute = path: '/library/site-audit/index-bloat', getParentRoute: () => MarketingRoute, } as any) +const MarketingLibraryRankTrackingWhichKeywordsToTrackRoute = + MarketingLibraryRankTrackingWhichKeywordsToTrackRouteImport.update({ + id: '/library/rank-tracking/which-keywords-to-track', + path: '/library/rank-tracking/which-keywords-to-track', + getParentRoute: () => MarketingRoute, + } as any) +const MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRoute = + MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRouteImport.update({ + id: '/library/rank-tracking/search-console-vs-rank-tracker', + path: '/library/rank-tracking/search-console-vs-rank-tracker', + getParentRoute: () => MarketingRoute, + } as any) +const MarketingLibraryRankTrackingLocalRankTrackingRoute = + MarketingLibraryRankTrackingLocalRankTrackingRouteImport.update({ + id: '/library/rank-tracking/local-rank-tracking', + path: '/library/rank-tracking/local-rank-tracking', + getParentRoute: () => MarketingRoute, + } as any) +const MarketingLibraryRankTrackingKeywordRankingReportRoute = + MarketingLibraryRankTrackingKeywordRankingReportRouteImport.update({ + id: '/library/rank-tracking/keyword-ranking-report', + path: '/library/rank-tracking/keyword-ranking-report', + getParentRoute: () => MarketingRoute, + } as any) const MarketingLibraryKeywordResearchSeedFromConversationRoute = MarketingLibraryKeywordResearchSeedFromConversationRouteImport.update({ id: '/library/keyword-research/seed-from-conversation', @@ -387,11 +422,16 @@ export interface FileRoutesByFullPath { '/library/keyword-research/positioning-to-demand': typeof MarketingLibraryKeywordResearchPositioningToDemandRoute '/library/keyword-research/search-intent-mapping': typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute '/library/keyword-research/seed-from-conversation': typeof MarketingLibraryKeywordResearchSeedFromConversationRoute + '/library/rank-tracking/keyword-ranking-report': typeof MarketingLibraryRankTrackingKeywordRankingReportRoute + '/library/rank-tracking/local-rank-tracking': typeof MarketingLibraryRankTrackingLocalRankTrackingRoute + '/library/rank-tracking/search-console-vs-rank-tracker': typeof MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRoute + '/library/rank-tracking/which-keywords-to-track': typeof MarketingLibraryRankTrackingWhichKeywordsToTrackRoute '/library/site-audit/index-bloat': typeof MarketingLibrarySiteAuditIndexBloatRoute '/library/site-audit/seo-audit-report-template': typeof MarketingLibrarySiteAuditSeoAuditReportTemplateRoute '/library/site-audit/technical-seo-audit-checklist': typeof MarketingLibrarySiteAuditTechnicalSeoAuditChecklistRoute '/library/competitive-analysis/': typeof MarketingLibraryCompetitiveAnalysisIndexRoute '/library/keyword-research/': typeof MarketingLibraryKeywordResearchIndexRoute + '/library/rank-tracking/': typeof MarketingLibraryRankTrackingIndexRoute '/library/site-audit/': typeof MarketingLibrarySiteAuditIndexRoute } export interface FileRoutesByTo { @@ -438,11 +478,16 @@ export interface FileRoutesByTo { '/library/keyword-research/positioning-to-demand': typeof MarketingLibraryKeywordResearchPositioningToDemandRoute '/library/keyword-research/search-intent-mapping': typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute '/library/keyword-research/seed-from-conversation': typeof MarketingLibraryKeywordResearchSeedFromConversationRoute + '/library/rank-tracking/keyword-ranking-report': typeof MarketingLibraryRankTrackingKeywordRankingReportRoute + '/library/rank-tracking/local-rank-tracking': typeof MarketingLibraryRankTrackingLocalRankTrackingRoute + '/library/rank-tracking/search-console-vs-rank-tracker': typeof MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRoute + '/library/rank-tracking/which-keywords-to-track': typeof MarketingLibraryRankTrackingWhichKeywordsToTrackRoute '/library/site-audit/index-bloat': typeof MarketingLibrarySiteAuditIndexBloatRoute '/library/site-audit/seo-audit-report-template': typeof MarketingLibrarySiteAuditSeoAuditReportTemplateRoute '/library/site-audit/technical-seo-audit-checklist': typeof MarketingLibrarySiteAuditTechnicalSeoAuditChecklistRoute '/library/competitive-analysis': typeof MarketingLibraryCompetitiveAnalysisIndexRoute '/library/keyword-research': typeof MarketingLibraryKeywordResearchIndexRoute + '/library/rank-tracking': typeof MarketingLibraryRankTrackingIndexRoute '/library/site-audit': typeof MarketingLibrarySiteAuditIndexRoute } export interface FileRoutesById { @@ -491,11 +536,16 @@ export interface FileRoutesById { '/_marketing/library/keyword-research/positioning-to-demand': typeof MarketingLibraryKeywordResearchPositioningToDemandRoute '/_marketing/library/keyword-research/search-intent-mapping': typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute '/_marketing/library/keyword-research/seed-from-conversation': typeof MarketingLibraryKeywordResearchSeedFromConversationRoute + '/_marketing/library/rank-tracking/keyword-ranking-report': typeof MarketingLibraryRankTrackingKeywordRankingReportRoute + '/_marketing/library/rank-tracking/local-rank-tracking': typeof MarketingLibraryRankTrackingLocalRankTrackingRoute + '/_marketing/library/rank-tracking/search-console-vs-rank-tracker': typeof MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRoute + '/_marketing/library/rank-tracking/which-keywords-to-track': typeof MarketingLibraryRankTrackingWhichKeywordsToTrackRoute '/_marketing/library/site-audit/index-bloat': typeof MarketingLibrarySiteAuditIndexBloatRoute '/_marketing/library/site-audit/seo-audit-report-template': typeof MarketingLibrarySiteAuditSeoAuditReportTemplateRoute '/_marketing/library/site-audit/technical-seo-audit-checklist': typeof MarketingLibrarySiteAuditTechnicalSeoAuditChecklistRoute '/_marketing/library/competitive-analysis/': typeof MarketingLibraryCompetitiveAnalysisIndexRoute '/_marketing/library/keyword-research/': typeof MarketingLibraryKeywordResearchIndexRoute + '/_marketing/library/rank-tracking/': typeof MarketingLibraryRankTrackingIndexRoute '/_marketing/library/site-audit/': typeof MarketingLibrarySiteAuditIndexRoute } export interface FileRouteTypes { @@ -544,11 +594,16 @@ export interface FileRouteTypes { | '/library/keyword-research/positioning-to-demand' | '/library/keyword-research/search-intent-mapping' | '/library/keyword-research/seed-from-conversation' + | '/library/rank-tracking/keyword-ranking-report' + | '/library/rank-tracking/local-rank-tracking' + | '/library/rank-tracking/search-console-vs-rank-tracker' + | '/library/rank-tracking/which-keywords-to-track' | '/library/site-audit/index-bloat' | '/library/site-audit/seo-audit-report-template' | '/library/site-audit/technical-seo-audit-checklist' | '/library/competitive-analysis/' | '/library/keyword-research/' + | '/library/rank-tracking/' | '/library/site-audit/' fileRoutesByTo: FileRoutesByTo to: @@ -595,11 +650,16 @@ export interface FileRouteTypes { | '/library/keyword-research/positioning-to-demand' | '/library/keyword-research/search-intent-mapping' | '/library/keyword-research/seed-from-conversation' + | '/library/rank-tracking/keyword-ranking-report' + | '/library/rank-tracking/local-rank-tracking' + | '/library/rank-tracking/search-console-vs-rank-tracker' + | '/library/rank-tracking/which-keywords-to-track' | '/library/site-audit/index-bloat' | '/library/site-audit/seo-audit-report-template' | '/library/site-audit/technical-seo-audit-checklist' | '/library/competitive-analysis' | '/library/keyword-research' + | '/library/rank-tracking' | '/library/site-audit' id: | '__root__' @@ -647,11 +707,16 @@ export interface FileRouteTypes { | '/_marketing/library/keyword-research/positioning-to-demand' | '/_marketing/library/keyword-research/search-intent-mapping' | '/_marketing/library/keyword-research/seed-from-conversation' + | '/_marketing/library/rank-tracking/keyword-ranking-report' + | '/_marketing/library/rank-tracking/local-rank-tracking' + | '/_marketing/library/rank-tracking/search-console-vs-rank-tracker' + | '/_marketing/library/rank-tracking/which-keywords-to-track' | '/_marketing/library/site-audit/index-bloat' | '/_marketing/library/site-audit/seo-audit-report-template' | '/_marketing/library/site-audit/technical-seo-audit-checklist' | '/_marketing/library/competitive-analysis/' | '/_marketing/library/keyword-research/' + | '/_marketing/library/rank-tracking/' | '/_marketing/library/site-audit/' fileRoutesById: FileRoutesById } @@ -904,6 +969,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MarketingLibrarySiteAuditIndexRouteImport parentRoute: typeof MarketingRoute } + '/_marketing/library/rank-tracking/': { + id: '/_marketing/library/rank-tracking/' + path: '/library/rank-tracking' + fullPath: '/library/rank-tracking/' + preLoaderRoute: typeof MarketingLibraryRankTrackingIndexRouteImport + parentRoute: typeof MarketingRoute + } '/_marketing/library/keyword-research/': { id: '/_marketing/library/keyword-research/' path: '/library/keyword-research' @@ -939,6 +1011,34 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MarketingLibrarySiteAuditIndexBloatRouteImport parentRoute: typeof MarketingRoute } + '/_marketing/library/rank-tracking/which-keywords-to-track': { + id: '/_marketing/library/rank-tracking/which-keywords-to-track' + path: '/library/rank-tracking/which-keywords-to-track' + fullPath: '/library/rank-tracking/which-keywords-to-track' + preLoaderRoute: typeof MarketingLibraryRankTrackingWhichKeywordsToTrackRouteImport + parentRoute: typeof MarketingRoute + } + '/_marketing/library/rank-tracking/search-console-vs-rank-tracker': { + id: '/_marketing/library/rank-tracking/search-console-vs-rank-tracker' + path: '/library/rank-tracking/search-console-vs-rank-tracker' + fullPath: '/library/rank-tracking/search-console-vs-rank-tracker' + preLoaderRoute: typeof MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRouteImport + parentRoute: typeof MarketingRoute + } + '/_marketing/library/rank-tracking/local-rank-tracking': { + id: '/_marketing/library/rank-tracking/local-rank-tracking' + path: '/library/rank-tracking/local-rank-tracking' + fullPath: '/library/rank-tracking/local-rank-tracking' + preLoaderRoute: typeof MarketingLibraryRankTrackingLocalRankTrackingRouteImport + parentRoute: typeof MarketingRoute + } + '/_marketing/library/rank-tracking/keyword-ranking-report': { + id: '/_marketing/library/rank-tracking/keyword-ranking-report' + path: '/library/rank-tracking/keyword-ranking-report' + fullPath: '/library/rank-tracking/keyword-ranking-report' + preLoaderRoute: typeof MarketingLibraryRankTrackingKeywordRankingReportRouteImport + 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' @@ -1058,11 +1158,16 @@ interface MarketingRouteChildren { MarketingLibraryKeywordResearchPositioningToDemandRoute: typeof MarketingLibraryKeywordResearchPositioningToDemandRoute MarketingLibraryKeywordResearchSearchIntentMappingRoute: typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute MarketingLibraryKeywordResearchSeedFromConversationRoute: typeof MarketingLibraryKeywordResearchSeedFromConversationRoute + MarketingLibraryRankTrackingKeywordRankingReportRoute: typeof MarketingLibraryRankTrackingKeywordRankingReportRoute + MarketingLibraryRankTrackingLocalRankTrackingRoute: typeof MarketingLibraryRankTrackingLocalRankTrackingRoute + MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRoute: typeof MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRoute + MarketingLibraryRankTrackingWhichKeywordsToTrackRoute: typeof MarketingLibraryRankTrackingWhichKeywordsToTrackRoute MarketingLibrarySiteAuditIndexBloatRoute: typeof MarketingLibrarySiteAuditIndexBloatRoute MarketingLibrarySiteAuditSeoAuditReportTemplateRoute: typeof MarketingLibrarySiteAuditSeoAuditReportTemplateRoute MarketingLibrarySiteAuditTechnicalSeoAuditChecklistRoute: typeof MarketingLibrarySiteAuditTechnicalSeoAuditChecklistRoute MarketingLibraryCompetitiveAnalysisIndexRoute: typeof MarketingLibraryCompetitiveAnalysisIndexRoute MarketingLibraryKeywordResearchIndexRoute: typeof MarketingLibraryKeywordResearchIndexRoute + MarketingLibraryRankTrackingIndexRoute: typeof MarketingLibraryRankTrackingIndexRoute MarketingLibrarySiteAuditIndexRoute: typeof MarketingLibrarySiteAuditIndexRoute } @@ -1111,6 +1216,14 @@ const MarketingRouteChildren: MarketingRouteChildren = { MarketingLibraryKeywordResearchSearchIntentMappingRoute, MarketingLibraryKeywordResearchSeedFromConversationRoute: MarketingLibraryKeywordResearchSeedFromConversationRoute, + MarketingLibraryRankTrackingKeywordRankingReportRoute: + MarketingLibraryRankTrackingKeywordRankingReportRoute, + MarketingLibraryRankTrackingLocalRankTrackingRoute: + MarketingLibraryRankTrackingLocalRankTrackingRoute, + MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRoute: + MarketingLibraryRankTrackingSearchConsoleVsRankTrackerRoute, + MarketingLibraryRankTrackingWhichKeywordsToTrackRoute: + MarketingLibraryRankTrackingWhichKeywordsToTrackRoute, MarketingLibrarySiteAuditIndexBloatRoute: MarketingLibrarySiteAuditIndexBloatRoute, MarketingLibrarySiteAuditSeoAuditReportTemplateRoute: @@ -1121,6 +1234,8 @@ const MarketingRouteChildren: MarketingRouteChildren = { MarketingLibraryCompetitiveAnalysisIndexRoute, MarketingLibraryKeywordResearchIndexRoute: MarketingLibraryKeywordResearchIndexRoute, + MarketingLibraryRankTrackingIndexRoute: + MarketingLibraryRankTrackingIndexRoute, MarketingLibrarySiteAuditIndexRoute: MarketingLibrarySiteAuditIndexRoute, } diff --git a/web/src/routes/_marketing/library/index.tsx b/web/src/routes/_marketing/library/index.tsx index bda448982..3c08ee3d0 100644 --- a/web/src/routes/_marketing/library/index.tsx +++ b/web/src/routes/_marketing/library/index.tsx @@ -3,16 +3,18 @@ import { buildPageSeo } from "@/lib/seo"; import { competitiveAnalysisStrategies, keywordResearchStrategies, + rankTrackingStrategies, siteAuditStrategies, } from "@/lib/strategy-libraries"; const PATH = "/library"; const description = - "Browse practical SEO strategies for finding search demand, sizing up competitors, auditing a site, mapping intent, and planning pages."; + "Browse practical SEO strategies for finding search demand, sizing up competitors, auditing a site, tracking rankings, mapping intent, and planning pages."; const featuredStrategies = [ ...keywordResearchStrategies.slice(0, 2), ...competitiveAnalysisStrategies.slice(0, 1), ...siteAuditStrategies.slice(0, 1), + ...rankTrackingStrategies.slice(0, 1), ]; export const Route = createFileRoute("/_marketing/library/")({ @@ -97,6 +99,23 @@ function StrategyLibraryIndexPage() {

+ +

+ Rank Tracking +

+

+ Pick the keywords worth watching, know where Search Console stops, + track local positions from where customers stand, and write the + ranking report that gets read. +

+

+ View all {rankTrackingStrategies.length} strategies{" "} + +

+
diff --git a/web/src/routes/_marketing/library/rank-tracking/index.tsx b/web/src/routes/_marketing/library/rank-tracking/index.tsx new file mode 100644 index 000000000..f2ca01cab --- /dev/null +++ b/web/src/routes/_marketing/library/rank-tracking/index.tsx @@ -0,0 +1,306 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { buildBreadcrumbJsonLd, buildPageSeo } from "@/lib/seo"; +import { rankTrackingStrategies } from "@/lib/strategy-libraries"; + +const PATH = "/library/rank-tracking"; + +const faqs = [ + { + question: "What is rank tracking?", + answer: + "Checking where a website appears in Google's results for a chosen set of keywords, from a chosen location and device, on a schedule, and recording the position each time so you can see movement. It answers a narrower question than Search Console, which reports an average position across every searcher, and a more precise one.", + }, + { + question: "How many keywords should I track?", + answer: + "Twenty to fifty for most sites. Take them from Search Console, where you can see which queries already show your site at positions 4 to 20 with real impressions, tie each one to a page that produces leads or revenue, and add one or two brand terms as a control. A tracker with 500 rows is a report nobody reads.", + }, + { + question: "How often should rankings be checked?", + answer: + "Weekly is the useful default. Positions move day to day for reasons that have nothing to do with your work, and a daily check mostly records that noise at thirty times the cost. Check daily during a migration or a launch, then go back to weekly.", + }, + { + question: "Should I track mobile or desktop rankings?", + answer: + "Mobile, unless you know your customers search from desktops. Most queries are mobile-first now and the two result pages differ. OpenSEO defaults to mobile and can track both, which doubles the cost of each check.", + }, + { + question: "Is Google Search Console a rank tracker?", + answer: + "Not quite. It reports an average position per query blended across devices, countries, and dates, with a delay of two to three days, for your own site only. That is enough for a single-location site that wants direction. A tracker adds a precise position per keyword and device, the ranking URL, SERP features, and competitors, and costs money for each check.", + }, + { + question: "How much does rank tracking cost in OpenSEO?", + answer: + "It depends on keywords, devices, depth, and schedule, and the app shows the estimate before anything runs. As a reference point, 100 keywords on mobile, checking the top 40 results weekly, comes to about a dollar a month in credits. Rank checks on the hosted app need the $10/month plan, which includes $10 of credits; self-hosted deployments pay their data provider directly.", + }, + { + question: "Can I track local rankings for a specific city?", + answer: + "Yes. Each tracker in OpenSEO has a location, so you can track the same keywords from several towns, and the MCP includes a local rank grid that runs a Maps search from each point of a 3x3 or 5x5 grid to show how far your visibility reaches.", + }, +]; + +const faqLd = { + "@context": "https://schema.org", + "@type": "FAQPage", + mainEntity: faqs.map((faq) => ({ + "@type": "Question", + name: faq.question, + acceptedAnswer: { "@type": "Answer", text: faq.answer }, + })), +}; + +const breadcrumbLd = buildBreadcrumbJsonLd([ + { name: "Strategy Library", path: "/library" }, + { name: "Rank Tracking", path: PATH }, +]); + +export const Route = createFileRoute("/_marketing/library/rank-tracking/")({ + head: () => + buildPageSeo({ + title: "Rank Tracking: The Strategy Library", + description: + "Four rank tracking strategies for people who report SEO upward without an SEO budget: pick the keywords, know where Search Console stops, track local positions properly, and write the report that gets read. Each includes a workflow and an OpenSEO MCP prompt.", + path: PATH, + titleSuffix: "OpenSEO", + }), + component: RankTrackingLibraryPage, +}); + +function RankTrackingLibraryPage() { + return ( +
+
+ +

+ The Rank Tracking Strategy Library +

+

+ Four strategies for the person who has to show someone a number every + month: which keywords to track and how many, where Search + Console’s free data stops, how to track a local business from + where its customers stand, and how to write the ranking report that + gets read. Each one includes a workflow and a copy-paste OpenSEO MCP + prompt. +

+
+ +
+

+ How do you track keyword rankings without wasting the budget? +

+

+ Start from what Search Console already shows, track only what someone + will read, measure local positions from where customers stand, and + report movement as an explanation for the business number rather than + a table. +

+
+ {rankTrackingStrategies.map((strategy, index) => { + const number = String(index + 1).padStart(2, "0"); + return ( + + + {number} + +

+ {strategy.title} + +

+

+ {strategy.description} +

+
+ ); + })} +
+
+ +
+

+ Why rank tracking gets bought and then ignored +

+

+ A rank tracker is the easiest SEO tool to justify and the easiest to + stop reading. It gets bought because positions are the number everyone + understands. It gets ignored because a table of 300 keywords with + green and red arrows does not tell the person paying for it whether + anything happened that matters, and after a couple of months they stop + opening it. +

+

+ The fix is upstream of the tool. Track a short list chosen from Search + Console, so every row is a query Google already shows your site for + and a page that earns something. Then report positions as the + explanation for what clicks did, not as the headline. The four + strategies above are that sequence: choose, measure, measure locally + if you are local, report. +

+
+ +
+

+ What OpenSEO rank tracking checks +

+

+ A tracker is a domain, a location, and a language. You choose the + devices (mobile, desktop, or both; mobile by default), how deep to + look (the top 10 to 100 results, 40 by default), and a schedule + (manual, daily, weekly, or monthly). Each check records, per keyword + and device, the position, the previous position, the URL that ranked, + and the SERP features on the page. A tracker holds up to 1,000 + keywords, and a project can hold up to 500 trackers, which is how you + track several locations for one business. +

+

+ Creating a tracker and adding keywords cost nothing. Checks cost + credits, and the app shows an estimate before every scheduled add and + every live run. Scheduled checks go through a queue that is much + cheaper per keyword than a one-off live check. The keyword count, + devices, depth, and schedule multiply, so 100 keywords on mobile at + depth 40, weekly, is about a dollar a month; both devices doubles it + and daily checks multiply it by about seven. +

+

+ On the hosted app, rank checks need the $10/month plan, which includes + $10 of credits each month; a free account can build a tracker but its + checks will not run. Self-hosted deployments are not gated and pay + their data provider directly. Search Console reads, which the + strategies here lean on, use no credits on either. +

+

+ You can run all of it from the{" "} + + rank tracking + {" "} + page, or through the{" "} + + OpenSEO MCP + + , which exposes the tracker to an AI assistant: create it, estimate + the cost, add and remove keywords, run a check, and read the latest + positions, alongside Search Console and the local rank grid in the + same conversation. +

+
+ +
+

+ Where rank tracking ends +

+

+ Three things a tracker cannot tell you, and where to get them. +

+
    +
  • + Whether anyone clicked. Positions describe a results page; clicks + describe what people did on it. Search Console is the record, and{" "} + + programmatic discovery with Search Console + {" "} + is how to read it at scale. +
  • +
  • + Whether the keyword was worth it. A term can move from 14 to 4 and + change nothing, because the demand was never there or the intent was + wrong.{" "} + + Search-intent mapping + {" "} + comes before the tracker, not after. +
  • +
  • + Whether an AI assistant recommends you. Tools that monitor that are + directional; answers are personalised and there is no first-party + report behind them. Report it as a signal, next to the tracker, + never as the same kind of number. +
  • +
+
+ +
+

+ Rank tracking FAQ +

+
+ {faqs.map((faq) => ( +
+

+ {faq.question} +

+

+ {faq.answer} +

+
+ ))} +
+
+ +
+
+

+ Track rankings with your own agent +

+

+ Each strategy ends with a copy-paste MCP prompt. OpenSEO is open + source, free to start, and does not require a credit card. +

+
+ + Start with OpenSEO + + +
+ +