Growth playbook · September 2026
The SEO & AEO Playbook Behind 1M+ Organic Clicks
Zero ad spend. Real engineering decisions, not marketing tips.
JobCannon spends $0 on marketing. No ad account, no paid distribution, no growth team. In the six months to 16 September 2026, Google Search Console recorded this instead:
71.8M
Impressions
1M+
Organic clicks
$0
Ad spend
Source: Google Search Console, trailing 6 months, measured 16 Sep 2026.
This is not a listicle of generic SEO tips. It is four real engineering decisions, the kind that show up as code review comments and build gates, not blog-post bullet points. Each one exists because the naive version of it broke something first.
The title is a budget, not something to truncate
The naive way to generate a page title is a template plus a variable: a fixed prefix or suffix wrapped around whatever string the page happens to carry. It works until the variable is long. Then the whole title runs past the SERP’s character budget.
The failure mode is not a clipped ellipsis at the end of the line. It is worse than that. When a title overflows, Google frequently throws the entire title away and writes its own from the page’s H1. The page keeps its ranking position, but the exact phrase it was ranking for is gone from the snippet, replaced by whatever Google’s rewrite picked instead. The click goes to a competitor whose title actually said the thing the searcher typed.
The fix is to stop treating the title as a string you truncate after the fact, and start treating it as a budget you assemble into. The head of the title (the part carrying the ranking keyword) is fixed and protected. The variable part is fit into whatever budget remains, and if it does not fit, it is dropped or shortened at a word boundary, never mid-phrase. The title that ships is always inside budget by construction, not by hope.
If you run Claude Code, here is that fix as an actual skill, not a description of one. Save it as .claude/skills/seo-title-budget/SKILL.md and it will find every place your codebase builds a title from a template plus an unbounded variable, and check each one against this budget-fit pattern instead of a blind truncate.
---
name: seo-title-budget
description: Audit and fix page titles that exceed the SERP character budget, so Google never discards the title and writes its own from the H1 instead. Use when auditing meta titles across a site, or building a title-generation function for a new page type.
user-invokable: true
args:
- name: path
description: File or directory of page templates / title-generating functions to audit (optional, defaults to the whole project)
required: false
---
Find and fix titles that get truncated or discarded by Google instead of fitting inside a character budget.
## Why this exists
A title built from `${head} — ${variable}` looks fine in a component, but if `variable` runs long, the rendered title blows past the safe SERP width (~60 characters on desktop). Google does not just clip the overflow — it frequently discards the whole title and writes a replacement from the page's H1. The page keeps its ranking position, but the exact phrase it was ranking for is gone from the snippet.
## What to check
1. Find every place a page title (`<title>`, `og:title`, `generateMetadata`, or equivalent) is built from a template with an unbounded variable.
2. For each one, confirm there is a real budget check. If the variable can be arbitrarily long — a user-generated name, a product title, a slug-derived string — and there is no check, flag it.
3. Confirm the fix fits the shape below, not a blind `.slice(0, 60)` — that cuts mid-word or mid-phrase, which is a smaller version of the same bug.
## The fix — fit into budget, protect the head
```ts
const SERP_TITLE_BUDGET = 60
function fitTitle(head: string, tail: string, sep = ' — '): string {
const budget = SERP_TITLE_BUDGET - head.length - sep.length
if (budget <= 0) return head
if (tail.length <= budget) return `${head}${sep}${tail}`
const words = tail.slice(0, budget + 1).split(' ')
words.pop() // drop whatever word would be cut mid-way
const fitted = words.join(' ').replace(/[,;:-]+$/, '')
return fitted ? `${head}${sep}${fitted}` : head
}
```
`head` carries the keyword you are ranking for and is never touched. `tail` fits inside whatever budget remains, or gets dropped at a word boundary, never mid-phrase. The title that ships is inside budget by construction, not by hope.
## Report format
List every title-construction site found, whether it currently has a budget check, and the character count of the worst real value seen in production (not a guess). A site with no budget check is a defect, not a style note.
The sitemap is a filter, not a list
A sitemap looks like a simple export: take every URL, dump it into XML, done. The bug hides in what “every URL” means once a site has noindex rules, redirect logic, and canonical decisions scattered across multiple files.
If the noindex decision lives in one place and the sitemap-generation logic lives in another, the two drift. A page gets marked noindex for a good reason, but the sitemap job does not know about that rule, so it keeps emitting the URL. Google now sees a sitemap telling it “index this” and a meta tag telling it “don’t index this” on the same URL. That contradiction is exactly the kind of signal that erodes crawl trust across the whole domain, not just on the one bad URL.
The fix is structural: filter at a single point. The sitemap generator calls the exact same functions that decide indexability and redirects everywhere else on the site, so there is only one place a page’s indexability is decided, and the sitemap is a read of that decision, never a second opinion on it.
No hardcoded numbers, ever
Programmatic SEO surfaces carry public counts: how many comparison pages, how many tests in a catalog, how many of anything. The obvious way to write copy that says “43 tests” is to type 43. The obvious way is wrong, because the number is only correct on the day it is typed. The catalog keeps growing, and every hardcoded copy of that number starts rotting the moment it ships.
The discipline is simple to state and easy to skip under deadline: every public-facing number is read from the live source it describes, never typed as a literal in the copy. If the array has 43 entries, the page renders array.length, not the digits 4 and 3. A stale number in public copy is not a cosmetic bug. It is a small, repeated credibility hit, and on a page an AI answer engine might cite verbatim, a wrong number is worse than no number.
Built from one source of truth, not typed by hand
161 comparison pages. A 43-test B2B catalog. Neither was written by hand, page by page. Both are generated from one source of truth, a single list of entries that drives page generation, sitemap emission, and internal linking together.
The alternative, hand-authoring each page, does not scale past a handful of entries before it becomes the bottleneck on every future addition, and it guarantees the pages drift out of sync with each other in formatting, structure, and the numbers discipline described above. One source of truth means adding an entry to one list produces a consistent page, a sitemap row, and correct internal links, in one motion. That is what actually lets a surface reach 161 pages without 161 separate acts of authorship, and without 161 separate chances to get a number wrong.
What this adds up to
None of these four are secrets. Every SEO engineer has run into title truncation, sitemap drift, stale copy, and the choice between hand-authoring and generating at scale. The playbook is not the idea, it is actually holding the line on all four at once, across a surface large enough that shortcuts are tempting on every one of them.
$0 ad spend was never the interesting part. The interesting part is that 71.8M impressions came from search engines and answer engines finding a surface that was built to be found, cleanly, at scale, without lying to them about what is on it.
Try it
See the surface these techniques built. jobcannon.io is a free assessment platform, the comparison pages and B2B catalog referenced above are part of it.