I searched Google for the service I sell, in my city, and did not show up. The complete method I used to fix it, step by step, with the scripts.

Free, in exchange for knowing who you are
You get the whole thing at no cost. I only ask for your email so I can write later and ask how it went: if something did not work for you, I want to know. No spam.
One confirmation email and that is it. You can leave whenever you want.
An audit gave my own site 75 out of 100. Worse: I searched Google for the exact service I sell, in my own city, and I did not show up. Not on the first page, not on the third.
I fixed every item, wrote down what worked and what cost me time, and packaged it into this skill. These are the final numbers, measured in production on the same site you are reading:
Open PageSpeed Insights, type chiragx.com, and check it yourself. I am not asking you to take my word for it.

This is a Claude Code skill. Unzip the folder inside your skills directory:
~/.claude/skills/web-playbook-100/
On Windows that is C:\Users\YOUR-USER\.claude\skills\. It activates on its own when you ask to audit or improve a site's SEO.
Not using Claude Code? It still works. Everything is Markdown and the scripts run on their own from the terminal. Open SKILL.md and read it as the manual it is.
The skill activates when you talk about SEO, PageSpeed, Core Web Vitals, fonts, images or "why do I not show up on Google". To not depend on that, name it: use the web-playbook-100 skill. And give it these four things in the first sentence, because without them it has to ask:
/es/, /en/).One change, one measurement. Always ask for the before and the after with the same command, and for a list of what it could not verify. The prompt I start any site with:
Use the web-playbook-100 skill. My site is https://mydomain.com, routes /es/ and /en/, hosted on [Cloudflare / WordPress / ...]. Run scripts/audit.sh with --sitemap and give me the gaps ordered by impact according to the skill, with the command you used to verify each one. Do not change anything yet.
Each step below carries the exact prompt, what Claude does on its own and what stays yours. At the end you get the full list of what nobody can do for you.
Almost everyone skips this step, which is why they end up fixing the wrong thing. Before touching a line:
Save all four results in a file. At the end you measure the same four again, and that is how you know whether any of it worked.
The scripts/audit.sh script does the technical half for you: with --sitemap it checks status codes, redirects, canonicals, hreflang and metadata across every URL in your sitemap in one pass, and it also checks that you force HTTPS, which security headers you send and what your robots.txt says to each AI crawler. And scripts/psi.js runs PageSpeed three times, takes the median and lists what is requested before the LCP.

