About Services Consulting Workshop Events Work Blog Contact 🇵🇦 Español Book 10 free minutes →
← All resources Skill

How to improve your website SEO step by step

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.

How to improve your website SEO step by step

Free, in exchange for knowing who you are

Download it free

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.

What you will get

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:

  • PageSpeed desktop: 92 → 100. Mobile: 77 → 91.
  • Layout shift (CLS): 0.137 → 0. Exactly zero, not rounded.
  • Fonts: 273 KB → 123 KB. 55% lighter.
  • Full load: 1,166 ms → 734 ms.
  • 102 of 102 URLs with title and description within limits.

Open PageSpeed Insights, type chiragx.com, and check it yourself. I am not asking you to take my word for it.

Google PageSpeed report from September 10, 2026 for chiragx.com: 100 performance, 96 accessibility, 100 best practices and 100 SEO
My own site on September 10, 2026, measured by Google: 100 in performance and 100 in SEO. What took the page there is exactly what follows.

Before you start: install the skill

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.

How to ask Claude so it does almost everything on its own

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:

  1. The URL and the routes per language (/es/, /en/).
  2. Where it is hosted and what you can touch. Cloudflare, WordPress, Shopify, a static HTML. And what you cannot touch.
  3. What you want first: measure and propose, or apply. The healthy version is "measure, propose, change nothing until I approve", and then "apply changes 1 to 3 and measure again with the same command".
  4. Access as environment variables, never pasted into the chat. A Cloudflare token with just the right permission, the PageSpeed API key. You create them and you revoke them when done.

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.

Step 1. Measure honestly, not the way you wish

Almost everyone skips this step, which is why they end up fixing the wrong thing. Before touching a line:

  1. Open your site on your phone using mobile data, not wifi. Your office wifi lies to you. Your clients visit you on the street.
  2. Run PageSpeed on mobile three times and take the middle value. A single measurement tells you nothing; it swings too much. Desktop almost always looks fine and is almost never where your visitors are.
  3. Search Google for your service plus your city, in incognito. If you are not there, that is your real problem, not the score.
  4. Count your services and count your pages. If you sell five things and have one page, that is your ceiling. Write that number down.

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.

Chiragx comparing his website's performance on a phone and on a laptop: mobile scores low while desktop scores high
Desktop always looks good. Measure on the phone, which is where people visit you.

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.

Step 2. One page per service, not a list on the home

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:

  • A title using the words people actually type, not the ones we use in the industry.
  • A concrete problem at the top, before talking about me.
  • Real frequently asked questions, the ones I get on calls.
  • Structured data for Service and FAQPage.
  • Cross links between related services, and back to the index.

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.

Step 3. Technical SEO: let Google read you without guessing

This is where most sites lose points to invisible things. The order that works:

  1. Structured data. Mark who you are (Person or Organization), what you sell (Service), your questions (FAQPage) and the navigation path (BreadcrumbList). Validate every page in the Rich Results Test.
  2. A canonical on every page. One official version of each URL.
  3. Hreflang if you have more than one language. Reciprocal: if the Spanish one points to the English one, the English one has to point back. If one fails, both fail.
  4. A generated sitemap with a real lastmod. A hand-written sitemap goes stale in two weeks.
  5. Titles and descriptions within limits. Title up to 60 characters, description up to 158. Past that, Google truncates them and you lose the message.

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.

And one almost nobody checks: force HTTPS

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.

Step 4. Performance: what actually moves the number

This is the area with the most myth per square meter. What actually worked, in order of impact:

Fonts

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:

  • Google serves a single variable file per family. If you download "regular", "medium" and "bold" separately, you are downloading the same file three times. Deduplicate by hash and declare weight ranges.
  • Use 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.

Images

  • Convert to AVIF and serve with <picture>, keeping the original as a fallback. My home page went from 1,013 KB to 533 KB with no visible difference.
  • Declare width and height on every image. Without them the browser cannot know how much space to reserve, and content jumps.
  • If you have a global 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.

Layout shift

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.

Serve the image at the size it is displayed, not just in the right format

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.

CSS backgrounds are never lazy, and that costs you mobile LCP

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.

Contrast can fail in one theme and pass in the other

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.

Step 5. Entity: let Google know you exist as a person or company

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:

  1. Mark Person or Organization on your site with a stable @id, and reference that same @id from every page.
  2. Link with sameAs to your real profiles: LinkedIn, GitHub, YouTube, X, your company listing.
  3. Make all those profiles say the same thing. Same description, same name, same link. If your LinkedIn says one thing and your site another, you are sending signals that contradict each other.
  4. If you have press coverage, talks, or existing entities that mention you, create your record on Wikidata and connect it to your companies.

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.