Ask for it like this:
Measure https://mydomain.com/es/ with scripts/psi.js three times on mobile (PSI_KEY is in the environment), run blocks 1 to 3 of scripts/browser-audit.js on the page and save everything in baseline.md. Change nothing.
What it does on its own: PageSpeed with a median, the list of what loads before the LCP, CLS and LCP in a real browser and the curl battery. What stays yours: get the free PageSpeed API key (five minutes, with your Google account), open the site on your phone on mobile data, search your service plus your city in incognito from your city, and count your services.
This moved the needle more than anything else, and none of it is technical.
If you sell five services and they live as five paragraphs inside your home page, Google has one page to rank for five different searches. It will lose all five. Each service needs its own URL, its own title, its own description and real content of its own.
In my case I went from a home page with a services section to twelve pages (six services in two languages), each with:
Service and FAQPage.The skill covers this in references/arquitectura.md, including how to decide what deserves its own page. Short rule: if someone could search for it by name, it deserves a page.
Ask for it like this:
These are my services: [list]. Using references/arquitectura.md tell me which ones deserve their own URL, propose a slug in Spanish and English, a title of up to 60 characters and a description of up to 158 for each, and the section structure. Write only once I give you prices, real client questions and cases.
What it does on its own: decide what deserves a page, propose slugs, metadata and structure, write, and link the silos in both directions if it has your code. What stays yours: the facts (prices, ranges, the questions people really ask you, cases with numbers), approving every text and, on a visual builder, creating the pages by hand.
This is where most sites lose points to invisible things. The order that works:
Person or Organization), what you sell (Service), your questions (FAQPage) and the navigation path (BreadcrumbList). Validate every page in the Rich Results Test.lastmod. A hand-written sitemap goes stale in two weeks.Once that is in place, verify the site in Google Search Console, submit the sitemap and request indexing for the new pages. Also register on Bing Webmaster Tools: it takes half an hour and it feeds a good share of what AI search engines answer with.
Search Console has a learning curve, and some decisions depend on how your site is built. I am listing it as a step because explaining it properly is a conversation, not a paragraph. Write me and we go through your case.
Having a certificate is not the same as forcing it. You can have the padlock and still serve the entire page unencrypted at http://yourdomain.com, with a 200 response and no redirect anywhere. It happens more than you would think, and you can check it in a second:
curl -sIL http://yourdomain.com | grep -i "^HTTP/\|^location:"
If that chain never jumps to https://, you have three problems at once. One: your contact form travels unencrypted. Two: old links pointing at http://www. count as a different URL, so the authority they send you gets split instead of added up. And three, the one nobody connects: every extra redirect costs mobile load time. In a real measurement I ran, the redirect chain was eating 0.61 seconds of rendering.
On Cloudflare it is one switch: SSL/TLS → Edge Certificates → Always Use HTTPS. If you are not on Cloudflare, it is a 301 redirect rule on your server.
While you are there, check whether you send any security headers. Odds are you send none. A _headers file (or your host's equivalent) covers the sensible minimum:
/* X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin X-Frame-Options: SAMEORIGIN
A warning about HSTS: it forces browsers to remember for months that your site is HTTPS only. It is good, but if something breaks you cannot undo it quickly, because the instruction already lives in every visitor's browser. Turn it on once the rest is stable, not on day one.
Ask for it like this:
Fix canonical, reciprocal hreflang with x-default, a sitemap with real lastmod, and the titles and descriptions outside the limits in [my code / my template]. Generate the JSON-LD per template and check that it parses. Check forced HTTPS and headers. Before and after with scripts/audit.sh --sitemap.
What it does on its own: everything that lives in the code or the template, plus the check from the outside. With a Cloudflare token, also the HTTPS switch and the headers. What stays yours: verifying the site in Search Console and Bing Webmaster Tools, submitting the sitemap, requesting indexing URL by URL (Google gives a small daily quota), deciding on HSTS and, without a token, flipping the switches in your hosting panel.
This is the area with the most myth per square meter. What actually worked, in order of impact:
Google Fonts costs you a connection to another domain before a single letter can paint. Download them and serve them yourself. Two warnings that cost me time:
font-display: optional on your body font. With swap the text repaints, and that is where a good chunk of your layout shift is born. swap is only acceptable once you have tuned the fallback with measured metrics (block 5 of browser-audit.js computes them): then the text repaints but nothing moves.scripts/selfhost-fonts.js handles the download and the deduplication for you.
<picture>, keeping the original as a fallback. My home page went from 1,013 KB to 533 KB with no visible difference.img{max-width:100%} rule, it must include height:auto. Without it your images get squashed. This bug lived on my site for weeks and I only caught it in a screen recording.Getting to zero is about reserving space: dimensions on images, heights on anything that loads late, and nothing that moves when the font arrives. scripts/browser-audit.js measures shift, largest paint and real contrast in an actual browser, which is different from what a lab reports.
Converting to AVIF is not enough. A 1000px-wide file dropped into a 340px slot still ships three times the pixels anyone sees. PageSpeed reports it as Improve image delivery and tells you exactly how much is wasted.
The rule is simple: look at the real on-screen width, multiply by two (for high-density screens) and generate that variant. A 340px slot means a 700px file. On my own site that was a single image wasting 23 KiB.
ffmpeg -i original.jpg -vf "scale=-2:868:flags=lanczos" -q:v 3 medium.jpg ffmpeg -i medium.jpg -c:v libaom-av1 -crf 34 -cpu-used 6 -still-picture 1 medium.avif
And one that costs you: if you change what an image shows, change its filename. Images are usually served with a thirty-day cache. Rewrite the same file and everyone who already saw it keeps seeing the old one for a month, while you tick it off as fixed.
loading="lazy" only exists for <img>. Any background-image, inline or in CSS, is requested the moment the element exists, wherever it sits. On my home page that was twenty card backgrounds (events, covers, YouTube thumbnails) several screens below the fold: 642 KB of images requested before LCP, competing on slow 4G with the hero text. Result: the main paragraph took 3.5 s to paint with its HTML ready since half a second in.
The first thing I tried, content-visibility: auto on the far sections, does not prevent it: Chrome stops painting the section but keeps downloading its backgrounds. I checked it in the trace. Rule it out for this.
What works is not declaring the background until it is needed: the declaration lives in a data-bg attribute and a ten-line script moves it into style when the element approaches the viewport. With a scroll-based checker, like the one used for reveal animations, so it does not depend on IntersectionObserver running:
<div class="event-img" data-bg="background-image:url('/images/event.jpg')"></div>
(function(){var els=[].slice.call(document.querySelectorAll('[data-bg]'));var t=null;
function apply(el){el.style.cssText+=';'+el.getAttribute('data-bg');el.removeAttribute('data-bg');}
function check(){t=null;var h=window.innerHeight+600;
els=els.filter(function(el){var r=el.getBoundingClientRect();
if(r.bottom>-600&&r.top<h){apply(el);return false;}return true;});}
function onS(){if(!t)t=setTimeout(check,80);}
window.addEventListener('scroll',onS,{passive:true});window.addEventListener('resize',onS);
window.addEventListener('load',check);check();})();
If your server renders the cards, the change goes in the template and the script in the shared footer, so every page has it. With that, bytes requested before LCP dropped from 958 KB to 434 KB. On Google's PageSpeed: mobile from 88 to 91 with LCP from 3.5 s to 3.2 s and CLS 0; desktop 99 with LCP 0.7 s. (Local Lighthouse gives 95 and 2.4 s; Google simulates a slower network, and that is the number that counts.)
The way to find this is not guessing: in the Lighthouse JSON, list the requests that started before LCP and sort them by size. That shows, on one screen, what is stealing bandwidth from the element that matters.
If your site has light and dark modes you have two palettes, and the audit measures one: whichever you serve by default. The same colour gave me 6.16 in dark and 4.47 in light. The minimum for small text is 4.5. Three hundredths short and it is flagged red.
Check both themes before calling accessibility done: in the browser, forcing the scheme and measuring the computed colour against its real background.
A warning on method: do not trust your own automated sweep. If text sits on an image or a gradient, computing contrast against the container's background colour produces invented numbers. A script like that handed me thirty-one failures, of which one was real. Lighthouse's own list is the reliable source here; your script confirms it, it does not replace it.
Ask for it like this:
Fonts: run scripts/selfhost-fonts.js with my Google Fonts URL, replace the external tags with local @font-face rules using weight ranges and compute the fallbacks with block 5 of browser-audit.js. Images: scripts/to-avif.sh over /images, picture with fallback, width and height, and data-bg for backgrounds below the first fold. One change per deploy, and CLS and LCP in the browser after each one.
What it does on its own: fonts, fallbacks with measured metrics, AVIF, dimensions, lazy backgrounds, contrast from the tokens and the measurement. What stays yours: approving every deploy and looking at the site on your phone after each one, because the number cannot see if something looks wrong. And the CDN settings if you gave no token.
Almost nobody does this, and it is what separates "one more website" from "the reference on this topic in your country".
Google does not only index pages, it maintains a graph of entities: people, companies, places and how they relate. If you are not an entity to Google, you compete on words alone. If you are, you compete on being who you are.
The steps, in order:
Person or Organization on your site with a stable @id, and reference that same @id from every page.sameAs to your real profiles: LinkedIn, GitHub, YouTube, X, your company listing.Wikidata has notability rules and your entry can be deleted if you do it wrong, so do not take it lightly. It is covered in references/entidad.md, but if you are doing this for your business, talk to me first and I will tell you whether you qualify and how to approach it.

Ask for it like this:
Generate the Person or Organization block with a stable @id and sameAs pointing to these profiles: [list]. Put it in every template referenced by @id. Write a description of up to 150 characters that works the same on Instagram, X, LinkedIn, YouTube and Facebook. Look for independent sources about me and tell me whether I qualify for Wikidata and with which references.
What it does on its own: the schema, the description and the draft of the item with its sources. What stays yours: editing each social profile with your session, creating the Wikidata item with your account and answering if it gets challenged, suggesting the change on the Google panel and claiming your business profile.
A site at 100 that publishes nothing drifts back down. You need two things.
Somewhere to publish without touching code. I built my own CMS on Cloudflare because I wanted control over the HTML that comes out, but anything works as long as you actually publish. What matters is that posting an article takes fifteen minutes, not an afternoon: if it is painful, you will not do it. The setup is in references/stack.md and references/cms-y-contenido.md.
Knowing where people come from. Measure first touch, not last: if someone found you through Google in March and messaged you on WhatsApp in May, that was Google, not WhatsApp. Store the source in the browser on the first visit and send it with the form. Without this you are guessing which channel brings you clients.
Ask for it like this:
Set up first-touch capture (utm_source, utm_medium, utm_campaign, referrer, landing page and date) stored in the browser and sent with the form. Generate my UTM links for [channels]. Propose an article calendar: two purchase-intent pieces for every news piece, with the search title of each one.
What it does on its own: the attribution code, the UTM links, the calendar and the drafts. What stays yours: connecting the CRM or the inbox where the leads land, actually using the UTM links on each channel, publishing on a rhythm and approving every article. The experience is yours; nobody invents it for you.
This is the step most people skip, and the only one where you cannot cheat.
Here is what will happen: you run an audit, it tells you that you have a hundred-odd inbound links from thirty domains, and you feel good. Then you look at the list and find domains like .sbs, .cfd, .icu, .party, .monster, with titles like "seo domain research", "Domain Report" or "URL Shared", almost all hosted in Finland or France.
Those are not links. They are automated junk. They generate themselves every time someone runs your domain through an analysis tool: the tool publishes a page with the result, and that page links to you. Running audits manufactures them. They do not hurt you, because search engines ignore them, but they add nothing either, and they give you a false sense that you already have links.
How to tell a good one from a bad one, without a paid tool:
Now what actually works, easiest first:
http://www.mydomain.com: unencrypted, with www, and two redirects before landing. The link existed, but it arrived at the wrong URL. Asking them to point it at the right one is a two-line message.What I do not recommend: buying links, mass link exchanges, or paying for "500 guaranteed backlinks". That builds exactly the profile I just described as junk, and you paid for it.
One note on expectations: this is slow and one afternoon will not fix it. It is the only step in this guide that depends on other people, which is why it is the one most people abandon.

Ask for it like this:
Here is the backlink list from my audit: [pasted or CSV]. Split it into real and automated using the skill's criteria. Then run scripts/enlaces.sh with my domain, my brand and these URLs where I appear: [list]. Write the two-line message for each correction.
What it does on its own: splitting the list, checking how each page links to you (unencrypted, with www, with hops, with nofollow) and who mentions you without a link, writing the messages and fixing the links on your own properties if it has access. What stays yours: sending each message, asking partners for reciprocity and waiting. It is the only step that depends on other people.
More and more people do not search: they ask. ChatGPT, Claude, Gemini, or the summary Google puts on top. If your site is tuned for the classic search engine but the models cannot read you, you disappear from half the queries without noticing.
The good news is you already did most of the work in steps 3 and 5. What is left is small:
curl -sL https://yourdomain.com/ | grep "a sentence from your page". If it does not show up, your page is blank as far as those models are concerned.robots.txt. If you use Cloudflare, look at whether automatic AI bot blocking is on: it ships enabled on some accounts and decides for you.llms.txt at the root. It is a text file that tells the model what your site is and which pages matter. The standard is young, it takes twenty minutes, and there is still no reason not to have one.Person or Organization from step 5. For a model, that block is the cleanest way to know who you are without guessing.This is what audits are starting to report as GEO (Generative Engine Optimization). It is new enough that almost nobody has it, which is exactly why it is the cheapest place to stand out right now.

Ask for it like this:
Check that the main content is in the HTML without JavaScript on my main pages, review robots.txt for GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot and Google-Extended, and write an llms.txt with my important pages. Tell me what I have to switch off in Cloudflare.
What it does on its own: the check, the robots.txt, the llms.txt and, with a token, both Cloudflare controls. What stays yours: without a token, switching off the AI bot block under Security and "Managed robots.txt" under AI Crawl Control. And deciding whether you want the AIs to read you. The answer is usually yes.
The skill documents twenty-four real mistakes in references/errores.md, each with what it cost. These six are the ones I see most:
curl, not by looking at the browser bar.
With the skill and a tightly scoped token, Claude does most of the work and verifies it. This part nobody can do for you, and it is exactly where almost everyone gets stuck:
Go back to the four numbers from step 1 and compare. You are there when:
If everything lands except showing up in the search, what is missing is usually time, entity (step 5) and links (step 7). They are the two slow steps and the ones that pay the most.
Free, in exchange for knowing who you are
You get the whole thing at no cost. I only ask for your email so I can write later and ask how it went: if something did not work for you, I want to know. No spam.
One confirmation email and that is it. You can leave whenever you want.
Stuck on a step?
The resource takes you from zero to a hundred on its own, but some steps land better in a conversation. If you got that far and something is off, send me your site and I will tell you what is going on.
Book a call →