Knowledge graph with a person's card at the center connected to their companies, events and publications
To Google you are a node connected to others, or you are nobody. That is the entity.

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.

Step 6. Content and measurement: so this does not die in a month

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.

Step 7. Links: why "I have 112 backlinks" can mean zero

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:

  • Did a human write that page? If the title is "Domain Report" or a number, no.
  • Does the anchor text say anything? "yoursite.com" repeated a hundred times is automated. "Founder profile" was written by a person.
  • Does it make sense for that site to mention you? An event organiser where you spoke, yes. An Estonian URL shortener, no.

Now what actually works, easiest first:

  1. Your own properties. If you run several companies, brands or projects with websites, check which ones link back. In my case, out of five of my own companies, three did not link to me: my site was sending them authority and getting nothing back. It is the easiest link there is and it costs nothing.
  2. Sites where you already appeared. Events where you spoke, podcasts, clients with case pages, associations. Many already mention you without linking, or link badly. One message and it is done.
  3. Check how the ones who do link are linking. Almost nobody does this and it is money on the floor. One organiser had me as 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.
  4. Partners and allies. If you already feature them on your site, asking for reciprocity is fair.

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.

A balance scale where three golden links outweigh a mountain of grey envelopes: real links versus automated links
Three links written by a person outweigh a hundred from automated domains.

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.

Step 8. Make sure the AIs can read you too

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:

  1. Your content must be in the HTML, not painted with JavaScript. This is the big one. Many AI crawlers do not run JS: they read what comes in the response and that is it. Check it with 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.
  2. Do not block AI crawlers. Check your 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.
  3. Add an 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.
  4. Identity schema. Same 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.

A robot reading a web page made of blocks and bars with a magnifying glass, without running anything
Many AI crawlers do not run JavaScript: what is not in the HTML does not exist for them.

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 mistakes that will cost you time

The skill documents twenty-four real mistakes in references/errores.md, each with what it cost. These six are the ones I see most:

  • Optimizing desktop and celebrating. Desktop always looks good. Your traffic is mobile.
  • Chasing the score instead of the business. A 100 that does not show up when people search your service is worth nothing. Measure both.
  • Trusting the lab. Tools simulate. Open a real browser and measure there before calling anything done.
  • Thinking the padlock is enough. Having a certificate and forcing HTTPS are different things. Check it with curl, not by looking at the browser bar.
  • Counting backlinks without looking at them. A hundred links from automated domains are worth less than three from sites that genuinely mention you.
  • Changing ten things at once. If it improves, you do not know which one did it. If it gets worse, same problem. One change, one measurement.
SEO audit flyer: your site failed, shall we review it?
SEO audit flyer: your site failed, shall we review it?

What you have to do yourself, no way around it

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:

  • Accounts and verifications. Search Console, Bing Webmaster Tools, the PageSpeed API key, your Google Business Profile. They are verified with your DNS or your account.
  • Requesting indexing on Google. Search Console, URL inspection, "Request indexing", one by one and with a daily quota. Bing accepts hundreds a day, and IndexNow can be automated.
  • Panel switches. Forcing HTTPS, HSTS, the two AI crawler blocks in Cloudflare, image transformations. With a token holding just the right permission Claude changes them; without one, you flip them.
  • Secrets. You create tokens and keys with the minimum permission, pass them as environment variables and revoke them when done. Never in the chat.
  • The facts of the business. Prices, ranges, real client questions, case numbers. Claude cannot invent them and must not.
  • Approving every text and every deploy. One change, one measurement, and the site on your phone afterwards.
  • Your phone and your city. Opening the site on mobile data and searching your service plus your city in incognito. No tool sees what you see from there.
  • Profiles and Wikidata. Editing each network with your session, creating the item with your account, suggesting changes on the Google panel.
  • Links. Sending the messages to organizers, partners and media. The only item on the list that depends on other people.
  • Rhythm. Publishing. A site at 100 that publishes nothing drops again.

How you know you got there

Go back to the four numbers from step 1 and compare. You are there when:

  • Mobile is above 85, measured three times.
  • Layout shift is at zero.
  • You show up when searching your service plus your city, in incognito.
  • Every service you sell has its own indexed URL.
  • Your domain forces HTTPS and no URL is left serving unencrypted.
  • The sites linking to you point at the right URL, and at least one was written by a person.

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

Download it free

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?

Write me and we look at it

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 →

Technical SEO · Web performance · Core Web Vitals · Entity