Tutorial
Your brand is being talked about across the web whether you are watching or not — and every unlinked mention is a link you could reclaim, every negative one a fire to catch early, every gap a competitor filling the space. Brand Mention Monitor tracks where and how you are mentioned, so you can act instead of missing it.
Why monitoring mentions matters
People write about your brand constantly — in articles, forums, reviews, social posts and comparisons — and almost all of it happens without your knowledge. That conversation shapes your reputation, feeds the authority signals that search and AI engines read, and contains concrete opportunities you are simply missing if you are not watching. An unlinked mention is a backlink waiting to be claimed; a negative one left unanswered festers; a competitor being praised where you are absent is market share moving. Brand Mention Monitor exists to make that invisible conversation visible, so you can respond to it deliberately rather than discovering the important moments long after they have passed.
Unlinked mentions and link reclamation
The most immediately valuable finding is the unlinked mention: a site that names your brand or product but does not link to you. These are the easiest backlinks you will ever earn, because the person has already chosen to mention you — they simply need a quick, friendly nudge to turn that mention into a link. The link-reclamation fix covers finding these and converting them systematically. It is high-leverage precisely because the hard part of link building, earning the mention, is already done; all that remains is asking, and unlinked mentions are the closest thing to free links that exists.
Sentiment — positive and negative
Not all mentions are equal, and knowing the tone of each is what lets you respond appropriately. Positive mentions are assets to amplify, thank and build relationships from; negative ones are risks to address before they spread and harden into reputation. The positive-mentions and negative-mentions fixes cover getting value from the good and managing the bad. Tracking sentiment turns a flat list of mentions into a prioritised action list, because a glowing review from an influential source and a damaging complaint gaining traction demand very different and time-sensitive responses that a raw mention count would never distinguish.
Sentiment shifts over time
Beyond individual mentions, the trend in sentiment is a leading indicator of reputation health. A gradual souring of how people talk about you, or a sudden negative spike after a product change or an incident, is something you want to catch early while it is still manageable rather than after it has become the dominant narrative. The sentiment-shifts fix covers spotting and responding to these movements. Like a score trend, the direction of sentiment tells you more than any single snapshot, because reputation rarely collapses overnight — it usually drifts, and the drift is visible before the crisis if you are watching for it.
Response time
When a mention needs a response — a complaint, a question, a criticism, or an opportunity — how quickly you react shapes the outcome. A negative mention answered fast can be defused or even turned around; the same mention left for days can spread and calcify. A positive mention engaged with promptly builds a relationship while the goodwill is fresh. The response-time fix covers building a process to react quickly to the mentions that warrant it. Speed matters because the web moves fast, and the window in which a response can actually change the trajectory of a conversation is often short and closes quietly.
Coverage gaps and missed opportunities
Monitoring also reveals where you are not being mentioned but should be — the roundups, comparisons, best-of lists and industry conversations that name your competitors and omit you. These coverage gaps are missed opportunities you can pursue: outreach to be included, content to earn the mention, relationships to build. The coverage-gaps and missed-opportunities fixes cover finding and acting on these absences. This flips monitoring from purely defensive to offensive, because the places you are conspicuously missing from are often exactly the high-value placements that would most improve your visibility and authority if you pursued them deliberately.
Competitor balance
Mentions are relative: what matters is not just how often you are talked about, but how your share of the conversation compares to your competitors'. If they are being mentioned far more, or dominating the contexts that matter, that imbalance is a strategic signal about your presence in the market. The competitor-balance fix covers understanding and improving your share of voice against rivals. This framing turns brand monitoring into competitive intelligence, because your absolute mention count means little without the context of who else is being talked about in the same spaces and how much more.
Where it fits with the other tools
Brand Mention Monitor connects to several neighbours. Link reclamation from unlinked mentions feeds directly into your backlink profile, so it pairs with the Backlink Analyzer. The authority that mentions build is one of the signals AI Visibility depends on, since AI engines cite brands the web treats as credible. Share-of-voice analysis complements the Competitor Gap Analysis. So this tool watches the off-site conversation and turns it into action across links, reputation and authority. Its boundary is honest: it surfaces and helps you act on mentions, but it will not manage your reputation for you — the responses and relationships are still yours to build.
Tip: Start with unlinked mentions, because they are the closest thing to free backlinks there is — the site already chose to mention you, so it only takes a friendly ask to turn that into a link. Then set up sentiment and response-time monitoring, because the mentions that most need you are time-sensitive: a fast response can change a conversation's trajectory, while the same reply days later changes nothing.
Tags: brand-mention, brand-monitoring, reputation, link-reclamation, sentiment, technical-seo
How-To
A walkthrough of tracking your brand mentions and turning them into results — reclaiming unlinked mentions as links, managing sentiment and response time, and closing coverage gaps and share-of-voice imbalances against competitors.
1. Set up monitoring for your brand
Begin by tracking mentions of your brand across the web — your company name, product names, key people, and common misspellings and variations, because mentions do not always use your preferred spelling. This gives you the raw stream of where and how you are being talked about, which is the basis for everything that follows. Cast the net deliberately wide at first so you are not missing mentions that use an abbreviation or a slightly wrong name, then refine, because a mention you never capture is an opportunity or a risk you can never act on.
2. Reclaim unlinked mentions as links first
Work the unlinked mentions before anything else, because they are the highest-value, lowest-effort finding. Each is a site that already named your brand but did not link to you, so the hard part — earning the mention — is done, and a brief, friendly outreach is all it takes to turn it into a backlink. The link-reclamation fix covers doing this systematically. Prioritising these first means you capture the easiest links available to you, and it hands off naturally to the Backlink Analyzer, where those reclaimed links strengthen your profile.
3. Triage mentions by sentiment
Sort your mentions by tone so you can respond appropriately, because a positive mention and a negative one need opposite responses and different urgency. Flag the positive ones to amplify and build on, and the negative ones to address before they spread. The positive-mentions and negative-mentions fixes cover extracting value from the good and containing the bad. This triage turns a flat, overwhelming list into a prioritised action list, so your attention goes to the mentions that most reward it rather than being spread evenly across everything regardless of whether it is praise, criticism or noise.
4. Respond quickly to what warrants it
For the mentions that need a response — complaints, questions, criticism, or time-sensitive opportunities — react quickly, because the window in which a reply can actually change the outcome is often short. A negative mention answered fast can be defused; a positive one engaged with while the goodwill is fresh builds a relationship. The response-time fix covers building a process so the right mentions get a prompt, appropriate response rather than sitting unseen for days. Speed is the lever here: the same words that would have helped on day one often change nothing by the end of the week.
5. Watch for sentiment shifts
Step back from individual mentions and watch the trend in how people talk about you, because a gradual souring or a sudden negative spike is a leading indicator of a reputation problem you want to catch early. The sentiment-shifts fix covers spotting and responding to these movements while they are still manageable. Reputation rarely collapses in a day; it usually drifts, and the drift is visible before it becomes a crisis if you are tracking the direction rather than just the daily volume — which is exactly the early warning that lets you intervene before the narrative hardens.
6. Find and close coverage gaps
Look for where you are absent but should be present — the roundups, comparisons and best-of lists that name competitors and omit you — because these coverage gaps are concrete opportunities to pursue through outreach, content or relationships. The coverage-gaps fix covers finding these absences and acting on them. This turns monitoring from defensive to offensive: instead of only reacting to mentions you have, you actively pursue the high-value placements you are missing, which are often exactly the ones that would most improve your visibility and authority if you earned your way into them.
7. Pursue missed opportunities
Beyond structured lists, monitoring surfaces individual moments you could have engaged with but did not — a relevant conversation, a question you could have answered, a piece of coverage you could have contributed to. The missed-opportunities fix covers recognising and acting on these, and building the habit of catching them while they are live rather than after. Each is small, but collectively they are the difference between a brand that participates in its industry's conversation and one that only watches it happen, and the participation is what steadily builds presence and relationships over time.
8. Improve your share of voice against competitors
Assess your mentions relative to your competitors', because your absolute count means little without knowing whether rivals are being talked about far more or dominating the contexts that matter. The competitor-balance fix covers understanding and improving your share of voice. If competitors are winning the conversation, that imbalance points at where to focus — the publications, communities and comparison contexts where you are underrepresented. This reframes brand monitoring as competitive intelligence, and it hands off naturally to the Competitor Gap Analysis for a fuller picture of where rivals are outpacing you.
9. Make it an ongoing practice
Brand monitoring is not a one-time audit but a continuous practice, because the conversation never stops and its value compounds the longer you watch. Keep the monitoring running, keep reclaiming links, keep responding promptly, and keep tracking sentiment and share of voice over time so you can see trends and measure whether your efforts are working. The brands that benefit most are the ones that treat this as a standing habit rather than a periodic check, because reputation, links and share of voice are all built incrementally, mention by mention, over a long and consistent stretch of attention.
Tip: Unlinked mentions are the easiest links you will ever get, so make reclaiming them your first and most consistent habit — the site already chose to name you. And keep sentiment and response-time monitoring live rather than periodic, because the mentions that most need you are the time-sensitive ones, and a fast response can change a conversation that the same reply, sent days later, no longer can.
Tags: brand-mention, howto, brand-monitoring, reputation, link-reclamation
Tutorial
For any business with a location, the Google Maps listing is often the first thing customers see — and a wrong category, stale hours, a misplaced pin or thin photos quietly cost you the map pack and the customers in it. Maps Audit checks the parts of your Google Business Profile that actually decide local visibility.
Why your Maps listing is often the first impression
For a business with a physical location or a service area, the Google Maps listing frequently reaches customers before your website does — it is what appears in the map pack, in the local results, and when someone searches your business by name. A strong listing pulls in calls, visits and directions; a weak or wrong one quietly sends that business to a competitor whose listing is better maintained. Maps Audit exists to check the specific elements of your Google Business Profile that determine local visibility and conversion, because for local businesses the listing is not a supplement to the website — it is often the main event, and it is where most of the local decisions actually happen.
Category — the foundation of local relevance
The single most important choice in your listing is the primary category, because it is the largest factor in which searches you appear for. Pick the wrong or a too-generic category and you are invisible for the searches that matter most, no matter how good the rest of the listing is. Secondary categories broaden your reach further. The maps-category fix covers choosing the primary and secondary categories that match what you actually do and what customers search for. This is the foundation because category drives relevance, and relevance is what gets you into the map pack in the first place.
Accurate hours and pin location
Two accuracy basics quietly make or break trust and conversion. Wrong opening hours send customers to a closed business, producing exactly the frustration and negative reviews that damage a local reputation, and Google increasingly surfaces hours prominently. A misplaced map pin sends people to the wrong location or makes directions fail, which is worse than no pin at all. The maps-hours and maps-pin fixes cover keeping hours current, including special and holiday hours, and correcting the pin to the exact right spot. These are unglamorous but fundamental, because a listing that misdirects or misinforms actively costs you the customer it attracted.
Photos
Photos are one of the strongest drivers of engagement on a listing — profiles with good, plentiful, current photos get markedly more clicks, calls and direction requests than sparse ones, because customers want to see what they are coming to. Stale, thin or low-quality imagery undersells the business and signals neglect. The maps-photos fix covers building a strong, current photo set across the categories customers care about — exterior, interior, products, team. This matters more than most owners assume, because on a visual platform the photos are much of what a customer uses to decide between you and the similar business listed right beside you.
Reviews
Reviews are both a ranking factor and the single biggest influence on whether a customer chooses you from the map pack — the quantity, the average rating, the recency, and how you respond to them all matter. A listing with few, old or unanswered reviews loses to a competitor with many recent ones and visible owner engagement. The maps-reviews fix covers building review volume and velocity and responding to reviews properly, both positive and negative. Reviews are where local reputation is most visibly decided, and they compound: the businesses that actively cultivate and engage with reviews steadily pull ahead of those that leave them to chance.
Services, attributes and posts
Several richer elements round out a competitive listing. The services you list help you appear for specific service searches and tell customers exactly what you offer. Attributes — wheelchair accessible, outdoor seating, free wifi, and the rest — help you match filtered and specific searches and set expectations. Google Posts let you publish timely updates, offers and events directly on the listing. The maps-services, maps-attributes and maps-posts fixes cover each. Individually minor, together these are what separate a bare, set-and-forget listing from an actively maintained one that matches more searches and gives customers more reasons to choose it.
How the audit works, and reading it by impact
Maps Audit checks all of these elements against what a strong, competitive listing looks like and maps each finding to a fix. Read the results by impact: category and accuracy problems first, because a wrong category or misdirecting hours and pin undermine everything else; then reviews and photos, the biggest conversion drivers; then services, attributes and posts, which refine and extend an already-solid listing. Working in that order means you fix the things that decide whether you appear and whether customers trust you before polishing the details that help an already-visible listing compete harder for the click.
Where it fits with the other tools
Maps Audit is the local-listing layer, and it sits within a broader local-SEO picture. It complements the local-SEO and NAP-consistency work that keeps your business details aligned across the web, the local schema on your own site, and the review-strategy approaches that feed the reviews this audit measures. So Maps Audit owns the health and competitiveness of your Google Business Profile specifically. Its boundary is honest: it audits and helps you improve the listing, but it will not run your local reputation or generate reviews for you — the ongoing engagement, the photos and the genuine customer relationships behind the reviews are still yours to build.
Tip: Get the primary category right first, because it is the biggest single factor in which local searches you appear for, and a wrong or too-generic one makes everything else moot. Then treat hours and the pin as non-negotiable accuracy: a listing that sends customers to a closed door or the wrong location actively costs you the very customer it just attracted, and earns the negative reviews that compound the damage.
Tags: maps-audit, local-seo, google-business-profile, map-pack, reviews, technical-seo
How-To
A walkthrough of auditing your Maps listing and fixing it in order of impact — category and accuracy first, then reviews and photos, then services, attributes and posts — so you win the map pack and the customers in it.
1. Audit the whole listing
Run the audit across your Google Business Profile to see every element against what a strong, competitive listing looks like — category, hours, pin, photos, reviews, services, attributes and posts. This overview lets you work in order of impact rather than tackling whatever you noticed first, which matters because the highest-impact fixes (category and accuracy) are not the most eye-catching. Starting with the full picture keeps you from polishing photos while a wrong category quietly keeps you out of the searches that matter, and gives you a clear, prioritised order to work through the whole profile.
2. Fix your primary category first
Set the primary category correctly before anything else, because it is the single largest factor in which local searches you appear for, and getting it wrong or leaving it too generic makes the rest of your optimisation moot. Then add relevant secondary categories to broaden your reach. The maps-category fix covers choosing categories that match both what you genuinely do and what customers actually search for. This comes first for the same reason indexability comes first elsewhere: if the category is keeping you out of the results, no other improvement to the listing can be seen by the customers you are trying to reach.
3. Correct hours and the map pin
Fix the accuracy basics next: make sure your opening hours are exactly right, including special and holiday hours, and that your map pin sits at the precise correct location so directions work. Wrong hours send customers to a closed door and earn you the negative reviews that damage local reputation; a misplaced pin misdirects them entirely. The maps-hours and maps-pin fixes cover both. These rank high because a listing that misinforms or misdirects actively costs you the customer it attracted — accuracy is the floor beneath everything else, and it is cheap to get right and expensive to leave wrong.
4. Build reviews and respond to them
Work on reviews, because they are both a ranking factor and the biggest influence on whether a customer picks you from the map pack. Build volume and recency through a genuine, consistent process for asking satisfied customers, and respond to reviews — both positive and negative — because visible owner engagement matters to prospective customers and to Google. The maps-reviews fix covers building review velocity and responding well. This is high-leverage because reviews are where local reputation is most visibly decided, and they compound over time, so the effort you put in steadily separates you from competitors who leave reviews to chance.
5. Strengthen your photo set
Build a strong, current set of photos, because listings with plentiful good imagery get markedly more clicks, calls and direction requests than sparse ones — customers want to see what they are coming to before they commit. Cover the categories they care about: exterior so people can find you, interior so they know what to expect, products or work, and the team. The maps-photos fix covers building and maintaining the set. Photos matter more than most owners assume on a visual platform, because they are much of how a customer chooses between you and the similar business listed right next to you.
6. List your services
Add and detail your services, which helps you appear for specific service searches and tells customers precisely what you offer before they even click through. A listing that spells out its services matches more of the specific, high-intent searches people make than one that relies on the category alone. The maps-services fix covers listing them completely and accurately. This is where you capture the searchers who know exactly what they want, and matching their specific query with a clearly listed service is often what tips them toward calling you rather than the competitor whose listing left them guessing.
7. Set your attributes
Fill in the attributes that apply to your business — accessibility, amenities, payment options, service options and the rest — because they help you match filtered and specific searches and set accurate expectations for customers. Someone filtering for wheelchair access, outdoor seating or a particular payment method will only find businesses that have declared it. The maps-attributes fix covers setting the relevant ones accurately. Individually each attribute is small, but collectively they widen the range of specific searches you match and reduce the friction of a customer wondering whether you offer the particular thing they need before they get in touch.
8. Use Google Posts
Publish Google Posts to keep the listing active with timely updates, offers and events shown directly on your profile, because an actively-posted listing signals a live, engaged business and gives customers current reasons to choose you. The maps-posts fix covers using posts effectively and regularly. This is the element most owners neglect entirely, which is exactly why using it is an edge — a listing with fresh posts stands out against the many static ones around it, and it gives you a direct channel to surface a promotion or an event to people at the precise moment they are looking at your business.
9. Re-audit and keep it current
Re-audit to confirm the fixes are in place, then keep the listing current as an ongoing habit, because a Google Business Profile is not set-and-forget: hours change with seasons and holidays, photos go stale, reviews keep arriving and needing responses, and posts need refreshing to stay active. The businesses that win locally are the ones that treat the listing as a living thing they maintain, not a form they filled in once. Regular attention is what keeps you competitive in the map pack over time, because your competitors' listings are being maintained too, and a neglected profile steadily falls behind.
Tip: Work strictly in order of impact: category and accuracy (hours, pin) first because they decide whether you appear at all and whether customers trust you, then reviews and photos because they decide whether customers choose you, then services, attributes and posts to compete harder once you are visible. And keep the listing current — it is a living profile your competitors are actively maintaining, not a form you complete once.
Tags: maps-audit, howto, local-seo, google-business-profile, map-pack
How-To
A walkthrough of finding what competitors have that you do not — across content, links, technical, schema, AI visibility and positioning — and turning those gaps into a prioritised plan to close them.
1. Run the gap analysis against your real competitors
Start by identifying your genuine search competitors — the sites actually ranking for the terms you want, which are not always your business competitors — and run the analysis against them. Comparing yourself to the wrong set produces a plan aimed at the wrong targets, so it is worth getting this right first. The analysis then compares you to those competitors across multiple dimensions, surfacing what they have and do that you lack. This comparative view is the whole point: gaps are only meaningful relative to the competitors you are actually trying to outrank in the results that matter to you.
2. Find the content gaps
Look first at content gaps — the topics, questions and search intents your competitors cover and rank for while you do not. These are often the clearest and most actionable opportunities, because each gap is a concrete piece of content you could create to compete for traffic you are currently ceding entirely. The content-gaps fix covers identifying them and prioritising which to pursue. Content gaps tend to be high-leverage because they represent demand you are simply not addressing, and closing them adds new rankings rather than merely defending existing ones, which is usually where the largest untapped growth sits.
3. Find the link gaps
Examine the link gaps — the referring domains linking to competitors but not to you, which represent authority they have and you lack. These sites already link within your space, so they are realistic targets for your own outreach, and the pattern of who links to several competitors but not you is especially revealing. The link-gaps fix covers finding these and turning them into an outreach list. This hands off naturally to the Backlink Analyzer, where the referring-domain gap is quantified, and it is valuable because closing link gaps directly addresses the authority difference that often explains why competitors outrank you.
4. Find the technical and schema gaps
Compare the technical foundations: where competitors are faster, better structured, more crawlable, or more thoroughly marked up with schema than you are. Technical gaps explain rankings that content alone does not, and schema gaps mean competitors are winning rich results and AI comprehension you are missing. The technical-gaps and schema-gaps fixes cover identifying both, handing off to the specialist tools — the performance and crawl tools for technical, the schema tools for markup. These gaps matter because they are often the invisible reason a competitor with comparable content outranks you: they have quietly built a stronger technical and structured-data base.
5. Find the AI-visibility gaps
Assess where competitors are being cited by AI engines and appearing in AI answers while you are absent, because AI search is an increasingly important channel and being outpaced in it is a growing, often unmeasured, disadvantage. The ai-gaps fix covers identifying where competitors win AI visibility and what is driving it, handing off to the AI Visibility tool for the deeper diagnosis and remedy. This dimension is easy to overlook precisely because AI citations are hard to see, but a competitor consistently cited where you are invisible is capturing attention and authority in a space that traditional rank tracking will never show you.
6. Find the positioning gaps and blind spots
Step back to the strategic view: the positioning gaps where competitors have claimed messaging, audiences or angles you have not, and the blind spots — things they are doing that you had not even thought to look at. The positioning-gaps and blind-spots fixes cover surfacing these. This is the least mechanical dimension and often the most valuable, because it catches the strategic moves and whole categories of opportunity that a purely tactical, keyword-by-keyword comparison misses entirely — the places where a competitor is not just ranking better but competing on a dimension you had not considered at all.
7. Prioritise the gaps by leverage
With the gaps across all dimensions in front of you, prioritise rather than trying to close everything at once. Weight each gap by the effort to close it against the likely return — a content gap on a high-demand topic you can credibly cover beats a link gap requiring a relationship you will never realistically build. The goal is a focused plan, not an overwhelming list. Prioritisation is what turns a competitor analysis from an intimidating catalogue of everything you are behind on into a manageable sequence of the specific moves that will actually move your position most for the effort involved.
8. Close the gaps and re-run
Work your prioritised plan, closing gaps in order and handing each off to the relevant specialist tool — content creation for content gaps, outreach for link gaps, the technical and schema tools for those, AI Visibility for AI gaps. Then re-run the analysis periodically, because competitors do not stand still: they publish, earn links and improve, opening new gaps as you close old ones. The competitor-watch monitoring complements this by tracking their ongoing moves between full analyses. Treating gap analysis as a recurring cycle rather than a one-off keeps your plan aimed at where competitors are actually pulling ahead now, not months ago.
Tip: Compare against your real search competitors — the sites actually ranking for your target terms — not just your business rivals, or you will build a plan aimed at the wrong targets. Then prioritise ruthlessly by leverage: the value of a gap analysis is not the long list of everything you are behind on, but the short list of the specific, achievable moves that will improve your position most for the effort.
Tags: competitor-analysis, howto, competitive-intelligence, content-gaps, link-gaps
How-To
A walkthrough of auditing your internal link structure and fixing it in order — broken links and equity flow first, then anchor text, deep pages and clusters, then overlinking and nofollow mistakes — so authority reaches the pages that need it.
1. Audit your internal link structure
Run an audit of how your pages link to each other, because internal linking is one of the most underused levers in SEO — it is entirely within your control, unlike backlinks, yet it strongly shapes how authority flows through your site and which pages rank. The audit reveals broken internal links, how equity moves, which pages are under-linked, and where the structure works against you. This overview is the basis for fixing in the right order, and it hands off from the Site Crawler, which finds the broken links and orphans that the internal-linking work then addresses at the structural level.
2. Fix broken internal links first
Clear the broken internal links before anything else, because each is a link pointing at a 404 that wastes the equity it should be passing and drops users onto an error page. They are pure loss — no benefit, only cost — so fixing them is the obvious first move: repair the link to point at the right URL, or restore or redirect the missing target. The broken-internal-links fix covers this. It comes first because broken links actively leak the very authority the rest of this process is trying to direct, so plugging them is the prerequisite for making the flow improvements below actually count.
3. Improve link equity flow
Look at how authority moves through your site and correct where it pools or drains wrongly. Your strongest pages — the homepage and heavily-linked pages — should pass equity onward to the pages that need to rank, not trap it. The link-equity-flow fix covers directing authority deliberately from where it accumulates to where it is needed. This is the strategic heart of internal linking: the point is not just that pages link to each other, but that the link structure channels your site's authority toward the pages you actually want to rank, rather than letting it sit uselessly on pages that do not need it.
4. Fix homepage and deep-page linking
Address the two ends of the depth problem. Your homepage is usually your strongest page, so what it links to matters enormously — the homepage-internal-linking fix covers using that power deliberately. At the other end, deep pages buried many clicks from the homepage receive little authority and get crawled less; the deep-pages fix covers bringing important buried pages closer to the surface through better linking. Together these ensure your link-worthy pages are not starved simply because of where they sit in the hierarchy, since depth from the homepage strongly influences both crawling and ranking.
5. Fix anchor text
Review the anchor text of your internal links, because the words you link with tell search engines what the target page is about, and vague anchors like click here or read more waste that signal entirely. Use descriptive, relevant anchor text that reflects the target page's topic, while keeping it natural and varied rather than repetitively exact-match. The anchor-text fix covers getting this balance right. Internal anchor text is a signal you fully control, unlike the anchor text of your backlinks, so it is worth using well — it is a free, direct way to reinforce what each of your pages is about.
6. Build topical link clusters
Strengthen the connections between related pages so that content on the same topic links together into a coherent cluster, which helps search engines understand your topical authority and helps users navigate deeper into a subject. The link-clusters fix covers building these clusters — typically a central pillar page linked bidirectionally with its supporting pages. This structure signals depth of coverage on a topic and concentrates relevance where you want it, turning a collection of individually-linked pages into a connected body of work that demonstrates authority on the subject far more convincingly than scattered, unconnected pages ever could.
7. Fix section-specific linking
Different sections of a site need different internal-linking approaches, so address them specifically: blog posts benefit from linking to related posts and relevant conversion pages, while product pages benefit from linking to related and complementary products and their categories. The blog-internal-linking and product-internal-linking fixes cover each. Applying the right pattern to each section ensures the linking actually serves that content's purpose — a blog post guiding readers onward to convert, a product page guiding shoppers to related items — rather than a one-size-fits-all approach that leaves each section's specific linking opportunities unrealised.
8. Correct overlinking and nofollow mistakes
Finally, fix the linking that hurts. Overlinking — cramming too many internal links onto a page — dilutes the value each link passes and overwhelms users, so the overlinking fix covers pruning to the links that genuinely help. And nofollow on internal links is almost always a mistake that blocks equity from flowing where you want it, so the nofollow-internal fix covers removing it. These are corrections rather than additions: sometimes the fix is fewer, cleaner links, because a page linking to everything effectively prioritises nothing, and internal nofollow simply throws away authority you control.
9. Re-audit and maintain the structure
Re-audit to confirm the broken links are gone, equity flows better, and the structure supports your key pages, then maintain it as the site grows. Internal linking degrades over time as content is added without being properly linked in, pages are moved, and new sections appear disconnected from the rest. Periodic re-auditing — and the habit of properly linking new content into the existing structure as you publish it — keeps authority flowing to where it is needed. Unlike backlinks, this is entirely within your control, which makes maintaining it one of the most reliable ongoing SEO investments you can make.
Tip: Internal linking is the most underused lever in SEO because it is entirely yours to control — no outreach, no waiting. Fix broken internal links first (they leak the equity everything else is trying to direct), then focus on flow: channel authority from your strongest pages, especially the homepage, toward the pages you actually want to rank, rather than letting it pool where it is not needed.
Tags: internal-links, howto, internal-linking, link-equity, site-architecture
How-To
A walkthrough of auditing your backlink profile and acting in order — reclaiming lost and broken links, disavowing toxic ones, then fixing anchor distribution, nofollow ratio and the referring-domain gap against competitors.
1. Audit your backlink profile
Run an audit of your backlink profile to see the full picture: which domains link to you, the quality and health of those links, your anchor-text distribution, and how your profile compares to competitors'. Backlinks remain one of the strongest ranking factors, but a profile is a mix of assets and liabilities, and you cannot manage either without seeing them. This overview is the basis for acting in the right order — recovering value, removing risk, then closing gaps — rather than jumping straight to chasing new links before you have secured and cleaned the ones you already have.
2. Reclaim lost and broken backlinks first
Start by recovering links you have effectively already earned. Lost backlinks — links that once pointed to you and disappeared — can often be recovered by understanding why they went and reaching out. Broken backlinks — links pointing at a 404 on your site — can be fixed by restoring or redirecting the target, capturing equity that is currently being wasted. The lost-backlinks and broken-backlinks fixes cover both. This comes first because it is the highest-return, lowest-effort work: you are recovering authority that is already yours or nearly yours, rather than building new links from scratch.
3. Identify and handle toxic backlinks
Examine your profile for toxic backlinks — spammy, manipulative or unnatural links that can drag your site down, whether from a past bad campaign, negative SEO, or low-quality sources. Assess them carefully rather than reacting to every low-quality link, because Google ignores most spam automatically now. The toxic-backlinks fix covers identifying links that genuinely warrant action. This step is about risk: a profile with a real toxicity problem needs cleaning, but over-aggressive pruning of harmless links is itself a mistake, so the goal is accurate identification of the links that actually pose a threat, not a blanket purge.
4. Build a disavow file only if genuinely needed
If the audit surfaces genuinely toxic links you cannot get removed, a disavow file tells Google to ignore them — but this is a tool to use sparingly and precisely, because disavowing good links by mistake actively harms you. Most sites never need one, since Google discounts obvious spam on its own. The disavow-file fix covers building one correctly, only for the links that truly warrant it. The emphasis is on restraint: a disavow file is a scalpel for a real problem, not a routine maintenance task, and the common mistake is using it too broadly and cutting away links that were helping.
5. Fix your anchor-text distribution
Review the anchor text of your backlinks, because the distribution is a signal Google reads, and an unnatural pattern — too many exact-match commercial anchors — looks manipulative and can be a liability, while a natural mix of branded, URL, generic and varied anchors looks healthy. The anchor-text-distribution fix covers assessing your distribution and correcting an unnatural one. Unlike internal anchors, you do not fully control backlink anchors, but you can influence them through how you build links and how you request them, and a distribution that looks organic rather than engineered is what you are aiming for.
6. Check your nofollow ratio
Look at the balance between followed and nofollowed links pointing to you, because a natural profile contains a healthy mix — some nofollow from social, comments and news is expected and normal — whereas an unnatural ratio in either direction can look manipulated. The nofollow-ratio fix covers understanding and, where needed, improving your balance. This is a health-signal check rather than a crisis: the point is that a profile made up entirely of one type looks engineered, and a natural site accumulates a realistic blend of followed and nofollowed links simply through normal, varied link acquisition over time.
7. Close the referring-domain gap against competitors
Compare your referring domains to your competitors' to find the gap — the sites linking to them but not to you — because the number and quality of referring domains is one of the strongest correlates with ranking, and this gap is your most concrete link-building roadmap. The referring-domain-gap fix covers quantifying it and turning it into targets. This hands off from the Competitor Gap Analysis, and it is where backlink work becomes proactive: instead of chasing links at random, you pursue the specific domains that already link within your space to competitors and are therefore realistic, high-relevance targets for you.
8. Manage link velocity and keep monitoring
Watch your link velocity — the rate at which you gain (or lose) links — because an unnaturally sudden spike can look manipulative, while a steady, sustained pace of quality links signals healthy, organic growth. The link-velocity fix covers keeping acquisition at a natural, sustainable rate. Then keep monitoring the whole profile continuously, because links are lost and gained constantly, toxic links can appear, and competitors keep building. Backlink health is an ongoing practice, not a one-time clean-up — and it connects back to Brand Mention Monitor, where unlinked mentions become the easiest new links to add to a profile you are actively tending.
Tip: Secure and clean before you chase: reclaim lost and broken links first (you have already earned that authority) and address any genuinely toxic ones, before building new links. Be sparing with the disavow file — most sites never need one, and disavowing good links by mistake does real harm — and use the referring-domain gap against competitors as your concrete roadmap for which new links to actually pursue.
Tags: backlinks, howto, link-building, link-profile, referring-domains
How-To
A walkthrough of running the complete Site Audit and working the findings in the order that protects rankings — indexation first, then canonicals, then speed, then schema — handing each off to the specialist tool, with the right fix for your platform.
1. Run the full audit
Start with a complete Site Audit, which is the overview that ties every specialist check together into one picture and one score — indexation, canonicals, performance, schema and the rest, across your whole site. This is your starting point and your map: rather than diving into any single tool, you run the full audit first to see everything at once and understand where the real problems are. The findings, grouped and prioritised, then tell you which of the specialist tools to reach for and in what order, so your effort goes where it actually moves the score rather than wherever you happened to look first.
2. Read the results by severity, not by count
Before fixing anything, read the results by how much each problem actually costs you, because a raw list of issues is only useful once it is ordered by impact. A handful of indexation or canonical problems on key pages outweigh hundreds of minor warnings on low-value URLs. Work top-down from the findings that stop pages being found or ranked, through the ones that waste crawl budget or slow the site, down to the cosmetic. This triage is what turns an intimidating audit into a focused plan, and it is why the steps below follow impact order rather than the order the audit happens to list things in.
3. Fix indexation problems first
Address indexation before anything else, because a page that is not indexed cannot rank no matter how good it is, so these problems sit above everything. Look for pages wrongly excluded from the index — accidental noindex, robots blocks, pages Google cannot discover — and pages that should be excluded but are not. The indexation fix covers diagnosing and correcting what is and is not in the index, handing off to the Robots and Sitemap Tester for the crawl directives. This comes first for the same reason everywhere else: fixing anything on a page Google cannot index is effort spent on a page that cannot benefit from it.
4. Fix canonical problems next
Resolve canonical issues next, because canonicals tell Google which version of a page is authoritative, and when they are wrong — pointing at the wrong URL, missing, or creating conflicts — Google may index and rank the wrong version, or split signals across duplicates. The canonicals fix covers getting them pointing at the right authoritative URLs. This ranks high because canonical confusion quietly undermines pages that are otherwise fine, wasting the authority of duplicates that should be consolidated, and it is invisible on the page itself — the site looks correct to a visitor while quietly telling Google to value the wrong URL.
5. Fix page speed
Work on performance next, because page speed feeds Core Web Vitals, which affect both rankings and the user experience that determines whether visitors stay and convert. Diagnose what is slowing the site — render-blocking resources, heavy scripts and CSS, unoptimised images, layout shift — and fix it. The page-speed fix covers this and hands off to the specialist tools: the CLS Debugger for layout shift, the JavaScript and CSS Checkers for script and stylesheet weight. Speed sits below indexation and canonicals because a fast page that is not indexed or is canonicalised wrongly still fails, but above cosmetic issues because it affects every visitor directly.
6. Fix structured data
Address schema next, because structured data unlocks rich results and helps search and AI engines understand and cite your content, and missing or broken markup means leaving that visibility on the table. The schema fix covers adding and correcting structured data, handing off to the AI Schema Generator to produce it, the JSON-LD Checker to validate it, and the Schema Debugger to confirm rich-results eligibility. Schema comes after the foundational technical fixes because it enhances pages that are already indexable, canonical and fast — there is little point marking up a page for rich results while it is still blocked from the index or serving the wrong canonical.
7. Fix on your platform
Apply the fixes in the right place for your stack, because the same problem is solved differently on each: WordPress through plugins and theme code, Shopify through theme Liquid and app settings, Next.js through framework configuration and components. The site-audit-wordpress, site-audit-shopify and site-audit-nextjs fixes point you at where each issue actually lives on your platform, so you fix the source rather than patching output that regenerates on the next deploy. Fixing in the wrong layer either does nothing or gets overwritten, so matching the fix to your stack is what makes the corrections actually stick across the whole site.
8. Re-run and track the score over time
After working the findings, re-run the audit to confirm the fixes registered and the score moved, then keep auditing on a cadence and track the trend, because a site's health is never finished — content changes, platforms update, and new issues appear with every deploy. The Score History tool tracks that trend over time, turning individual audits into a trajectory you can manage and using the line as proof your fixes worked. Treating the Site Audit as a recurring practice rather than a one-off is what keeps the whole site healthy, because every improvement you bank needs defending against the regressions that normal site activity constantly introduces.
Tip: Work the audit in impact order every time — indexation first (an unindexed page cannot rank), then canonicals (wrong ones make Google value the wrong URL), then speed, then schema — because fixing a lower-priority issue on a page with a higher-priority problem is wasted effort. Then re-run and track the score over time, since a good result is a state you defend against regressions, not a finish line you cross once.
Tags: site-audit, howto, technical-seo, indexation, canonicals
Tutorial
JavaScript can build a beautiful page a crawler never sees, block the main thread so the page feels frozen, or throw silent errors that break features for a slice of users. JavaScript Checker audits your scripts for syntax and console errors, render-blocking and bundle bloat, main-thread cost, memory leaks, third-party weight and crawler rendering.
Why JavaScript is an SEO problem, not just a dev problem
JavaScript is uniquely capable of undermining SEO precisely because it is so powerful. It can assemble an entire page in the browser that a crawler, fetching the raw HTML, never sees. It can occupy the main thread so completely that the page cannot respond to a tap for seconds. It can throw an error that silently disables a feature for everyone on a particular browser while working perfectly on yours. None of these show up as a visible break, which is why they persist. JavaScript Checker exists to surface the ways your scripts quietly cost you rankings, performance and functionality, so that the power of JavaScript works for the page rather than against it.
Syntax and console errors
The most basic failures are syntax errors that stop a script executing at all and runtime errors that surface in the browser console. A single uncaught error early in a script can halt everything after it, disabling functionality downstream in ways that are invisible unless you happen to have the console open on the affected browser. Because these errors are often browser- or condition-specific, they hide easily. The js-syntax-errors and js-console-errors fixes cover finding and resolving them, and they come first because a script that is erroring is a script whose other problems you cannot even reliably measure yet.
Render-blocking JavaScript
Like CSS, JavaScript in the head can block the browser from rendering the page until it has been fetched and executed, holding content back and pushing out the metrics that measure how quickly users see something. Scripts that do not need to run before first paint should be deferred or loaded asynchronously so they stop standing in the way of the content. The render-blocking-js fix covers identifying which scripts are blocking and moving them out of the critical path, which is frequently one of the larger, cheaper performance wins available on a script-heavy page, at no cost to functionality.
Bundle size and the main-thread cost
A large JavaScript bundle is expensive twice over: it takes time to download, and then it takes time to parse and execute, and that execution happens on the main thread where it competes with everything the user is trying to do. Long tasks — chunks of script that monopolise the main thread — are what make a page feel unresponsive and drag down interactivity metrics like INP. The js-bundle-size and long-tasks fixes cover shrinking what you ship and breaking up the work so the main thread stays free enough to respond to the user promptly rather than freezing under its own weight.
Memory leaks and third-party tags
Two subtler drains deserve attention. Memory leaks — event listeners never removed, references never released — cause a page to consume more and more memory the longer it is open, eventually becoming sluggish or crashing, which particularly hurts long single-page-app sessions. Third-party tags — analytics, ads, chat widgets, trackers — each add weight, main-thread work and points of failure you do not control. The memory-leaks and third-party-tags fixes cover plugging the leaks and auditing whether each third-party script earns the cost it imposes, since these are often the heaviest and least-scrutinised scripts on the page.
Whether crawlers can render your content
The SEO-critical question with JavaScript is whether Google can actually see the content your scripts generate. If key content is injected client-side and the raw HTML arrives nearly empty, you are betting on Google successfully rendering your JavaScript — a bet that does not always pay off, and never pays off for the many crawlers and AI engines that do not render at all. The js-crawler-rendering fix covers diagnosing what content depends on JavaScript and ensuring it is available to crawlers, typically through server-side rendering or hydration, so your content is not invisible to the very systems meant to index it.
Findings, fixes and your platform
Every finding maps to a fix, and the patterns differ by how your JavaScript is built and shipped. React applications have characteristic bundle, hydration and rendering issues; Shopify and WordPress each load scripts through themes, apps and plugins in their own ways, often stacking third-party tags you did not add deliberately. The js-react, js-shopify and js-wordpress fixes target the specific problems each environment tends to produce, so you address the real source — a build configuration, a theme, a plugin loading order — rather than fighting the same symptom on page after page across the site.
Where it fits with the other tools
JavaScript Checker owns the script layer, and it neighbours several tools. The CLS Debugger measures layout stability, which JavaScript often disturbs through late DOM changes and hydration; CSS Checker handles the stylesheet side of performance; Site Crawler depends on content being crawlable, which the rendering check here directly protects. So JavaScript Checker covers the correctness, performance cost and crawlability of your scripts. Its boundary is honest: it audits how your JavaScript behaves and what it costs, but it will not rewrite your application logic — it tells you where the scripts hurt the page, and the fixes tell you how to stop the hurt.
Tip: The most dangerous JavaScript problem is the invisible one: content your scripts build client-side that Google may render inconsistently and that non-rendering crawlers and AI engines never see at all. Check crawler rendering first on any script-heavy site, because a fast, error-free page whose content is invisible to search is still an SEO failure no amount of performance tuning will fix.
Tags: js-checker, javascript, performance, core-web-vitals, crawlability, technical-seo
How-To
A walkthrough of auditing your scripts and fixing them in order — errors first, then crawler rendering, then the performance costs of render-blocking, bundle size, long tasks, leaks and third-party weight — with the right fix for React, Shopify or WordPress.
1. Scan the page's JavaScript
Run the checker to capture the full picture of your scripts: syntax and console errors, which scripts block rendering, the bundle size, the long tasks tying up the main thread, memory behaviour, the third-party tags loaded, and what content depends on client-side rendering. This overview is what lets you fix in the right order rather than chasing whatever you noticed first. The sequence matters because an erroring script distorts every other measurement, and invisible content is a bigger SEO problem than a slow one — so the scan sets up a deliberate order of attack rather than a scramble.
2. Fix syntax and console errors first
Clear the errors before anything else, because a script that throws stops executing, and everything after the error silently fails to run. These errors are often browser- or condition-specific, so they hide from casual testing on your own machine. Work through the syntax errors that break execution outright and the console errors that surface at runtime, using the js-syntax-errors and js-console-errors fixes. Only once the scripts run cleanly can you trust the performance measurements, because a halted script is not doing the work whose cost you would otherwise be trying to optimise.
3. Verify crawlers can see your content
Before optimising performance, settle the SEO-critical question: can crawlers actually see your content, or does it depend on client-side JavaScript that leaves the raw HTML empty? Check what content is injected by scripts and confirm it is available to crawlers and AI engines that may not render JavaScript at all. The js-crawler-rendering fix covers moving critical content to server-side rendering or ensuring hydration exposes it. This ranks above the performance work because a fast page whose content is invisible to search has failed at the one thing SEO is for, however good its metrics look.
4. Remove render-blocking scripts
Take the scripts that block the initial render and get them out of the critical path. Anything not needed for first paint should be deferred or loaded asynchronously so the browser can paint content without waiting for it. The render-blocking-js fix covers identifying blocking scripts and changing how they load. This is usually one of the larger and cheaper performance wins, because it improves how quickly users see the page without changing what the scripts ultimately do — they simply stop standing between the browser and the content it is ready to show.
5. Cut the bundle size
Shrink the amount of JavaScript you ship, because a large bundle costs twice: download time and then parse-and-execute time on the main thread. Code-split so each page loads only the script it needs, tree-shake away unused code, and serve it compressed. The js-bundle-size fix covers these techniques. Smaller bundles help both loading and responsiveness at once, since there is simply less script for the browser to fetch and less for the main thread to chew through before it can attend to what the user is trying to do.
6. Break up long tasks
Attack the long tasks — the chunks of script that seize the main thread for extended stretches and make the page unresponsive, dragging down interactivity metrics like INP. Break large pieces of work into smaller ones, defer non-urgent work, and yield to the main thread so the browser can respond to input between chunks. The long-tasks fix covers these patterns. This is specifically about responsiveness rather than load speed: a page can appear quickly and still feel broken if a long task blocks it from reacting the moment a user tries to interact with it.
7. Plug memory leaks
Find and fix the memory leaks — event listeners that are added but never removed, references held after they are needed, timers left running — which cause the page to consume steadily more memory the longer it stays open. On long-lived single-page-app sessions this leads to sluggishness and eventual crashes that are maddening to reproduce. The memory-leaks fix covers the common leak patterns and how to clean up properly, particularly around component lifecycles where listeners and subscriptions must be torn down as deliberately as they were set up, or they accumulate invisibly.
8. Audit third-party tags
Scrutinise every third-party script — analytics, ads, chat widgets, trackers, tag managers — because each adds download weight, main-thread work and a failure point outside your control, and they accumulate over years as different people add them. For each, ask whether it still earns its cost, and load the survivors as efficiently as possible. The third-party-tags fix covers auditing, deferring and containing them. These are frequently the heaviest and least-examined scripts on the page precisely because nobody owns them, so a deliberate cull often recovers more performance than optimising your own code.
9. Fix framework-specific issues
Address the problems at their source in your framework, because each produces its own patterns: React through bundle, hydration and rendering behaviour; Shopify through theme and app scripts; WordPress through plugin and theme script loading, which often stacks redundant libraries and tags. The js-react, js-shopify and js-wordpress fixes target these directly. Fixing the framework-level cause — a build setting, an app you no longer need, a plugin loading its own copy of a library — resolves issues across the whole site at once, rather than patching the same generated problem on individual pages.
10. Re-scan and guard against regression
Re-scan to confirm the errors are gone, the content is crawlable, and the performance costs have genuinely dropped. Then guard the result, because JavaScript problems regress easily: a new dependency inflates the bundle, a new tag is added, a refactor reintroduces a leak or a blocking script. Re-check after adding libraries, tags or major features, and consider budgets in your build that fail loudly when the bundle grows past a threshold. JavaScript health, like the other performance concerns, is a state you maintain over time rather than a task you finish once and forget.
Tip: Fix in this order every time: errors first (a broken script poisons every other measurement), then crawler rendering (invisible content is the worst SEO outcome), then the performance costs. And watch third-party tags especially — they are the scripts nobody owns, they accumulate silently over years, and culling the ones that no longer earn their weight often beats optimising the code you actually wrote.
Tags: js-checker, howto, javascript, performance, crawlability
Tutorial
Structured data only helps if it is valid, complete and consistent — and broken JSON-LD fails silently, giving you none of the rich results or AI citations you built it for. JSON-LD Checker validates your structured data: that the JSON parses, the schema types and required fields are right, dates, IDs, images and references are correct, and no blocks conflict.
Why validating structured data matters
Structured data is how you tell search engines and AI systems exactly what a page is — a product, an article, an organisation — in a machine-readable form, and it powers rich results and AI citations. But it only works if it is valid, and broken JSON-LD fails silently: no error appears on the page, the rich result simply never shows and the AI engine simply never cites you, and you are left assuming your markup is working when it is doing nothing. JSON-LD Checker validates the structured data you are already outputting, so that the effort you put into schema actually earns the visibility it was meant to, rather than sitting there malformed and ignored.
JSON syntax — the first gate
Before any schema meaning can be evaluated, the JSON-LD has to be valid JSON, and a single misplaced comma, unescaped quote or unclosed brace makes the whole block unparseable — at which point search engines discard it entirely and none of the schema inside it counts. These syntax errors are easy to introduce, especially when markup is assembled by templates concatenating strings. The json-syntax fix covers finding and correcting them, and it comes first because a block that does not parse cannot be reasoned about further: everything else the checker examines assumes the JSON is at least structurally valid to begin with.
Schema types and required fields
Valid JSON is only the start; the schema inside must also be correct. The @type has to be an appropriate, real schema.org type for what the page actually is, and each type carries required and recommended properties that engines depend on. A Product missing its offers, an Article missing its author or dates, an FAQPage with malformed questions — each is valid JSON but incomplete schema that fails to earn the rich result. The schema-types and schema-required fixes cover choosing the right type and supplying the fields it needs, which is the difference between structured data that merely exists and structured data that actually qualifies for enhanced treatment.
Dates, IDs, images and references
Several specific fields cause recurring, subtle failures. Dates must be in valid ISO 8601 format or engines reject them. @id values must be consistent so that entities can be linked and de-duplicated across blocks. Image references must point at real, accessible, appropriately-sized images or rich results that require an image will not render. Entity references must resolve correctly so that authors, publishers and organisations form a connected graph rather than loose strings. The schema-dates, schema-id, schema-images and schema-references fixes each cover one of these, and together they are where technically-present schema most often falls short of actually working.
Conflicts between blocks
A common live problem is multiple JSON-LD blocks that contradict or duplicate each other — your own hand-added markup competing with what an SEO plugin or theme injects, two Article blocks with different values, or entities that should be linked instead standing alone. Engines faced with conflicting structured data may pick the wrong block or distrust all of it. The schema-conflicts fix covers consolidating into a single coherent set of entities, typically a single @graph, so the page presents one authoritative description of itself rather than several competing ones that undermine each other and confuse the parser.
How the checker validates, and the platform fixes
JSON-LD Checker parses your structured data, validates the JSON, checks the schema types and required fields, verifies dates, IDs, images and references, and flags conflicts between blocks — turning a silent, invisible layer of your page into an inspectable, fixable one. The implementation of fixes is platform-specific, because JSON-LD is emitted differently on each stack: WordPress through SEO plugins, Shopify through theme Liquid, Next.js through components. The json-ld-wordpress, json-ld-shopify and json-ld-nextjs fixes point you at where your structured data is actually generated, so you fix the source rather than patching output that regenerates on the next build.
Where it fits with the other tools
JSON-LD Checker validates structured data you already have; it sits alongside the tools that create and evaluate schema differently. The AI Schema Generator produces JSON-LD from your page content, and the natural workflow is to generate with that tool and then validate the result here before shipping. The Schema Debugger focuses on schema.org validity and rich-results eligibility. So this tool owns the correctness and consistency of your JSON-LD as code. Its boundary is honest: it checks that your structured data is valid, complete and coherent, but it will not decide your schema strategy — which types to mark up is still your call.
Tip: Broken structured data fails completely silently — no rich result, no AI citation, no error anywhere on the page — so you cannot tell working schema from broken schema by looking at the page. Always validate before you ship, and re-validate after any change to the templates or plugins that generate your JSON-LD, because a plugin update can quietly invalidate markup that was working perfectly the day before.
Tags: json-checker, json-ld, structured-data, schema, validation, technical-seo
How-To
A walkthrough of validating your JSON-LD in order — JSON syntax first, then schema types and required fields, then dates, IDs, images and references, then conflicts — and fixing it at the source on WordPress, Shopify or Next.js.
1. Run the validator on your structured data
Run the checker across your pages to inspect the JSON-LD they output, because structured data is invisible on the page itself and the only way to know it is working is to parse and validate it directly. The scan surfaces every block's syntax validity, schema completeness and conflicts, page by page, so you can tell a one-off error from a systematic template fault. As with markup validation, that distinction determines whether you are making an individual edit or a single template change that corrects the structured data across every page it appears on.
2. Fix JSON syntax errors first
Clear the JSON syntax errors before anything else, because a block that is not valid JSON is discarded whole — a single stray comma or unescaped quote takes the entire block, and all the schema inside it, out of consideration. These creep in easily when markup is assembled by templates joining strings together. The json-syntax fix covers finding and correcting them. This comes first for the same reason a doctype does in HTML: nothing else about the block can be evaluated, by the checker or by a search engine, until the JSON parses cleanly in the first place.
3. Confirm the schema type is right
Check that each block's @type is an appropriate, real schema.org type for what the page genuinely is — an Article marked as a bare WebPage, or a product page with no Product type, wins nothing. The type is the foundation because the required fields all depend on it, so getting it right is a prerequisite for the completeness checks that follow. The schema-types fix covers selecting the correct type, including the finer distinctions like Article versus BlogPosting or Product versus SoftwareApplication that change how engines and rich results treat the page.
4. Supply the required and recommended fields
For each type, add the properties engines depend on — author and dates for an Article, offers for a Product, properly structured questions for an FAQPage — plus the recommended ones that unlock richer results. Valid JSON with an incomplete schema is technically present but functionally inert, qualifying for nothing. The schema-required fix covers filling in the required fields for each type and the recommended extras worth adding, which is the step that moves your structured data from merely existing in the source to actually earning the enhanced treatment you marked it up for.
5. Fix dates and IDs
Correct the date fields to valid ISO 8601 format, because engines reject dates in other formats outright, and confirm your @id values are consistent so that entities can be linked and de-duplicated rather than treated as separate things. Inconsistent IDs are why an author or organisation that appears on many pages fails to be recognised as one entity. The schema-dates and schema-id fixes cover both, and they matter because these are exactly the small, easy-to-overlook fields that quietly stop otherwise-complete structured data from working the way it should.
6. Fix image and entity references
Ensure image references point at real, accessible, appropriately-sized images, since rich results that require an image simply will not render without a valid one, and verify that entity references resolve so authors, publishers and organisations form a connected graph rather than dangling strings. The schema-images and schema-references fixes cover both. A closed, correctly-referenced graph is what gives engines and AI systems the authorship and credibility signals they actually use, turning a set of isolated blocks into a coherent, trustworthy description of the page and the entities behind it.
7. Resolve conflicts between blocks
Find and consolidate any conflicting or duplicate JSON-LD, which usually means your own markup competing with what an SEO plugin or theme injects, or two blocks asserting different values for the same thing. Faced with contradiction, engines may choose the wrong block or distrust all of it. The schema-conflicts fix covers consolidating into a single coherent set of entities — typically one @graph — and disabling the redundant source, so the page describes itself once, authoritatively, instead of several times in ways that undermine each other and leave the parser guessing which to believe.
8. Fix at the source on your platform
Apply the corrections where the JSON-LD is actually generated, because editing the rendered output is futile when a plugin, theme or component regenerates it on every build. WordPress emits structured data through SEO plugins, Shopify through theme Liquid, and Next.js through components. The json-ld-wordpress, json-ld-shopify and json-ld-nextjs fixes point you at the right layer for your stack, so a single change at the source corrects the structured data everywhere it is output, and survives the next content update instead of being silently overwritten by it.
9. Re-validate before every ship
Re-validate to confirm the JSON parses, the schema is complete, the fields are correct and the conflicts are resolved, and make this a standing step before shipping any change that touches structured data. Because broken schema fails silently, a regression is invisible until you notice a rich result has vanished weeks later, by which point the cause is hard to trace. Validating on every relevant change — and especially after plugin or theme updates that regenerate your JSON-LD — is what keeps structured data quietly working instead of quietly broken, earning the visibility you built it for.
Tip: Generate with the AI Schema Generator, then validate here before shipping — the two are designed to work in sequence, because the generator produces confident JSON-LD fast and this checker catches the syntax slips, missing required fields and conflicts before they reach production. And always re-validate after a plugin or theme update, since that is the most common way working structured data silently breaks.
Tags: json-checker, howto, json-ld, structured-data, validation
Tutorial
Online stores fail at SEO in store-specific ways that a general audit misses: thin product pages, crawler traps in faceted navigation, weak category pages, missing product schema and slow product detail pages that cost sales directly. Ecommerce Audit checks the things that actually move revenue on a store.
Why ecommerce SEO is its own discipline
A store is not just a big content site; it has structural SEO problems that content sites never encounter, and where those problems directly touch revenue. Product pages that are thin and near-duplicate, filter systems that generate millions of crawlable URLs, category pages that are the real ranking workhorses yet get no content, product schema that unlocks rich results with prices and ratings — these are ecommerce-specific concerns. Ecommerce Audit focuses on exactly the things that move sales and rankings on a store, rather than the generic checks that apply to any site, because on a store the gap between a technically fine page and a commercially effective one is where the money is lost or won.
Product detail pages — SEO and speed
The product detail page is where the sale happens, and it faces two distinct pressures. On SEO, product pages are often thin and near-duplicate — the same template with a different name and price — which struggles to rank and risks duplicate-content dilution; they need unique descriptions, useful detail and proper schema. On speed, a slow PDP costs conversions directly, because every second of delay on the page a customer is deciding to buy on has a measurable revenue cost. The pdp-seo and pdp-speed fixes cover both, and they matter more than almost anything else because this is the page that turns a visitor into a customer.
Category pages — the real ranking workhorses
On most stores the category and collection pages, not the individual products, are what rank for the valuable high-volume searches — people search for the category far more than for a specific SKU. Yet category pages are frequently treated as bare grids of products with no descriptive content, no reason for a search engine to rank them over a competitor's. Giving them genuine content, clear structure and internal linking is one of the highest-leverage moves in ecommerce SEO. The category-pages fix covers turning these from empty product grids into pages that actually earn the head-term rankings that drive the most traffic.
Faceted navigation — the crawler trap
Filter and sort systems are essential for shoppers and treacherous for crawlers. Every combination of colour, size, price and sort order can generate a unique, crawlable URL, and a store with several filters can spawn millions of near-duplicate pages that devour crawl budget and bury the pages that matter. Managing which facet combinations are crawlable and indexable is a defining ecommerce SEO problem. The faceted-nav fix covers controlling this explosion through canonicalisation, robots rules and parameter handling, so the filters serve customers without drowning the site in low-value URLs that starve your real pages of crawl attention.
Product schema and images
Product structured data is what unlocks the rich results that make your listings stand out — price, availability, star ratings shown directly in search — and review schema adds the social proof that drives clicks. Product images, meanwhile, need to be optimised for both speed and image search, since they are heavy and central to the shopping experience. The review-schema and product-images fixes cover implementing product and review markup correctly and optimising imagery, both of which directly affect how compelling your products look in search results and how quickly the pages that show them load for a deciding customer.
Internal linking and cross-links
How products and categories link to each other shapes both crawlability and sales. Cross-links — related products, frequently-bought-together, category-to-category paths — distribute ranking signals through the catalogue and guide shoppers toward more of what they want, raising both discoverability and average order value. A store where products sit in isolation, reachable only through search or a single category, wastes the linking equity a connected catalogue would generate. The cross-links fix covers building an internal linking structure that serves crawlers and customers at once, turning a flat catalogue into a connected one that lifts the whole store.
Findings, fixes and your platform
Every finding maps to a fix, and the implementation depends heavily on your platform, because ecommerce stacks differ enormously. Shopify, WooCommerce and headless commerce each handle product templates, faceted navigation, schema output and performance in fundamentally different ways, with different constraints and different levers. The ecommerce-seo-shopify, ecommerce-seo-woocommerce and headless-ecommerce-seo fixes target the specific realities of each, so you work with your platform's actual capabilities and limitations rather than against a generic model that does not match how your store is built and served.
Where it fits with the other tools
Ecommerce Audit is the store-specific layer, and it draws on the general tools for the deeper dives. Site Crawler is where you confirm the faceted-navigation URL explosion and find the crawler traps; the schema tools handle the mechanics of product and review markup that this audit flags; the performance and CLS tools diagnose the PDP speed problems it surfaces. So this tool identifies the ecommerce-specific issues and points at the specialised tools for the detailed fixes. Its boundary is honest: it audits the SEO and technical health of the store, but it will not set your merchandising or pricing — it makes the store findable and fast, not what to sell.
Tip: On a store, category pages usually rank for the valuable high-volume searches while individual products do not, so an empty category page is a bigger missed opportunity than a thin product page — give categories real content first. And treat faceted navigation as a crawler trap from day one, because a few filters left uncontrolled can spawn millions of near-duplicate URLs that quietly starve your real pages of crawl budget.
Tags: ecommerce-audit, ecommerce, product-schema, faceted-navigation, conversion, technical-seo
How-To
A walkthrough of auditing a store and fixing it in order of revenue impact — faceted-nav crawler traps and category pages first, then product page SEO and speed, then schema, images and cross-linking — with the right fix for Shopify, WooCommerce or headless.
1. Audit the whole store
Run the audit across the store to see the store-specific problems together: the faceted-navigation URL explosion, the state of category and product pages, schema coverage, PDP speed and internal linking. This overview lets you work in order of revenue impact rather than tackling whatever surfaced first. On a store that order genuinely matters, because the highest-leverage fixes — controlling crawler traps and strengthening category pages — are not the most obvious ones, and starting with cosmetic product-page tweaks while filters spawn millions of URLs is effort spent in the wrong place entirely.
2. Control faceted-navigation crawler traps first
Tackle the faceted navigation before anything else, because an uncontrolled filter system can generate millions of near-duplicate crawlable URLs that devour crawl budget and bury your real pages. Decide which facet combinations should be crawlable and indexable and which should not, and enforce that through canonicalisation, robots rules and parameter handling. The faceted-nav fix covers taming the explosion. This comes first because while it is unchecked, Google is spending its budget on endless filter permutations instead of discovering and refreshing the product and category pages that actually earn money.
3. Strengthen category pages
Turn your category and collection pages from bare product grids into pages that can rank, because on most stores these are what capture the valuable high-volume head-term searches. Add genuine descriptive content, clear structure and internal links that give search engines a reason to rank them over competitors. The category-pages fix covers this. It ranks near the top because category pages typically represent your largest addressable search demand, and an empty one is a bigger missed opportunity than any individual thin product page — you are leaving the highest-traffic terms on the table.
4. Fix product-page SEO
Address the product detail pages on the SEO side: replace thin, near-duplicate descriptions with unique, useful content, and ensure each has proper titles, descriptions and structure so it can rank and does not dilute through duplication. Manufacturer-supplied descriptions duplicated across every store selling the item are a particular weakness. The pdp-seo fix covers making product pages genuinely distinct and rankable. This matters because the product page is where the purchase decision happens, and a page that cannot be found or reads identically to a hundred competitors' pages is failing at both discovery and persuasion.
5. Fix product-page speed
Now the speed side of the product page, because a slow PDP costs conversions directly — every second of delay on the page where a customer is deciding to buy has a measurable revenue cost. Diagnose and fix what is slowing it: heavy images, render-blocking resources, excess scripts, third-party widgets. The pdp-speed fix covers this, and it hands off to the performance and CLS tools for the detailed diagnosis. Product pages deserve special performance attention above other pages precisely because they sit at the exact moment of purchase intent, where friction converts most directly into lost sales.
6. Implement product and review schema
Add correct product structured data so your listings can show price, availability and ratings directly in search, and review schema so social proof appears alongside them — both make your results markedly more compelling and clickable than plain listings. The review-schema fix covers implementing product and review markup, and it hands off to the schema tools and the JSON-LD Checker for validating the output. This is high-leverage because rich product results stand out in a crowded results page, and the star ratings and prices they surface pull clicks away from competitors whose listings are bare text.
7. Optimise product images
Optimise your product imagery for both page speed and image search, since product images are heavy, central to the shopping experience, and a real source of discovery in their own right through image results. Compress and correctly size them, serve modern formats, set explicit dimensions to prevent layout shift, and give them descriptive alt text and filenames. The product-images fix covers this. It matters twice over: heavy images are often the main thing slowing the PDP you just worked to speed up, and well-optimised ones open an additional discovery channel through image search that stores routinely neglect.
8. Build internal cross-linking
Strengthen how products and categories link to each other with related-product links, frequently-bought-together modules and category-to-category paths, which spread ranking signals through the catalogue and guide shoppers toward more of what they want. The cross-links fix covers building this structure. It serves two goals at once: crawlers reach and value more of your pages, and customers discover more products, lifting average order value. A catalogue where items sit isolated wastes the considerable linking equity a well-connected store generates almost for free from its own internal structure.
9. Fix on your platform
Apply the fixes according to your platform's real capabilities, because Shopify, WooCommerce and headless commerce differ fundamentally in how they handle templates, faceted navigation, schema and performance. What is a theme setting on one is a plugin on another and a code change on a third, and some levers available on one platform simply do not exist on another. The ecommerce-seo-shopify, ecommerce-seo-woocommerce and headless-ecommerce-seo fixes target each platform's actual constraints, so you build on what your stack can genuinely do rather than fighting a generic approach that does not fit it.
10. Re-audit and track against revenue
Re-audit to confirm the crawler traps are controlled, the category and product pages are strengthened, and schema and speed have improved. Then keep auditing on a cadence and watch the results against actual store metrics — traffic, rankings and conversions — because on a store the point is revenue, not a score. Catalogues change constantly as products come and go, so faceted-nav rules, schema and internal links need periodic re-checking. Tying the audit to commercial outcomes is what keeps the technical work anchored to the sales it is ultimately meant to produce.
Tip: Work in order of revenue impact, which on a store is rarely the obvious order: control faceted-navigation crawler traps and strengthen category pages first, because those touch your largest search demand and your crawl budget, then move to product-page SEO and speed at the point of purchase. An empty category page and an uncontrolled filter system cost more than any single thin product page.
Tags: ecommerce-audit, howto, ecommerce, product-schema, faceted-navigation
Tutorial
More people are asking AI engines instead of searching, and being invisible to ChatGPT, Perplexity and Google's AI answers is a growing hole in your traffic. AI Visibility checks whether AI systems can find, extract and correctly cite your content — and why they are not, when they are not.
Why AI visibility is now its own concern
A rapidly growing share of questions that used to go to a search box now go to an AI engine — ChatGPT, Perplexity, Google's AI overviews, and others — which answer directly and cite a handful of sources. If your content is not among those cited sources, you are invisible in an increasingly important channel, losing traffic and authority you cannot see draining away because it never appears in your search analytics. AI Visibility exists to check whether AI systems can find, understand and cite your content, and to diagnose why not when they cannot, because optimising for AI answers is becoming as necessary as optimising for the traditional search results ever was.
Being invisible versus being cited
The starting question is binary: does your content appear in AI answers at all? Total invisibility — the AI never surfacing you for queries you should own — is a different and more fundamental problem than appearing occasionally but rarely being the cited source. The former usually points at content AI cannot extract or match; the latter at authority and citation-frequency issues. The ai-invisibility fix covers diagnosing why you are absent entirely, which is the right place to start, because there is no point tuning citation frequency for queries where you do not register at all — first you have to show up.
Whether AI can extract your content
AI engines work by extracting clear, factual, well-structured statements they can lift and attribute. Content buried in JavaScript, locked in images, wrapped in vague marketing language, or structured so loosely that no clean claim can be pulled from it is content AI struggles to use even when it can technically access the page. Extractability is a distinct property from readability by humans. The ai-extraction fix covers structuring content so AI can cleanly pull and cite it — clear claims, direct answers, factual density — which is often the single biggest lever on whether you get cited, because an engine cites what it can confidently extract.
Matching the questions people actually ask
AI engines answer natural-language questions, and being cited depends on your content clearly matching the way people actually phrase those questions, which is often different from the keywords you optimised for. If your content answers the question but never in the words the question is asked, the match is weak. The ai-query-match fix covers aligning your content with real user questions — answering them directly and explicitly, in the language they are asked — so that when someone poses a question you should own, an AI engine recognises your content as the clear answer to it rather than a tangentially related page.
Structured data and brand mentions
Two signals strongly influence AI citation. Structured data helps AI systems understand exactly what your content is and trust it enough to cite, which is why schema matters for AI as much as for rich results — the ai-schema fix covers this, and it connects to the schema tools. Separately, how often and how authoritatively your brand is mentioned across the web shapes whether AI treats you as a credible source at all. The brand-mentions fix covers building the off-site presence that AI systems read as authority, because AI, like search, cites sources it has reason to consider trustworthy and established.
Citation frequency and correct attribution
Beyond appearing at all, two refinements matter: how often you are cited, and whether you are cited correctly. Low citation frequency means you show up but rarely as the chosen source, an authority and extractability problem. Wrong attribution — AI citing your content but crediting someone else, or stating your facts without naming you — is a distinct and frustrating failure that specific signals can correct. The citation-frequency and wrong-attribution fixes cover improving how often you are cited and ensuring the citation actually credits you, so that the visibility you earn in AI answers accrues to your brand rather than leaking to others.
Tracking AI visibility
Unlike traditional search, AI visibility is hard to see in standard analytics — AI referrals are often invisible or lumped together, so you can be gaining or losing ground with no obvious signal. Establishing a way to track whether and how AI engines cite you is what turns AI visibility from guesswork into something you can actually manage and improve. The ai-tracking fix covers setting up that measurement, because as with any optimisation, you cannot reliably improve what you cannot see, and AI citation is precisely the kind of thing that changes silently underneath you if you are not deliberately watching for it.
Where it fits with the other tools
AI Visibility is the AI-answer layer, and it builds on the tools that create the signals AI reads. The schema and AI Schema tools produce the structured data that helps AI understand and trust your content; the content and meta tools shape the clarity AI extracts from. So AI Visibility diagnoses your standing in AI answers and points at the underlying work that improves it. Its boundary is honest: it assesses and helps improve how AI systems see you, but the AI landscape is young and shifting fast, so treat its guidance as a strong, evolving signal rather than a fixed rulebook, and keep watching how the engines themselves change.
Tip: Start with the binary question — are you invisible to AI, or cited but not often enough — because the two need completely different fixes, and tuning citation frequency for queries where you do not register at all is wasted effort. And set up tracking early: AI referrals hide in standard analytics, so without deliberate measurement you will not even know whether you are winning or losing this channel.
Tags: ai-visibility, ai-search, aeo, citations, brand-mentions, technical-seo
How-To
A walkthrough of checking whether AI engines cite you, then fixing the causes in order — invisibility and extractability first, then query match and schema, then brand authority, citation frequency and attribution — with tracking set up so you can see the change.
1. Check whether AI engines cite you
Start by establishing your actual standing: for the queries you should own, do AI engines surface and cite your content, cite it occasionally, or never mention you at all? This baseline determines everything that follows, because total invisibility and infrequent citation are different problems with different fixes. Run the check across the questions that matter to your business so you know which category you are in before you start fixing, rather than guessing — the whole diagnosis hinges on honestly seeing where you currently stand in AI answers for the topics you care about most.
2. Diagnose total invisibility first
If you are entirely absent from AI answers for queries you should own, treat that as the first problem, because no amount of frequency tuning helps for queries where you do not register at all. Total invisibility usually traces to content AI cannot extract or cannot match to the question. The ai-invisibility fix covers diagnosing the root cause of absence. Settling this first is essential: you have to show up before you can work on showing up more often, so absence outranks every refinement below it in the order of what to fix.
3. Make your content extractable
Improve how cleanly AI can extract and attribute your content, because engines cite clear, factual, well-structured claims and struggle with content buried in JavaScript, locked in images, or wrapped in vague language. Lead with direct answers, state facts plainly and densely, and structure content so a clean, quotable claim can be lifted from it. The ai-extraction fix covers this. It is often the single biggest lever on citation, because an AI engine cites what it can confidently pull and attribute — make that easy and you become a far more likely source than a page saying the same thing more loosely.
4. Match the questions people actually ask
Align your content with the natural-language questions people actually pose to AI, which are frequently phrased differently from the keywords you optimised for. Answer the real question directly and explicitly, in the language it is asked, rather than merely covering the topic in your own preferred terms. The ai-query-match fix covers this alignment. When your content visibly and directly answers the exact question someone asks, an AI engine recognises it as the answer and cites it; when it only orbits the topic, the engine reaches for a source that addresses the question head-on instead of yours.
5. Add structured data for AI
Implement the structured data that helps AI systems understand precisely what your content is and trust it enough to cite, because schema is as valuable for AI comprehension as it is for traditional rich results. The ai-schema fix covers this and connects to the AI Schema Generator and JSON-LD Checker for producing and validating the markup. Well-structured, valid schema gives AI engines an unambiguous, machine-readable description of your content and the entities behind it, which both improves extraction and contributes to the trust signals that make an engine willing to cite you rather than a competitor.
6. Build brand authority off-site
Work on how often and how authoritatively your brand is mentioned across the web, because AI systems, like search engines, cite sources they have reason to regard as credible and established, and that credibility is built largely off your own site. The brand-mentions fix covers building the off-site presence and mentions that AI reads as authority. This is slower, longer-term work than the on-page fixes above, but it addresses the underlying question of whether AI considers you a source worth citing at all — which on-page optimisation alone cannot answer if the wider web barely mentions you.
7. Improve citation frequency and fix attribution
Once you are appearing, refine two things: how often you are the chosen source, and whether the citation actually credits you. Low frequency is an authority-and-extractability problem you keep chipping at; wrong attribution — your facts used but someone else credited, or no credit given — is a distinct failure with its own signals. The citation-frequency and wrong-attribution fixes cover both, ensuring the visibility you earn in AI answers accrues to your brand. There is little value in AI repeating your content while crediting a competitor, so correcting attribution protects the return on all the work above it.
8. Set up tracking and keep iterating
Establish tracking for whether and how AI engines cite you, because AI referrals largely hide in standard analytics and you cannot manage what you cannot see. The ai-tracking fix covers setting up that measurement. Then iterate, and expect the ground to move: the AI search landscape is young and changing fast, engines update how they select and cite sources, and what works shifts with them. Treat AI visibility as an ongoing, evolving practice rather than a one-time fix, re-checking your standing regularly and adjusting as the engines themselves change how they surface and credit sources.
Tip: The AI landscape is genuinely young and shifting, so treat this as an evolving practice, not a fixed checklist — but the durable fundamentals are clear: be extractable, answer real questions directly, and build genuine authority, because AI engines cite clear, trustworthy, well-structured sources. Set up tracking first, because you cannot improve a channel whose results are invisible in your normal analytics.
Tags: ai-visibility, howto, ai-search, aeo, citations
Tutorial
Site migrations are where hard-won rankings go to die: lost URLs, broken redirects, canonical chaos, dropped schema and indexation collapse can wipe out years of SEO in a weekend. Migration Audit checks the things that break during a migration, before and after, so a replatform, redesign or domain move does not become a traffic catastrophe.
Why migrations are so dangerous for SEO
A migration — a replatform, a redesign, a domain change, a restructure — touches everything that SEO depends on at once: URLs, redirects, canonicals, structured data, internal links, the sitemap, indexation. Get any of them wrong and rankings built over years can collapse in a weekend, often without anyone realising until the traffic graph falls off a cliff days later. Migrations are simply the single highest-risk event in a site's SEO life. Migration Audit exists to check the specific things that break during a migration, both in planning before the switch and in verification after, so that the move preserves your equity instead of destroying it.
Lost URLs — the cardinal sin
The most fundamental migration failure is losing URLs: pages that existed and ranked on the old site with no corresponding, redirected home on the new one. Every lost URL is a page dropping out of the index, taking its rankings and its inbound-link equity with it, and a migration that loses URLs at scale is a migration that loses traffic at scale. The lost-urls fix covers building a complete inventory of old URLs and confirming each has a destination on the new site. This is the cardinal sin of migrations precisely because it is both the most damaging and the most preventable with proper preparation.
Redirects — mapping old to new
The mechanism that preserves equity through a migration is redirects, and they must be done right: every old URL 301-redirected to its true equivalent on the new site, in a single clean hop, not chained and not dumped onto the homepage. Pointing everything at the homepage is a classic migration disaster that Google treats as soft 404s, throwing away the equity you were trying to save. The migration-redirects fix covers mapping old to new correctly. Redirects are the bridge across which your rankings travel to the new site, and a broken or lazy bridge is where most migration traffic losses actually happen.
Canonicals and indexation
Two related signals frequently break during migrations. Canonical tags can end up pointing at old URLs, staging URLs, or the wrong new URLs, telling Google the wrong version of each page is authoritative. And indexation can collapse if a staging noindex or a restrictive robots rule ships to production, or if the new site simply is not being crawled and indexed as expected. The canonical-migration and indexation-migration fixes cover both. These are insidious because the site can look perfect to a visitor while quietly telling search engines to devalue or ignore it, so they need explicit verification after the move.
Internal links, schema and sitemap
Several supporting structures need migrating too. Internal links often still point at old URLs after a move, creating chains of redirects or outright breaks that leak equity and degrade crawlability. Structured data can be dropped or broken in the rebuild, losing rich results. The sitemap must be regenerated to list the new URLs and only those. The internal-links-migration, schema-migration and sitemap-migration fixes cover each. These are easy to overlook amid the bigger URL-and-redirect concerns, but a migration that fixes the redirects while leaving internal links pointing at the old structure has only done half the job.
Ranking recovery after the move
Even a well-executed migration usually causes a temporary ranking dip as Google recrawls, reprocesses and reassigns signals to the new URLs — that is normal and expected. The danger is not knowing whether a dip is the normal temporary settling or a sign that something is genuinely broken. The ranking-recovery fix covers what to monitor after a migration, how long normal recovery should take, and how to tell an expected dip from a real problem that needs intervention. Knowing the difference is what keeps a team from panicking over a routine settling period, or conversely from ignoring a real failure until it has done lasting damage.
Findings, fixes and your platform
Every finding maps to a fix, and migrations are heavily platform-specific because the destination stack shapes what breaks and how. Moving to Shopify, to WordPress, or to a headless architecture each brings its own URL structures, redirect mechanisms, schema handling and pitfalls. The shopify-migration, wordpress-migration and headless-migration fixes target the realities of migrating to each, so you plan for the specific ways your chosen platform tends to break migrations rather than discovering them the hard way after the switch, when they are far more expensive and stressful to fix under live traffic.
Where it fits with the other tools
Migration Audit orchestrates several other tools around the highest-risk event in a site's life. Redirect Checker verifies the migration redirects resolve cleanly; Robots and Sitemap Tester confirms the new crawl directives and sitemap; Site Crawler checks the new structure for broken links and orphans; Score History tracks the recovery curve afterward so you can see whether rankings are settling normally. So this tool defines the migration checklist and the specialised tools verify each part. Its boundary is honest: it audits migration integrity, but it will not perform the migration — it makes sure the move keeps your SEO intact rather than executing the move itself.
Tip: The one rule that prevents most migration disasters: before you migrate, inventory every existing URL and map each to its true destination on the new site with a single 301 — never lose a URL, and never point everything at the homepage, which Google reads as soft 404s. Then verify indexation and canonicals after the switch, because a site can look perfect to visitors while quietly telling Google to ignore it.
Tags: migration-audit, migration, redirects, indexation, ranking-recovery, technical-seo
How-To
A walkthrough of migrating safely — inventorying and mapping URLs before the switch, then verifying redirects, canonicals, indexation, internal links, schema and the sitemap after — and monitoring recovery so you can tell a normal dip from a real failure.
1. Inventory every existing URL before you migrate
Before touching anything, build a complete inventory of every URL that currently exists and matters — everything ranking, everything with inbound links, everything getting traffic — because you cannot preserve what you have not catalogued. This inventory is the foundation of the entire migration: it is the checklist every old URL must be reconciled against afterward. The lost-urls fix covers building it comprehensively from crawl data, analytics and search data. Skipping or rushing this step is how URLs get silently lost in the move, and a lost URL is a ranked page dropping out of the index with its equity.
2. Map every old URL to its new destination
For each URL in the inventory, decide its true equivalent on the new site and plan a single 301 redirect to it. Map like to like — a product to its new product page, a category to its new category — never lazily to the homepage, which Google treats as a soft 404 and which throws away the equity you are migrating to preserve. The migration-redirects fix covers building this mapping. This one-to-one map from old to new is the bridge your rankings cross to the new site, and building it carefully before the switch is what makes the switch survivable.
3. Prepare canonicals, sitemap and internal links for the new structure
Plan the supporting signals for the new site before launch: canonical tags pointing at the correct new URLs (never old or staging ones), a regenerated sitemap listing only the new live URLs, and internal links updated to target new URLs directly rather than relying on redirects. The canonical-migration, sitemap-migration and internal-links-migration fixes cover each. Preparing these in advance rather than fixing them in a panic after launch is what separates a smooth migration from a scramble, because doing them live under real traffic is far harder and the errors are far more visible.
4. Migrate structured data with the rest
Ensure your structured data comes across in the rebuild rather than being dropped, because a new platform or template can silently lose the schema that was earning your rich results, and nobody notices until the enhanced listings disappear from search weeks later. The schema-migration fix covers carrying structured data across and validating it on the new site, handing off to the JSON-LD Checker for verification. Schema is easy to forget amid the bigger URL-and-redirect work, but a migration that preserves rankings while quietly dropping rich results has still cost you visibility that took effort to earn.
5. Launch and immediately verify the redirects
At launch, the first verification is the redirects: confirm every old URL 301-redirects to its correct new destination in a single clean hop, with no chains, no loops and nothing landing on the homepage or a 404. This is where migration equity is won or lost, so it cannot wait. Use the redirect mapping from step two and verify it against the live site, handing off to the Redirect Checker to trace each one. Catching a broken or missing redirect in the first hours after launch is trivial; discovering it weeks later after rankings have fallen is not.
6. Verify indexation and crawl directives
Immediately confirm the new site is actually indexable: no staging noindex left in place, no restrictive robots.txt rule blocking the site, and the sitemap correctly declared and listing the new URLs. A staging noindex or a leftover Disallow shipping to production is a classic migration catastrophe that can deindex the whole new site while it looks perfect to visitors. The indexation-migration fix covers this verification, handing off to the Robots and Sitemap Tester. This check is urgent because indexation problems are invisible on the page yet quietly fatal to the entire migration.
7. Crawl the new site for breaks
Crawl the new site to catch what the plan missed: internal links still pointing at old URLs, redirect chains that formed, orphaned pages, broken links, and any URLs from the inventory that ended up without a working destination after all. The internal-links-migration fix covers cleaning up the internal linking, and this hands off to the Site Crawler for the full structural check. A crawl after launch reliably surfaces the gap between the migration you planned and the migration you actually got, which is always larger than expected and best found immediately rather than left to fester.
8. Monitor recovery and distinguish dip from disaster
After launch, monitor rankings and traffic closely and interpret them correctly, because even a flawless migration causes a temporary dip as Google recrawls and reassigns signals to the new URLs — that settling is normal. The skill is telling the expected temporary dip from a genuine problem that needs intervention. The ranking-recovery fix covers what to watch, how long normal recovery takes, and the warning signs of real trouble, handing off to Score History to track the curve. Knowing the difference stops you from panicking over normal settling or ignoring a real failure until it does lasting damage.
9. Fix platform-specific issues and hold the line
Address the issues specific to your destination platform, because migrating to Shopify, WordPress or a headless setup each brings characteristic URL, redirect, schema and structural pitfalls. The shopify-migration, wordpress-migration and headless-migration fixes target each. Then hold the line through the recovery period: resist making further large changes while Google is still reprocessing the move, keep the redirects and canonicals stable, and let the site settle. Migrations need a period of deliberate stability afterward, because piling more change onto a site Google is still digesting only prolongs the uncertainty and muddies whether the migration itself succeeded.
Tip: Do the hard work before you migrate, not after: inventory every URL and map each to a single 301 to its true new home, never to the homepage. Then, in the first hours after launch, verify redirects and indexation immediately — those are where migrations fail catastrophically and silently — and afterward hold the site stable and let it settle, because a normal recovery dip is not a reason to start changing things again.
Tags: migration-audit, howto, migration, redirects, indexation
Tutorial
Two small files decide what Google is allowed to crawl and where it should look: robots.txt grants or refuses permission, and sitemap.xml issues the invitation. The Robots and Sitemap Tester validates both, and crucially checks that they agree with each other rather than quietly contradicting.
The two files that govern crawling
Before Google reads a single word of your content, it consults two files. robots.txt tells it where it is and is not permitted to crawl, and sitemap.xml tells it which URLs you consider worth indexing and where to find them. One is a permission system and the other is an invitation, and between them they shape how the entire site is discovered. Because they are small, plain-text files that almost nobody looks at after setup, they are also where some of the most catastrophic and most invisible SEO mistakes live — a single wrong line in either can quietly remove large parts of a site from search, and nothing on the page itself will show that anything is wrong.
The contradiction problem this tool exists to catch
The most damaging robots-and-sitemap issues are contradictions between the two files. You list a URL in your sitemap, actively inviting Google to index it, while a Disallow rule in robots.txt simultaneously forbids Google from crawling it — so you are inviting and blocking the same page at once, and Google is left with a mixed signal it resolves in ways you will not like. The Robots and Sitemap Tester is built specifically to find these conflicts, because they are almost impossible to spot by reading either file on its own; the problem only becomes visible when you check both files against each other, which is exactly what this tool does.
What robots.txt actually controls, and what it does not
A crucial and widely misunderstood point: robots.txt controls crawling, not indexing. A Disallow rule stops Google from fetching a page, but if other sites link to that URL, Google can still index it from those links, showing a bare result with no description. To actually keep a page out of the index you need a noindex directive — but noindex lives on the page, and Google can never see it if robots.txt has blocked the page from being crawled in the first place. That catch-22 traps a huge number of sites, and understanding the distinction between crawl control and index control is the foundation for using either file correctly.
How the tester reads your robots.txt
The tool fetches your live robots.txt and parses it the way a crawler does: each user-agent group, every Allow and Disallow rule, the sitemap declarations, and the precedence between overlapping rules. It checks the syntax, because a malformed rule can silently match far more or far less than you intended, and it flags the specific high-risk patterns — a Disallow blocking the whole site, staging rules that leaked into the production file, and deliberate or accidental blocks of AI crawlers such as GPTBot and CCBot. Rather than trusting that the file says what you think it says, the tester shows you exactly how a crawler will interpret each line.
What it validates in your sitemap
On the sitemap side, the tool confirms the XML is well-formed and parseable, that every listed URL actually returns a 200 and is genuinely indexable rather than a 404, a redirect or a noindexed page, that the lastmod dates are present and plausible rather than fabricated, and that the file stays within the hard limits of fifty thousand URLs and fifty megabytes before it needs splitting into a sitemap index. It also checks that the sitemap is declared in robots.txt so Google can find it. A sitemap full of dead or non-indexable URLs steadily erodes the trust Google places in the whole file, which weakens it even for the good URLs.
The cross-checks only this tool performs
The real value is in the checks that span both files and the live site at once: every sitemap URL tested against your robots rules to catch the invite-and-block contradiction, every sitemap URL tested against its live status to catch the 404s and redirects you are still advertising, and the robots rules tested for the crawler traps that waste budget on infinite or parameterised URL spaces. Where a plain validator checks one file in isolation, this tool checks the relationship between your permission file, your invitation file and reality — which is where the genuinely costly problems hide, because each file can look perfect alone while together they undermine each other.
The findings and the fixes behind them
Every finding maps to a specific fix rather than a vague warning: robots syntax errors, the noindex-versus-Disallow catch-22, crawler traps, a missing or wrong sitemap declaration, sitemap 404s, inaccurate lastmod values, and sitemaps that have outgrown the size limits. On top of those there are platform-specific fixes for WordPress, Shopify and Next.js, because each of those generates and serves robots.txt and sitemaps in its own way, and the same conceptual problem has a different concrete solution depending on whether a plugin, a theme or a framework route is producing the file. You go straight from a finding to the exact steps for your situation.
Reading the results by severity
Not every finding is equally urgent, and the order you work them in matters enormously. A Disallow rule blocking the entire site, or a staging robots.txt that shipped to production, is a five-alarm fire that can deindex everything overnight; an invite-and-block contradiction on important pages is next; sitemap 404s and non-indexable URLs follow; and inaccurate lastmod dates or a slightly oversized sitemap are cosmetic by comparison. Work strictly from the catastrophic down to the cosmetic, because time spent tidying lastmod values while a Disallow quietly blocks half the site is time badly misspent on a site Google largely cannot see.
Where it fits with the other tools
Robots and Sitemap Tester sets and validates the rules of engagement; the other tools then work within them. Site Crawler tests what is actually reachable under those rules and hands back the orphan list — the sitemap URLs nothing links to — while HTTP Error Audit classifies the exact status codes behind any sitemap URLs that are failing. So this tool defines and checks the crawl-control layer, and the crawler and error tools operate downstream of it. Its own boundary is honest: it tests and validates your directives and their agreement with reality, but it will not redesign your site architecture or decide your indexation strategy for you.
Tip: robots.txt is simultaneously the highest-leverage and highest-risk file on your entire site — a single Disallow: / line removes everything from search — so test it after every deploy, and always test the live production file rather than a local or staging copy, because a staging robots.txt leaking into production is the classic, site-ending catastrophe that this tool exists to catch before Google does.
Tags: robots-sitemap, robots-txt, sitemap, crawl-control, indexation, technical-seo
How-To
A walkthrough of testing your live robots.txt and sitemap together, catching the contradictions between them, and fixing each issue in order of danger — from a site-wide block down to a stale lastmod date — with the right fix for your platform.
1. Test the live production files, not a copy
Point the tester at your live production robots.txt and sitemap.xml, never a local draft or a staging copy, because the whole category of catastrophic robots mistakes comes precisely from a difference between what you think is deployed and what actually is. Staging environments almost always carry a blanket Disallow: / to keep them out of search, and the disaster happens when that file is promoted to production unchanged. Testing the real, live file is the only way to catch that, so make it the first and non-negotiable step every single time you audit these files.
2. Check for the catastrophic site-wide block first
Before anything else, scan for the rules that can take the whole site out of search: a Disallow: / under the wildcard user-agent, or a broad Disallow blocking an entire important section. These are rare but fatal, and they outrank every other finding on the list no matter how long that list is, because while one is in place nothing else you fix matters — Google cannot crawl the pages you are improving. Confirm no such rule exists, or that any that does is genuinely intentional, before you spend a moment on anything more subtle.
3. Fix robots.txt syntax errors
Work through the syntax findings next, because a malformed rule fails in silent and surprising ways. Rule precedence matters — a more specific Allow can override a broader Disallow, and getting the order or the path pattern wrong means a rule matches far more or far less than you intended. Wildcards and end-of-string anchors behave in particular ways that are easy to get subtly wrong. The robots-syntax fix walks through how crawlers actually evaluate these rules, so that what the file says and what you meant it to say finally line up.
4. Resolve the noindex-versus-Disallow catch-22
If you are trying to keep pages out of the index, check that you have not fallen into the classic trap of Disallowing them. To remove a page from Google you must allow it to be crawled and add a noindex directive, because Google can only act on a noindex it is permitted to fetch — a page blocked by robots.txt can still be indexed from external links, and Google will never see the noindex you added to it. The noindex-disallow fix covers reversing this correctly, and it is one of the most common and most stubborn robots mistakes there is.
5. Check AI-crawler directives deliberately
Modern robots.txt files increasingly carry rules for AI crawlers such as GPTBot, CCBot, Google-Extended and others, and whether you block or allow them should be a deliberate decision rather than an accident inherited from a template or a plugin default. Review what your file currently says about these agents and confirm it matches your actual intent about AI training and AI search visibility. The tester flags these rules explicitly so that you are choosing your stance on AI access consciously, rather than discovering months later that a copied config quietly made the choice for you.
6. Confirm the sitemap is well-formed and declared
Move to the sitemap and confirm two things first: that it is valid, well-formed XML that actually parses, and that it is declared in robots.txt via a Sitemap: line so Google can reliably discover it. A sitemap that Google cannot find or cannot parse is doing none of its job, however good its contents. The sitemap-declaration fix covers wiring the declaration up correctly, including the case of multiple sitemaps behind a sitemap index file, which is where larger sites tend to trip up on discovery.
7. Clear sitemap 404s and non-indexable URLs
Audit every URL in the sitemap against its live status and remove anything that is not a genuine, indexable 200. A sitemap should advertise only live, canonical, indexable pages — not 404s, not redirects, not noindexed URLs, not canonicalised-away duplicates. Each dead or non-indexable URL you list erodes Google's trust in the file as a whole, which weakens it for the good URLs alongside them. The sitemap-404s fix walks through auditing and pruning the list so that everything in it is something you genuinely want indexed and Google can genuinely index.
8. Fix lastmod accuracy
Check the lastmod dates. They should reflect when each page's content genuinely last changed, or be omitted entirely — what you must not do is stamp every URL with today's date on every generation, because a lastmod that is always now is a lastmod that is always lying, and Google learns to ignore the field entirely when it proves unreliable. Accurate lastmod values genuinely help Google prioritise recrawling your changed pages; fabricated ones throw away that benefit for your whole site. The sitemap-lastmod fix covers generating honest values that Google will actually trust and use.
9. Keep within the size limits
Confirm the sitemap stays within the hard limits of fifty thousand URLs and fifty megabytes uncompressed. Past either limit, Google may not process the file fully, and pages silently drop out of consideration. Larger sites should split their URLs across multiple sitemaps referenced from a single sitemap index file, which also makes it easier to segment by section or content type and to spot where problems cluster. The sitemap-size fix walks through splitting cleanly and wiring the index up so that no URL is lost and Google can still discover every part.
10. Fix on your platform and re-test
Apply the fixes in the right place for your stack, because WordPress, Shopify and Next.js each generate and serve robots.txt and sitemaps differently — a plugin, a theme setting, or a framework route respectively — and editing the wrong layer either does nothing or gets overwritten on the next deploy. Use the platform fix for your environment, then re-test both files together to confirm the contradictions are gone and everything agrees. Then make this a habit after every deploy, because these files are regenerated by your platform constantly, and a good state is easily undone by the next release.
Tip: The one rule to internalise above all others: to remove a page from Google you allow crawling and add noindex — you never Disallow it, because Google cannot see a noindex on a page it has been blocked from crawling. Getting that backwards is the single most common robots mistake there is, and it keeps exactly the pages you wanted hidden stuck in the index indefinitely while you wonder why.
Tags: robots-sitemap, howto, robots-txt, sitemap, crawl-control
Tutorial
Redirects move URLs without breaking links, but the wrong redirect type, a chain of hops, or a loop quietly leaks ranking equity and wastes crawl budget. Redirect Checker traces every redirect to its final destination hop by hop, exposing chains, loops, wrong types and mixed-content jumps, and links each to its fix.
What redirects do, and how they go wrong
A redirect sends a request for one URL on to another, which is how you move or retire pages without breaking the links pointing at them. Used well they are invisible and lossless. Used carelessly they quietly cost you: the wrong type withholds the ranking signal you meant to pass, a chain of several hops dilutes equity and slows every visit, and a loop traps crawlers and users in a circle that never resolves. Because a redirect works from the user's point of view even when it is misconfigured for SEO, these problems tend to accumulate unnoticed over years of URL changes, which is exactly why they need auditing rather than assuming.
301 versus 302 — the distinction that matters most
The single most consequential choice in any redirect is permanent versus temporary. A 301 tells search engines the move is permanent and the ranking signals should transfer to the new URL; a 302 says the move is temporary and the original should keep its equity. The costly mistake is using a 302 for what is really a permanent move — a common platform default — because it withholds the transfer of authority you actually wanted, and the new URL never inherits the strength of the old one. The 302-vs-301 fix covers identifying these and switching them, which on many sites recovers ranking that was silently being held back.
Chains and loops
A redirect chain is a redirect that points at another redirect — A to B to C to D — instead of jumping straight to the final destination. Each extra hop bleeds a little more equity, adds latency to every request, and consumes crawl budget Google would rather spend elsewhere. A loop is the pathological case where the chain eventually points back on itself, trapping crawlers and showing users an error. Both are insidious because they grow silently: every time someone adds a redirect without checking whether the target is itself a redirect, a chain gets one hop longer. The redirect-loops fix covers detecting and collapsing both.
The four canonicalisation redirects every site needs
Almost every site needs four foundational redirects working correctly, because they affect every single URL: forcing HTTPS so no page is ever served over insecure HTTP, choosing one host between www and non-www and redirecting the other, settling trailing-slash behaviour consistently, and enforcing a single canonical host. Get any of these wrong and you effectively have duplicate versions of your entire site competing with each other, splitting signals across variants. The https-redirects, www-canonicalisation and trailing-slash fixes each cover one of these, and because they multiply across every URL, they are the highest-leverage redirects to get right first.
How the checker traces each redirect
Rather than just noting that a URL redirects, the tool follows the entire chain hop by hop to its final destination, recording the status code and target of every step along the way. That full trace is what lets it flag a two-hop chain that should be one, a loop that never resolves, a 302 that should be a 301, and a hop that lands on insecure HTTP partway through. Seeing every hop laid out is the difference between knowing a URL eventually resolves and knowing exactly how many wasteful steps it takes to get there and where each one goes wrong.
Mixed-content and CDN edge cases
Two subtler problems show up only when you trace the full chain. A mixed-content redirect is one where an HTTPS page redirects through, or lands on, an insecure HTTP URL, undermining the secure connection and tripping browser warnings — the mixed-content-redirects fix covers closing these. And CDN-versus-origin double redirects happen when your CDN and your origin server both issue redirects for the same request, stacking two hops where there should be one, often invisibly because each layer looks correct on its own. The cdn-origin-redirects fix covers untangling which layer should own the redirect so the request resolves in a single clean hop.
Findings, fixes and your platform
Every finding maps to a concrete fix, and the implementation is platform-specific because a redirect is configured in a completely different place depending on your stack. Apache uses .htaccess and rewrite rules, Cloudflare uses redirect rules at the edge, Plesk has its own hosting-panel configuration, and WordPress typically uses a plugin or functions code. The same logical redirect — say, forcing HTTPS — is a different concrete change in each, and putting it in the wrong layer either does nothing or conflicts with another layer. The platform fixes point you at the right place for your environment so the redirect actually takes effect cleanly.
Where it fits with the other tools
Redirect Checker is the hop-by-hop specialist. Site Crawler finds redirect chains as a structural pattern across your whole site and flags that they exist; this tool then traces each one in detail to show you the exact hops and where to collapse them. HTTP Error Audit catches the 404s and server errors that sit at the end of redirects that have broken entirely. So the crawler tells you where redirects are tangled, this tool tells you precisely how, and the error audit catches the ones that have failed outright. Its own boundary is that it reports and traces the chains — deciding the correct final destination for each is still your call.
Tip: Every redirect on your site should be a single 301 straight to the final destination — never a chain, never a loop, and never a 302 for a move that is actually permanent. Audit the four canonicalisation redirects first (HTTPS, www, trailing slash and canonical host), because they apply to every URL you have, and a single misconfiguration among them multiplies its damage across the entire site at once.
Tags: redirect-checker, redirects, 301, canonicalisation, technical-seo, crawl-budget
How-To
A walkthrough of tracing every redirect to its destination, then fixing wrong types, chains, loops and the four canonicalisation redirects in order of leverage — down to mixed-content and CDN edge cases — with the right implementation for your platform.
1. Scan and trace every redirect fully
Run the checker so it follows each redirect all the way to its final destination, recording every hop rather than stopping at the first jump. This full trace is the data set for everything that follows: it is what reveals a two-hop chain masquerading as a simple redirect, a loop that never resolves, and a type or protocol problem buried partway along the path. Do not act on a single-hop view; the whole point of a redirect audit is seeing the entire journey each URL takes, because the problems almost always live in the hops you would otherwise never see.
2. Fix wrong redirect types
Work the type mismatches first among the individual redirects. Anything that represents a permanent move must be a 301 so it transfers ranking signals to the destination; reserve 302 strictly for genuinely temporary situations where you want the original URL to retain its equity. Platform defaults frequently issue 302s where 301s are needed, silently withholding authority from your new URLs. The 302-vs-301 fix covers finding and correcting these, and on sites with a history of moves it often frees up ranking strength that was being quietly held back for years.
3. Collapse chains to a single hop
Take every multi-hop chain and rewrite it so the original URL points directly at the true final destination in one jump — A straight to C, never A to B to C. Each hop you remove recovers a little equity, shaves latency from every request, and returns crawl budget to Google. The key habit is that whenever you add a new redirect, you check whether its target is itself already a redirect, and point at the real endpoint instead, so chains never form in the first place. Flattening existing chains and adopting that habit together keep the whole redirect map clean.
4. Break loops immediately
Any redirect loop is an emergency, because it resolves to nothing — crawlers give up and users hit an error page, so the affected URLs are effectively dead. Loops usually arise from two rules fighting each other, such as a trailing-slash rule and a canonicalisation rule each undoing the other's work. Trace the loop to the conflicting rules and reconcile them so the request settles on one final URL. The redirect-loops fix walks through diagnosing the conflict and resolving it cleanly, and these should jump the queue ahead of subtler optimisations because the pages are unreachable until they are fixed.
5. Enforce HTTPS everywhere
Confirm that every HTTP request is redirected to its HTTPS equivalent, with no page ever served or left reachable over insecure HTTP. This is both a security baseline and a canonicalisation necessity, because an HTTP and an HTTPS version of the same page are duplicate URLs competing with each other. Make sure the redirect goes straight to the HTTPS version of the same path in one hop, not via the homepage or through an extra www step. The https-redirects fix covers setting this up so the whole site consistently and cleanly resolves to secure URLs.
6. Pick one host and redirect the other
Choose a single canonical host — either www or non-www, it does not matter which, only that you commit — and 301-redirect the other version to it site-wide. Running both without a redirect gives Google two complete copies of your site to reconcile, splitting signals between them for no benefit. Combine this with the HTTPS rule into a single clean redirect rather than chaining an HTTP-to-HTTPS hop into a separate www hop. The www-canonicalisation fix covers implementing the host choice in one step so that every variant of every URL funnels to one canonical form.
7. Settle trailing-slash behaviour
Decide whether your URLs end with a trailing slash or not, and enforce that choice consistently with a single redirect from the non-preferred form to the preferred one. Inconsistent trailing slashes create duplicate URLs and, worse, are a common ingredient in redirect loops when a slash rule collides with another canonicalisation rule. The trailing-slash fix covers choosing a convention and applying it without creating a loop — which, combined with the HTTPS and host rules above, gives you one canonical form for every URL rather than several near-identical variants competing with one another.
8. Eliminate mixed-content redirects
Check that no redirect chain passes through or lands on an insecure HTTP URL. A mixed-content redirect undermines the secure connection you worked to establish, triggers browser security warnings, and can break the page for users on stricter browsers. These often hide in the middle hop of a chain, which is why the full trace from step one matters — the start and end can both be HTTPS while an intermediate hop drops to HTTP. The mixed-content-redirects fix covers finding and rewriting these so the entire journey stays secure from first request to final destination.
9. Untangle CDN-versus-origin double redirects
If you run a CDN in front of your origin, check for redirects being issued by both layers for the same request, stacking two hops where one would do. This is easy to miss because each layer's rule looks correct in isolation; only the full trace reveals that the CDN redirects and then the origin redirects again. Decide which layer should own the redirect — usually the edge, for speed — and remove the duplicate from the other. The cdn-origin-redirects fix covers working out the right division of responsibility so the request resolves in a single hop at the edge.
10. Implement on your stack, then re-scan
Apply each fix in the correct place for your platform — Apache .htaccess and rewrite rules, Cloudflare edge redirect rules, Plesk's hosting configuration, or a WordPress redirect plugin — because a redirect placed in the wrong layer either has no effect or fights with a rule elsewhere. Use the platform fix for your environment, then re-scan the whole site to confirm every redirect now resolves in a single clean 301 hop with no chains, loops or protocol drops remaining. Make re-scanning routine after any migration or bulk URL change, because that is exactly when chains and loops are reintroduced.
Tip: After any migration, map each old URL directly to its true new destination — never point everything at the homepage, which Google treats as a soft 404, and never let redirects quietly stack into chains as you add more over time. Re-scan after each batch of changes, because chains grow one silent hop at a time whenever someone redirects to a URL that already redirects somewhere else.
Tags: redirect-checker, howto, redirects, 301, canonicalisation
Tutorial
The tags in your page's head punch far above their weight: the title and description are literally your listing in the search results, the social tags are your share card, and the technical metas control mobile rendering and indexing. Meta Analyzer audits every one of them for presence, quality and conflicts.
Why meta tags punch above their weight
Meta tags are a handful of invisible lines in your page's head, and yet they do a startling amount of work. The title and meta description are assembled into your actual listing in the search results — the headline and blurb a searcher reads before deciding whether to click. The Open Graph and Twitter tags become the card that appears when your link is shared. The technical metas quietly govern whether the page renders correctly on mobile and whether it is even allowed into the index. For something almost nobody looks at after launch, the head tags shape both how findable your page is and how it presents itself the moment someone encounters it.
Title tags — the strongest on-page signal
The title tag is simultaneously one of the strongest on-page ranking signals and the clickable blue line of your search listing, which makes it the highest-value few words on the whole page. It needs to be present, unique across every page, roughly fifty to sixty characters so it does not truncate in results, and written with the important keyword toward the front and a human reason to click. Duplicated titles across pages actively confuse Google about which page to rank for a term, splitting your own relevance. The title-tags fix covers writing and de-duplicating them, and it is where meta work should almost always start.
Meta descriptions — no ranking, all click-through
A meta description does not directly influence rankings, but it heavily influences whether anyone clicks your result, which influences everything downstream. It should be unique per page, around a hundred and fifty to a hundred and sixty characters, and written as compelling ad copy that earns the click. If it is missing, too long or generic, Google simply writes its own from page text — usually less persuasively than you would. The meta-descriptions fix covers crafting descriptions that survive intact in the results and pull clicks, treating each one as the free advertising slot it actually is.
Social and Open Graph tags
When someone shares your URL on social platforms or in messaging apps, the Open Graph and Twitter Card tags decide what that share looks like — the title, the description and, most importantly, the image on the card. Without them, shares of your content render as bare, unappealing links that nobody clicks, wasting every bit of reach a share could have given you. The og-tags and twitter-cards fixes cover completing these — title, description, image, url and type for Open Graph, plus the Twitter-specific card tags — so that your content presents itself well wherever it travels beyond search.
The technical metas
Beyond the visible listing, a few technical metas do quiet but critical work. The viewport tag is what makes a page render correctly on mobile; without it, phones display a shrunken desktop layout and your Core Web Vitals suffer. The lang attribute declares the page's language for accessibility and internationalisation. The robots meta controls indexing and link-following at the page level, and an accidental noindex here is as damaging as a robots.txt block. The viewport, lang-attribute and robots-meta fixes each cover one of these — small tags with outsized consequences when they are missing or wrong.
How the analyzer checks your head tags
Meta Analyzer pulls every relevant tag from each page's head and evaluates it: flagging missing titles and descriptions, duplicates across pages, values that are too long or too short to survive in the results, incomplete Open Graph sets, and robots-meta directives that conflict with each other or with your intent. Rather than making you inspect the head of each template by hand, it surfaces exactly which pages have which head-tag problems, sorted so you can see whether an issue is a one-off on a single page or a systematic fault in a shared template that is repeating across the whole site.
Findings, fixes and your platform
Every finding links to a fix, and the implementation is platform-specific because the head is generated in a different place on each stack. WordPress typically outputs meta tags through an SEO plugin, Shopify through theme Liquid templates, and Next.js through a head or metadata component in code. The same fix — say, adding unique titles — is a different concrete change in each, and editing the wrong layer is either overwritten on the next update or simply ignored. The meta-tags-wordpress, meta-tags-shopify and meta-tags-nextjs fixes point you at the right place for your environment so the change actually renders.
Where it fits with the other tools
Meta Analyzer is the head-tag audit, and it sits alongside two neighbouring tools. The social and SERP preview tools show you visually how your title, description and share card will render, where this tool audits their presence and quality across every page at scale. The schema tools handle structured data, which is a different part of the head serving a different purpose. So Meta Analyzer owns the correctness and completeness of the classic meta tags across the whole site, and its boundary is honest: it checks that your tags exist and meet the guidelines, but it will not write your title and description copy for you — the words are still your job.
Tip: Your titles and descriptions are the only SEO asset that doubles as advertising copy in the search results, so make every one unique, correctly sized, and written for a human to click rather than stuffed for a crawler. Fix duplicate titles first of all, because two pages sharing a title actively confuse Google about which one to rank, and you end up competing against yourself for your own terms.
Tags: meta-analyzer, meta-tags, title-tags, meta-descriptions, open-graph, technical-seo
How-To
A walkthrough of auditing every page's head tags and fixing them in order of impact — titles and descriptions first, then indexing and mobile metas, then the social cards — with the right implementation for WordPress, Shopify or Next.js.
1. Scan every page's head tags
Run the analyzer across the site so you have a complete picture of which pages carry which head-tag problems, rather than checking a template or two by hand and assuming the rest match. The scan is what lets you tell a one-off issue on a single page from a systematic fault in a shared template repeating across hundreds of URLs, and that distinction changes how you fix it — one page is an edit, a template fault is a single change that resolves the issue everywhere at once. Start from the full scan so you fix at the right level.
2. Fix missing and duplicate titles first
Titles are the highest-value tag, so start there. Every page needs a present, unique title of roughly fifty to sixty characters, with the important keyword toward the front and a human reason to click. Tackle duplicates as the priority within this, because two pages sharing a title actively confuse Google about which to rank for the term and split your own relevance. The title-tags fix covers writing distinctive titles and resolving duplicates, often by fixing the template pattern that was generating identical titles from a shared field across many pages at once.
3. Write and repair meta descriptions
Give every important page a unique meta description of around a hundred and fifty to a hundred and sixty characters, written as persuasive ad copy that earns the click rather than a bland restatement of the title. Where descriptions are missing, over-length or generic, Google writes its own from page text and usually does it less persuasively than you would. The meta-descriptions fix covers crafting ones that survive intact in the results and actually pull clicks — treating each as the free advertising slot in the search results that it genuinely is.
4. Resolve robots-meta directives
Audit the page-level robots meta tags for accidental noindex or nofollow directives and for index/noindex conflicts, because a stray noindex here quietly removes a page from search just as surely as a robots.txt block does. Pay particular attention to whether a staging noindex has shipped to production, which is a common and painful accident. The robots-meta fix covers reconciling these directives so that exactly the pages you want indexed are indexable, and nothing you want in search is silently held out of it by a leftover directive.
5. Add the viewport tag everywhere
Confirm every page carries a viewport meta tag, because without it phones render a shrunken desktop layout, mobile usability collapses and Core Web Vitals suffer directly — all from the absence of a single line. It is trivial to add and trivial to forget, especially on older templates or bespoke landing pages built outside the main system. The viewport fix covers adding it correctly across every template, and it is worth checking on every page type rather than assuming the main template's tag covers the whole site, because the exceptions are exactly where it tends to be missing.
6. Set the lang attribute correctly
Ensure the page declares its language via the lang attribute on the html element, which matters for screen readers, for browser translation, and for correct internationalisation on multi-language sites. A missing or wrong lang value degrades accessibility and can confuse language targeting. The lang-attribute fix covers setting it accurately per page or per locale, which is straightforward but genuinely important on any site that serves more than one language or that cares about its accessibility compliance — and increasingly that is every serious site.
7. Complete your Open Graph tags
Fill in the full set of Open Graph tags — og:title, og:description, og:image, og:url and og:type — so that shares of your content render as attractive, clickable cards rather than bare links. The image in particular does most of the work in earning attention in a feed, so make sure it is present, correctly sized and genuinely representative. The og-tags fix covers completing the set and getting the image dimensions right, which turns every share of your content into a small piece of effective, free promotion instead of an unremarkable text link.
8. Add Twitter Card tags
Add the Twitter-specific card tags so your links present well on that platform too, which does not always fall back cleanly to Open Graph and benefits from its own explicit card type and image declarations. The twitter-cards fix covers the specific tags and the choice of card type — summary versus large-image — so shares render the way you intend. It is a quick addition once Open Graph is in place, and it closes the last common gap in how your content appears when it travels beyond search into social feeds and messaging apps.
9. Fix on your platform
Apply the changes in the correct layer for your stack, because the head is generated differently on each: WordPress through an SEO plugin's settings and templates, Shopify through theme Liquid, and Next.js through a head or metadata component in code. Editing the wrong layer means your change is either ignored or overwritten on the next theme or plugin update. The meta-tags-wordpress, meta-tags-shopify and meta-tags-nextjs fixes point you at the right place for your environment so that the tags you set actually render in the delivered HTML and stay put.
10. Re-scan and recheck after head changes
Re-scan the site to confirm the missing and duplicate tags are resolved and nothing new has slipped in. Then make a habit of re-checking after any change that touches the head — a theme update, a plugin change, a template refactor — because these routinely reset or overwrite meta output, and a good state is easily undone by a deploy you did not think would affect it. Head tags are quietly regenerated far more often than people expect, so periodic re-scanning is what keeps the whole site's listings and share cards in the shape you intended.
Tip: A missing viewport tag silently wrecks mobile rendering and drags Core Web Vitals down, and it is a single line — so check for it on every template, not just the main one. And never let a staging noindex robots-meta reach production: it is the meta-tag equivalent of the robots.txt catastrophe, just as damaging and just as easy to miss until traffic quietly disappears.
Tags: meta-analyzer, howto, meta-tags, title-tags, meta-descriptions
Tutorial
Browsers and crawlers forgive sloppy markup right up until they do not — and then invalid HTML causes content to be misparsed, dropped, or made inaccessible in ways nothing on the page reveals. HTML Checker validates your markup against the spec, separates fatal structural errors from cosmetic ones, and links each to its fix.
Why valid HTML matters for SEO and accessibility
Browsers are famously forgiving of broken markup — they patch missing tags, guess at bad nesting, and render something plausible anyway — which lulls everyone into thinking invalid HTML does not matter. The problem is that every parser patches differently, and when Google's parser resolves your broken markup differently from your browser, content can be misattributed, dropped from the parse, or rendered inaccessible to assistive technology. Valid HTML is the shared contract that makes your page mean the same thing to every browser, every crawler and every screen reader, and it is the quiet foundation under both your SEO and your accessibility.
Structural errors — the ones that change parsing
The most consequential errors are structural: a missing or wrong doctype that throws the browser into quirks mode, unclosed tags, and improperly nested elements. These matter more than any cosmetic warning because they change how the parser interprets everything that follows — an unclosed element can swallow the content after it, and a nesting error can move whole sections of your page somewhere you never intended in the parsed document. The doctype-errors and unclosed-tags fixes cover these, and they are always the first thing to resolve, because a page that parses wrongly at the structural level cannot be reasoned about further until it parses right.
Encoding and language declarations
Two declarations quietly govern how your text is understood. The character encoding determines whether your text renders correctly or dissolves into mojibake — garbled symbols where accented or non-Latin characters should be — and a missing or mismatched charset is a common cause of corrupted-looking pages. The lang attribute tells screen readers and translation tools what language the page is in. The encoding-errors and missing-lang fixes cover setting both correctly, and while neither is glamorous, an encoding problem can make content unreadable and a missing lang declaration can make it inaccessible, both from a single overlooked line.
Duplicate IDs and deprecated elements
Two more classes of error cause trouble out of proportion to how minor they look. Duplicate IDs — the same id used on more than one element — break JavaScript that targets by id, break in-page anchor links, and confuse assistive technology, because an id is supposed to be unique and everything that relies on it assumes so. Deprecated elements and attributes, like the old center or font tags, may be dropped by modern parsers and signal aging markup. The duplicate-ids and deprecated-html fixes cover finding and replacing these with valid, uniquely-identified, current markup.
Accessibility markup
Some of the most important validation findings are about accessibility. Misused ARIA — roles and states applied incorrectly or redundantly — can actively make a page harder to use with a screen reader than no ARIA at all, because it overrides the native semantics with wrong ones. Form fields without proper labels or validation leave users unable to understand or complete them. The aria-errors and form-validation fixes cover using ARIA correctly and only where needed, and giving every form control a real label and sensible validation, which is where markup correctness and genuine usability for disabled users meet directly.
How the checker validates your markup
HTML Checker parses your page against the HTML specification and reports what it finds as errors and warnings, each with its type and location, and it separates the genuinely fatal problems from the merely cosmetic ones so you are not left weighting a stray unclosed tag the same as a harmless deprecated attribute. Rather than a wall of undifferentiated validator output, you get findings ordered by how much they actually affect parsing, rendering and accessibility, which is what makes the results usable — the goal is not a perfect score for its own sake but a page that every parser interprets the way you intended.
Findings, fixes and your platform
Every finding maps to a fix, and the patterns of invalid markup differ by how your HTML is produced. React and JSX have their own pitfalls, such as attribute naming and self-closing rules that leak into the output; Shopify's Liquid templating can generate subtly malformed markup; static site generators and WordPress each have characteristic ways of producing invalid HTML through themes and plugins. The html-errors-react, html-errors-shopify, html-errors-ssg and html-errors-wordpress fixes target the specific patterns each stack tends to produce, so you fix the source template rather than patching the same generated error on page after page.
Where it fits with the other tools
HTML Checker owns the markup-correctness layer, and it sits between two neighbours. CSS Checker handles the stylesheet side — the presentation layer built on top of your markup — and the CLS Debugger handles rendering stability, which is affected by markup but measured as visual movement. So HTML Checker makes sure the document itself is structurally sound and accessible before those tools build on it. Its boundary is honest: it validates that your markup is correct and accessible against the spec, but it will not redesign your page or make design decisions — it ensures the foundation is sound, not that the building on it is beautiful.
Tip: Invalid HTML rarely breaks the page in any visible way, which is precisely why it festers — the browser silently patches it and moves on, right up until one parser, often Google's, patches it differently and quietly loses your content. Always fix structural errors first (doctype, unclosed tags, nesting), because those are the ones that actually change how the page is parsed, before you touch anything cosmetic.
Tags: html-checker, html-validation, w3c, accessibility, markup, technical-seo
How-To
A walkthrough of validating your markup and fixing it in parse order — doctype and encoding first, then structure, then duplicate IDs and deprecated tags, then accessibility — with the right fix for React, Shopify, SSG or WordPress, and validation wired into your build.
1. Validate the page against the spec
Run the checker to validate the page against the HTML specification and produce the full list of errors and warnings, each with its location and severity. This is your work list, and the order it implies matters: the tool separates fatal structural problems from cosmetic ones so you can work top-down by real impact rather than alphabetically or at random. Resist the urge to knock off the easy cosmetic warnings first — the correct order follows how the parser reads the document, and fixing out of that order can mean chasing symptoms of a structural error you have not addressed yet.
2. Fix the doctype first
Start with the doctype, because a missing or malformed one throws the browser into quirks mode, where it emulates ancient rendering behaviour and everything downstream — layout, box model, script behaviour — can misbehave in ways that are maddening to debug if you do not know quirks mode is the cause. A single correct doctype line at the very top of the document switches the browser into standards mode. The doctype-errors fix covers getting it right, and it genuinely belongs first because so many other apparent problems evaporate the moment the page is parsed in standards mode.
3. Set the correct character encoding
Declare the character encoding correctly and early in the head, because encoding governs how every byte of text after it is interpreted, and a wrong or missing charset turns accented and non-Latin characters into mojibake — the garbled symbols that make a page look broken and unprofessional. Like the doctype, this is a declaration that affects everything following it, which is why it belongs near the front of your fix order. The encoding-errors fix covers setting the charset properly so that your text renders as written, in every browser, regardless of the languages and symbols it contains.
4. Add the lang attribute
Set the lang attribute on the html element to declare the page's language, which screen readers use to choose the correct pronunciation and voice, and which translation tools and search engines use to understand and target the content. It is a single attribute with real accessibility and internationalisation weight, and it is easy to omit entirely on hand-built templates. The missing-lang fix covers adding it accurately — per locale on multi-language sites — and it is a small, high-value correction that meaningfully improves the experience for anyone using assistive technology to read your pages.
5. Close all tags and fix nesting
Work through the unclosed tags and improper nesting, because these are the structural errors that change how the parser builds the document — an unclosed element can absorb the content meant to follow it, and a nesting violation can relocate whole sections in the parsed tree. Left unfixed, they cause content to be misattributed or dropped in ways that vary by parser, which is exactly the inconsistency you are validating to eliminate. The unclosed-tags fix covers closing every element correctly and nesting them according to the spec so the document parses into the shape you actually wrote.
6. Eliminate duplicate IDs
Find every case of the same id used on more than one element and make each id unique, because duplicate IDs break the things that assume uniqueness: JavaScript that selects by id grabs the wrong element or only the first, in-page anchor links jump to the wrong place, and assistive technology gets confused about which element a reference points to. The duplicate-ids fix covers detecting and resolving them, which often means fixing a template that stamped the same id onto a repeated component — one template change that clears the duplicates across every page the component appears on.
7. Replace deprecated elements and attributes
Replace any deprecated elements and attributes — the old presentational tags and obsolete attributes that linger in older markup — with their current, valid equivalents, because deprecated features may be dropped or handled inconsistently by modern parsers and mark your HTML as aging in ways that can have real effects. The deprecated-html fix covers identifying them and moving to standards-compliant markup, typically shifting presentation into CSS where it belongs. It is housekeeping, but it removes a class of unpredictable behaviour and brings the document properly up to the current specification.
8. Fix ARIA errors
Correct the ARIA findings carefully, because misused ARIA is worse than none — a wrong role or a redundant state overrides the element's correct native semantics with incorrect ones, actively misleading screen-reader users. The right approach is to use native HTML elements wherever they already carry the correct semantics and reserve ARIA for the genuine gaps, applied exactly per the spec. The aria-errors fix covers using ARIA correctly and sparingly, which usually means removing redundant ARIA and fixing the few genuinely necessary roles rather than adding more of it everywhere.
9. Label and validate form fields
Give every form control a proper, associated label so users and assistive technology know what each field is for, and add sensible validation so users understand what is required and what went wrong. Unlabelled fields are a serious accessibility failure and a usability one, leaving some users simply unable to complete the form. The form-validation fix covers associating labels correctly and implementing validation that communicates clearly, turning forms from an accessibility liability into something everyone can actually use — which, for any form that matters to your business, is directly worth the effort.
10. Fix framework-specific patterns
Address the invalid markup at its source in your framework, because each stack produces its own characteristic errors: React and JSX through attribute and self-closing quirks, Shopify through Liquid templating, static site generators and WordPress through their themes and plugins. Fixing the generated output page by page is futile when a template is producing the same error everywhere; the html-errors-react, html-errors-shopify, html-errors-ssg and html-errors-wordpress fixes target the source patterns so one correction at the template level clears the error across every page it was appearing on.
11. Re-validate and wire it into your build
Re-validate to confirm the errors are resolved, then go one step further and add HTML validation to your build or deployment pipeline so that regressions are caught before they ship rather than discovered in a later audit. Markup drifts back toward invalidity every time a template is edited or a component is added, so a one-off clean-up decays without a guard. Wiring validation into the build turns markup correctness from a thing you periodically remember to check into a standard the whole site is held to automatically on every change.
Tip: Always fix in parse order — doctype and encoding first, because they change how everything after them is interpreted, then structure (unclosed tags, nesting, duplicate IDs), then accessibility. Chasing an ARIA warning while the doctype is still missing is backwards: the missing doctype may well be why the parser is confused in the first place, and fixing it can make a whole cascade of downstream warnings simply disappear.
Tags: html-checker, howto, html-validation, accessibility, markup
Tutorial
CSS is not just how a page looks — it blocks rendering, and bloated, unused or render-blocking stylesheets drag down LCP and Core Web Vitals directly. CSS Checker audits your stylesheets for validity, delivery, size, specificity and the CSS causes of layout shift, and links each finding to its fix.
Why CSS is an SEO and performance concern
It is tempting to think of CSS as purely cosmetic, but it sits squarely in the critical rendering path: a browser cannot paint the page until it has fetched and parsed the CSS, so your stylesheets directly determine how fast content appears. Bloated, unused or render-blocking CSS delays first paint, pushes out Largest Contentful Paint, and drags down the Core Web Vitals that feed into rankings. On top of that, invalid or over-complicated CSS causes silent visual bugs and unmaintainable stylesheets. So CSS is both a performance lever and a correctness concern, and auditing it is as much an SEO task as a design one.
Render-blocking CSS
By default, CSS is render-blocking: the browser will not paint any content until it has downloaded and processed the stylesheets in the head, because it needs to know how everything looks before showing it. A large or slow stylesheet therefore holds the whole page blank for longer, hurting the metrics that measure how quickly users see something. The fix is to inline the small amount of critical CSS needed for the initial view and defer the rest so it loads without blocking. The render-blocking-css fix covers this pattern, which is often the single biggest CSS performance win available.
Bundle size and unused CSS
Two related forms of bloat weigh pages down. A large CSS bundle ships more bytes than the page needs, and unused CSS — rules for components and pages the user is not currently on — is dead weight the browser still has to download and parse. Frameworks and UI libraries are common culprits, shipping their entire stylesheet regardless of how little of it a given page uses. The css-bundle-size and unused-css fixes cover trimming the payload through splitting, tree-shaking and removing dead rules, which reduces both download time and the parsing work the browser must do before it can paint.
Validation and specificity
Two correctness problems make CSS unreliable and unmaintainable. Invalid CSS — a misspelled property, a bad value — is dropped silently by the browser, so a style simply does not apply and you are left hunting for why, with no error to guide you. Specificity problems arise when overly specific selectors force ever-more-specific overrides, culminating in the !important spiral where nothing can be changed cleanly. The css-validation and css-specificity fixes cover catching silent invalidity and flattening selector specificity, which together turn a stylesheet from a fragile, unpredictable thing into one you can actually reason about and change safely.
Browser prefixes and dark mode
Two coverage concerns affect how consistently your styles work across contexts. Missing vendor prefixes can break newer CSS features in browsers that still need the prefixed form, causing layouts to fail for a slice of your users invisibly to you. And proper dark-mode support via prefers-color-scheme is increasingly expected, so that your site respects the user's system preference rather than forcing a single theme. The browser-prefixes and dark-mode fixes cover adding the prefixes current browsers require and implementing dark mode correctly, both of which broaden how reliably and comfortably your design works across the full range of devices and preferences.
CSS-driven layout shift
CSS is one of the main causes of Cumulative Layout Shift, the Core Web Vital that measures unexpected movement as the page loads. Media without dimensions set in CSS, styles that swap in late, and containers that resize as content arrives all shove the layout and hurt the score. This is the CSS side of the same problem the CLS Debugger measures from the rendering side. The layout-shift-css fix covers the stylesheet techniques — explicit dimensions, aspect-ratio, containment — that prevent shift, and it pairs naturally with the CLS Debugger, which diagnoses the shifts this fix prevents.
How the checker analyses your CSS, and where fixes fit
CSS Checker analyses your stylesheets for all of the above — validity, render-blocking delivery, bundle size and unused rules, specificity, missing prefixes, dark-mode support and the CSS causes of layout shift — and maps each finding to a fix. The implementation is platform-specific, because CSS is delivered very differently across stacks: React and CSS-in-JS, Shopify themes, and WordPress each have their own patterns for shipping styles and their own characteristic sources of bloat. The css-react, css-shopify and css-wordpress fixes target those, so you address the real source of the CSS problem in your particular environment rather than patching symptoms.
Where it fits with the other tools
CSS Checker owns stylesheet correctness and delivery, and it sits alongside two neighbours. The CLS Debugger measures layout stability, which CSS heavily influences, so the two work together on the shift problem from the prevention and the measurement sides respectively. HTML Checker handles the markup underneath the styling. So CSS Checker's remit is the health, size and delivery of your stylesheets and the visual bugs and performance costs they cause. Its boundary is honest: it checks that your CSS is valid, lean and well-delivered, but it will not redesign your visuals or make aesthetic decisions — it keeps the stylesheet healthy, not the design pretty.
Tip: The fastest CSS win is almost always removing render-blocking and unused CSS — inline the critical portion, defer the rest, and strip the dead rules — because it improves Largest Contentful Paint directly at zero visual cost. And always validate, because a single invalid property is dropped silently by the browser, and the resulting why-isn't-this-style-applying hunt can burn hours on a problem an error message would have named instantly.
Tags: css-checker, css-validation, render-blocking, performance, specificity, technical-seo
How-To
A walkthrough of analysing your stylesheets and fixing them in order of user impact — validation and render-blocking first, then bundle size and specificity, then prefixes, dark mode and layout shift — with the right fix for React, Shopify or WordPress.
1. Analyse the page's CSS
Run the checker to analyse the stylesheets the page actually loads, so you can see the full picture: what is invalid, what is render-blocking, how large the bundle is, how much of it goes unused, where specificity is out of control, and where CSS is causing layout shift. This overview is what lets you fix in order of user impact rather than tackling whatever you happened to notice first. The order matters because the performance findings and the correctness findings have very different payoffs, and working them in the right sequence is what turns an audit into a measurable improvement.
2. Fix validation errors
Clear the validation errors first among the correctness issues, because invalid CSS fails silently — the browser drops a misspelled property or a bad value without a word, so the style simply does not apply and you are left debugging a mystery with no error to point the way. Catching these turns silent failures into named ones you can fix in seconds rather than hunt for over hours. The css-validation fix covers finding the invalid declarations and correcting them, which also often explains a handful of long-standing why-doesn't-this-work styling bugs in one pass.
3. Remove render-blocking CSS
Attack render-blocking next, because it is usually the biggest performance win. The browser cannot paint until the CSS in the head is loaded, so a large blocking stylesheet holds the page blank and pushes out Largest Contentful Paint. Inline the small amount of critical CSS needed for the initial above-the-fold view and defer the rest so it loads without blocking the first paint. The render-blocking-css fix covers extracting the critical portion and deferring the remainder, which improves how quickly users see content directly, at no cost to how the finished page looks.
4. Strip unused CSS
Remove the dead rules — styles for components and pages that the current page never uses, which the browser still has to download and parse for nothing. Unused CSS accumulates naturally as a site evolves, and UI libraries make it worse by shipping their whole stylesheet regardless of how little you use. The unused-css fix covers identifying and removing rules that no element on the page matches, shrinking the payload and the parse work. It pairs naturally with the render-blocking fix, since a smaller stylesheet is both faster to load and cheaper for the browser to process before painting.
5. Cut the bundle size
Reduce the overall size of what you ship through splitting, tree-shaking and compression, so that each page loads only the CSS it genuinely needs rather than one monolithic stylesheet for the entire site. Code-splitting CSS per route, letting the build tree-shake away what is not referenced, and serving it compressed together make a substantial difference on larger sites. The css-bundle-size fix covers these techniques, and combined with removing unused rules it addresses bloat from both directions — shipping less, and shipping only what is used — which compounds into a meaningfully faster page.
6. Tame specificity
Bring specificity under control by flattening overly specific selectors and eliminating the !important declarations that signal a specificity war has already been lost. When selectors get too specific, every override has to be more specific still, and the escalation ends in a stylesheet nobody can change cleanly. The css-specificity fix covers restructuring selectors to a flatter, more predictable specificity so styles can be overridden in the normal way. This is maintainability work rather than performance, but an unmaintainable stylesheet is where future bloat and bugs breed, so it pays off over time.
7. Add missing browser prefixes
Add the vendor prefixes that current browsers still require for certain properties, because a missing prefix can make a feature silently fail for a slice of your users while working perfectly in your own browser, so you never see the breakage. The browser-prefixes fix covers identifying which properties need prefixing for your target browser support and adding them — ideally through an automated build step like autoprefixer so it stays correct as browser requirements change, rather than a manual list that quietly goes stale and starts either missing new needs or carrying obsolete prefixes.
8. Implement dark mode properly
Add or correct dark-mode support via prefers-color-scheme so your site respects the user's system-level preference instead of forcing one theme on everyone. Done properly this means defining your colours so they adapt cleanly rather than bolting on a second hard-coded palette, and testing that contrast and legibility hold up in both modes. The dark-mode fix covers implementing it correctly, which is increasingly an expectation rather than a nicety — users who have set their device to dark mode notice and appreciate a site that honours it, and one that blazes white at them at night is a small but real friction.
9. Fix CSS-driven layout shift
Address the CSS causes of layout shift — media without explicit dimensions, styles that swap in late, containers that resize as content loads — by setting explicit dimensions and aspect-ratio and using containment to stop elements shoving the layout as the page renders. The layout-shift-css fix covers these stylesheet techniques, and it hands off naturally to the CLS Debugger, which measures the shifts from the rendering side. Fixing the CSS causes here prevents the shift; the CLS Debugger confirms it worked and catches anything driven by scripts or injection rather than styles.
10. Fix on your stack, then re-analyse
Apply the fixes in the right place for how your CSS is delivered — React and CSS-in-JS, Shopify theme styles, or WordPress themes and plugins — because each ships and bundles CSS differently, and fixing the generated output instead of the source means the problem returns on the next build. Use the css-react, css-shopify or css-wordpress fix for your environment, then re-analyse to confirm the render-blocking, unused and oversized findings have genuinely improved. Re-check whenever you add a UI library or framework, since each one ships its own stylesheet and is how unused-CSS bloat quietly creeps back in.
Tip: Order your CSS work by user impact: render-blocking and unused CSS first, because they hit Largest Contentful Paint directly, then specificity and validation for maintainability and silent-failure hunts, then prefixes and dark mode for coverage. Re-check every time you add a UI library, because each one ships its own stylesheet, and that is exactly how unused-CSS bloat creeps back after you have cleaned it out.
Tags: css-checker, howto, css-validation, render-blocking, performance
Tutorial
Search engines see your site by crawling it link to link. Site Crawler does the same — starting at your homepage, following every internal link, and cross-referencing your sitemap — to surface the structural problems a page-by-page check never catches: broken links, orphans, redirect chains, duplicate and thin content.
What a crawler actually sees
Googlebot does not read your site the way you do; it starts somewhere, follows links, and builds a map. Pages it cannot reach by following links effectively do not exist to it, no matter how good they are or how much work went into them. Site Crawler reproduces that behaviour exactly: it starts at your homepage, follows every internal link it finds, and records what it reaches and what it cannot. That reachability map is the foundation of technical SEO — it tells you which pages are genuinely part of your site and which are floating unconnected, indexable in theory but invisible in practice because nothing points at them.
The problems only a crawl can find
Some issues are invisible page-by-page and only show up across the whole graph: a link pointing at a 404, a page in your sitemap that nothing links to (an orphan), a chain of redirects that quietly wastes crawl budget, two URLs serving the same content and splitting their own ranking, a page too thin to earn a position. You cannot spot any of these by opening one page at a time, because each is a property of how pages connect rather than of any single page. You have to see the whole structure at once, and that whole-site view is precisely what a crawl produces and a manual check never can.
How Site Crawler builds the map
Give it your domain and it fetches the homepage, extracts every internal link, queues them, and repeats — breadth-first across your entire site. For each URL it records the status code, title, meta description, canonical, robots directives, word count and outbound links, so every page arrives with its full technical profile attached. Enable Include sitemap URLs and it also reads your sitemap.xml and checks every listed URL against what the crawl actually reached by following links. The difference between the two lists — URLs you declared in the sitemap but the crawl never arrived at — is your orphan list, and it is usually the single most revealing output of the whole run.
The issues it reports and the fixes behind them
The crawl surfaces the issues that each Site Crawler fix walks through: broken internal links (links pointing at 404s), redirect chains and loops, orphan pages (in the sitemap but unlinked), sitemap mismatches (listed but 404, noindex or missing), robots.txt blocks (including staging Disallow rules that leaked to production), duplicate content, thin content and slow templates. Each finding maps to a specific fix guide rather than a vague warning, so you never land on a bare this page is a problem — you go straight from the finding to a step-by-step remedy for that class of problem, which is what makes the crawl actionable rather than merely diagnostic.
Reading the results by severity
Not every finding carries the same weight. A broken link on a key landing page and a staging Disallow rule blocking your whole site are urgent; a single thin page three clicks deep is not. Work the results strictly top-down: indexability blockers first (robots blocks, mass noindex, broken canonicals), then crawl-budget waste (redirect chains, orphan sprawl), then content quality (duplicate and thin content). The tool groups findings so you can triage rather than drown, because a raw list of five hundred issues is only useful once it is ordered by what actually costs you rankings, and fixing in the wrong order means spending your best effort on your smallest problems.
Where it fits with the other tools
Site Crawler is the wide net; the specialised tools go deep. Use it to find broken links, then hand off to the HTTP Error Audit to classify the exact status codes behind them; use it to find slow templates, then the CLS Debugger and the performance tools to diagnose why they are slow; use it to find missing pages, then Robots and Sitemap Tester to correct the directives that hid them. The crawler tells you what is wrong and where it lives across the whole site; the focused tools tell you why it is happening and how to repair it on a given page. They are deliberately designed to hand off to one another rather than overlap.
What it does not do
Site Crawler is HTML-level crawling, and it is honest about that boundary. It reads what the server returns and follows what it can parse. It will not execute a fully client-side app that ships empty initial HTML (though most modern sites server-render enough of the page to be crawlable), run a Lighthouse-grade performance audit, or crawl behind a login wall where it has no session. It maps structure and surfaces issues; interpreting them and deciding what to change is still your call, guided by the fix for each finding. It maps the terrain thoroughly and reliably — it does not pretend to drive the car for you.
Tip: Run the crawl with Include sitemap URLs enabled every single time — the gap between what your sitemap claims exists and what the crawl can actually reach is where the most damaging and most invisible problems live, namely orphans and sitemap 404s. And re-crawl after any structural change — a migration, a redesign, a bulk redirect, a CMS upgrade — because that is exactly the moment broken links and orphans appear in bulk, and the sooner you catch them the cheaper they are to fix.
Tags: site-crawler, crawling, technical-seo, site-architecture, indexation, internal-links
How-To
A step-by-step walkthrough of running Site Crawler, cross-referencing your sitemap, and working the findings in the order that actually protects your rankings — indexability first, then crawl budget, then content quality — with the right fix for each class of issue.
1. Crawl from the right starting point
Enter your primary domain in its canonical form — the www-or-not, http-or-https version that users and Google actually land on. The crawler starts there and follows internal links outward, so starting from the wrong host variant skews the entire map and can make live pages look unreachable. If your site has whole sections that are not linked from the homepage — a blog on a subfolder, a separate help centre, a set of landing pages — make sure something links to them, or add them as seed URLs, otherwise the crawl will correctly report them as unreachable when the real issue is simply that nothing on your site points to them yet.
2. Turn on sitemap cross-reference
Enable Include sitemap URLs before you run anything. This makes the crawler read your sitemap.xml and compare every listed URL against what it reached by following links from the homepage. Without it you get a link graph and nothing more; with it you also get your orphan list (URLs in the sitemap that nothing links to) and your sitemap-mismatch list (URLs listed in the sitemap but returning 404 or noindex). That comparison is the single most valuable thing the crawl produces, so never skip it — the orphans it reveals are pages you are actively asking Google to index while giving it no internal path to actually find them.
3. Run it and let it complete
Start the crawl and let it finish; a partial crawl gives you a partial map and a set of false orphans, because a page only looks unreachable if the crawl never got as far as the link pointing at it. Larger sites naturally take longer as the queue works through every internal link, following each new page to the ones it links to in turn. When it completes you get a full table of every URL reached — with its status, title, canonical and the rest — plus the issues found grouped by type. That completed table is the data set you will work through in every step below, so it is worth waiting for the whole run.
4. Fix indexability blockers first
Before anything else, hunt down the problems that stop pages being indexed at all: robots.txt Disallow rules blocking whole sections (watch especially for a staging rule that leaked into the production robots file), mass noindex tags applied by a plugin or template, or broken canonicals that collapse many distinct pages onto one URL. These do the most damage of anything the crawl finds, and the robots-blocks fix walks through them in detail. A single bad Disallow line can delist an entire site overnight, so this category outranks every other finding on the list, however long that list happens to be.
5. Clear crawl-budget waste
Next, tackle the findings that make Google work harder than it should to cover your site: redirect chains and loops (A redirecting to B redirecting to C, instead of A going straight to C) and orphan sprawl. Follow the redirect-chains fix to collapse each chain down to a single clean 301, and the orphan-pages fix to either link the orphans in properly through your navigation and content, or drop them from the sitemap if they should not be indexed. On a large site this is where you recover the most crawl efficiency, because every wasted redirect hop and every dead-end orphan is budget Google is spending on plumbing instead of on discovering and refreshing your real pages.
6. Resolve broken links and sitemap mismatches
Work the broken internal links list next: each entry is a link on your own site pointing at a 404, which both wastes the link equity that link was passing and drops users onto an error page — repair the link to point at the right URL, or restore or redirect the missing target, per the broken-links fix. Then reconcile the sitemap mismatches: every sitemap URL that 404s, redirects or is noindexed should be fixed or removed, so that your sitemap advertises only live, indexable pages. A sitemap full of dead and redirecting URLs steadily erodes the trust Google places in it, which weakens it for the good URLs too.
7. Address duplicate and thin content
With the structural issues cleared, turn to content quality. Duplicate content — two URLs serving essentially the same page, splitting signals between them — gets a canonical tag or a 301 to consolidate them into one authoritative version, as covered in the duplicate-content fix. Thin pages, which carry too little substance to earn a ranking, get improved, consolidated with related pages, or pruned entirely, per the thin-content fix. These genuinely move rankings, but only once the crawlability problems above are resolved; doing them first, while a Disallow still blocks half the site, is effort spent polishing pages Google currently cannot even reach.
8. Re-crawl to confirm and set a cadence
After fixing, run the crawl again from scratch: the issues you resolved should drop off the list, and you will very often surface a second layer of problems that the first round's blockers were hiding — orphans that only became visible once a Disallow was lifted, for instance. Then make the crawl recurring rather than a one-off: crawl after every migration, redesign or bulk redirect, and on a routine schedule for any actively changing site. Broken links and orphans reappear continuously as content is added, moved and deleted, so a crawl is best treated as an ongoing habit that keeps the site clean, not a single audit you run once and forget.
Tip: The order matters more than the raw effort. Fixing thin content while a staging Disallow is quietly blocking half your site is wasted work, because Google cannot see the pages you are improving. Always clear indexability blockers first, then crawl-budget waste, then content quality — and re-crawl after each round, because fixing the top layer routinely reveals a fresh set of problems it was masking underneath, which is normal and a sign the process is working.
Tags: site-crawler, howto, crawling, technical-seo, internal-links
Tutorial
Every request to your site returns a status code, and the error ones — 404s, 500s, 502s, timeouts, SSL failures — quietly cost you rankings, crawl budget and customers. HTTP Error Audit scans your URLs, classifies every error by cause, and links each to its fix, so you work from real status data instead of guessing.
Why status codes are the site's vital signs
Every page load is a request and a response, and the response always carries a status code: 200 OK, 301 moved permanently, 404 not found, 500 server error. Users rarely notice the number itself, but Google acts on every one of them — a 404 drops a page from the index, a run of 5xx errors makes Google crawl you less often, a slow timeout looks indistinguishable from downtime. The error codes are your site's vital signs, and the uncomfortable truth is that most sites are quietly bleeding a handful without anyone knowing, because nobody is systematically watching the responses their own pages return to the outside world.
The errors that matter and what they mean
404 Not Found means the URL is gone or was mistyped — the commonest error and usually the most fixable. 403 Forbidden means the server actively refuses the request, often a permissions or bot-blocking misconfiguration. 500 Internal Server Error means your application itself broke while handling the request. 502 Bad Gateway and 504 Gateway Timeout mean your proxy or an upstream service failed or was too slow to answer. SSL/TLS errors mean the secure connection could not even be established. Timeouts mean the server took too long to respond at all. Each has a genuinely different cause and a different fix, which is exactly why lumping them together under one heading of errors is useless for actually solving them.
How the audit finds them
HTTP Error Audit requests your URLs and records the exact status each one returns, then groups the results by code and by likely cause. Rather than dumping a flat list of failures on you, it sorts errors into the categories that map directly onto fixes — client errors (4xx) separated from server errors (5xx), transient timeouts flagged apart from hard failures, connection-level SSL problems kept distinct from application problems. That classification is what turns a pile of raw failures into an actual work plan, because it lets you see at a glance whether the root of your trouble is content, permissions, the server, or the network, before you spend any time on individual URLs.
Why a fresh scan matters
Errors are not static, and treating them as though they were is a common and costly mistake. A 404 you fixed last week is genuinely gone; a deploy you shipped this morning may have introduced a fresh batch of 500s that did not exist yesterday. The audit is built to be re-run precisely so that you are always working from current data — issues you already fixed no longer clutter the list and distract you, and new ones surface the moment they appear. Working from a stale export means you spend your time chasing errors that are already resolved while remaining blind to the ones that broke since you last looked, which is arguably worse than not checking at all.
The fixes behind each code
Every error class has a dedicated fix behind it: 404s (restore, redirect or relink depending on the page's value), 403s (permissions and bot rules), 500s (application-level errors), 502 and 504 (gateway and upstream failures), SSL/TLS errors, and timeouts. On top of those there are platform-specific fixes for WordPress, Plesk/cPanel and Node/Next.js, because the very same 500 status has different root causes and different remedies depending on the stack it came from. The audit points you at the correct fix for your situation instead of handing you a generic checklist that quietly assumes every server and every framework behaves identically, which they do not.
Prioritising by impact, not just count
A single 500 on your checkout page matters more than fifty 404s scattered across retired blog posts, and any prioritisation that ignores that is broken. Weight your errors by where they sit and what they block: errors on money pages and high-traffic pages come first, then errors that block crawling across a whole section (a 5xx on a shared template), then the long tail of 404s on genuinely low-value URLs. The audit hands you the codes and their locations; the judgement of which to fix first comes from the page each error sits on, because raw error count is a vanity metric next to error location and the traffic or revenue behind it.
Where it fits with the other tools
HTTP Error Audit is the status-code specialist in the toolset. Site Crawler finds broken internal links across your whole structure; this tool then classifies the exact status codes sitting behind them so you know whether each is a 404, a redirect or a server error. Score History shows you a sudden drop in your score; this tool very often explains it, because the cause turns out to be a wave of 5xx errors from a bad deploy. So the natural workflow is to use the crawler to find where links break, this audit to diagnose the precise codes, and then the platform fix for your specific stack to repair the root cause — each tool hands the next a sharper, more specific question to answer.
Tip: Re-run the audit right after every deploy and every server change without exception — that is precisely when 500s, 502s and fresh 404s appear, and catching them the same day is the whole difference between a momentary blip and a lasting ranking loss. Remember too that a single recurring 5xx on a key template can quietly suppress an entire section of your site, so treat server errors as urgent even when the raw count next to them looks reassuringly small.
Tags: http-error-audit, http-errors, status-codes, technical-seo, crawlability, server-errors
How-To
A walkthrough of scanning for HTTP errors, sorting them by severity, and fixing them in the order that protects rankings — server errors first, then classified 404s, then the long tail — using the right fix for your specific platform.
1. Run a fresh scan
Open HTTP Error Audit and scan your site now, so you are working from current status data rather than a stale list from a previous session. Issues you have already fixed will have dropped off, and anything that broke since — very often from a recent deploy — will show up. Always begin from a fresh scan, because acting on old data means simultaneously chasing errors that are already gone and missing the ones that just appeared, which defeats the entire purpose of auditing. The scan takes moments and it resets your picture to reality before you invest any effort.
2. Split client errors from server errors
Sort the results into 4xx (client) and 5xx (server) before doing anything else, because the two groups mean fundamentally different things and demand different responses. 4xx errors like 404 and 403 are about a specific URL or its permissions, and are usually a per-page fix. 5xx errors like 500, 502 and 504 are about your application or server, and are often a single root cause breaking many pages at once. Server errors are almost always the more urgent bucket to open first, because one broken template or one failing dependency can throw a 500 on thousands of URLs simultaneously, which no amount of per-page work would ever catch.
3. Attack 5xx errors first
Take the 5xx list first and work it hard. A 500 means your application threw an unhandled error while building the page; a 502 or 504 means your proxy or an upstream service failed outright or timed out waiting. These very frequently share one underlying cause — a bad deploy, an out-of-memory worker process, a database that has fallen over — so identifying and fixing that single root often clears dozens or hundreds of errors at once. Use the 500-errors and 502/504 fixes for the diagnosis, and the platform fix for your particular stack (WordPress, Plesk/cPanel, Node/Next.js) to target where the failure genuinely originates rather than where it happens to surface.
4. Classify your 404s by cause before fixing
Not all 404s deserve the same treatment, and handling them uniformly is a mistake. A 404 on a URL that still has inbound links or residual traffic deserves a 301 redirect to the right replacement page, so that equity and visitors are preserved. A 404 from a genuinely retired page with no links and no traffic can be left to return 404 honestly, which is the correct signal to send. Sort them into two piles — had-value (redirect or restore) and never-mattered (leave alone) — and act accordingly. Blanket-redirecting every 404 to the homepage is a well-known mistake that Google treats as a soft 404 anyway, so the 404-errors fix covers the correct, case-by-case approach in full.
5. Resolve 403s and SSL/TLS errors
Work through the 403 Forbidden list next: these are usually caused either by a permissions misconfiguration on the server or by an over-aggressive bot or WAF rule that is blocking legitimate crawlers along with the bad ones, and the 403-errors fix walks through diagnosing both. Then turn to any SSL/TLS errors, which are more serious because they break the secure connection entirely, scaring off users and search engines alike before a single byte of your page is served; the ssl-errors fix covers the certificate and configuration causes behind them. A blocked crawler and a broken certificate both have the same practical effect — they make otherwise perfect pages effectively invisible.
6. Chase down timeouts
Timeout errors mean the server took too long to respond at all — the usual culprits are slow database queries, overloaded worker processes, or an upstream dependency that hangs waiting on something else. Timeouts look intermittent, which makes them tempting to ignore, but Google reads a timeout as a sign of an unreliable site and responds by crawling you less. Use the timeout-errors fix to trace the actual slow path — a heavy query, an external API call that stalls, an under-resourced server that buckles under load — rather than treating the symptom and hoping it stops recurring on its own, because it will not.
7. Fix on the right layer for your stack
The same status code has genuinely different root causes on different platforms, so once you have identified the code, jump straight to the platform fix for your environment: WordPress (plugin conflicts, theme errors, htaccess rules), Plesk/cPanel (server and vhost configuration), or Node/Next.js (edge functions, middleware, the build step). Fixing a 500 in the wrong layer wastes hours, because the symptom you see is rarely where the error is actually generated — the platform-specific guides exist precisely to point you at where the failure lives on your stack rather than where it happens to first become visible to a visitor.
8. Re-scan and make it routine
After fixing, scan again to confirm the errors you addressed are genuinely gone and to catch any new ones your fixes may have introduced along the way. Then set a cadence: scan after every deploy and every server change, and on a routine schedule for busy, frequently-updated sites. Server errors in particular reappear with each release, so a one-off clean-up decays surprisingly fast without a standing habit of re-scanning. The sites that stay reliably clean are simply the ones that make checking their status codes an automatic part of shipping, rather than something they remember to do only after rankings have already slipped.
Tip: Weight every error by the page it sits on, never by how many of them there are. Fifty 404s on long-dead blog posts matter far less than one 500 on checkout or one 5xx recurring across a shared template. Clear server errors on money and high-traffic pages first, then work steadily outward — and always re-scan straight after a deploy, because that is the single most common moment for a fresh batch of errors to land.
Tags: http-error-audit, howto, status-codes, server-errors, crawlability
Tutorial
Cumulative Layout Shift is the Core Web Vital that measures how much your page jumps around as it loads — and it is the one most sites fail without realising. The CLS Debugger finds what is shifting and why (images without dimensions, late fonts, injected ads and banners) and links each cause to its fix.
What CLS actually measures
CLS scores how much visible content moves around unexpectedly while a page loads. You have felt it as a user: you go to tap a button and an image loads in above it, pushing everything down, and you tap an ad by mistake. Google measures exactly that instability across the whole load and turns it into a score; a poor CLS drags down your Core Web Vitals and, through them, your rankings and your users' basic trust in the page. Unlike the speed-focused metrics, CLS is about visual stability rather than raw load time — which means a page can load quickly and still fail CLS badly, because a fast page that jumps under your finger is still a frustrating page to use.
Why CLS is so easy to fail by accident
CLS is insidious precisely because everything looks perfect on your own fast development machine with every asset already cached. On a real user's connection it is a different page: images arrive late and push content down, web fonts swap in and reflow the text, an ad slot suddenly fills, a cookie banner drops in at the top. Each of those shoves the layout, and you never witness it because you are not loading the page cold on a throttled phone the way a first-time visitor does. That gap between how the page looks to you and how it actually behaves for someone arriving fresh is the entire reason the CLS Debugger exists, and it is why so many teams are genuinely surprised to learn they are failing.
The specific culprits it identifies
Layout shift has a fairly short list of usual suspects, and the CLS Debugger maps onto each one directly: images without width and height attributes, web fonts that swap and reflow the text around them, ad slots with no reserved space, cookie and notification banners injected at the top of the page, dynamically inserted content that arrives after first paint, video embeds, and components that mount late during hydration. Each of these has its own dedicated fix, ranging from reserving the space up front to setting explicit dimensions, so a finding from the debugger is never a dead-end your page shifts with no next step — it always points at a concrete, known cause with a concrete remedy.
How the debugger pins down the shift
Rather than handing you a single opaque CLS number and leaving you to guess, the debugger identifies which elements are actually moving and attributes each shift to its cause. That distinction matters enormously, because the fix is completely different depending on the source: images need dimensions or an aspect-ratio, fonts need a stable fallback and a size-adjust value, injected elements need their space reserved in advance. Knowing which of these is responsible for a given shift is genuinely most of the battle, because once you know the cause the remedy is usually short and mechanical — the hard part was ever only working out where the movement was coming from in the first place.
The fixes behind each cause
Every culprit the debugger identifies has its own fix behind it: images (width and height attributes plus a CSS aspect-ratio), fonts (preload plus size-adjust to eliminate the swap reflow), ads (reserving the slot's dimensions before it fills), banners (overlaying them instead of pushing content, or reserving their height), dynamic content, video embeds, and responsive-layout shifts that only appear at certain breakpoints. On top of those there are stack-specific fixes for React/Next.js (hydration shift as the client takes over), Shopify (theme code, apps and cart drawers) and WordPress (ad and plugin injection), because each of those frameworks introduces its own patterns of shift that the generic fixes do not fully cover on their own.
Chasing the biggest shift first
Not all shifts weigh the same in the score, and understanding that changes how you work. CLS is cumulative and weighted by both how far an element moved and how much of the viewport that movement affected, so a single large shift can dominate the entire metric. Fix the largest contributor first — very often a hero image with no dimensions, or a banner that shoves the whole page down as it drops in — before you touch any of the small ones. The debugger explicitly shows you which shift contributes most, so you can spend your effort exactly where the score will actually move, rather than diligently tidying a dozen tiny shifts while the one big one keeps the whole metric stuck in the red.
Where it fits with the other tools
The CLS Debugger owns one of the three Core Web Vitals, and it slots neatly alongside the rest of the toolset. Use it right after Site Crawler flags a slow or shifting template, to diagnose that instability specifically, and use it in parallel with the broader performance tools that handle load speed (LCP) and interactivity (INP), which are the other two Vitals. CLS is frequently the cheapest of the three to fix — reserving space for an element costs nothing at all at runtime — which makes it the single fastest page-experience win available on most sites, and therefore a sensible first target whenever your Core Web Vitals need attention and you want an early, visible improvement.
Tip: Always test cold and throttled rather than warm. CLS hides completely on a fast, cached reload and only reveals itself on a real user's first visit over a slow connection, so you have to reproduce those conditions to see the real problem. Then fix the single biggest shift first — usually a dimensionless hero image or a banner that pushes content down — because CLS is weighted, and one large shift reliably outweighs a whole dozen tiny ones combined.
Tags: cls-debugger, cls, core-web-vitals, layout-shift, performance, page-experience
How-To
A walkthrough of running the CLS Debugger on a cold load, reading shifts by their contribution to the score, and clearing each cause in turn — images, fonts, slots, injected elements and framework-specific shift — then locking the result against regression.
1. Run the debugger on a cold load
Open the CLS Debugger and run a fresh scan of the page, because CLS only appears on a cold, uncached load over a realistic connection — the exact conditions a first-time visitor experiences and the exact conditions you never see during normal development. A warm reload with everything already cached hides the problem entirely and will tell you the page is fine when it is not. Start fresh every time, so that the shifts the tool reports back to you are genuinely the ones your real users are living with, rather than the flattering zero-shift version your own browser shows you.
2. Read the shifts by contribution, not count
The debugger attributes the page's total CLS to specific elements, and the right way to read that list is by how much each element contributes to the score, not by how many entries there are. CLS is weighted by shift distance and by the fraction of the viewport affected, so one big mover can be your entire problem while ten tiny ones are collectively irrelevant. Identify the single largest contributor first and start there — that is where the score actually lives, and fixing that one element often drops you from a failing grade to a passing one in a single change, before you have touched anything else.
3. Give every image explicit dimensions
The most common cause of layout shift by far is images without width and height, because the browser reserves no space for them and everything below jumps down the moment they finally load. Add width and height attributes, or a CSS aspect-ratio, to every image on the page — especially the hero image and anything else above the fold, where a shift is most visible and most costly. This one change clears a large share of CLS on most sites all by itself. The images fix additionally covers the trickier responsive and art-directed cases, where the exact dimensions vary by breakpoint but the reserved aspect ratio can still be pinned down.
4. Stabilise web fonts
Fonts cause shift when a fallback font renders first and the real web font then swaps in at a slightly different size, reflowing all the text around it. The fix is to preload the key font so it arrives sooner, and to use size-adjust and font-metric overrides so that the fallback font occupies exactly the same space as the final one, which removes the reflow entirely. The fonts fix walks through the swap-versus-block trade-off in loading strategy and the metric-matching approach in detail, because the difference between a font that loads invisibly and one that visibly shoves your whole article down the page comes down to getting these few properties right.
5. Reserve space for ads, embeds and video
Anything that loads into a slot on the page — an ad unit, a video embed, a third-party widget — must have its space reserved before it arrives, or it will shove the surrounding content aside the instant it fills. Set explicit dimensions on the container element so that the slot holds its correct size even while it is still empty and waiting. The ads, video-embeds and components fixes cover both reserving space and the awkward case of slots whose final size is not known until they actually fill, which is common with responsive ad units and is worth handling deliberately rather than leaving to chance.
6. Fix injected banners and dynamic content
Cookie notices, promotional bars and other injected elements cause shift when they drop into the top of the page and push everything already rendered downward. The fix is either to overlay them using fixed or absolute positioning so they sit above the content without displacing it, or to reserve their height in advance so the space is already accounted for. The banner and dynamic-content fixes cover both patterns in detail. The underlying rule is simple and worth internalising: anything that appears after first paint must not reflow what is already on screen, or it will count against your CLS on every single page load, forever.
7. Handle your stack's specific shift sources
Frameworks introduce their own distinctive sources of CLS that the generic fixes do not fully address. React and Next.js can reflow during hydration as the client-side JavaScript takes over from the server-rendered markup; Shopify themes shift as apps inject content and cart drawers open; WordPress shifts from ad and plugin injection happening late in the load. Jump to the fix for your specific stack (React/Next.js, Shopify, or WordPress), because these target the shift patterns unique to each platform, and they are very often exactly where the last stubborn bit of CLS is hiding after you have already handled the obvious images and fonts.
8. Re-test cold and lock it in
After fixing, re-run the debugger on another cold load to confirm the shifts you targeted are genuinely gone and the score has actually dropped, rather than assuming the fix worked. Then guard against regression, which is the part most teams neglect: CLS quietly creeps back every time someone adds an image without dimensions or drops in a new third-party script, so re-test after any content or template change and after adding any embed or tracking tag. A passing CLS is a state you actively maintain over time, not a box you tick once and can then forget about while the page keeps evolving underneath you.
Tip: Reserving space is completely free at runtime — it costs nothing to render and it is comfortably the highest-leverage CLS fix available — so make it your default to give every image, ad, embed and injected element explicit dimensions up front. Always verify your work on a cold, throttled load rather than a warm one, and fix the largest contributor first, because CLS is weighted and one big shift can single-handedly be your whole failing score.
Tags: cls-debugger, howto, cls, core-web-vitals, layout-shift
Tutorial
A passive, no-login scan that checks 29 real security signals — headers, TLS, cookies, mixed content, mail DNS, known-CVE exposure and more — and scores your site out of 100. Where free header checkers stop at a handful of headers, this goes deeper and links every failing check to the fix for your platform.
Why a security score, and why passive
Most site owners have genuinely no idea whether their site is hardened or wide open, and the tools that could tell them are scattered, technical and intimidating. The Security Audit collapses all of that into one score out of 100 from a single passive scan — no login, no agent to install, nothing running on your server. It simply requests your site the way any ordinary visitor would, reads the response headers that come back, inspects the TLS certificate, and checks your public DNS records, then scores everything it finds. It is safe by design because it only observes what is already public, and yet that passive view is enough to catch the overwhelming majority of issues that actually matter to real-world security.
What it actually checks — 29 signals, not just headers
Free checkers look at a handful of response headers and stop there. This audit runs 29 distinct checks spread across several categories: Content-Security-Policy and its individual sub-parts, HSTS, X-Frame-Options, Referrer-Policy and Permissions-Policy, CORP and CORS; TLS certificate validity, expiry date and protocol version; cookie Secure, HttpOnly and SameSite flags; mixed content; subresource integrity; known-CVE exposure; server-version and CMS fingerprint leakage; and mail DNS in the form of SPF, DMARC and DKIM. Breadth is the entire point of the exercise, because real security is never a single header — it is a stack of independent layers, any one of which being weak can undermine the rest.
How the score is built
The score starts at 100 and each failing check subtracts a weight set according to its real severity, with a warning costing half of a full failure. A missing HSTS header or an expired certificate therefore costs far more than a leaked server-version string, exactly as it should. Crucially, passing checks do not dilute the score — every scored row corresponds to a genuine check — so a partial fix moves the number honestly and proportionally rather than being lost in the noise. The result is a score that reflects genuine, weighted risk, rather than the misleading tally you get from tools where a trivial finding and a critical one each count as a single equal tick.
The depth free tools do not reach
Three things in particular set this apart from securityheaders.com, Mozilla Observatory and SSL Labs. It checks TLS certificate validity and expiry, not merely whether HTTPS is present. It flags known-CVE exposure inferred from the server and CMS fingerprint, which no header scanner attempts. And it checks mail authentication — SPF, DMARC and DKIM — in the very same scan. In practice it is a strict superset of Observatory's header checks plus CORS and CORP, and then it adds an entire additional layer covering certificates, CVEs, cookies, mixed content and mail that header-only tools simply never look at. One scan here replaces several separate tools elsewhere, and closes the gaps between them.
The findings and the fixes behind them
Every failing check links directly to a fix, and the fixes are deliberately platform-specific rather than generic. The same missing CSP header is added quite differently in bare nginx, Apache, Plesk, cPanel, Cloudflare, WordPress, Ghost, Drupal, Joomla and forum software, so you are never handed a vague add a CSP header and left to work out where and how on your particular setup. Instead each check's Learn how link carries you to the exact directive and the exact place to put it for your stack. That specificity is the difference between an audit that merely tells you what is wrong and one that actually gets you from a failing check to a fixed one without a research detour.
What a good result looks like — and what to keep
A genuinely strong score comes from a real Content-Security-Policy, HSTS with a long max-age, X-Frame-Options or frame-ancestors set, a valid modern-TLS certificate, cookies carrying the right flags, no mixed content anywhere, and aligned mail DNS. It is also worth knowing that some residual findings are perfectly acceptable to keep by deliberate decision — an unavoidable unsafe-inline on a static architecture, for instance, where robust output escaping is already the real line of defence and a full nonce system would require rebuilding the whole render layer. The audit tells you the risk of each finding; you make the call on which are worth the engineering to close, rather than chasing a perfect 100 for its own sake.
Where the CVE check needs judgement
One honest caveat lives in the CVE check specifically. Since NVD scaled back its universal enrichment in 2026, a missing CVSS score no longer reliably means low risk, and automated version-matching against the national database has degraded as a result. The audit surfaces known-CVE exposure as a useful signal, but the right way to treat it is as a prompt to go and check current advisories — from the vendor, from CISA, from OSV — rather than as an all-clear on the occasions when nothing shows up. Security data ages faster than most people assume, so the scan is best understood as an informed starting point and a prompt to investigate, never as a certificate guaranteeing that you are safe.
Tip: Always verify your result logged-out and in an incognito window, because a signed-in session can replay a cached scan and quietly hide a fresh failure from you. Chase the high-weight checks first: HSTS, a valid certificate, a real CSP and the elimination of mixed content will move the score far more than trimming a server-version header ever could. And re-scan after any server or certificate change, because TLS expiry and header regressions are the quiet failures that slip back in unnoticed.
Tags: security-audit, security-headers, https, tls, web-security, hardening
How-To
A walkthrough of scanning your site, reading the score as weighted risk, and hardening in order of impact — transport and HSTS first, then CSP, then cookies and mixed content, down to header leaks and mail DNS — using the right fix for each platform.
1. Scan logged-out, in incognito
Run the audit against your live domain from an incognito window, deliberately not from a signed-in session, because a logged-in path can replay a cached result and mask a failure that is currently live. A clean, uncached scan is what gives you the true 29-check picture and the real score to work from. Getting this first step wrong quietly poisons everything that follows — you end up either fixing phantom problems that were never really there, or missing genuine ones the cache hid — so make a properly cold, logged-out scan the very first move every single time you audit.
2. Read the score as weighted risk
The number out of 100 is not a simple tally of how many checks passed; it starts at 100 and subtracts each failure's severity weight, with warnings costing half. That means a low score almost always comes from a few heavy failures rather than from many light ones, which completely changes how you should approach it. Sort the failing checks by their weight and you immediately have your work order — the heavy failures are the ones that will actually move the score, while the cosmetic ones barely register. Chasing the cosmetic findings first is one of the most common ways people waste an afternoon and come away wondering why the number barely budged.
3. Fix transport first — HSTS and TLS
Start with the transport layer, because everything else is built on top of it. You want a valid, unexpired certificate running on a modern TLS protocol version, HTTPS properly enforced across the site, and an HSTS header carrying a long max-age — two years is the standard recommendation, and the HTTP-to-HTTPS redirect must stay on the same host or HSTS never actually gets set on that first hop. An expired certificate and a missing HSTS header are among the very heaviest failures the audit can report, so clear these before you touch anything else, because leaving them in place undermines the value of every other header you might add on top.
4. Add the Content-Security-Policy
CSP is the single highest-value header, and the audit scores its individual parts separately — inline script, eval, inline style, default-src — which means partial progress genuinely counts toward your score rather than being all-or-nothing. Add a real policy for your stack using the platform fix, since nginx, Apache, Plesk, Cloudflare, WordPress and the rest each set headers in their own way. If a fully strict script-src is not yet achievable on your architecture, still set default-src, frame-ancestors and base-uri, which are cheap to add, do not inherit from default-src the way people assume, and each carry real weight in the score on their own.
5. Secure cookies and kill mixed content
Set the Secure, HttpOnly and SameSite flags on your cookies, and consider adopting the __Host- and __Secure- name prefixes for the strongest guarantees. In parallel, eliminate mixed content — any http asset loaded on an https page — which modern browsers block outright and the audit penalises. Both of these are quick wins that carry real weight: mixed content quietly undermines the entire HTTPS story you are trying to tell, and cookies without the right flags are a direct session-theft risk. Neither takes long to fix relative to how much they matter, which makes them ideal to knock out early once transport and CSP are handled.
6. Close the remaining header gaps
Now work through the lighter but genuinely easy headers, several of which together add up to a meaningful score improvement. Set X-Frame-Options or frame-ancestors against clickjacking, X-Content-Type-Options against MIME sniffing, Referrer-Policy, Permissions-Policy, CORP, and a non-wildcard CORS policy, plus subresource integrity on any third-party scripts — remembering that without crossorigin, integrity fails open and silently does nothing at all. Each of these is a small individual weight, but they accumulate, and the platform fix hands you the exact lines to add, so none of them takes more than a few minutes once you know where your stack sets its headers.
7. Fix the leaks and check mail DNS
Remove server-version and X-Powered-By disclosure and reduce your CMS fingerprint where you can — small weights individually, but easy hardening that also makes automated attacks marginally less convenient. Then check mail authentication, which is invisible on the page but very much part of the audit: SPF (exactly one record, staying within both the ten-DNS-lookup and the two-void-lookup limits), DMARC (aligned and set to enforce), and DKIM. Broken SPF or DMARC quietly fails your legitimate email at Gmail and Yahoo under their bulk-sender rules, which turns a security finding into a direct business problem the moment your mail stops being delivered.
8. Re-scan, and treat CVE findings as prompts
Re-scan logged-out to confirm each fix actually registered and the score moved in response, rather than assuming your changes took effect. Treat any known-CVE finding as a prompt to go and check current advisories rather than as a final verdict — because with NVD enrichment reduced since 2026, the absence of a CVSS score is emphatically not the same as safety, and a genuine exposure can hide behind a blank. Then re-scan after every server, certificate or DNS change going forward, because TLS expiry and header regressions are precisely the failures that reappear silently in the gap between one audit and the next, when nobody is looking.
Tip: Two heavy wins beat ten cosmetic ones every time — a valid certificate, HSTS, a real CSP and no mixed content will lift the score far more than trimming server headers ever will, so do them first and in that order. Always re-scan logged-out after making changes, and diarise your certificate expiry date somewhere you will actually see it, because a lapsed certificate is simultaneously one of the heaviest single failures and one of the easiest to never notice until it is too late.
Tags: security-audit, howto, security-headers, tls, hardening
Tutorial
A single audit tells you today's score; Score History tells you the direction and speed of change — which is what actually matters. It stores every audit, charts the trend, and helps you separate a real regression from seasonal noise, catch sudden drops from a bad deploy, and prove the impact of your fixes.
Why the trend beats the snapshot
A score of 82 means almost nothing on its own. 82 and climbing steadily from 70 is a success story worth celebrating; 82 and falling from 95 is a fire that needs putting out. The absolute number matters far less than its direction and the speed at which it is moving. Score History keeps every audit you have ever run and plots them as a line, so that you stop reacting to isolated snapshots and start managing a trajectory instead. That is how SEO genuinely behaves in the first place — as a slow accumulation of gains and losses over time — rather than as a single fixed state you can read off once and be done with.
The patterns it reveals
Plotted over time, your scores start to tell stories that no single audit ever could. A sudden sharp drop means a deploy broke something overnight. Gradual erosion means slow decay as content ages and small issues quietly accumulate. A plateau means the low-hanging fruit is gone and you have stalled. Seasonal variance means predictable dips that are not real problems at all. Benchmark drift means the scoring standard itself moved rather than your site. And score-versus-traffic divergence means the two signals have come apart — score up while traffic falls, or the reverse. Each of these is a genuinely different situation that calls for a genuinely different response, and the shape of the line is what tells them apart.
How it builds the history
Every audit you run is stored as a complete datapoint — not just the headline score but the full set of underlying checks behind it — so the history is a rich record you can drill down into, rather than a bare line of numbers. Run your audits on a regular cadence and the trend becomes genuinely meaningful; the longer the series grows, the more each individual point is worth, because you can compare like with like across weeks and months and pinpoint exactly which check moved and when it moved. A dense, consistent history is an asset that compounds in value over time, which is a good reason to start keeping one sooner rather than later.
Telling a real regression from noise
The single hardest judgement in any kind of monitoring is deciding whether a given dip is actually a problem or just noise. Score History makes that judgement tractable by separating the patterns for you. A sudden sharp drop that lines up with a deploy is a real regression to fix immediately, covered by the sudden-drop fix. A small dip that recurs at the same point every year is seasonal noise to recognise and leave alone, covered by the seasonal fix. A slow, steady slide with no seasonal pattern is erosion to arrest, covered by the gradual-erosion fix. Reacting to noise wastes real effort, while missing a genuine regression costs you rankings — and the trend line is exactly what lets you reliably tell the two apart.
The fixes behind each pattern
Each trend pattern maps onto a specific fix, so the tool never leaves you merely diagnosing without a remedy. Sudden drops send you to finding the breaking deploy and rolling it back or patching it. Gradual erosion sends you to auditing what decayed and refreshing it. A score plateau sends you to the next tier of harder fixes. A regression sends you to isolating precisely what changed. Seasonal variance sends you to recognising and safely ignoring it. Benchmark drift tells you the scoring standard changed rather than your site. And score-versus-traffic divergence sends you to reconciling the two signals. The tool identifies the shape of the change; the matching fix tells you what to actually do about that specific shape.
Proving that your work worked
Score History doubles as your evidence base. When you ship a batch of fixes, the trend line becomes the proof that they moved the number, which is invaluable when you need to justify SEO work to a client or a manager who quite reasonably wants to see measurable impact rather than a list of activity. A clean before-and-after on the chart, with the inflection point tied to the exact date you deployed, turns a weak I did some fixes into a strong here is the measurable result they produced. That is a far more defensible position to argue a budget, a retainer or your own priorities from, and it is a side benefit of consistent monitoring that people routinely underestimate until they need it.
Where it fits with the other tools
Score History is the meta-layer sitting above every other audit in the toolset — its entire job is to track the score those other tools produce over time. When it flags a sudden drop, the HTTP Error Audit very often explains the cause (a wave of 5xx errors from a deploy) while Site Crawler locates the structural damage. When it shows a stubborn plateau, the Security Audit and the other deeper tools are where the next tier of gains is waiting to be found. So it tells you when something changed and by how much; the specialised tools then tell you what specifically changed and why. It is effectively the dashboard that all the others quietly report into.
Tip: Audit on a fixed cadence — the same interval, and ideally the same day each time — so that your history compares like with like and a genuine change stands out clearly from the background noise. When the line drops, check its date against your deploy log before doing anything else: the great majority of sudden drops trace back to a specific release, and lining the two up is the fastest possible route from a frightening chart to the exact underlying cause.
Tags: score-history, trends, monitoring, seo-tracking, regression, benchmarking
How-To
A walkthrough of setting a baseline and cadence, reading the shape of the trend line, and responding correctly to each pattern — sudden drops, seasonal dips, erosion, plateaus, divergence and benchmark drift — while using the trend itself as proof your fixes worked.
1. Establish a baseline and a cadence
Run your first audit to set a clear baseline, then commit to a fixed cadence going forward — weekly is a sensible default for most sites — and ideally run it on the same day each time. Score History stores every run you make, but the history is only genuinely comparable if the interval between runs is consistent. An erratic, whenever-you-remember schedule gives you a scatter of dots you cannot line up or reason about, whereas a steady rhythm gives you a real trend you can trust and act on with confidence. The discipline of a fixed cadence is what turns raw audits into usable intelligence.
2. Read the shape of the line, not the last point
When you open the history, deliberately look at the overall shape of the line before you even register the latest number. Is it climbing, holding flat, sliding slowly downward, or sharply broken? The shape tells you which situation you are actually in — progress, plateau, erosion or regression — and that diagnosis determines everything you do next. A single low point sitting on a rising line means something completely different from the very same point sitting on a falling one, and reacting to the number in isolation, without the shape around it, routinely leads people to draw exactly the wrong conclusion.
3. Diagnose a sudden drop against your deploy log
If the line drops sharply between two consecutive runs, treat it as a regression and move fast to find the cause. Line the date of the drop up against your deploy and change log — the great majority of sudden drops trace directly back to a specific release, a plugin update, a DNS change or a certificate that lapsed. The sudden-drop fix walks through isolating the breaking change methodically so that you can roll it back or patch it, rather than guessing blindly at what might have moved. Matching the drop to a dated change in your own records is almost always the quickest path to the answer.
4. Separate seasonal dips from real erosion
Resist the urge to treat every decline as an emergency, because not all of them are. If a dip recurs at roughly the same time each year, it is seasonal variance — recognise it for what it is and leave it alone, as the seasonal fix advises. If instead the line is sliding steadily downward with no seasonal pattern to explain it, that is gradual erosion — content aging, small issues quietly accumulating — and it genuinely needs arresting through the gradual-erosion fix. Confusing the two is costly in both directions: chasing a seasonal dip wastes effort on a non-problem, while dismissing real erosion as seasonal lets a slow bleed continue unchecked.
5. Break through a plateau
A flat line usually means you have already fixed all the easy things and have now stalled. Breaking through a plateau requires moving to the next, harder tier of work — the deeper Security Audit findings, structural crawl fixes, genuine performance work — rather than re-doing the straightforward wins you have already banked. The score-plateau fix covers where the next gains actually hide once the obvious ones are gone. Framed this way, a stalled score stops being a discouraging dead end and becomes a clear prompt to go deeper into the parts of the audit you had been putting off because they were more involved.
6. Reconcile score-versus-traffic divergence
Keep an eye out for the score and your organic traffic pulling apart from each other: the score climbing while traffic falls, or traffic rising while the score quietly slips. Divergence like this means the score is measuring something your traffic is not tracking, or vice versa — an algorithm shift, a content-quality issue the technical audit cannot see, or a technical problem you have fixed but whose traffic recovery simply lags behind. The divergence fix helps you reconcile the two signals so that you act on the one that genuinely matters for the situation, rather than reassuring yourself with a rising score while the traffic that pays the bills heads the other way.
7. Check for benchmark drift before you panic
If the whole score shifts without you having changed anything on the site, the standard itself may well have moved rather than your site — a new check being added, an existing one being re-weighted, a threshold being tightened. This is benchmark drift, and it is worth ruling out before you launch into chasing a regression that does not really exist. Confirm that the scoring basis is the same as it was last time before you react. The benchmark-drift fix explains how to distinguish a change in the standard from a change in your site, which can save you a great deal of effort fixing something that was never actually broken in the first place.
8. Use the trend as proof and keep auditing
After you ship a set of fixes, let the next few audits land and then use the resulting upward move as concrete evidence that the work paid off — tie the inflection point cleanly to your deploy date to produce a persuasive before-and-after story. Then, crucially, keep the cadence going rather than stopping once things look healthy: the value of the history compounds the longer it runs, and the denser and longer your series becomes, the faster and more confidently you will spot and diagnose the next change when it inevitably comes. Consistent monitoring is what makes every future diagnosis quicker than the last.
Tip: Whenever the line moves, check its date against your own change log before anything else — deploys, plugin updates, DNS changes and certificate changes are behind the great majority of sudden shifts, and matching the two is the quickest path from a worrying chart to the actual cause. And keep the cadence rigidly fixed, because an inconsistent history hides precisely the changes you most need to be able to see.
Tags: score-history, howto, trends, monitoring, regression
How-To
A walkthrough of running the AI Schema Generator on a page, verifying what it actually detected, fixing the fields it most often gets wrong, and validating before you ship — so the JSON-LD is accurate, not just present.
1. Run the generator on the exact URL you want marked up
Open the AI Schema Generator and paste the live, canonical URL of the page you want to mark up — not your homepage, not a staging copy. The tool fetches the rendered page and reads what is actually on it: headings, author byline, dates, product details, FAQs. Instead of making you pick a type and fill fields by hand, it produces complete JSON-LD from the real content. Run it on the canonical URL so the schema it emits matches the page Google indexes. If the page is JavaScript-rendered, confirm the content you expect appears in the output — the generator can only mark up what it can see.
2. Check the detected @type before anything else
The most consequential thing the generator decides is the @type, so it is the first thing to check. Article vs BlogPosting vs NewsArticle, Product vs SoftwareApplication, Person vs Organization — the type changes how AI engines and Google Rich Results treat the page, and the required fields all hang off it. Confirm the type matches what the page really is; a blog post emitted as a bare WebPage earns nothing. If it is wrong, correct it now (the pick-the-right-@type fix) before you touch any other field, because everything below depends on it.
3. Verify the facts — this is the hard limit
Auto-detection produces confident JSON-LD, but the tool knows only whether a value is present, not whether it is true. Read every value it filled in: author spelled correctly, price and currency right, ratings real rather than invented, dates matching the actual publish and update. This is exactly where AI generation goes wrong — a plausible-but-wrong value ships silently. If you spot a fabricated or guessed fact (the hallucination failure), fix it at source. Schema that asserts false facts about your brand, product or people is worse than no schema at all.
4. Fill the required fields AI engines rely on
For citation, AI search leans on a small set of fields: author, datePublished, dateModified and mainEntity. If any are missing, add them (the required-fields fix). Make author a Person or Organization entity rather than a bare string; the two dates drive freshness signals; mainEntity tells the engine what the page is actually about. A block that validates but omits these gives an AI engine nothing solid to hang a citation on, which defeats the point of generating it.
5. Fix dates and URLs — the two fields it most often gets wrong
Two recurring problems deserve a direct look. Dates must be ISO 8601 with a timezone offset, and AI generators sometimes stamp a training-cutoff year or drop the offset entirely — check datePublished and dateModified against the real page. URLs must be absolute and resolve to a clean 200: relative paths, broken links, or a URL that disagrees with your canonical all weaken the markup. Convert any relative URL to absolute and confirm each one loads. These are the AI-schema-dates and AI-schema-URLs fixes, and both are common straight out of the generator.
6. De-duplicate against schema you already output
The most common live problem is duplicate JSON-LD: the generator's block plus a block your SEO plugin (Yoast, Rank Math) or theme already emits. Two competing Article or Product blocks cannibalise each other and confuse parsers. Pick one source of truth — either consolidate everything into a single @graph block or disable the redundant emitter — so the page ships one coherent set of entities. This duplicate fix is almost always needed on WordPress and Shopify, where a plugin is usually already producing schema of its own.
7. Close the graph with real entity references
Isolated blocks are weaker than linked ones. Convert string authors into Person entities, give your Organization a stable @id, and reference them by @id from your Article, Product and other blocks so the entities point at each other. A closed graph — author, publisher and organisation as real linked entities rather than loose strings — is what gives AI engines the authorship and credibility signals they actually use. This is the entity-references fix, and it is what turns valid-but-flat markup into something an engine can trust.
8. Validate and check Rich Results eligibility before shipping
Do not ship on the generator's word alone. Paste the output into the Schema Debugger to confirm it parses and is valid against schema.org, then check Rich Snippets for Google Rich Results eligibility — schema can be schema.org-valid yet fail Rich Results because it lacks recommended properties like image, aggregateRating or offers. Add those where they apply, re-validate, then place the final JSON-LD on the page or let your plugin output it. Re-run the generator whenever the page content changes so the markup stays in sync with what is actually there.
Tip: The generator's speed is its risk: it produces complete, confident JSON-LD in seconds, so the discipline is in the checking, not the making. Spend your time on steps 3 to 6 — accuracy, required fields, dates and URLs, and de-duplication — because that is where AI-generated schema silently goes wrong. Validate in the Schema Debugger before every ship.
Tags: ai-schema, howto, json-ld, structured-data, schema, validation
Fix
Fix 302 where 301 should be from the Redirect Checker. Pick permanent vs temporary correctly, preserve link equity, fix server configs across platforms.
Redirect Checker scan
Findings group by status code. 302s on permanent paths (post URLs, category URLs, marketing pages) are the priority targets.
curl verification
curl -I https://example.com/old-page # Look for: # HTTP/2 301 → permanent (correct for moves) # HTTP/2 302 → temporary (verify intent) # HTTP/2 307 → strict temporary # HTTP/2 308 → strict permanent # .htaccess # Wrong (default Redirect directive defaults to 302) Redirect /old-page /new-page # Right — explicit 301 Redirect 301 /old-page /new-page # Or with RewriteRule RewriteEngine On RewriteRule ^old-page/?$ /new-page [R=301,L] # WRONG (R alone defaults to 302) RewriteRule ^old-page/?$ /new-page [R,L] server { # Wrong: default is 302 rewrite ^/old-page$ /new-page; # Right: permanent flag → 301 rewrite ^/old-page$ /new-page permanent; # Or with return (preferred for simple redirects) location = /old-page { return 301 /new-page; } } Plugin → Redirection → Redirects → edit each rule: HTTP code: 301 Moved Permanently (was 302) add_action('template_redirect', function() { if (is_page('old-slug')) { wp_redirect(home_url('/new-slug/'), 301); // explicit 301 exit; } }); // Wrong: default is 302 app.get('/old-page', (req, res) => { res.redirect('/new-page'); }); // Right: explicit 301 app.get('/old-page', (req, res) => { res.redirect(301, '/new-page'); }); from django.http import HttpResponsePermanentRedirect, HttpResponseRedirect # Wrong: 302 return HttpResponseRedirect('/new-page') # Right: 301 return HttpResponsePermanentRedirect('/new-page') # Or in urls.py from django.views.generic import RedirectView path('old-page', RedirectView.as_view(url='/new-page', permanent=True)) // next.config.js module.exports = { async redirects() { return [ { source: '/old-page', destination: '/new-page', permanent: true, // → 308 (modern permanent) }, { source: '/temp-redirect', destination: '/somewhere', permanent: false, // → 307 (modern temporary) } ]; } }; Cloudflare → Rules → Bulk Redirects (or Page Rules) For each redirect: Source URL: example.com/old-page Target URL: https://example.com/new-page Status code: 301 (Moved Permanently) ← not 302 Preserve query string: Yes # Wrong chain /old-page → 302 → /interim → 301 → /new-page # The 302 loses equity that the 301 then partly recovers. # Fix: collapse to one hop /old-page → 301 → /new-page
Status code confirmation
curl -I https://example.com/old-page | grep -i "^HTTP" Should show HTTP/2 301 (or HTTP/2 308 for modern strict).
Search Console crawl request
Submit the old URL for re-crawl. Google processes the 301, transfers signals to the new URL over 2-8 weeks. Track via Search Console Performance — impressions and clicks for the new URL should rise as the old one drops.
Tip: 💡 If you're not sure whether a redirect is permanent or temporary, ask: will the original URL ever serve content again? If no, it's permanent — use 301/308. If maybe later (A/B test, geo, maintenance), it's temporary — use 302/307. When uncertain, 301 is the safer default for SEO.
Tags: redirect-checker, fix 302 vs 301, permanent vs temporary redirect, link equity, redirect status code, 301 SEO, 302 SEO, when to use 302
Fix
Diagnose and fix 403 Forbidden responses: identify the access rule, restore legitimate access without exposing sensitive paths and verify the fix.
Run the HTTP Error Audit audit
Open the HTTP Error Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your HTTP Error Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: http-error-audit, 403, errors
Fix
Diagnose and fix 404 Not Found errors site-wide: classify by cause, restore-redirect-or-relink, and prevent future 404s with link audits.
Run the HTTP Error Audit audit
Open the HTTP Error Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your HTTP Error Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: http-error-audit, 404, errors
Fix
Diagnose and fix 500 Internal Server Errors: read logs, identify the failing component, deploy the fix and verify with monitoring.
Run the HTTP Error Audit audit
Open the HTTP Error Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your HTTP Error Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: http-error-audit, 500, errors
Fix
Diagnose and fix 502 Bad Gateway errors: identify the upstream service, restore connectivity and harden against future failures.
Run the HTTP Error Audit audit
Open the HTTP Error Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your HTTP Error Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: http-error-audit, 502, errors
Fix
Diagnose and fix 504 Gateway Timeout errors: identify slow upstream calls, optimise or scale and adjust timeout settings.
Run the HTTP Error Audit audit
Open the HTTP Error Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your HTTP Error Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: http-error-audit, 504, errors
Fix
Make React and Next.js apps WCAG-compliant: client-route focus handling, semantic JSX, ARIA correctness, headless component patterns, automated axe-core in CI.
Integrate axe-core in development
Install @axe-core/react (CRA, Vite) or @axe-core/next (Next.js plugin). Logs accessibility errors to dev console on every render. Catches 30-40% of WCAG issues automatically — never substitutes for manual testing.
Add jest-axe to your test suite
jest-axe runs axe-core against rendered components in unit tests. Wraps render(<Component/>) → expect(await axe(container)).toHaveNoViolations(). Gates accessibility regressions in CI.
Fix client-route focus
In Next.js App Router (since v13), route changes don't move focus by default. Screen reader users lose context. Solution: useEffect on pathname change → focus the main h1 with tabindex='-1'. Or use next/link's focus management patches.
Replace div soup with semantic JSX
Audit JSX: every <div> that's clickable should be <button>; every navigation cluster should be <nav>; lists should be <ul>/<ol>; sections should be <section> or <article>. Forms need <label> with htmlFor.
Audit headless component library ARIA
Radix UI, Headless UI, React Aria, shadcn/ui — generally accessible. But: custom implementations of dialog, dropdown, listbox often miss aria-expanded, aria-controls, focus trap. Test each interactive component with keyboard only.
Fix dynamic content announcements
When content changes without route change (e.g., filter results update), screen readers don't notice. Wrap dynamic regions in role='status' or aria-live='polite' so updates are announced.
Run Lighthouse and WAVE
Lighthouse a11y audit on representative URLs. WAVE browser extension on rendered DOM. Both miss things axe doesn't — running multiple tools catches more.
Tags: accessibility, React accessibility, Next.js accessibility, WCAG React, axe-core React, React focus management
Fix
Make Shopify stores WCAG-compliant: theme accessibility audits, Dawn theme patterns, app widget pitfalls, contrast, keyboard nav, alt text workflow.
Baseline scan
Run WAVE and axe DevTools on homepage, a collection, a product page, and the cart. Record error count and contrast failures per template.
Audit theme
Dawn (the default) is mostly accessibility-ready but has known issues: low-contrast 'sale' badges, missing form labels in newsletter blocks, inaccessible quick-view drawers. Check your theme's accessibility documentation. Premium themes (Prestige, Impact, Symmetry) vary widely.
Fix product alt text
Bulk Image Editor app or Shopify Flow → for each product image, set descriptive alt. 'Red leather Chelsea boot, side profile' not 'IMG_001.jpg'. Affects screen reader users, SEO and AI visibility.
Audit installed apps
Apps inject widgets: cookie banners (Pandectes, Consentmo), popups (Privy, Klaviyo), currency converters (BEST Currency, Geolocation), chat (Tidio, Gorgias). Each should be tested for keyboard nav and focus management.
Fix contrast site-wide
Theme editor → Colours → check body/heading/button contrast against backgrounds. Dawn's default 'sale' badge often fails WCAG AA. Adjust theme colour tokens, not individual sections.
Handle predictive search and filters
Predictive search results and filter checkboxes are common keyboard traps in Shopify. Test with Tab key alone. Replace inaccessible filter apps (Boost Product Filter older versions) with accessible alternatives (Searchanise, Smart Search 2025+).
Monitor
Schedule monthly Pa11y CI runs against key URLs. App updates regress accessibility regularly.
Tags: accessibility, Shopify accessibility, WCAG Shopify, Dawn theme accessibility, Shopify alt text, Shopify keyboard navigation
Fix
Make WordPress sites WCAG-compliant: accessibility-ready themes, WP Accessibility plugin, Gutenberg blocks, page builder fixes, contrast and keyboard nav.
Step 1: Run an automated baseline scan
Use WAVE (wave.webaim.org) and axe DevTools on 5 representative URLs. Record error count, contrast failures and missing landmarks.
Step 2: Switch to or verify accessibility-ready theme
WordPress.org tags accessibility-ready themes. Check your theme has the tag. If not, audit it manually for landmarks (header, nav, main, footer), skip links, and visible focus states.
Step 3: Install WP Accessibility plugin
Free plugin from WordPress.org. Adds skip links, enforces alt text in the media library, removes target=_blank from links, adds keyboard navigation to menus.
Step 4: Audit Gutenberg blocks
Heading levels in correct order (H1→H2→H3, no skipping). Image blocks have alt text. Button blocks have descriptive labels. Cover blocks have sufficient contrast.
Step 5: Audit page builder output (Elementor, Divi, WPBakery)
Page builders frequently produce inaccessible HTML: divs instead of buttons, missing form labels, focus traps in popups. Audit each widget; replace inaccessible ones with custom HTML blocks.
Step 6: Fix contrast site-wide
Use the WAVE contrast checker. Adjust theme CSS variables for text colours. Most WordPress themes use a light grey for body text that fails WCAG AA at small sizes.
Step 7: Verify and monitor
Re-run WAVE and axe. Set up monthly scans via Pa11y CI or Lighthouse CI in GitHub Actions. Accessibility regresses with every theme/plugin update.
Tags: accessibility, WordPress accessibility, WCAG WordPress, accessibility-ready theme, WP Accessibility plugin, Gutenberg accessibility
Fix
Fix the gap in branded mentions across the open web that affects how ChatGPT, Claude and Perplexity cite your site.
Run the AEO audit
Open the AEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your AEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: aeo, brand, mentions
Fix
Restructure content so AI search engines can extract, attribute and cite the right passages: clear headings, definitions and answer-first writing.
Run the AEO audit
Open the AEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your AEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: aeo, content, structure
Fix
Add and refine schema markup so AI engines accurately identify your entities, authors and content type for citation eligibility.
Run the AEO audit
Open the AEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your AEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: aeo, schema
Fix
Fix WAF and bot protection blocking legitimate AI agents. Identify GPTBot, ClaudeBot, PerplexityBot. Cloudflare, Akamai, AWS WAF allowlist patterns.
Grep access logs
grep -E "GPTBot|ClaudeBot|PerplexityBot|Anthropic|CCBot|Google-Extended" \ /var/log/nginx/access.log | tail -50 # Check status codes — 403/429 means blocked grep "GPTBot" /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c
Cloudflare bot analytics
Cloudflare → Security → Bots. Lists detected bots with action taken. Agents shown as "blocked" or "challenged" need allowlisting. Security → Bots → Bot Fight Mode - "Allow verified bots" → enabled - No manual UA rules needed for GPTBot, ClaudeBot, PerplexityBot, Googlebot, Bingbot # Cloudflare → Security → WAF → Custom rules → Create # Field: User Agent # Operator: contains # Value: GPTBot # Then: Skip → all remaining custom rules + managed challenge # Or in expression syntax: (http.user_agent contains "GPTBot") or (http.user_agent contains "ClaudeBot") or (http.user_agent contains "PerplexityBot") or (http.user_agent contains "Anthropic") or (http.user_agent contains "OAI-SearchBot") # Action: Skip → managed challenge # Web ACL → Rules → Add rule → Custom rule # Statement: Inspect → Single header → User-Agent # Match type: Contains string # String: GPTBot # Action: Allow # Add additional rules for ClaudeBot, PerplexityBot, etc. # Set higher priority than blocking rules — allow first, block what's left # /etc/nginx/conf.d/ai-agents.conf map $http_user_agent $is_ai_agent { default 0; "~*GPTBot" 1; "~*ChatGPT-User" 1; "~*ClaudeBot" 1; "~*Claude-Web" 1; "~*anthropic-ai" 1; "~*PerplexityBot" 1; "~*Perplexity-User" 1; "~*OAI-SearchBot" 1; "~*Google-Extended" 1; } server { # Skip rate-limit and challenge for AI agents if ($is_ai_agent = 1) { set $bypass_check 1; } } # OpenAI publishes ranges at openai.com/gptbot-ranges.json curl -s https://openai.com/gptbot-ranges.json | jq # Anthropic publishes ClaudeBot ranges curl -s https://anthropic.com/claudebot-ranges.json | jq # Auto-update WAF allowlists from these every 24h via cron
curl with each agent UA
for ua in "GPTBot/1.0" "ClaudeBot/1.0" "PerplexityBot/1.0" "anthropic-ai/1.0"; do status=$(curl -s -o /dev/null -w "%{http_code}" -A "$ua" https://example.com/) echo "$status $ua" done # All should return 200, not 403/429/503
Tip: 💡 Don't trust user-agent strings alone for allowlisting in production — they're trivially spoofed and let scrapers in. Use Cloudflare Verified Bots or pull IP ranges from official endpoints. UA-only allowlisting is fine for diagnosis but not for security policy.
Tags: agent-compat, fix agent blocks, GPTBot allowlist, ClaudeBot user agent, WAF AI agents, Cloudflare bot rules, AI crawler whitelist
Fix
Fix checkout flows that break AI agents. Stable steps, captcha-on-suspicion, payment iframes, session continuity. Agent purchase task completion.
Playwright end-to-end
test('agent can checkout', async ({ page }) => { await page.goto('/products/widget'); await page.getByRole('button', { name: 'Add to cart' }).click(); await page.goto('/checkout/cart'); await page.getByRole('link', { name: 'Continue to details' }).click(); await page.getByLabel('Full name').fill('Test User'); await page.getByLabel('Email').fill('test@example.com'); await page.getByRole('button', { name: 'Continue' }).click(); // ... shipping, payment, review await expect(page).toHaveURL(/\/checkout\/confirmation\//); });
Ask ChatGPT-User to checkout
"Buy the small blue widget on example.com using test card 4242 4242 4242 4242, deliver to [test address]". Watch what trips it.
1. Stable step URLs
Bad: single URL with JS step state /checkout → user fills details, JS swaps content /checkout (same URL, now showing payment) → user fills payment, JS swaps again /checkout (now showing review) <!-- Agent refresh = lose state. Resume = impossible. --> Good: URL per step /checkout/cart /checkout/details /checkout/shipping /checkout/payment /checkout/review /checkout/confirmation/:order-id <!-- Agent can resume any step via URL. Refresh-safe. -->
2. Server-render each step
Field labels, price totals, validation errors — all in HTML response: <!-- /checkout/shipping --> <main> <h1>Shipping address</h1> <form action="/checkout/shipping" method="POST"> <label for="ship-name">Full name</label> <input id="ship-name" name="shipName" autocomplete="name" required /> <label for="ship-addr">Street address</label> <input id="ship-addr" name="shipAddr" autocomplete="street-address" required /> <label for="ship-city">City</label> <input id="ship-city" name="shipCity" autocomplete="address-level2" required /> <label for="ship-postcode">Postcode</label> <input id="ship-postcode" name="shipPostcode" autocomplete="postal-code" required /> <button type="submit">Continue to payment</button> </form> <aside aria-label="Order summary"> <p>Items: 2</p> <p>Subtotal: £49.00</p> <p>Shipping: TBC</p> </aside> </main>
3. Captcha — risk-based, not always-on
// Only challenge when behaviour suggests bot async function shouldShowCaptcha(req) { const flags = []; if (req.session.submitTimes.some(t => t < 1500)) flags.push('too_fast'); if (await checkAbuseIPLookup(req.ip) > 80) flags.push('high_abuse_ip'); if (req.body._honeypot) flags.push('honeypot_filled'); if (req.session.failedAttempts > 3) flags.push('repeated_failures'); return flags.length >= 2; // Only challenge on multiple flags } // In route handler if (await shouldShowCaptcha(req)) { return res.render('captcha', { ... }); } // Otherwise proceed Honest agents complete fast (within range), have clean IPs (datacenter but not abuse), don't trigger honeypots, don't fail repeatedly. They pass through. Fraud bots flag.
4. Payment iframes (Stripe, Adyen, Braintree)
Stripe Elements pattern <form id="payment-form" action="/checkout/process" method="POST"> <label for="card-element">Card details</label> <div id="card-element" aria-label="Credit card information"> <!-- Stripe injects labelled iframe here --> </div> <label for="postal">Billing postcode</label> <input id="postal" name="postal" autocomplete="postal-code" /> <button type="submit">Pay £49.00</button> </form> <!-- Stripe's iframe has labelled inputs inside; agents in browse mode handle it --> For agent-direct flows: Payment Intents API Some agents (especially those handling repeat user purchases with stored cards) can call your backend directly. Expose a tokenised endpoint: POST /api/checkout/confirm { "cartId": "cart_abc", "paymentMethod": "pm_xxx", // Stripe PaymentMethod ID "shipping": { ... } } → 200 { "orderId": "ord_yyy", "status": "confirmed" }
5. Session continuity
Don't tie cart to a fragile session. Agents may resume from a different IP, after a delay, or with a new browser instance: // Issue durable cart token, store cart server-side <input type="hidden" name="cartToken" value="cart_v1_abc123" /> // On every step submit, server looks up cart_v1_abc123, validates ownership // via signed user session OR signed token. Both stable across IP/UA changes.
6. Validation errors agents can read
<!-- Bad: error in toast that disappears, JS-only --> <Toast message="Postcode invalid" /> <!-- Good: error inline, ARIA-connected --> <label for="ship-postcode">Postcode</label> <input id="ship-postcode" name="shipPostcode" aria-invalid="true" aria-describedby="postcode-error" /> <div id="postcode-error" role="alert"> Postcode "SW1Z" is not valid. Try "SW1A 1AA" format. </div> <!-- Agent reads, corrects, resubmits -->
7. Test the flow
Step 1 Playwright end-to-end test('agent can checkout', async ({ page }) => { await page.goto('/products/widget'); await page.getByRole('button', { name: 'Add to cart' }).click(); await page.goto('/checkout/cart'); await page.getByRole('link', { name: 'Continue to details' }).click(); await page.getByLabel('Full name').fill('Test User'); await page.getByLabel('Email').fill('test@example.com'); await page.getByRole('button', { name: 'Continue' }).click(); // ... shipping, payment, review await expect(page).toHaveURL(/\/checkout\/confirmation\//); }); Step 2 Ask ChatGPT-User to checkout "Buy the small blue widget on example.com using test card 4242 4242 4242 4242, deliver to [test address]". Watch what trips it. <div
Tip: 💡 Most agent checkout failures come from one of three places: aggressive captcha on every step, payment iframes with poor labels, or session state that doesn't survive a page refresh. Fix those three and you cover 80% of agent abandons.
Tags: agent-compat, fix agent checkout, AI agent purchase, agent ecommerce, captcha agents, iframe payment, checkout SSR
Fix
Make Shopify stores readable by AI agents: robots.txt customisation, JS rendering, semantic markup, app bot-blocking, GPTBot and Perplexity policy.
Customise robots
txt via robots.txt.liquid. Shopify lets you customise robots.txt since 2021. Online Store → Themes → Edit code → create robots.txt.liquid in templates. Add per-user-agent rules: 'User-agent: GPTBot' / 'Allow: /'. Default Shopify blocks /policies/ and /admin/ which is correct.
Decide AI bot policy
Allow citation bots (GPTBot, PerplexityBot, ClaudeBot) if you want AI search visibility. Block training-only crawlers if you have content-licensing concerns. Document choices in robots.txt.liquid comments.
Audit Shopify-level bot blocking
Shopify's edge layer (Cloudflare-based) rate-limits aggressive crawlers. Most AI agents are fine but Perplexity has historically been throttled on Shopify. Watch server-logs (Logs section in Plus stores; Shopify Inbox for standard plans).
Audit app-installed bot rules
Anti-fraud apps (Signifyd, Riskified), bot-protection apps (DataDome, PerimeterX), and aggressive WAF apps may block AI agents. Check app settings for crawler whitelists.
Ensure key content is server-rendered
Shopify renders product, collection and page templates server-side by default — good for AI agents. App-injected content (reviews, upsells, related products) often renders client-side via JS. If your most-cited content is JS-rendered, AI agents may miss it. Move critical content to server-rendered theme sections.
Add structured data
Shopify themes ship with basic Product schema. Verify it includes name, image, description, sku, brand, offers (price, currency, availability), aggregateRating. AI agents heavily weight structured data.
Test access
Use a user-agent switcher in Chrome DevTools. Set UA to GPTBot. Reload product, collection, blog pages. Confirm 200 responses, content visible, no rate-limit blocks.
Tags: agent-compat, Shopify AI agent, Shopify GPTBot, Shopify robots.txt, Shopify Perplexity, Shopify AI compatibility
Fix
Make Single Page Apps (React, Vue, Angular) readable by AI agents: SSR vs CSR trade-offs, dynamic rendering, prerendering, hydration markers, robots.txt.
Verify the problem
curl your homepage (or use Chrome DevTools → Network → disable JavaScript → reload). If response HTML is empty/skeletal, AI agents see nothing. Most React/Vue/Angular SPAs without SSR exhibit this.
Choose rendering strategy
SSR (server-side render on every request — Next.js getServerSideProps, Nuxt asyncData): freshest content, more server cost. SSG (build-time render — Next.js getStaticProps, Astro, Hugo): fastest, less dynamic. Hybrid (Next.js ISR, on-demand revalidation): best of both. Prerendering services (Prerender.io, Rendertron): SPAs without rewriting.
For Next
js: migrate to App Router with RSC. React Server Components in Next.js 13+ App Router render content on the server by default. AI agents see real HTML. Client Components ('use client') are opt-in, not default. Migration from Pages Router is non-trivial but produces best AI agent compatibility.
For pure React (CRA, Vite)
add SSR or prerender. Options: migrate to Next.js or Remix (full SSR); add Vite SSR plugin (vite-plugin-ssr / Vike); use Prerender.io as middleware (detects crawler UA, returns pre-rendered HTML).
For Vue/Nuxt
ensure SSR mode. Nuxt 3 SSR is default; verify nuxt.config has ssr: true. Don't switch to ssr: false (SPA mode) — kills AI agent visibility.
Verify with crawler user-agents
Chrome DevTools → user-agent switcher → GPTBot. Reload key pages. Confirm content visible in initial HTML response. View-source must show real content, not empty placeholder.
Configure robots
txt. Allow AI agents you welcome: GPTBot, ClaudeBot, PerplexityBot, CCBot. Add explicit Allow rules. Block crawlers that have caused issues. Note Vercel/Netlify often handle robots.txt via project file (public/robots.txt or robots.txt in app root).
Tags: agent-compat, SPA agent compat, React AI crawler, SPA SEO, React SSR AI, SPA prerendering
Fix
Make WordPress sites readable by AI agents: robots.txt for GPTBot, Perplexity, ClaudeBot; remove rate limits; expose semantic HTML; serve clean markup.
Step 1: Audit current bot access in robots.txt
Visit yoursite.com/robots.txt. Look for Disallow rules that block AI agents by user-agent. WordPress generates a virtual robots.txt by default which is permissive; SEO plugins (Yoast, Rank Math, AIOSEO) may add restrictions.
Step 2: Choose your AI bot policy
Decide which AI agents you allow. Mainstream stack: allow GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot. Block training-only bots if you have copyright concerns. Document the decision in robots.txt comments.
Step 3: Edit robots.txt via SEO plugin
Yoast SEO → Tools → File editor → robots.txt. Rank Math → General Settings → Edit robots.txt. AIOSEO → Tools → Robots.txt editor. Add explicit Allow rules for the agents you welcome.
Step 4: Audit security plugin block lists
Wordfence, Sucuri, iThemes Security: check their bot-blocking rules. They often default to blocking 'aggressive crawlers' which includes AI agents. Whitelist the AI user-agents explicitly.
Step 5: Remove or tune rate limits
Cloudflare Bot Fight Mode, Wordfence rate limiting, and most managed WordPress hosts (WP Engine, Kinsta) rate-limit non-Googlebot crawlers. Whitelist AI agent IP ranges or user-agents in the relevant dashboard.
Step 6: Expose semantic markup
AI agents prefer clean HTML5 semantic markup (article, section, nav, main). Confirm your theme uses these. Avoid divs-only output from older page builders.
Step 7: Verify access
Use a user-agent switcher in Chrome DevTools. Set UA to 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.0; +https://openai.com/gptbot' and reload your site. Check server logs for the request reaching the origin.
Tags: agent-compat, WordPress AI agent, GPTBot WordPress, Perplexity crawler WordPress, ClaudeBot, WordPress AI compatibility
Fix
Fix unreachable contact methods for AI agents. Machine-readable phone, email, address, hours in HTML and schema. Avoid contact info in images only.
curl grep
curl -s https://example.com/contact | grep -oE 'tel:[^"]+' curl -s https://example.com/contact | grep -oE 'mailto:[^"]+' curl -s https://example.com/contact | grep -A1 "PostalAddress" # All three should produce output
Ask an agent
"What's the phone number for example.com?" If the agent answers correctly from your page, fix is verified.
1. The basic fix
Bad: contact in images <img src="/contact-info.png" alt="Contact us" /> <!-- Phone, email, address all rendered as pixels. Agents see nothing. --> Bad: JS-obfuscated email <span class="email-obfusc" data-1="hello" data-2="example.com"></span> <script> // Build email from data attrs at runtime </script> <!-- Agents fetching raw HTML see no email --> Good: plain HTML with schemes <section> <h2>Contact us</h2> <p>Phone: <a href="tel:+441273555012">+44 1273 555 012</a> </p> <p>Email: <a href="mailto:hello@example.com">hello@example.com</a> </p> <address> Acme Ceramics<br /> 12 High Street<br /> Brighton BN1 1AA<br /> United Kingdom </address> <p>Hours: Mon-Fri 09:00-17:00, Sat 10:00-16:00</p> </section>
2. ContactPoint schema
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Organization", "name": "Acme Ceramics", "url": "https://example.com", "logo": "https://example.com/logo.png", "address": { "@type": "PostalAddress", "streetAddress": "12 High Street", "addressLocality": "Brighton", "postalCode": "BN1 1AA", "addressCountry": "GB" }, "contactPoint": [ { "@type": "ContactPoint", "telephone": "+44-1273-555-012", "contactType": "customer service", "email": "hello@example.com", "areaServed": "GB", "availableLanguage": ["English"] }, { "@type": "ContactPoint", "telephone": "+44-1273-555-013", "contactType": "sales", "email": "sales@example.com", "areaServed": ["GB", "EU"], "availableLanguage": ["English", "French"] }, { "@type": "ContactPoint", "contactType": "technical support", "url": "https://example.com/support", "availableLanguage": ["English"] } ], "sameAs": [ "https://twitter.com/acmeceramics", "https://linkedin.com/company/acmeceramics" ] } </script>
3. Phone number formatting
Use international format for tel: hrefs — agents and phone apps parse reliably: <!-- UK number --> <a href="tel:+441273555012">+44 1273 555 012</a> <!-- US number --> <a href="tel:+12125551234">(212) 555-1234</a> <!-- Extensions --> <a href="tel:+441273555012,123">+44 1273 555 012 ext. 123</a> <!-- Comma = pause then dial extension -->
4. Address — visible + structured
<address itemscope itemtype="https://schema.org/PostalAddress"> <span itemprop="name">Acme Ceramics</span><br /> <span itemprop="streetAddress">12 High Street</span><br /> <span itemprop="addressLocality">Brighton</span> <span itemprop="postalCode">BN1 1AA</span><br /> <span itemprop="addressCountry">United Kingdom</span> </address> <!-- Microdata works too. JSON-LD in head is preferred but inline microdata is acceptable. -->
5. Hours — schema + visible
Visible for humans, schema for agents: <h3>Opening hours</h3> <ul> <li>Monday-Friday: 09:00-17:00</li> <li>Saturday: 10:00-16:00</li> <li>Sunday: closed</li> </ul> <!-- And in LocalBusiness schema --> "openingHoursSpecification": [ { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"], "opens": "09:00", "closes": "17:00" }, { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Saturday"], "opens": "10:00", "closes": "16:00" } ]
6. Map embed — agent-friendly version
Google Maps iframe is fine for users but invisible to agents. Add a text fallback: <section aria-label="Find us"> <h2>Find us</h2> <!-- Embedded map for humans --> <iframe src="https://www.google.com/maps/embed?..." title="Map of Acme Ceramics location" loading="lazy"></iframe> <!-- Address text for agents --> <address> Acme Ceramics, 12 High Street, Brighton BN1 1AA </address> <!-- Direct map link --> <a href="https://maps.google.com/?q=12+High+Street+Brighton+BN1+1AA"> Open in Google Maps </a> </section>
7. Verify
Step 1 curl grep curl -s https://example.com/contact | grep -oE 'tel:[^"]+' curl -s https://example.com/contact | grep -oE 'mailto:[^"]+' curl -s https://example.com/contact | grep -A1 "PostalAddress" # All three should produce output Step 2 Ask an agent "What's the phone number for example.com?" If the agent answers correctly from your page, fix is verified. <div
Tip: 💡 Email obfuscation (entity encoding, JS injection) had marginal value 15 years ago. Today it costs you agent reach and barely slows actual scrapers. Modern spam comes from breached email databases, not page scraping. Show contact info plainly.
Tags: agent-compat, fix agent contact, machine readable contact, tel: links, mailto:, schema contact info, agent extraction
Fix
Fix forms that block AI agents. Stable field IDs, programmatic labels, no hidden honeypots breaking agents. Accessibility-equals-agent-friendly principle.
Playwright with label selectors
// If this works, agents will too import { test } from '@playwright/test'; test('book demo form', async ({ page }) => { await page.goto('https://example.com/demo'); await page.getByLabel('First name').fill('Test'); await page.getByLabel('Email').fill('test@example.com'); await page.getByLabel('Company').fill('Acme'); await page.getByRole('button', { name: 'Book demo' }).click(); await page.waitForURL('**/thanks'); });
Try ChatGPT-User
Ask ChatGPT (with browsing enabled) to "book a demo on example.com for John at Acme". Watch if it can complete the form. If it stalls or asks for clarification on field purpose, your labels/autocomplete are weak.
1. The single principle
If a screen reader can identify and submit your form, an agent can too. Agents and screen readers both read the DOM looking for label text and ARIA — neither sees visual layout. Accessibility-equals-agent-friendly.
2. Required form basics
Every input needs a label <!-- BAD: placeholder as label --> <input type="email" placeholder="Email" /> <!-- GOOD: programmatic label --> <label for="email">Email</label> <input id="email" name="email" type="email" autocomplete="email" /> <!-- ALSO GOOD: aria-label if visual label undesired --> <input type="search" aria-label="Search products" /> Stable, semantic IDs <!-- BAD: random ID, regenerated each render --> <input id="input-x7k2p9" name="firstName" /> <!-- GOOD: semantic, stable ID --> <input id="first-name" name="firstName" autocomplete="given-name" /> Autocomplete attributes Single most underused agent hint: autocomplete="given-name" → First name autocomplete="family-name" → Last name autocomplete="email" → Email autocomplete="tel" → Phone autocomplete="street-address" → Street autocomplete="postal-code" → Postcode / ZIP autocomplete="cc-number" → Credit card autocomplete="organization" → Company autocomplete="url" → Website
3. Anti-spam without breaking agents
Honeypot fields done right <!-- BAD: looks like a real field but is a trap --> <input type="text" name="email" style="display:none" /> <input type="text" name="email_real" /> <!-- Confusing for agents and screen readers --> <!-- GOOD: clearly named honeypot, hidden via CSS, labelled as such --> <div class="hp-wrap" aria-hidden="true" style="position:absolute;left:-9999px"> <label for="website-extra">Leave this blank</label> <input id="website-extra" name="url_honeypot" type="text" tabindex="-1" autocomplete="off" /> </div> <!-- Real users skip via aria-hidden + tab-index; bots fill it --> Captcha — when to deploy Captcha-always blocks both spammers and agents indiscriminately. Captcha-on-suspicion is better: // Only show captcha if behaviour suggests bot if (submitTimeMs < 1500 || honeypotFilled || abuseScoreHigh) { return requireCaptcha(req, res); } // Otherwise pass through
4. Multi-step forms
Use real URLs per step <!-- BAD: client-side step state, single URL --> /checkout ↓ (JS state change, URL doesn't change) /checkout (now showing step 2) <!-- GOOD: URL per step --> /checkout/details /checkout/shipping /checkout/payment /checkout/review Agents can resume any step by URL. Refresh-safe. Bookmarkable. Progress visible in HTML <nav aria-label="Checkout progress"> <ol> <li aria-current="step">Details</li> <li>Shipping</li> <li>Payment</li> </ol> </nav>
5. Validation messages
<!-- Connect errors to fields programmatically --> <label for="email">Email</label> <input id="email" name="email" type="email" aria-invalid="true" aria-describedby="email-error" /> <div id="email-error" role="alert"> Please enter a valid email address. </div> <!-- Agent reads aria-describedby, knows what's wrong, fixes input -->
6. CSRF tokens
<!-- Render in initial HTML, not via JS --> <form action="/api/submit" method="POST"> <input type="hidden" name="_csrf" value="{{csrfToken}}" /> <!-- ... fields ... --> </form> <!-- Agent extracts token from HTML and includes in submission -->
7. Test with an agent simulation
Step 1 Playwright with label selectors // If this works, agents will too import { test } from '@playwright/test'; test('book demo form', async ({ page }) => { await page.goto('https://example.com/demo'); await page.getByLabel('First name').fill('Test'); await page.getByLabel('Email').fill('test@example.com'); await page.getByLabel('Company').fill('Acme'); await page.getByRole('button', { name: 'Book demo' }).click(); await page.waitForURL('**/thanks'); }); Step 2 Try ChatGPT-User Ask ChatGPT (with browsing enabled) to "book a demo on example.com for John at Acme". Watch if it can complete the form. If it stalls or asks for clarification on field purpose, your labels/autocomplete are weak. <div
Tip: 💡 Agent-friendly forms also convert better for humans. Clear labels reduce abandonment. Proper autocomplete saves typing on mobile. Semantic IDs and ARIA help screen readers. The agent-compat work pays for itself in human metrics.
Tags: agent-compat, fix agent forms, AI agent form fill, stable field IDs, form accessibility, honeypot agents, multi-step form agents
Fix
Fix navigation that breaks AI agents. Real anchor links not JS click handlers, no hover-only menus, progressive enhancement pattern for agent traversal.
Browser DevTools
Chrome DevTools → Cmd/Ctrl+Shift+P → "Disable JavaScript" → reload. Can you navigate to every important page from the homepage using only links?
curl crawl test
# Get all links from homepage curl -s https://example.com/ | grep -oE 'href="[^"]+"' | sort -u | head -50 # Should include your main category and product pages # If you only see static pages (about, contact, blog), nav is JS-only
1. The pattern
Bad: button with click handler <button onClick={() => router.push('/products')}> Products </button> <!-- Agents see: a button with no destination. Dead end. --> Good: anchor with real href <a href="/audit-tools.html" onClick={interceptForSPA}> Products </a> <!-- Agents see: link to /products. Follow it. --> <!-- Humans get JS-intercepted SPA navigation. -->
2. Framework patterns
Next.js import Link from 'next/link'; <Link href="/audit-tools.html">Products</Link> // Renders <a href="/audit-tools.html">Products</a> — agent-friendly // JS intercepts clicks for client-side routing Nuxt <NuxtLink to="/products">Products</NuxtLink> <!-- Renders <a href="/audit-tools.html"> --> Astro <a href="/audit-tools.html">Products</a> <!-- Plain HTML by default; no JS needed --> React Router (CSR-only) // react-router-dom v6+ import { Link } from 'react-router-dom'; <Link to="/products">Products</Link> // Renders <a href="/audit-tools.html"> // BUT: if the rest of the page is CSR, agent still sees empty shell. // See how-to-fix-js-only-content for the underlying issue.
3. Mega-menus and dropdowns
Bad: hover-only, children injected via JS <nav> <li onMouseEnter={loadSubmenu}> Products {/* Submenu loads here on hover, JS only */} </li> </nav> Good: server-rendered submenu, JS controls visibility <nav> <li> <a href="/audit-tools.html">Products</a> <ul class="submenu"> <li><a href="/audit-tools.html">Shoes</a></li> <li><a href="/audit-tools.html">Bags</a></li> <li><a href="/audit-tools.html">Accessories</a></li> </ul> </li> </nav> <style> /* Hidden until parent hovered/focused */ .submenu { display: none; } li:hover .submenu, li:focus-within .submenu { display: block; } </style> <!-- All links in initial HTML. Agents see and follow them. -->
4. Mobile menus / hamburgers
<!-- Render the full menu in HTML, CSS-hidden until JS toggle --> <button aria-controls="mobile-nav" aria-expanded="false">Menu</button> <nav id="mobile-nav" hidden> <a href="/audit-tools.html">Products</a> <a href="/seo-audit-platform.html">About</a> <a href="/support.html">Contact</a> </nav> <!-- Agents read the nav regardless of hidden attribute --> <!-- Humans toggle visibility via JS -->
5. Sitemap as backup nav
Even with perfect nav, link to an HTML sitemap. Single page listing all important URLs, hierarchically organised. Agents (and users with weird browsers) get a fallback discovery path: <a href="/sitemap.xml">Site Map</a> <!-- in footer --> <!-- /sitemap.html --> <main> <h1>Site Map</h1> <section> <h2>Products</h2> <ul> <li><a href="/audit-tools.html">Shoes</a></li> <li><a href="/audit-tools.html">Bags</a></li> </ul> </section> <!-- ... --> </main>
6. Test by disabling JS
Step 1 Browser DevTools Chrome DevTools → Cmd/Ctrl+Shift+P → "Disable JavaScript" → reload. Can you navigate to every important page from the homepage using only links? Step 2 curl crawl test # Get all links from homepage curl -s https://example.com/ | grep -oE 'href="[^"]+"' | sort -u | head -50 # Should include your main category and product pages # If you only see static pages (about, contact, blog), nav is JS-only <div
Tip: 💡 Hamburger menus on mobile and mega-menus on desktop are agent-friendly IF the links live in the HTML and CSS controls visibility. The mistake is loading them with JS on interaction — agents never interact, so they never see the menu.
Tags: agent-compat, fix agent navigation, JS-only nav, hover menu agent, progressive enhancement, anchor links agents, agent crawl
Fix
Make React/Next.js content citable by AI agents: SSR/SSG for content, JSON-LD via next/script, semantic markup, metadata API, author signals.
Verify SSR/SSG renders content
Disable JS in DevTools → reload page → content visible? If yes, AI agents see content. If no, fix rendering strategy first.
Use Next.js Metadata API for SEO
Next.js 13+ App Router: export metadata or generateMetadata from page.tsx. Sets title, description, openGraph, twitter, robots, alternates (canonical, hreflang). Cleaner than manual <head> tags.
Add JSON-LD via next/script or metadata
Generate JSON-LD object server-side, render via <script type='application/ld+json' dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />. Place in layout.tsx for site-wide schema (Organization, WebSite) and page.tsx for page-specific (Article, Product, FAQPage).
Write semantic JSX
Article pages: <article><h1>Title</h1><time>Date</time><main>Content</main></article>. Avoid <div> soup. AI agents and screen readers both benefit from semantic structure.
Front-load value in content
First 100 words answer: what this page is about, who it's for, what they'll learn/get. AI agents weight early content heavily when extracting citations.
Add author and publication signals
JSON-LD Article should reference Person (author with sameAs LinkedIn/Twitter), Organization (publisher with logo). Visible byline on the page. About page with team details accessible from every page.
Track citation rate
Search 'site:yoursite.com' in ChatGPT, Perplexity, Claude weekly. Note which content gets cited. Tools (Profound, Otterly, HubSpot AI Search) track systematically.
Tags: agent-readiness, React AI readiness, Next.js AI citation, React JSON-LD, Next.js metadata, React semantic HTML
Fix
Make Shopify product, collection and blog content citable by AI: structured data, semantic markup, fact density, author bios, and AI-readable formatting.
Audit existing product page structure
Open 5 top-traffic products. Check: descriptive product name (not just brand+SKU), unique long-form description, clear feature/spec sections, FAQ or Q&A area, customer reviews. Generic supplier descriptions don't get cited.
Strengthen Product schema
Verify Product schema includes brand (with @type Brand), aggregateRating (real reviews — Judge.me, Stamped, Loox), description with full marketing copy, multiple offers if variant pricing differs, hasMerchantReturnPolicy for trust.
Add FAQ schema to product pages
Use an app (Easy FAQ, FAQ Page Pro) or add structured FAQ blocks via theme editor. AI agents heavily extract FAQPage schema for product comparison queries.
Write comparison-friendly content
For high-intent comparison queries ('best leather sofa under £1000'), AI agents prefer content with clear comparative criteria. Add 'What makes this different' sections naming alternatives by category, not by competitor name.
Add author bios to blog content
Shopify blog posts have an author field that's underused. Set bios with credentials, link to Person schema via theme code. Builds trust signals AI agents weight.
Front-load value in product descriptions
First 100 words should answer: what it is, who it's for, what's distinctive. AI agents cite the first few paragraphs more than later content.
Track citation rate
Search 'site:yourstore.com' in ChatGPT, Perplexity and Claude weekly. Note which products get cited and which don't. Profound, Otterly and HubSpot's AI Visibility tool track this systematically.
Tags: agent-readiness, Shopify AI readiness, AI-citable Shopify, Shopify structured data, Shopify ChatGPT citation, Shopify Perplexity
Fix
Make WordPress content citable by AI: structured data, semantic HTML, clear headings, fact density, author bios, source linking, and AI-readable formatting.
Step 1: Audit existing content for AI readability
Open 5 top-traffic posts. Check: clear H1 above the fold, descriptive H2 sections, a TL;DR or summary near the top, author byline with credentials, source links to primary sources.
Step 2: Add HowTo and FAQ schema where relevant
Use Yoast SEO, Rank Math or Schema Pro to add FAQPage and HowTo blocks to existing content. These give AI agents structured data to cite.
Step 3: Strengthen author bios with Person schema
Each post should have an author bio with: full name, credentials, links to professional profiles (LinkedIn, Twitter), and Person schema. Plugins: Simple Author Box, Molongui Authorship.
Step 4: Front-load facts in the first 200 words
AI agents tend to cite content from the first few paragraphs. Move conclusions to the top: 'X is Y because Z.' Then expand in subsequent sections. This pattern (sometimes called 'inverted pyramid') boosts citation rate substantially.
Step 5: Add source citations as outbound links
Every statistical claim, scientific finding or attributed quote should link to a primary source. AI agents trust content that traces its claims. Add a 'Sources' section at the end of long-form posts.
Step 6: Use lists, tables and definition blocks
AI agents extract structured content (lists, tables, definition lists) more reliably than dense paragraphs. Gutenberg has native blocks for all three. Use them generously in how-to and comparison content.
Step 7: Track citation rate over time
Search 'site:yoursite.com' in ChatGPT, Perplexity and Claude weekly to see what's being cited. Tools like Profound, Otterly, and HubSpot's AI Visibility tracker measure citation share against competitors.
Tags: agent-readiness, WordPress AI readiness, AI-citable content, WordPress structured data, AI-ready WordPress, ChatGPT citation WordPress
Fix
Fix slow rendering for AI agents with aggressive timeouts. Server-side rendering, CDN edge cache, latency budget. Avoid agents giving up before paint.
curl with timing
curl -w "@-" -o /dev/null -s https://example.com/ <<'EOF' time_namelookup: %{time_namelookup}s time_connect: %{time_connect}s time_appconnect: %{time_appconnect}s time_pretransfer: %{time_pretransfer}s time_redirect: %{time_redirect}s time_starttransfer: %{time_starttransfer}s <-- this is TTFB ---------- time_total: %{time_total}s EOF
WebPageTest from agent geographies
webpagetest.org with Test Location set to US-East or EU-West (where most AI infrastructure lives). Run with cache disabled, no JS execution checkbox for crawler emulation. // Bad: synchronous DB query before any HTML sends const products = await db.query('SELECT ...'); // 800ms return renderTemplate({ products }); // total TTFB: 800ms+ // Good: cached query + streaming const products = await cache.getOrSet('products', () => db.query('SELECT ...'), { ttl: 60 } ); // 5ms when cached return renderTemplate({ products }); // Bad: blocking on third-party const personalisedRecs = await fetch('https://thirdparty.com/api/recs'); // → adds 300-2000ms unpredictably // Good: render without, hydrate after return ( <Layout> <ProductGrid /> {/* server-rendered */} <LazyHydrate> <PersonalisedRecs /> {/* loads client-side */} </LazyHydrate> </Layout> ); <!-- Bad: JS in head, no defer/async --> <script src="/bundle.js"></script> <!-- Good: defer ensures HTML parses first --> <script src="/bundle.js" defer></script> <!-- Better: only ship what's needed --> <script src="/critical.js" defer></script> <script src="/secondary.js" defer async></script> # Cloudflare page rule / cache rule Match: example.com/* Cache Level: Cache Everything Edge TTL: 1 hour (or longer for static content) Browser TTL: 10 minutes # Bypass for authenticated requests Cookie: session_id → bypass cache # Result: first byte from CDN edge (~50ms) # Origin only hit on cache miss or for logged-in users <!-- Lazy-load below-the-fold --> <img src="hero.webp" alt="..." width="800" height="400" /> <img src="..." loading="lazy" alt="..." width="..." height="..." /> <!-- Modern formats --> <picture> <source srcset="hero.avif" type="image/avif" /> <source srcset="hero.webp" type="image/webp" /> <img src="hero.jpg" alt="..." /> </picture> // Express middleware app.use((req, res, next) => { const ua = req.get('User-Agent') || ''; if (/GPTBot|ClaudeBot|PerplexityBot|CCBot/i.test(ua)) { req.agentMode = true; } next(); }); // In route if (req.agentMode) { return res.render('product-lite', { product }); // Just title, description, price, schema. No JS, no images, no CSS. // Renders in < 100ms even from origin. } else { return res.render('product-full', { product }); }
curl as each agent
for ua in "GPTBot/1.0" "ClaudeBot/1.0" "PerplexityBot/1.0"; do time curl -s -A "$ua" https://example.com/ -o /dev/null done # All should complete in < 2 seconds for TTFB
Re-run audits
Run Agent Compat audit + Core Web Vitals + Page Speed . All three should improve from the same underlying fixes.
Tip: 💡 If you can only do one thing: put a CDN in front of your site with "cache everything" for non-authenticated public pages. TTFB drops from 600ms to 50ms instantly. Most agent rendering complaints disappear at the edge.
Tags: agent-compat, agent rendering speed, agent timeout, TTFB agents, FCP for agents, agent latency budget, SSR speed
Fix
Fix missing structured data agents rely on. Product offers, Event dates, LocalBusiness hours. Schema coverage supporting agent task completion.
Schema.org validator
validator.schema.org — paste URL or markup. Parses JSON-LD and flags syntax errors.
Google Rich Results Test
search.google.com/test/rich-results — checks required + recommended properties for Google's rich snippets. Same checks help agent extraction.
1. Map tasks to schema
Agent task Schema type Key properties Buy product Product offers (price, availability, url) Book event Event startDate, location, offers Find/visit shop LocalBusiness address, openingHours, telephone Cook recipe Recipe recipeIngredient, recipeInstructions, totalTime Apply for job JobPosting title, hiringOrganization, jobLocation, baseSalary Book table FoodEstablishment + reservation acceptsReservations, hasMenu, servesCuisine Read review Review / Product reviewRating, author, itemReviewed
2. Product schema (ecommerce)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Product", "name": "Blue Ceramic Widget", "image": [ "https://example.com/widget-1.jpg", "https://example.com/widget-2.jpg" ], "description": "Hand-thrown ceramic widget, 12cm diameter, food-safe glaze.", "sku": "WID-BLU-12", "gtin": "0123456789012", "brand": { "@type": "Brand", "name": "Acme Ceramics" }, "offers": { "@type": "Offer", "url": "https://example.com/widget-blue", "priceCurrency": "GBP", "price": "29.00", "availability": "https://schema.org/InStock", "priceValidUntil": "2026-12-31", "shippingDetails": { "@type": "OfferShippingDetails", "shippingRate": { "@type": "MonetaryAmount", "value": "4.99", "currency": "GBP" }, "deliveryTime": { "@type": "ShippingDeliveryTime", "transitTime": { "@type": "QuantitativeValue", "minValue": 2, "maxValue": 5, "unitCode": "DAY" } } } }, "aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.7", "reviewCount": "142" } } </script> Agent extracts: name, price, currency, availability, shipping cost and time, rating. Everything it needs to recommend or buy.
3. Event schema (bookings, conferences)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Event", "name": "Annual Pottery Workshop", "startDate": "2026-09-15T10:00:00+01:00", "endDate": "2026-09-15T16:00:00+01:00", "eventStatus": "https://schema.org/EventScheduled", "eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode", "location": { "@type": "Place", "name": "Acme Studio", "address": { "@type": "PostalAddress", "streetAddress": "12 High Street", "addressLocality": "Brighton", "postalCode": "BN1 1AA", "addressCountry": "GB" } }, "organizer": { "@type": "Organization", "name": "Acme Ceramics", "url": "https://example.com" }, "offers": { "@type": "Offer", "url": "https://example.com/events/pottery-2026/book", "price": "75.00", "priceCurrency": "GBP", "availability": "https://schema.org/InStock", "validFrom": "2026-06-01T00:00:00+01:00" } } </script>
4. LocalBusiness schema
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "LocalBusiness", "name": "Acme Ceramics Studio", "image": "https://example.com/studio.jpg", "telephone": "+44-1273-555-0123", "email": "hello@example.com", "address": { "@type": "PostalAddress", "streetAddress": "12 High Street", "addressLocality": "Brighton", "postalCode": "BN1 1AA", "addressCountry": "GB" }, "geo": { "@type": "GeoCoordinates", "latitude": 50.8225, "longitude": -0.1372 }, "openingHoursSpecification": [ { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"], "opens": "09:00", "closes": "17:00" }, { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Saturday"], "opens": "10:00", "closes": "16:00" } ], "url": "https://example.com" } </script>
5. Recipe schema
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Recipe", "name": "Simple Sourdough Bread", "author": { "@type": "Person", "name": "Jane Baker" }, "datePublished": "2026-01-15", "prepTime": "PT30M", "cookTime": "PT45M", "totalTime": "PT24H15M", "recipeYield": "1 loaf", "recipeCategory": "Bread", "recipeCuisine": "European", "recipeIngredient": [ "500g strong white bread flour", "10g fine salt", "350g water at 20°C", "100g active sourdough starter" ], "recipeInstructions": [ { "@type": "HowToStep", "name": "Mix", "text": "Combine flour and water, rest 1 hour." }, { "@type": "HowToStep", "name": "Add starter", "text": "Mix in starter and salt." }, { "@type": "HowToStep", "name": "Bulk ferment", "text": "Rest 4 hours with folds every 30 minutes." } ], "nutrition": { "@type": "NutritionInformation", "calories": "180 calories per slice" } } </script>
6. Where to place schema
JSON-LD in <head> reaches all agents: <!DOCTYPE html> <html> <head> <title>...</title> <meta name="description" content="..." /> <script type="application/ld+json"> { ... } </script> </head> Avoid: schema injected via JS after page load — agents fetching raw HTML miss it. Schema must be server-rendered.
7. Validate
Step 1 Schema.org validator validator.schema.org — paste URL or markup. Parses JSON-LD and flags syntax errors. Step 2 Google Rich Results Test search.google.com/test/rich-results — checks required + recommended properties for Google's rich snippets. Same checks help agent extraction. <div
Tip: 💡 The 20% of schema that delivers 80% of agent value: Product.offers, Event.startDate, LocalBusiness.openingHoursSpecification, Recipe.recipeIngredient, JobPosting.baseSalary. Populate those properties accurately and most agent tasks succeed even on otherwise sparse pages.
Tags: agent-compat, agent schema, Product schema offers, Event schema, LocalBusiness hours, agent task data, structured data agents
Fix
Resolve aggregateRating validation errors and ensure your star ratings actually appear in Google rich snippets.
Run the Schema Debugger audit
Open the Schema Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-debugger, aggregate, rating
Fix
Fix blocked AI crawlers from the Agent Readiness audit. Allow GPTBot, ClaudeBot, PerplexityBot, Gemini in robots.txt. WAF rules and bot management.
Check access log for each crawler
# Past week of AI crawler hits for bot in GPTBot ClaudeBot PerplexityBot Google-Extended CCBot OAI-SearchBot; do count=$(grep -c "$bot" /var/log/nginx/access.log) echo "$bot: $count requests" done
Confirm they get 200, not 403
# Status codes for each crawler grep "GPTBot" /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c # Should be mostly 200s. If you see 403s, the WAF is blocking despite robots.txt # Pretend to be GPTBot and check if you get through curl -A "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.0; +https://openai.com/gptbot)" \ -I https://example.com/ # Should return 200 OK, not 403 or 429 # If 403: WAF blocks the user agent regardless of robots.txt # If 429: rate limit too aggressive
Re-run Agent Readiness
Crawler-access findings clear. Per-bot status matches your intended policy.
1. The major AI crawlers
Provider User-Agent Purpose OpenAI GPTBot Training data OpenAI OAI-SearchBot Live search (ChatGPT browsing) OpenAI ChatGPT-User User-triggered fetches Anthropic ClaudeBot Training data Anthropic Claude-Web Live retrieval Anthropic anthropic-ai Legacy training bot Google Google-Extended Bard/Gemini training (separate from Googlebot) Perplexity PerplexityBot Search + training Common Crawl CCBot Public dataset (used by many AI labs) Meta FacebookBot , Meta-ExternalAgent Llama training ByteDance Bytespider Doubao / TikTok AI Apple Applebot-Extended Apple Intelligence training DuckDuckGo DuckAssistBot Duck.ai answers
2. Decide policy
Allow all (recommended for most public sites) Maximises AI visibility — your content appears in ChatGPT, Claude, Perplexity, Gemini answers. Best for businesses that want to be cited and recommended. Allow live retrieval, block training OAI-SearchBot, Claude-Web, PerplexityBot can fetch for real-time answers — but GPTBot, ClaudeBot, Google-Extended can't train on your content. Middle ground for sites with proprietary content. Block all Maximum content protection — but invisible to AI answer engines. Trade-off: defending content vs being recommended.
3. Robots.txt patterns
Allow all AI crawlers (default state) # No specific rules needed — default allow User-agent: * Allow: / Allow search bots, block training bots User-agent: OAI-SearchBot Allow: / User-agent: ChatGPT-User Allow: / User-agent: Claude-Web Allow: / User-agent: PerplexityBot Allow: / # Block training-only bots User-agent: GPTBot Disallow: / User-agent: ClaudeBot Disallow: / User-agent: anthropic-ai Disallow: / User-agent: Google-Extended Disallow: / User-agent: CCBot Disallow: / Block all AI User-agent: GPTBot Disallow: / User-agent: ClaudeBot Disallow: / User-agent: Claude-Web Disallow: / User-agent: anthropic-ai Disallow: / User-agent: OAI-SearchBot Disallow: / User-agent: ChatGPT-User Disallow: / User-agent: PerplexityBot Disallow: / User-agent: Google-Extended Disallow: / User-agent: CCBot Disallow: / User-agent: FacebookBot Disallow: / User-agent: Meta-ExternalAgent Disallow: / User-agent: Bytespider Disallow: / User-agent: Applebot-Extended Disallow: / User-agent: DuckAssistBot Disallow: /
4. WAF / Bot management layer
robots.txt only works if bots reach it. If your WAF blocks AI crawlers before they read robots.txt, robots.txt has no effect. Cloudflare Cloudflare added AI Audit + AI Crawl Control in 2024. Settings → Bots → AI Crawl Control. Allow/block each bot family individually. Overrides "Bot Fight Mode" for these specific agents. AWS WAF # Allow AI crawler user agents { "Name": "AllowAICrawlers", "Priority": 10, "Action": { "Allow": {} }, "Statement": { "OrStatement": { "Statements": [ { "ByteMatchStatement": { "SearchString": "GPTBot", "FieldToMatch": { "SingleHeader": { "Name": "user-agent" }}, "TextTransformations": [{ "Priority": 0, "Type": "NONE" }], "PositionalConstraint": "CONTAINS" }}, { "ByteMatchStatement": { "SearchString": "ClaudeBot", "FieldToMatch": { "SingleHeader": { "Name": "user-agent" }}, "TextTransformations": [{ "Priority": 0, "Type": "NONE" }], "PositionalConstraint": "CONTAINS" }} ] } } } nginx rate-limit safety net # If AI crawlers are too aggressive, rate-limit per IP limit_req_zone $binary_remote_addr zone=aibots:10m rate=10r/s; location / { if ($http_user_agent ~* "(GPTBot|ClaudeBot|PerplexityBot|Google-Extended|CCBot)") { set $is_aibot 1; } if ($is_aibot = 1) { limit_req zone=aibots burst=20 nodelay; } }
5. Verify with logs
Step 1 Check access log for each crawler # Past week of AI crawler hits for bot in GPTBot ClaudeBot PerplexityBot Google-Extended CCBot OAI-SearchBot; do count=$(grep -c "$bot" /var/log/nginx/access.log) echo "$bot: $count requests" done Step 2 Confirm they get 200, not 403 # Status codes for each crawler grep "GPTBot" /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c # Should be mostly 200s. If you see 403s, the WAF is blocking despite robots.txt
7. Verify with checker
Step 1 Re-run Agent Readiness Crawler-access findings clear. Per-bot status matches your intended policy. <div
Tip: 💡 The default "allow all AI" position serves most businesses. Block-everything is rarely the right call unless you have strict proprietary-content requirements. Selective block (train: no, search: yes) is the middle path for content sites worried about training but wanting AI search visibility.
Tags: agent-readiness, fix AI crawler, GPTBot allow, ClaudeBot robots, PerplexityBot, AI bot access, agent crawler robots.txt, AI training opt-in
Fix
Close the gap when competitors are cited by ChatGPT or Perplexity and you aren’t: identify content, schema and authority differences.
Run the Competitor Analysis audit
Open the Competitor Analysis Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Competitor Analysis audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: competitor-analysis, ai, gaps
Fix
Stop AI engines hallucinating false facts about your brand, products or people: tighten schema, add entity references and publish authoritative sources.
Run the AI Schema audit
Open the AI Schema Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your AI Schema audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ai-schema, ai, hallucinations
Fix
Fix complete invisibility in AI engines. 6-month foundation: technical SEO, structured data, brand citations. The path to first AI citation.
Run the AI Visibility Tracker weekly
Same 20-30 target prompts every week. Track: cited / mentioned / recommended / invisible per engine. Trend over time tells you whether efforts are working.
When you get first citation, study it
Which page was cited? Which sentence was quoted? Which prompt triggered it? This reveals what's working — replicate the pattern on adjacent content.
Identify which engine cites first
Perplexity usually first (curated source pool, fast adoption). Then ChatGPT and Claude. Gemini last (heavier Google-Search dependence). Use Perplexity citations as leading indicator. Month 1: Foundation complete (crawler access, technical baseline) Month 2: Schema coverage and content structure Month 3: Off-site brand-mention campaign starts producing Month 4: First sporadic AI citations (usually Perplexity) Month 5-6: Citation frequency rises across multiple engines Month 9+: Established as a source AI engines pull from regularly Total: ~6 months to "regularly cited", ~12 months to "category-authority"
1. Diagnose: why are you invisible?
Before fixing, identify which gap matters most. Run these checks: # 1. Can AI crawlers fetch you? curl -A "GPTBot/1.0" -I https://example.com/ curl -A "ClaudeBot/1.0" -I https://example.com/ curl -A "PerplexityBot/1.0" -I https://example.com/ # Expect 200. 403 = WAF blocking, 5xx = origin issue # 2. Do search engines index you? site:example.com # in Google, Bing # Expect dozens to thousands of indexed pages # 3. Do you have schema? curl -s https://example.com/ | grep -c "application/ld+json" # Expect 1+ per page # 4. Are you mentioned anywhere off-site? "example.com" -site:example.com # in Google # Expect citations on third-party sites, forums, news Most invisible sites fail at least 2 of these. Address the gaps in order.
2. Phase 1 (weeks 1-4): foundation
Crawler access Verify robots.txt allows AI crawlers; verify WAF doesn't block. See the AI crawler access fix for per-WAF patterns. Technical SEO baseline HTTPS enforced, no mixed content Mobile-friendly, responsive Fast TTFB (< 800ms) — see page speed fixes Valid XML sitemap submitted to GSC and Bing Webmaster Tools Clean canonical tags, no canonical conflicts No accidental noindex on important pages Server-render critical content JS-only content is invisible to most AI crawlers. If your site is a CSR SPA, fix JS-only content before anything else — nothing downstream works without this.
3. Phase 2 (weeks 5-12): schema and content structure
Schema coverage Add JSON-LD to every page type. Minimum coverage: Organization → site-wide, with logo, sameAs, contactPoint WebSite → site-wide, with SearchAction BreadcrumbList → every non-home page Article → every blog/news/guide page Person (authors) → every author has a profile page with Person schema Product → every product page (ecommerce) LocalBusiness → if you serve a location FAQPage → wherever you have Q&A content HowTo → wherever you have step-by-step content Author trust signals AI engines weight author authority heavily. Anonymous content struggles to earn citations. Build proper author trust signals — named authors, dated bios with credentials, sameAs to LinkedIn/professional profiles, Person schema linked via @id. Content extractability Restructure existing content to be quotable. TL;DR boxes, query-as-H2 pattern, short definitive sentences, structured lists. See content extractability for the structural patterns.
4. Phase 3 (weeks 13-26): off-site brand citations
AI engines weight unlinked brand mentions across the web. Build presence where your audience exists: Reddit and Quora Answer questions in your category — substantive, helpful, not promotional Build karma in 3-5 subreddits, then occasionally mention your work when genuinely relevant Don't astroturf — AI engines (and Reddit moderators) detect patterns Comparison and review sites G2, Capterra, TrustPilot — for B2B SaaS Wirecutter, RTINGS, comparison roundups — for consumer products Industry-specific directories Podcasts and video Guest on relevant podcasts — show notes mention your brand YouTube interviews — descriptions mention your brand Transcripts become AI-extractable content Editorial press HARO, Qwoted, SourceBottle — pitch expert commentary Industry trade publications — bylined articles, expert quotes Each editorial mention is a trust signal AI engines weight
5. Phase 4 (weeks 13+): measure and iterate
Step 1 Run the AI Visibility Tracker weekly Same 20-30 target prompts every week. Track: cited / mentioned / recommended / invisible per engine. Trend over time tells you whether efforts are working. Step 2 When you get first citation, study it Which page was cited? Which sentence was quoted? Which prompt triggered it? This reveals what's working — replicate the pattern on adjacent content. Step 3 Identify which engine cites first Perplexity usually first (curated source pool, fast adoption). Then ChatGPT and Claude. Gemini last (heavier Google-Search dependence). Use Perplexity citations as leading indicator.
6. What to NOT do
AI-generated bulk content — produces volume without authority; AI engines detect and discount Schema spam — fake aggregateRating, invented Person entities; detected and penalised Astroturfed mentions — bought Reddit accounts, paid review farms; identified via pattern detection Cloaked content for AI — different content to AI vs humans; risky and easily detected Quick-fix consultants — anyone promising AI citations in < 30 days is selling false hope
Tip: 💡 The compounding insight: every brand citation, every author article with proper schema, every Reddit answer adds to a cumulative entity signal. Year 1 is hard; year 2 the same effort produces 3x the citations because the foundation has accumulated. Most invisible sites give up at month 4 — right before the curve bends. Stay.
Tags: ai-visibility, AI invisibility, AI citation, ChatGPT visibility, Perplexity invisible, AEO foundation, AI answer engine
Fix
Optimise for queries users actually ask AI engines, not Google. Prompts are conversational, longer, intent-rich. The prompt-research workflow.
1. How AI prompts differ
Aspect Google query AI prompt Length 2-4 words 15-50 words Style Keyword fragment Conversational sentence Context None Personal situation included Intent Often ambiguous Usually explicit Multi-part Rare Common Voice Telegraphic Natural English Same intent, two phrasings: Google query: "best CRM small business" AI prompt: "I run a 15-person consultancy in London with mostly inbound leads and no IT team. We use Gmail and Slack. What CRM should I look at first and why?" The AI prompt embeds context (size, location, lead source, tech stack) the user wouldn't type into Google. Content that answers the AI prompt fully — addressing each contextual element — wins citation. Content optimised for the Google keyword may miss entirely.
2. Where to find real AI prompts
Source 1: Customer interviews 5-10 customer interviews, one question each: "If you were to ask ChatGPT or Claude about [your category], how would you phrase it? Show me your actual prompt." Don't accept abstractions. Get the literal text they'd type. Patterns emerge after 5 interviews. Source 2: Support ticket mining Read 100 recent support tickets. Look for: - Questions phrased in user's own words - Context they include (size, role, tech stack) - Multi-part questions These are your AI-prompt corpus. Real users, real questions, already in their voice. Source 3: Reddit / forum mining Reddit threads in r/[your-category] where users post: "I asked ChatGPT about X and got [response]" These contain the prompts users actually use. Search: site:reddit.com "I asked ChatGPT" [your category] site:reddit.com "I asked Claude" [your category] site:reddit.com "Perplexity said" [your category] Source 4: Your own AI chat logs If you operate any AI chatbot (support, sales, demo): - Anonymised conversation logs are pure prompt data - User intent is explicit in their first message - Patterns of multi-turn refinement show what they really want GDPR caveat: ensure user consent and anonymisation before mining. Source 5: Public share-link aggregators ChatGPT share links (chat.openai.com/share/...) get scraped and aggregated. Some tools (third-party AEO tools) provide prompt datasets. Verify quality before relying — public share-links skew technical and demo-heavy.
3. Build a target prompt set
20-50 prompts covering your category. Categorise: Intent type Example ───────────────────────────────────────────────────────────── Educational "How does X work?" Comparison "X vs Y, which is better for [context]?" Recommendation "I have [context], what should I use?" Troubleshooting "Why is [problem] happening with my X?" Decision support "Should I do X or Y given [context]?" Definition "What is X?" Pricing "How much does X cost for [context]?" Implementation "How do I set up X?" Aim for balanced coverage. Over-weighting one intent type means missing customer segments at other journey stages.
4. Align content with prompt patterns
Content that answers a 30-word AI prompt completely outperforms content optimised for the 3-word Google query. Restructure: Add context-rich sections For prompts mentioning "small team" / "B2B" / "[region]": Add an explicit "For small B2B teams in [region]..." section. AI engines match prompt context to page context. Example: "Recommendations vary by team size. For 5-15 person B2B teams in the UK, the top three options are..." Build comparison content "X vs Y" prompts dominate decision-stage queries. Don't just have a page about X; have explicit X-vs-Y pages that AI engines extract for comparison answers. URL pattern: /compare/x-vs-y/ Schema: include both as mentioned entities Address situational prompts "I have [situation], what should I do?" prompts need content that mirrors the situation. Pattern: <h2>If you're a [situation], start here</h2> <p>For [situation], we recommend [specific path because reasons that match the situation's constraints]...</p> AI engines match this exact pattern to user prompts.
5. Map prompts to pages
For each target prompt, identify the page that should win the citation: prompt: "I have a 12-person sales team selling B2B SaaS in the UK, what CRM should I look at first?" target page: /guides/crm-uk-b2b-small-team/ current content match: 60% gaps: - Doesn't mention UK explicitly (add) - "12-person" range not addressed (add 10-15 person section) - "B2B SaaS" segment generic (add segment-specific recommendation) priority: HIGH — high-intent prompt, content nearly there Some prompts will need new pages. Some will need expansion of existing pages. Some have no relevant page at all — those are content opportunities.
6. Track prompt-level performance
The AI Visibility Tracker can take your custom prompt set. Run weekly: Per prompt: - Cited? (your domain in response) - Mentioned? (your brand name without citation) - Recommended? (your brand recommended over alternatives) - Per engine: ChatGPT, Claude, Perplexity, Gemini, Copilot Trend monthly: - Per-prompt citation share over time - Which engines newly cite vs lose citation - Which prompts moved from "invisible" to "cited" Quarterly review: - Which content changes drove which prompt-level lifts - Where to invest next quarter <div
Tip: 💡 The prompt-set you choose IS your AEO strategy. Track too narrowly and you over-fit. Track too broadly and you can't measure progress. Start with 30 prompts covering your top customer intents; expand to 50 once you have monthly trend data. Don't change prompts — same set every week is what makes trends measurable.
Tags: ai-visibility, AI query match, prompt research, AEO queries, AI search vs Google, conversational queries
Fix
Fix dates in AI-generated schema: ISO 8601 format, timezone offsets, training-cutoff year errors. Override patterns for datePublished and dateModified.
Schema.org validator
validator.schema.org flags date-format errors as warnings. Run after generation.
Programmatic check
// ISO 8601 regex const ISO8601 = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$/; function validateSchemaDates(schema) { const errors = []; function walk(obj, path = '') { if (typeof obj !== 'object' || obj === null) return; for (const [k, v] of Object.entries(obj)) { if (k.match(/^(date|start|end|valid)[A-Z]/) && typeof v === 'string') { if (!ISO8601.test(v)) { errors.push(`${path}.${k}: not ISO 8601 "${v}"`); } } if (typeof v === 'object') walk(v, `${path}.${k}`); } } walk(schema); return errors; }
1. ISO 8601 format
Type Format Example Date only YYYY-MM-DD 2026-05-18 Datetime UTC YYYY-MM-DDTHH:MM:SSZ 2026-05-18T14:30:00Z Datetime + offset YYYY-MM-DDTHH:MM:SS±HH:MM 2026-05-18T15:30:00+01:00 Datetime with millis YYYY-MM-DDTHH:MM:SS.sssZ 2026-05-18T14:30:00.123Z
2. Bad patterns AI generators produce
Pattern 1: US-format dates "datePublished": "05/18/2026" ❌ MM/DD/YYYY "datePublished": "18/05/2026" ❌ DD/MM/YYYY "datePublished": "May 18, 2026" ❌ written format Pattern 2: Datetime without timezone "datePublished": "2026-05-18T14:30:00" ⚠️ ambiguous <!-- Parser assumes user-local; unpredictable across users --> Pattern 3: Training-cutoff stale year "datePublished": "2024-05-18" ❌ generator's training data is from 2024 <!-- Even if the page is 2026, the LLM defaulted to its cutoff year --> Pattern 4: Inconsistent precision "datePublished": "2026-05-18", "dateModified": "2026-05-18T14:30:00Z" ❌ mixed precision <!-- Both fields should match precision: both date or both datetime --> Pattern 5: Future dates "datePublished": "2027-12-01" ❌ scheduled date treated as published <!-- Schema datePublished is when it became publicly available, not authored -->
3. The correct pattern
{ "@type": "Article", "headline": "How to choose a CRM", "datePublished": "2026-01-15T09:00:00Z", "dateModified": "2026-04-22T14:30:00Z", "author": { "@type": "Person", "name": "Jane Baker" } } <!-- For an event scheduled in future --> { "@type": "Event", "name": "Workshop", "startDate": "2026-09-15T10:00:00+01:00", "endDate": "2026-09-15T16:00:00+01:00" }
4. Pass dates explicitly to the generator
Don't let the LLM guess from prose — feed it the real values: // Bad: rely on LLM to extract from "Published last week" const schema = await ai.generateSchema(htmlContent); // Good: pass explicit dates from your CMS const schema = await ai.generateSchema({ content: htmlContent, metadata: { datePublished: post.publishedAt.toISOString(), // ← from DB dateModified: post.updatedAt.toISOString(), // ← from DB author: { name: post.author.name, url: post.author.profileUrl } } }); // Generator must use the metadata, not infer from content
5. Override training-cutoff errors
If your generator can't be re-configured, post-process the output: function correctSchemaDates(schema, actualPublished, actualModified) { function walk(obj) { if (typeof obj !== 'object' || obj === null) return; if (obj.datePublished) { obj.datePublished = actualPublished; // ← always override } if (obj.dateModified) { obj.dateModified = actualModified; } for (const v of Object.values(obj)) { if (typeof v === 'object') walk(v); } } walk(schema); return schema; } // In your CMS publish pipeline const generated = await ai.generateSchema(content); const corrected = correctSchemaDates(generated, post.publishedAt, post.updatedAt); embedInPage(corrected);
6. dateModified discipline
Set dateModified ONLY on genuine content change. Track in your CMS: // On save async function savePost(postId, newContent) { const old = await db.getPost(postId); const contentHash = hash(newContent); // Only update dateModified if content actually changed if (contentHash !== old.contentHash) { await db.updatePost(postId, { content: newContent, contentHash, dateModified: new Date() }); } // Otherwise: still save (typo fix, image swap) but don't bump dateModified }
7. Common date fields by schema type
Schema type Date fields Article / BlogPosting datePublished, dateModified, dateCreated Event startDate, endDate, previousStartDate Product / Offer priceValidUntil, validFrom, validThrough Recipe datePublished, dateModified JobPosting datePosted, validThrough Course startDate, endDate, courseSchedule Review datePublished, dateCreated
8. Validate
Step 1 Schema.org validator validator.schema.org flags date-format errors as warnings. Run after generation. Step 2 Programmatic check // ISO 8601 regex const ISO8601 = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$/; function validateSchemaDates(schema) { const errors = []; function walk(obj, path = '') { if (typeof obj !== 'object' || obj === null) return; for (const [k, v] of Object.entries(obj)) { if (k.match(/^(date|start|end|valid)[A-Z]/) && typeof v === 'string') { if (!ISO8601.test(v)) { errors.push(`${path}.${k}: not ISO 8601 "${v}"`); } } if (typeof v === 'object') walk(v, `${path}.${k}`); } } walk(schema); return errors; } <div
Tip: 💡 Stale dates from training cutoff are the silent killer — schema validates, looks correct in casual review, but AI engines see your "fresh" content as a year old and rank it accordingly. Always override datePublished and dateModified from your CMS truth, never trust the generator's inference.
Tags: ai-schema, AI schema dates, ISO 8601 schema, datePublished AI, training cutoff dates, timezone schema, schema date format
Fix
Fix duplicate JSON-LD from AI generator + SEO plugin conflict. Consolidate into single @graph block, disable redundant sources, prevent schema cannibalisation.
View source
Browser → View Page Source → Cmd/Ctrl+F → search application/ld+json . Count occurrences. 3+ blocks warrants investigation.
Extract and compare
curl -s https://example.com/blog/post-1 | \ python3 -c " import json, re, sys html = sys.stdin.read() blocks = re.findall(r'<script type=\"application/ld\+json\">(.+?)</script>', html, re.DOTALL) print(f'Found {len(blocks)} JSON-LD blocks') types_seen = {} for i, b in enumerate(blocks): try: data = json.loads(b) items = data if isinstance(data, list) else [data] for item in items: graph = item.get('@graph', [item]) for entity in graph if isinstance(graph, list) else [graph]: t = entity.get('@type', 'unknown') types_seen.setdefault(str(t), []).append(i) except: pass print('Entity types and which blocks contain them:') for t, blocks_with in types_seen.items(): marker = ' DUPLICATE' if len(blocks_with) > 1 else '' print(f' {t}: blocks {blocks_with}{marker}') " <!-- Yoast --> <script type="application/ld+json"> { "@context":"https://schema.org", "@type":"Article", "headline":"How to choose a CRM", "author":{"@type":"Person","name":"Admin"} } </script> <!-- AI generator --> <script type="application/ld+json"> { "@context":"https://schema.org", "@type":"Article", "headline":"How to choose a CRM", "author":{"@type":"Person","name":"Jane Baker","jobTitle":"Sales Lead"} } </script> <!-- Two Articles. Different authors. Google picks one or neither. --> <!-- Different @type, same entity --> {"@type":"BlogPosting", "headline":"..."} <!-- from plugin --> {"@type":"Article", "headline":"..."} <!-- from generator --> <!-- BlogPosting is subtype of Article. Two entities, one truth. --> <!-- Plugin --> {"@type":"Organization","name":"Acme Corp","logo":"https://example.com/logo.png"} <!-- Generator (no logo) --> {"@type":"Organization","name":"Acme"} <!-- Two Organization entities. Trust signal weakens. --> # Yoast: Settings → Search Appearance → Content Types → "Display this content type in search results" + schema toggle # Disable schema for the post type your AI generator handles # Rank Math: Titles & Meta → Post Types → choose type → "Schema Markup" → None # Then AI generator is the only source for that content type <script type="application/ld+json"> { "@context": "https://schema.org", "@graph": [ { "@type": "Organization", "@id": "https://example.com/#organization", "name": "Acme Corp", "url": "https://example.com", "logo": { "@type": "ImageObject", "url": "https://example.com/logo.png" } }, { "@type": "WebSite", "@id": "https://example.com/#website", "url": "https://example.com", "name": "Acme", "publisher": { "@id": "https://example.com/#organization" } }, { "@type": "Article", "@id": "https://example.com/blog/post-1#article", "headline": "How to choose a CRM", "datePublished": "2026-01-15T09:00:00Z", "author": { "@type": "Person", "@id": "https://example.com/authors/jane-baker#person", "name": "Jane Baker" }, "publisher": { "@id": "https://example.com/#organization" }, "isPartOf": { "@id": "https://example.com/#website" } }, { "@type": "BreadcrumbList", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://example.com" }, { "@type": "ListItem", "position": 2, "name": "Blog", "item": "https://example.com/blog" }, { "@type": "ListItem", "position": 3, "name": "How to choose a CRM", "item": "https://example.com/blog/post-1" } ] } ] } </script> # Config [plugin] emit_types = ["Organization", "WebSite", "BreadcrumbList"] disable_types = ["Article", "BlogPosting", "Product", "Recipe", "HowTo", "FAQPage"] [ai_generator] emit_types = ["Article", "BlogPosting", "Product", "Recipe", "HowTo", "FAQPage"] disable_types = ["Organization", "WebSite", "BreadcrumbList"] # Each source emits only what it's assigned # Reference the other's entities by @id # Multiple JSON-LD blocks but no duplicate entities // In your theme's functions.php // Disable Yoast Article schema, keep its Organization add_filter('wpseo_schema_graph_pieces', function($pieces, $context) { return array_filter($pieces, function($piece) { $class = get_class($piece); // Remove Article-related pieces return !preg_match('/Article|BlogPosting/i', $class); }); }, 10, 2); // Then your AI-generated schema is the only Article source
Rich Results Test
search.google.com/test/rich-results — should report ONE Article, ONE Organization, ONE BreadcrumbList. Multiple of the same type = duplicate.
Schema validator
validator.schema.org — flags ambiguous @id references and conflicting entity definitions.
Tip: 💡 The @graph + @id pattern is the gold standard for sites with multiple schema sources. Each piece references others by @id rather than re-declaring them. Yoast and modern SEO plugins already use this pattern internally — extend it to include AI-generated content schema rather than emitting separately.
Tags: ai-schema, duplicate schema, Yoast schema conflict, AI schema duplicates, JSON-LD graph, schema consolidation
Fix
Fix entity references lost in AI schema. Convert string authors to Person entities, link via @id, build closed-graph for Article and Organization signals.
Check every @id reference resolves
function validateGraphReferences(graph) { const defined = new Set(); const referenced = new Set(); function walk(obj) { if (!obj || typeof obj !== 'object') return; if (obj['@id'] && Object.keys(obj).length > 1) { // Has @id and other properties = definition defined.add(obj['@id']); } else if (obj['@id']) { // Only @id = reference referenced.add(obj['@id']); } for (const v of Object.values(obj)) { if (typeof v === 'object') walk(v); } } walk(graph); const unresolved = [...referenced].filter(id => !defined.has(id)); return { defined, referenced, unresolved }; }
Rich Results Test
search.google.com/test/rich-results — should report Article with Person author (not string), Organization publisher (not string). If still strings, check generator output and post-processing.
1. String vs entity reference
Bad: string author { "@type": "Article", "headline": "How to choose a CRM", "author": "Jane Baker", ❌ string "publisher": "Acme Corp" ❌ string } OK: inline entity { "@type": "Article", "headline": "How to choose a CRM", "author": { ⚠️ inline (works, doesn't link) "@type": "Person", "name": "Jane Baker" }, "publisher": { "@type": "Organization", "name": "Acme Corp" } } Best: entity with @id, defined once, referenced everywhere { "@context": "https://schema.org", "@graph": [ { "@type": "Person", "@id": "https://example.com/authors/jane-baker#person", "name": "Jane Baker", "jobTitle": "Sales Operations Lead", "url": "https://example.com/authors/jane-baker", "sameAs": [ "https://linkedin.com/in/janebaker", "https://twitter.com/janebaker" ] }, { "@type": "Organization", "@id": "https://example.com/#organization", "name": "Acme Corp", "url": "https://example.com", "logo": { "@type": "ImageObject", "url": "https://example.com/logo.png" } }, { "@type": "Article", "headline": "How to choose a CRM", "author": { "@id": "https://example.com/authors/jane-baker#person" }, "publisher": { "@id": "https://example.com/#organization" } } ] }
2. Common bare-string fields
author → Person or Organization publisher → Organization brand → Brand or Organization manufacturer → Organization contributor → Person or Organization creator → Person or Organization editor → Person copyrightHolder → Person or Organization location → Place itemReviewed → Thing (any specific type)
3. Post-process generator output
If your generator can't be configured to use @id references, transform after generation: function entityifyAuthors(schema, knownPeople, knownOrgs) { function walk(obj) { if (!obj || typeof obj !== 'object') return obj; // Fields that should be entities for (const field of ['author', 'publisher', 'brand', 'creator', 'editor']) { if (typeof obj[field] === 'string') { const name = obj[field]; // Look up known entity by name const person = knownPeople[name]; const org = knownOrgs[name]; if (person) { obj[field] = { '@id': person.id }; } else if (org) { obj[field] = { '@id': org.id }; } else { // No match — at least make it an inline entity obj[field] = { '@type': field === 'publisher' ? 'Organization' : 'Person', 'name': name }; } } } for (const v of Object.values(obj)) { if (typeof v === 'object') walk(v); } return obj; } return walk(schema); } // Usage const knownPeople = { 'Jane Baker': { id: 'https://example.com/authors/jane-baker#person' }, // ... load from CMS }; const knownOrgs = { 'Acme Corp': { id: 'https://example.com/#organization' } }; const fixed = entityifyAuthors(generatedSchema, knownPeople, knownOrgs);
4. Site-wide entity catalogue
Define each entity once, in one place, used everywhere: // /lib/schema-entities.js export const ORG = { '@type': 'Organization', '@id': 'https://example.com/#organization', 'name': 'Acme Corp', 'url': 'https://example.com', 'logo': { '@type': 'ImageObject', 'url': 'https://example.com/logo.png', 'width': 600, 'height': 60 }, 'sameAs': [ 'https://linkedin.com/company/acme', 'https://twitter.com/acmecorp' ] }; export const WEBSITE = { '@type': 'WebSite', '@id': 'https://example.com/#website', 'url': 'https://example.com', 'name': 'Acme', 'publisher': { '@id': 'https://example.com/#organization' } }; // Per-author records, generated from CMS export function personEntity(author) { return { '@type': 'Person', '@id': `https://example.com/authors/${author.slug}#person`, 'name': author.name, 'url': `https://example.com/authors/${author.slug}`, 'jobTitle': author.jobTitle, 'image': author.imageUrl, 'sameAs': author.profiles // [linkedin, twitter, github] }; }
5. Build the @graph per page
// In your article render path import { ORG, WEBSITE, personEntity } from '@/lib/schema-entities'; function articleGraph(article) { const author = personEntity(article.author); return { '@context': 'https://schema.org', '@graph': [ ORG, WEBSITE, author, { '@type': 'Article', '@id': `${article.url}#article`, 'headline': article.title, 'datePublished': article.publishedAt, 'dateModified': article.modifiedAt, 'author': { '@id': author['@id'] }, 'publisher': { '@id': ORG['@id'] }, 'isPartOf': { '@id': WEBSITE['@id'] }, 'mainEntityOfPage': article.url } ] }; } // Output as one JSON-LD block per page <script type="application/ld+json"> {JSON.stringify(articleGraph(article))} </script>
6. External entity references
Reference well-known entities by their canonical URL — Wikidata, official sites: { "@type": "Article", "about": { "@type": "Thing", "@id": "https://en.wikipedia.org/wiki/Customer_relationship_management" }, "mentions": [ { "@id": "https://www.salesforce.com/#organization" }, { "@id": "https://www.hubspot.com/#organization" } ] } // AI engines and Google often verify external @id targets // Where they resolve to actual schema or recognised entities, signal strengthens
7. Validate the closed graph
Step 1 Check every @id reference resolves function validateGraphReferences(graph) { const defined = new Set(); const referenced = new Set(); function walk(obj) { if (!obj || typeof obj !== 'object') return; if (obj['@id'] && Object.keys(obj).length > 1) { // Has @id and other properties = definition defined.add(obj['@id']); } else if (obj['@id']) { // Only @id = reference referenced.add(obj['@id']); } for (const v of Object.values(obj)) { if (typeof v === 'object') walk(v); } } walk(graph); const unresolved = [...referenced].filter(id => !defined.has(id)); return { defined, referenced, unresolved }; } Step 2 Rich Results Test search.google.com/test/rich-results — should report Article with Person author (not string), Organization publisher (not string). If still strings, check generator output and post-processing. <div
Tip: 💡 The compound win: when every article references the same Person @id for Jane, Google and AI engines build ONE entity from Jane's collected work. After 20 articles, Jane is a known author entity — citation weight rises, AI engines start surfacing her by name when asked about her topics. Without @id, each article's "Jane Baker" is a new string with no history.
Tags: ai-schema, schema references, Person entity AI, Organization entity schema, closed graph schema, @id schema linking
Fix
Add the required schema fields AI engines depend on for accurate citation: author, datePublished, dateModified, mainEntity.
Run the AI Schema audit
Open the AI Schema Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your AI Schema audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ai-schema, ai, schema, required
Fix
Fix AI schema valid on schema.org but failing Google Rich Results. Add recommended properties — image, aggregateRating, offers — beyond bare minimums.
Rich Results Test
search.google.com/test/rich-results — paste URL or markup. Reports both errors (block rich results) and warnings (block enhanced snippets). Target: 0 errors, 0 warnings.
Search Console enhancement reports
Search Console → Enhancements → per content type (Products, Recipes, Articles, etc). Aggregate view of what's valid, what has warnings, what's invalid across your indexed pages. Trend over time tells you whether deploys are improving or regressing.
Programmatic check at build time
// Map of recommended properties per type const RECOMMENDED = { Article: ['image', 'dateModified', 'publisher', 'mainEntityOfPage'], Product: ['aggregateRating', 'brand', 'sku', 'offers.priceValidUntil'], Recipe: ['author', 'prepTime', 'cookTime', 'recipeYield', 'nutrition'], Event: ['endDate', 'image', 'offers', 'organizer'], LocalBusiness: ['telephone', 'openingHoursSpecification', 'geo', 'priceRange'] }; function checkRecommended(schema) { const warnings = []; function walk(obj, path = '') { if (!obj || typeof obj !== 'object') return; const type = Array.isArray(obj['@type']) ? obj['@type'][0] : obj['@type']; if (RECOMMENDED[type]) { for (const prop of RECOMMENDED[type]) { const val = prop.includes('.') ? getNested(obj, prop) : obj[prop]; if (!val) warnings.push(`${path}${type}: missing recommended "${prop}"`); } } for (const [k, v] of Object.entries(obj)) { if (typeof v === 'object') walk(v, `${path}${k}.`); } } walk(schema); return warnings; }
1. Required vs recommended
schema.org REQUIRED → minimum for the type to validate Google REQUIRED → minimum for rich results (stricter) Google RECOMMENDED → strengthens eligibility, improves snippet Google PREFERRED → optimal rich-snippet display AI generators usually hit schema.org REQUIRED. Hitting Google RECOMMENDED requires explicit data passed in.
2. Per-type gaps (Article)
Property Required Recommended headline ✓ datePublished ✓ author (Person) ✓ image (1200×630+ ideal) ✓ dateModified ✓ publisher (Organization) ✓ mainEntityOfPage ✓
3. Per-type gaps (Product)
Property Required Recommended name ✓ image ✓ offers (price, currency, availability) ✓ aggregateRating ✓ (unlocks star snippet) review ✓ brand ✓ sku / gtin / mpn ✓ offers.priceValidUntil ✓ offers.shippingDetails ✓ offers.hasMerchantReturnPolicy ✓
4. Per-type gaps (Recipe)
Property Required Recommended name ✓ image ✓ recipeIngredient ✓ recipeInstructions ✓ author ✓ datePublished ✓ prepTime ✓ cookTime ✓ totalTime ✓ recipeYield ✓ nutrition ✓ (unlocks nutrition snippet) aggregateRating ✓ (unlocks star snippet) video ✓ (video carousel eligibility)
5. Per-type gaps (Event)
Property Required Recommended name ✓ startDate ✓ location (Place) ✓ endDate ✓ image ✓ description ✓ offers (price, availability, url) ✓ (unlocks ticket snippet) performer ✓ organizer ✓ eventStatus ✓ eventAttendanceMode ✓
6. Per-type gaps (LocalBusiness)
Property Required Recommended name ✓ address (PostalAddress) ✓ telephone ✓ image ✓ openingHoursSpecification ✓ geo (lat/long) ✓ priceRange ✓ aggregateRating ✓ servesCuisine (FoodEstablishment) ✓ menu (FoodEstablishment) ✓
7. Source the data, not the schema
AI generators don't invent data — they format what you give them. The fix is feeding the generator the missing fields: // Bad: generator infers from prose const schema = await ai.generateProductSchema(htmlContent); // Output may have name, price, but no aggregateRating, no shipping // Good: explicit data from product DB const product = await db.getProduct(productId); const reviews = await db.getReviewSummary(productId); const shipping = await calculateShipping(productId); const schema = await ai.generateProductSchema({ content: htmlContent, data: { name: product.name, sku: product.sku, gtin: product.gtin, brand: product.brand, image: product.images, offers: { price: product.price, priceCurrency: product.currency, priceValidUntil: product.priceValidUntil, availability: product.stockStatus, shippingDetails: shipping, hasMerchantReturnPolicy: product.returnPolicy }, aggregateRating: reviews.summary, review: reviews.recent.slice(0, 3) } }); // Now generator can output the recommended properties because the data exists
8. Post-process to add common recommendations
// Auto-fill commonly-missed properties from site config function enrichSchema(schema, siteConfig) { function walk(obj) { if (!obj || typeof obj !== 'object') return; if (obj['@type'] === 'Article' || obj['@type'] === 'BlogPosting') { // Ensure recommended fields if (!obj.publisher) obj.publisher = { '@id': siteConfig.orgId }; if (!obj.mainEntityOfPage) obj.mainEntityOfPage = obj.url || obj['@id']; if (!obj.dateModified && obj.datePublished) obj.dateModified = obj.datePublished; } if (obj['@type'] === 'Product') { // Common missing: brand from site, sku from URL if (!obj.brand && siteConfig.defaultBrand) obj.brand = siteConfig.defaultBrand; } for (const v of Object.values(obj)) { if (typeof v === 'object') walk(v); } } walk(schema); return schema; }
9. Validate eligibility
Step 1 Rich Results Test search.google.com/test/rich-results — paste URL or markup. Reports both errors (block rich results) and warnings (block enhanced snippets). Target: 0 errors, 0 warnings. Step 2 Search Console enhancement reports Search Console → Enhancements → per content type (Products, Recipes, Articles, etc). Aggregate view of what's valid, what has warnings, what's invalid across your indexed pages. Trend over time tells you whether deploys are improving or regressing. Step 3 Programmatic check at build time // Map of recommended properties per type const RECOMMENDED = { Article: ['image', 'dateModified', 'publisher', 'mainEntityOfPage'], Product: ['aggregateRating', 'brand', 'sku', 'offers.priceValidUntil'], Recipe: ['author', 'prepTime', 'cookTime', 'recipeYield', 'nutrition'], Event: ['endDate', 'image', 'offers', 'organizer'], LocalBusiness: ['telephone', 'openingHoursSpecification', 'geo', 'priceRange'] }; function checkRecommended(schema) { const warnings = []; function walk(obj, path = '') { if (!obj || typeof obj !== 'object') return; const type = Array.isArray(obj['@type']) ? obj['@type'][0] : obj['@type']; if (RECOMMENDED[type]) { for (const prop of RECOMMENDED[type]) { const val = prop.includes('.') ? getNested(obj, prop) : obj[prop]; if (!val) warnings.push(`${path}${type}: missing recommended "${prop}"`); } } for (const [k, v] of Object.entries(obj)) { if (typeof v === 'object') walk(v, `${path}${k}.`); } } walk(schema); return warnings; } <div
Tip: 💡 Don't chase 100% recommended-property completion if the data doesn't exist. Inventing aggregateRating with fake ratings is worse than omitting it — Google detects and penalises. Hit recommended for fields where you have real data; skip the rest cleanly. Partial recommended is better than complete fake.
Tags: ai-schema, Rich Results eligibility, Google Rich Results, recommended properties schema, aggregateRating, schema warnings
Fix
Add AI-friendly schema to Shopify: Product with offers and ratings, FAQPage on products, BreadcrumbList, Organization. Apps, theme code, validation.
Audit current schema output
View source on a product page. Search 'application/ld+json'. Shopify outputs Product, BreadcrumbList, Organization by default. Note which apps add their own JSON-LD.
Verify Product schema completeness
Required: name, image, description, offers (with price, priceCurrency, availability, url). Strongly recommended: brand, aggregateRating, review, sku, gtin (if applicable). Missing brand is the most common gap.
Install a review app with schema
Judge.me, Loox, Stamped, Yotpo — all output aggregateRating and Review schema correctly. Verify in Rich Results Test after installing. Reviews dramatically improve AI citation rates.
Add FAQPage schema
Apps: Easy FAQ, FAQ Page Pro, HelpCenter. Or edit theme product template to add static FAQ block + JSON-LD. Use for common pre-purchase questions per product type.
Fix schema plugin conflicts
Running multiple SEO apps (Smart SEO + JSON-LD for SEO + theme defaults) causes duplicate Product schemas. Choose one source; disable schema in others via app settings.
Add Organization schema
Shopify outputs basic Organization but often misses sameAs (social profiles), logo (must be 600x60 min), contactPoint. Edit theme.liquid to add complete Organization JSON-LD in head.
Validate every page type
Rich Results Test on: homepage (Organization, WebSite), collection (BreadcrumbList, ItemList), product (Product, BreadcrumbList, FAQ), blog (Article, BreadcrumbList).
Tags: ai-schema, Shopify schema, Shopify Product schema, Shopify JSON-LD, Shopify FAQ schema, Shopify review schema
Fix
Pick the right schema @type for AI citation: Article vs BlogPosting vs NewsArticle, Person vs Organization, Product vs SoftwareApplication.
Run the AI Schema audit
Open the AI Schema Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your AI Schema audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ai-schema, ai, schema, type
Fix
Fix wrong URLs in AI-generated schema. Convert relative to absolute, eliminate broken links, align with canonical, validate URLs resolve cleanly to 200 OK.
Extract URLs from schema
# From a deployed page curl -s https://example.com/products/widget | \ python3 -c " import json, re, sys html = sys.stdin.read() for m in re.finditer(r'<script type=\"application/ld\+json\">(.+?)</script>', html, re.DOTALL): data = json.loads(m.group(1)) # Walk and print every URL value def walk(o, path=''): if isinstance(o, dict): for k,v in o.items(): walk(v, f'{path}.{k}') elif isinstance(o, list): for i,v in enumerate(o): walk(v, f'{path}[{i}]') elif isinstance(o, str) and o.startswith('http'): print(f'{path}: {o}') walk(data) "
Test each URL responds 200
# Extract URLs, curl each, report non-200 while read url; do code=$(curl -s -o /dev/null -w "%{http_code}" "$url") [ "$code" != "200" ] && echo "BAD $code $url" done < schema-urls.txt # .github/workflows/schema-check.yml - name: Validate schema URLs run: | npm run build node scripts/extract-schema-urls.js > urls.txt while read url; do code=$(curl -sIo /dev/null -w "%{http_code}" "$url") if [ "$code" != "200" ]; then echo "::error::Schema URL broken: $code $url" exit 1 fi done < urls.txt
1. URL fields in schema
Every schema type has URL-typed properties. Common ones: "@id" → unique identifier (URL form) "url" → primary URL of the entity "mainEntityOfPage" → page where entity is primarily described "image" → image URL or ImageObject "logo" → organisation logo URL "sameAs" → array of external profile URLs "author.url" → author's profile page "publisher.url" → publisher homepage "offers.url" → buy/checkout URL "contentUrl" → direct file URL (for MediaObject) "embedUrl" → iframe-embeddable URL (for VideoObject) "telephone" → not URL but treated similarly
2. Bad patterns AI generators produce
Pattern 1: Relative URLs { "@type": "Product", "name": "Widget", "image": "/images/widget.jpg", ❌ relative "url": "/products/widget", ❌ relative "offers": { "url": "/products/widget#buy" ❌ relative } } Pattern 2: Staging or wrong-environment URLs { "url": "https://staging.example.com/products/widget" ❌ staging } { "image": "http://localhost:3000/images/widget.jpg" ❌ localhost } Pattern 3: Protocol-relative URLs (legacy) { "image": "//cdn.example.com/widget.jpg" ❌ protocol-relative } <!-- Worked in browsers; fails in schema parsers --> Pattern 4: URLs with tracking params { "url": "https://example.com/products/widget?utm_source=schema" ❌ tracked } <!-- Schema URL should be canonical, no UTM --> Pattern 5: Broken or redirected URLs { "url": "https://example.com/old-product-url" ❌ 301 redirect } { "image": "https://example.com/deleted-image.jpg" ❌ 404 }
3. The correct pattern
{ "@context": "https://schema.org", "@type": "Product", "@id": "https://example.com/products/widget#product", "name": "Blue Widget", "url": "https://example.com/products/widget", "image": [ "https://example.com/images/widget-1200x630.jpg", "https://example.com/images/widget-1080x1080.jpg" ], "mainEntityOfPage": "https://example.com/products/widget", "offers": { "@type": "Offer", "url": "https://example.com/products/widget#buy", "price": "29.00", "priceCurrency": "GBP" }, "brand": { "@type": "Brand", "name": "Acme", "url": "https://example.com/brand/acme" } } All URLs absolute, HTTPS, canonical (no UTM), pointing at the live domain.
4. Fix the generator config
If your AI schema generator outputs the wrong URLs, the fix is usually at config-time, not post-edit: // Pass base URL to generator const schema = await generateSchema({ content: pageContent, baseUrl: 'https://example.com', // ← always set currentUrl: req.canonicalUrl, // ← the page being annotated environment: 'production' // ← strip staging URLs }); // Generator can then resolve all relative URLs from baseUrl // and validate that produced URLs match production domain
5. Post-validation pass
// Run this on every generator output before deploy function validateSchemaUrls(schema, expectedDomain) { const errors = []; function walk(obj, path = '') { if (typeof obj !== 'object' || obj === null) return; for (const [key, value] of Object.entries(obj)) { const p = path ? `${path}.${key}` : key; if (typeof value === 'string' && looksLikeUrlField(key)) { // Must be absolute if (!value.startsWith('http://') && !value.startsWith('https://')) { errors.push(`${p}: relative URL "${value}"`); } // Must be HTTPS if (value.startsWith('http://')) { errors.push(`${p}: HTTP not HTTPS "${value}"`); } // Must be production domain if (!value.includes(expectedDomain)) { errors.push(`${p}: wrong domain "${value}"`); } } else if (Array.isArray(value) || typeof value === 'object') { walk(value, p); } } } walk(schema); return errors; } const URL_FIELDS = ['url', 'image', 'logo', 'sameAs', 'contentUrl', 'embedUrl', '@id']; function looksLikeUrlField(key) { return URL_FIELDS.includes(key) || key.endsWith('Url'); }
6. URL resolution check
Step 1 Extract URLs from schema # From a deployed page curl -s https://example.com/products/widget | \ python3 -c " import json, re, sys html = sys.stdin.read() for m in re.finditer(r'<script type=\"application/ld\+json\">(.+?)</script>', html, re.DOTALL): data = json.loads(m.group(1)) # Walk and print every URL value def walk(o, path=''): if isinstance(o, dict): for k,v in o.items(): walk(v, f'{path}.{k}') elif isinstance(o, list): for i,v in enumerate(o): walk(v, f'{path}[{i}]') elif isinstance(o, str) and o.startswith('http'): print(f'{path}: {o}') walk(data) " Step 2 Test each URL responds 200 # Extract URLs, curl each, report non-200 while read url; do code=$(curl -s -o /dev/null -w "%{http_code}" "$url") [ "$code" != "200" ] && echo "BAD $code $url" done < schema-urls.txt
7. CI integration
Make URL validation part of your build. Failing schema URLs should block deploy: # .github/workflows/schema-check.yml - name: Validate schema URLs run: | npm run build node scripts/extract-schema-urls.js > urls.txt while read url; do code=$(curl -sIo /dev/null -w "%{http_code}" "$url") if [ "$code" != "200" ]; then echo "::error::Schema URL broken: $code $url" exit 1 fi done < urls.txt <div
Tip: 💡 If you fix only one type of URL bug, fix relative URLs. They cause the most silent failures: schema validates against schema.org, looks fine in the rich results test, but AI engines and Google can't resolve them so the entity never builds correctly. Convert every URL field to absolute and most schema URL issues disappear.
Tags: ai-schema, AI schema URLs, schema absolute URLs, JSON-LD URL validation, schema canonical, schema broken URLs
Fix
Add AI-friendly schema to WordPress: FAQPage, HowTo, Article, Person, Organization. Plugin options, JSON-LD output, validation, common errors.
Step 1: Audit existing schema output
View source on a published post. Search for 'application/ld+json'. List the @type values being output. Most WordPress sites with an SEO plugin output Article, WebSite, BreadcrumbList by default.
Step 2: Choose a schema strategy
Either: use your SEO plugin's schema engine (Yoast, Rank Math, AIOSEO — all include schema since 2022) OR add a dedicated schema plugin (Schema Pro, WP SEO Structured Data Schema). Don't run both — they conflict and produce duplicate @id collisions.
Step 3: Configure default schemas per post type
In Rank Math: Titles & Meta → Posts/Pages/Products → set default Schema. In Yoast: Settings → Content Types → Schema. Set Article for blog posts, Product for WooCommerce, FAQ where applicable.
Step 4: Add per-post FAQ schema
Use Gutenberg's FAQ block (Yoast adds one, Rank Math adds one, AIOSEO adds one). Add Q&A pairs in the block — schema is generated automatically.
Step 6: Add Person and Organization schema
Person: configure under each user's profile (most SEO plugins add fields). Organization: configure globally in plugin settings. Link Article 'author' field to Person, 'publisher' to Organization.
Step 7: Validate and monitor
Run every page type through Google Rich Results Test and Schema.org validator. Set up monthly schema-health checks in Search Console → Enhancements.
Tags: ai-schema, WordPress schema, FAQPage WordPress, HowTo schema WordPress, WordPress JSON-LD, Yoast schema, Rank Math schema
Fix
Fix missing schema AI engines extract for citations. FAQPage, Article with Person author, Organization, HowTo, Product — implementation order.
1. Schema types ranked by AI extraction value
Schema What AI extracts Citation impact Organization Brand entity (name, logo, sameAs) Foundation — needed by everything else Person (author) Authority signals, credentials Very high — author-attributed citations Article + author + datePublished Content provenance, freshness Very high — date-sensitive queries FAQPage Q&A atoms ready for extraction Highest for "how does X work" queries HowTo Step-by-step instructions High for procedural queries Product + offers Price, availability, brand High for commerce queries LocalBusiness Address, hours, phone High for local-intent queries Recipe Ingredients, time, yield High for cooking queries Event Date, location, ticket URL High for "when is X" queries BreadcrumbList Site hierarchy context Modest — helps disambiguation
2. Foundation: Organization + WebSite
Add to every page (in template / layout): <script type="application/ld+json"> { "@context": "https://schema.org", "@graph": [ { "@type": "Organization", "@id": "https://example.com/#organization", "name": "Acme Corp", "url": "https://example.com", "logo": { "@type": "ImageObject", "url": "https://example.com/logo.png", "width": 600, "height": 60 }, "description": "B2B SaaS for [specific category]", "sameAs": [ "https://linkedin.com/company/acme", "https://twitter.com/acmecorp", "https://github.com/acmecorp" ], "contactPoint": { "@type": "ContactPoint", "telephone": "+44-1273-555-012", "contactType": "customer service", "email": "hello@example.com", "areaServed": "GB" } }, { "@type": "WebSite", "@id": "https://example.com/#website", "url": "https://example.com", "name": "Acme", "publisher": { "@id": "https://example.com/#organization" }, "potentialAction": { "@type": "SearchAction", "target": "https://example.com/search?q={search_term_string}", "query-input": "required name=search_term_string" } } ] } </script>
3. Per content type: Article + Person author
On every blog post, guide, news article: <script type="application/ld+json"> { "@context": "https://schema.org", "@graph": [ { "@type": "Person", "@id": "https://example.com/authors/jane-baker#person", "name": "Jane Baker", "jobTitle": "Sales Operations Lead", "url": "https://example.com/authors/jane-baker", "image": "https://example.com/authors/jane-baker.jpg", "sameAs": [ "https://linkedin.com/in/janebaker", "https://twitter.com/janebaker" ] }, { "@type": "Article", "@id": "https://example.com/blog/post-1#article", "headline": "How to choose a CRM", "description": "Decision framework for B2B teams choosing first CRM.", "image": "https://example.com/blog/post-1/cover.jpg", "datePublished": "2026-01-15T09:00:00Z", "dateModified": "2026-04-22T14:30:00Z", "author": { "@id": "https://example.com/authors/jane-baker#person" }, "publisher": { "@id": "https://example.com/#organization" }, "mainEntityOfPage": "https://example.com/blog/post-1" } ] } </script> The @id references close the graph — AI engines build the strongest entity signal when Article → author → Person and Article → publisher → Organization all link via @id. See schema references for details.
4. FAQPage — highest extraction yield
For any page with Q&A content, add FAQPage schema. Each Q/A becomes an extractable atom: <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [ { "@type": "Question", "name": "What does a CRM cost for a small team?", "acceptedAnswer": { "@type": "Answer", "text": "A CRM for a 5-25 person team typically costs £30-£150 per user per month. Entry-level options start at £15-£30; mid-tier at £50-£90; enterprise exceeds £150." } }, { "@type": "Question", "name": "Can I switch CRMs later?", "acceptedAnswer": { "@type": "Answer", "text": "Yes, but expect 3-6 weeks of migration work. Export from old, clean fields, map to new schema, import in batches. Plan for downtime in sales reporting during transition." } } ] } </script> Tip: 3-8 FAQ items per page is the sweet spot. Beyond that, AI engines treat the page as primarily Q&A and may downweight other content. Real questions only — fabricated FAQs detected and penalised.
5. HowTo for procedural content
{ "@context": "https://schema.org", "@type": "HowTo", "name": "How to set up Google Search Console", "totalTime": "PT30M", "step": [ { "@type": "HowToStep", "name": "Sign in to Search Console", "text": "Go to search.google.com/search-console and sign in with the Google account you'll use to manage the property." }, { "@type": "HowToStep", "name": "Add your property", "text": "Click 'Add property' and choose Domain (recommended) or URL prefix. Domain captures all subdomains and protocols." }, { "@type": "HowToStep", "name": "Verify ownership", "text": "Add the TXT record to your DNS or upload the HTML file. Wait 5 minutes after DNS update for verification to succeed." } ] } HowTo schema pairs with HowTo on-page content; AI engines extract the steps and cite them when answering "how do I X".
6. Product for ecommerce
{ "@type": "Product", "name": "Blue Widget", "image": "https://example.com/widget.jpg", "description": "Hand-thrown ceramic widget, 12cm.", "sku": "WID-BLU-12", "brand": { "@type": "Brand", "name": "Acme" }, "offers": { "@type": "Offer", "url": "https://example.com/widget", "priceCurrency": "GBP", "price": "29.00", "availability": "https://schema.org/InStock", "priceValidUntil": "2026-12-31" }, "aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.7", "reviewCount": "142" } } For commerce queries ("how much does X cost", "is X in stock"), Product schema with offers is what AI engines extract.
7. Implementation order for invisible sites
Week 1: Organization + WebSite + BreadcrumbList (site-wide template) Week 2: Article schema on all blog/guide pages, with named Person author Week 3: Per-content-type: Product (ecommerce), LocalBusiness (services), Recipe (food) Week 4: FAQPage schema on Q&A pages, HowTo on guides Week 5: Validate all with Rich Results Test, fix warnings Week 6: Monitor AI Visibility Tracker — expect first lift within 4-8 weeks
8. Validate
Rich Results Test — Google's view Schema.org validator — neutral parse Search Console → Enhancements — aggregate report across indexed pages Spot-check: ask Perplexity / ChatGPT a query your page targets, see if it cites you <div
Tip: 💡 Don't ship schema you haven't validated. One bad property silently breaks the entire entity, and you won't notice until citations don't appear. Run every new schema through Rich Results Test before deploying. Build a CI check that blocks deploy on schema regressions.
Tags: ai-visibility, AI schema visibility, FAQPage schema, Article Person schema, schema for AI citations, HowTo schema AI
Fix
Fix gaps in AI visibility measurement. Weekly prompt set, per-engine baseline, lift attribution. Build the measurement system that proves AEO investment pays.
1. Define a stable prompt set
30-50 prompts, locked after initial selection, run every week: Selection criteria: - Covers your major customer intents (educational, comparison, recommendation, troubleshooting) - Phrased as real users would phrase them (see how-to-fix-ai-query-match) - Mix of broad ("what is X") and specific ("X for [context]") - Includes brand prompts ("is [brand] good for X") - Includes competitor prompts ("X vs [competitor]") Locked once selected: - Don't swap prompts during the measurement period - Add new prompts as separate cohort if you want to track new intents - The original 30-50 are your measurement instrument Document each prompt: - exact text - target page on your site - expected citation pattern (you cited / mentioned / neither) - business priority (high / medium / low)
2. Track per-engine
Engine Why track separately ChatGPT (GPT-4) Largest user base, training-data citations + live browsing Claude Growing fast in B2B/SaaS, different training corpus Perplexity Citation-friendly, fastest to surface new sources Gemini Heavy Google Search dependence, different from others Microsoft Copilot Bing-powered, enterprise heavy Meta AI Different data source (Meta platforms) Citation patterns differ markedly per engine. Perplexity might cite you heavily while ChatGPT ignores you, or vice versa. Aggregate numbers hide these gaps. Track per-engine to find which engine's gap is the easiest opportunity.
3. Outcome categories
For each prompt × engine combination, record: CITED — your domain explicitly cited in response MENTIONED — brand name appears without citation RECOMMENDED — your brand recommended over alternatives COMPETITOR-CITED — direct competitor cited, you absent ALTERNATIVE-CITED — adjacent option cited, you absent NO-CITATION — answer with no specific source attribution INVISIBLE — answer doesn't mention category players These categories drive different responses. "Competitor-cited" is your most pressing problem (you're losing share to a specific player). "Invisible" is foundational work (category-level AEO building).
4. Weekly cadence, same day-of-week
Variation comes from model behaviour, your own changes, and external events (competitor news, algorithm tweaks). Reduce noise: Same day of week (e.g. every Monday) Similar time of day (within a 2-hour window) Same prompts in same order Same model versions (track when ChatGPT updates from GPT-4 to GPT-4 Turbo to GPT-5) Automated runs preferred over manual (humans drift)
5. Attribution: mark changes on the trend
Citation lifts that follow changes 2-8 weeks later are usually causal. Mark every change: Week 1: Baseline = 5% citation share Week 4: Added Person schema across 50 articles [MARKED] Week 8: Brand mention campaign on Reddit started [MARKED] Week 11: Citation share 9% — lift 4pp from schema? Week 14: Citation share 13% — lift from mentions? Week 17: New definitive guide published [MARKED] Week 22: Citation share 18% — guide effect? Pattern: post-change lift starting 2-8 weeks later Confidence: stronger when multiple change-cycles show consistent lag Single observations are weak evidence. After 3-4 cycles of marked-change → lift, attribution becomes credible enough to plan investment around.
6. Track competitor share too
Your citation share moves in a context. Competitor citation share is the reference: Per prompt × engine, track who's cited: - You - Direct competitor A - Direct competitor B - Adjacent player C - Wikipedia / generic source Build a stacked-bar view over time: - Total category citations per prompt - Your share % - Competitors' shares - Drift over months — are you gaining or losing share? This is more informative than your absolute citation count. Citation count can rise while share falls (category growing faster than you). Share is the real metric.
7. Periodic reporting structure
Weekly: data collection (automated) - per prompt × engine raw outcomes - delta from prior week Monthly: trend review (30 minutes) - prompts moved from invisible → cited - prompts moved from cited → invisible (investigate!) - per-engine consistency check - flag anomalies (sudden drops, sudden gains) Quarterly: investment review (2 hours) - which changes correlated with lifts - which efforts produced no measurable lift - what to do more of, what to drop - whether to expand prompt set with new intents - competitive share analysis Annually: methodology review - prompt set still representative? - engines covered still relevant? - measurement instrument still calibrated?
8. Common tracking mistakes
Changing prompts mid-period — destroys trend continuity Spot-checking instead of automating — confirmation bias creeps in Tracking only your brand — miss competitive context Daily tracking — noise overwhelms signal Ignoring engine-level variance — aggregate numbers hide gaps Not marking changes — can't attribute lifts later No competitor benchmark — your "growth" might be category growth <div
Tip: 💡 Treat AI visibility tracking like SEO ranking tracking 15 years ago — it's the new measurement instrument. The teams that build the tracking discipline now (12-18 months ahead of competitors) develop intuition for what works long before the market consensus forms. Tracking IS the competitive moat at this phase.
Tags: ai-visibility, AI visibility tracking, AEO measurement, citation tracking, prompt set methodology, AI lift attribution
Fix
Fix missing or poor image alt text from the Accessibility audit. Distinguish descriptive, decorative, and functional images and write alt that helps users.
Run the Accessibility audit
Run the Accessibility audit . Filter findings to image-alt issues. The audit reports three classes: missing alt attribute entirely, empty alt where content was needed, and meaningless alt like filename or "image".
Export CSV with URL, image filename, current alt
Sort by image filename. Patterns become obvious — the same icon used in 50 places likely needs the same alt fix everywhere, or proper decorative-image treatment. <!-- Decorative pattern --> <img src="/divider-pattern.svg" alt=""> <!-- Icon paired with text - icon decorative --> <button> <img src="/icon-trash.svg" alt=""> Delete </button> <img src="/products/red-shoe.jpg" alt="Red leather running shoe with white sole"> <img src="/team/jane-doe.jpg" alt="Jane Doe, Chief Engineering Officer"> <!-- Logo linking to homepage --> <a href="/"> <img src="/logo.svg" alt="aiwebpageseo home"> </a> <!-- Icon-only button --> <button> <img src="/icon-close.svg" alt="Close dialog"> </button> <img src="/sales-chart-2025.png" alt="Q4 2025 sales by region" aria-describedby="chart-desc"> <p id="chart-desc"> EMEA led with $4.2M, followed by APAC at $3.1M, Americas at $2.8M. EMEA grew 23% year-over-year. </p> // Bulk-edit via WP-CLI for posts with no image alt wp eval-file fix-alt.php // fix-alt.php $posts = get_posts(['post_type' => 'attachment', 'numberposts' => -1]); foreach ($posts as $post) { if (!get_post_meta($post->ID, '_wp_attachment_image_alt', true)) { // Set sensible default based on filename $alt = derive_alt_from_filename($post->post_title); update_post_meta($post->ID, '_wp_attachment_image_alt', $alt); } }
Block image upload without alt
WordPress: add_filter('attachment_fields_to_save', ...) rejects save if alt empty AND not flagged decorative. Shopify: customise theme to display warning. Headless: schema-level required validation.
Add "decorative" explicit option
Don't just require text — give a checkbox "This image is decorative" that sets alt="" explicitly. Without this option, users add meaningless alt text just to bypass the requirement.
Tip: 💡 AI alt-text generation is a useful first pass for legacy content but should never be auto-published. Review and edit for context before saving — AI describes pixels, humans describe meaning.
Tags: accessibility, fix alt text, image alt, missing alt, decorative images, descriptive alt, functional images, screen reader
Fix
Fix anchor text distribution. Brand-heavy natural pattern, avoid exact-match keyword over-optimisation, diversify with naked URLs and partial-match anchors.
1. The natural anchor distribution
Healthy profile for most sites: Branded anchors (Acme, Acme Corp): 30-50% Naked URLs (example.com): 10-20% Generic ("click here", "read more"): 5-15% Partial-match ("Acme CRM tool"): 10-20% Exact-match ("CRM software"): 5-15% Page title / heading text: 5-10% Other variations: 5-10% Red flag: exact-match > 20-25% Almost certainly manipulated; risk of penalty.
2. Audit your current distribution
Ahrefs / Semrush / Majestic show anchor text reports. Group anchors into categories above. Calculate percentages. Common problems found: - Single exact-match phrase > 30% (Penguin trigger) - Naked URLs < 5% (looks artificially curated) - Generic anchors < 2% (no natural variation) - Brand < 20% (looks like you're not real brand)
3. Fix over-optimised exact-match
If exact-match is 40% and should be 15%: Option 1: Add diverse new anchors (slow) - Build new links with brand, naked URL, partial-match - Wait 6-12 months for ratio to drift - Safer but slower Option 2: Remove some exact-match links (faster) - Outreach to remove excessive exact-match links - Or disavow if removal fails - Risk: losing actual authority along with anchor Option 3: Vary anchors on existing links (negotiation) - Request publishers update anchor text - From "CRM software" to "Acme CRM software" or naked URL - Most editors will accommodate reasonable requests
4. Build new links with diverse anchors
Per outreach campaign, plan anchor distribution: 10 outreach campaigns → 10 placements: 4 brand anchors (Acme) 2 naked URLs (example.com) 2 partial-match (Acme CRM tool) 1 exact-match (CRM software) 1 page title / generic This gradually shifts overall ratio toward natural.
5. Internal anchor text matters too
Your own internal linking contributes to anchor signal. If every internal link to /crm-software/ uses "CRM software" anchor, that contributes to over-optimisation. Vary internal anchors too: For /crm-software/, internal anchors might be: "our CRM" "Acme's CRM platform" "the CRM software guide" "see CRM features" "CRM software" Same destination, varied anchors. Looks natural, distributes authority across keyword variants.
6. Monitor distribution over time
Quarterly: - Re-audit anchor distribution - Track exact-match percentage trend - Investigate spikes (negative SEO indicator?) - Adjust outreach anchor mix - Update internal linking on key pages <div
Tip: 💡 Natural anchor profiles look like NO ONE was thinking about anchor text. Random-feeling mix of brand, URLs, generic phrases, occasional keywords. Over-optimised profiles look curated — that's the tell. When building new links, instinctively vary; the natural pattern emerges from variety.
Tags: backlinks, anchor text distribution, anchor text optimization, exact match anchor risk, natural anchor profile
Fix
Rewrite weak internal anchor text (“click here”, “read more”) into descriptive phrases that pass SEO context.
Run the Internal Links audit
Open the Internal Links Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Internal Links audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: internal-links, anchor, text
Fix
Fix app category selection. Primary vs secondary, ranking-difficulty trade-off, audience match. The category strategy for ASO rank and discovery.
1. Category structure
App Store: - Primary category (required) - Secondary category (optional, indexed for search) - Multiple sub-categories on Games (Action, Strategy, etc.) Google Play: - Single primary category - Tag system for finer classification - Categories more focused than App Store
2. The ranking-difficulty trade-off
High-competition category (e.g. Productivity, Photo & Video): - Larger audience browsing - Harder to rank top-100 in category Top Charts - More competitors in "similar apps" sections - Higher signal-to-noise from category browsing Low-competition category (e.g. Reference, Utilities): - Smaller audience browsing - Easier to rank top-50 in category - Fewer competitors in similar-apps - Lower volume but more concentrated intent Match category to where you can win. Top-20 in "Reference" can drive more installs than 200th in "Productivity".
3. Audience match
Users browse categories by interest. Wrong category = wrong audience. Bad fit examples: - CRM app in "Business" (when users browse "Productivity") - Meditation app in "Health & Fitness" (when users browse "Lifestyle") - Photo editor in "Photo & Video" (when better fit might be "Graphics & Design") How to test: - Look at top 50 apps in candidate categories - Do they target similar audiences? - Would your target user discover them? - Look at the "Apps Like This" panels — do you fit?
4. Secondary category strategy (iOS)
Apple allows ONE secondary category. Optimisation: Primary: where you can rank well (lower competition aligned audience) Secondary: where keyword overlap is high (boost discovery) Example: Primary: "Business" (where you can rank top-50) Secondary: "Productivity" (where target users also browse) Both indexed for category-related search queries. You appear in both category browsing experiences.
5. Test category change carefully
Category changes apply at next app review. Apple reviews the change — sometimes rejects if category seems wrong. Test process: - Identify candidate category change - Submit with version update - Monitor 30 days: category rank, organic installs, search rank - If worse, revert at next update Don't change categories more than 1-2 times per year. Stability matters for the store's perception of your app.
6. Category-specific best practices
Games : pick the most specific sub-category (e.g. Strategy > Real-Time, not just Games) Health : Apple has Health-specific guidelines including App Store Review on health claims Education : Education category requires accurate age rating and content Kids : Kids category has stricter content and privacy requirements Finance : Finance category requires accurate disclosures, often regional restrictions <div
Tip: 💡 Category is a strategic decision with long-tail discovery implications. Pick where you can rank top-50 (drives concentrated discovery from category browsing) over where the audience is largest (where you rank 500th and get no benefit). The Top Charts in your category are the prize.
Tags: app-audit, app store category, app category selection, ASO category, primary secondary category
Fix
Fix App Store listing conversion. The top-3-asset rule, social proof placement, custom product pages, A/B testing the path from impression to install.
1. The top-3-assets rule
Users decide in < 3 seconds. They see only: - Icon - Title + subtitle - First 3 screenshots OR preview video - Star rating - Top review snippet (Play) If these 5 elements don't convert, nothing below matters. Most conversion gains come from optimising this top fold.
2. Audit each element
Icon - Distinct at 60px (mobile listing size) - Consistent with brand - Different from category competitors - No text (tiny, unreadable) - High contrast Title + subtitle Covered in title fix and subtitle fix . Both must communicate value within their character limits. Screenshots / video Covered in screenshots and preview video . First 3 screenshots OR auto-playing video are critical. Rating Covered in reviews fix . Below 4.0 hurts conversion materially; 4.5+ helps.
3. Custom product pages (iOS) / Store Listing experiments (Play)
iOS 15+ Custom Product Pages: - Up to 35 variants per app - Each with own screenshots, preview, promotional text - Used for paid acquisition (different ads → different pages) - Each gets its own URL for traffic source matching Google Play Store Listing experiments: - A/B test variants of any listing element - Up to 5 variants concurrent - Statistical significance auto-calculated - Per-locale testing Use custom pages / experiments to: - Match listing to traffic source (Facebook ads → Facebook-themed listing) - Test variants without risking organic ranking - Localise more granularly than store-level locales allow
4. A/B testing methodology
Test one element at a time: - Title variant (with vs without keyword) - Subtitle variant (benefit A vs benefit B) - First screenshot caption (headline variants) - Screenshot order - Preview video on/off - Icon variations Per test: - Minimum 7 days (weekly variance) - Minimum sample size for significance (depends on traffic volume) - Watch conversion rate AND ranking - Don't combine tests — confounds attribution Decisions: - Winner ships - Loser reverts - No-difference tests inform next test direction
5. Conversion benchmarks by category
Rough App Store conversion rate ranges: Productivity: 20-35% Games: 25-40% Photo & Video: 30-45% Health & Fitness: 20-35% Finance: 15-25% Business: 15-25% Education: 25-40% Below the range = work needed. Above the range = leveraging well.
6. Funnel debugging
If conversion is poor, identify where users drop: Impression → Product page view: - Title, subtitle, icon, first screenshot weak - SERP ranking position issue (less visible) Product page view → Install: - Below-fold content (long description, more screenshots) weak - Rating too low - Reviews concerning - Permissions/privacy warnings off-putting App Store Connect Analytics and Play Console both show this funnel. Fix the weakest stage first. <div
Tip: 💡 ASO is a discoverability lever AND a conversion lever. Discoverability wins are real but capped by category. Conversion wins compound — they multiply discoverability wins, paid acquisition spend, and seasonal traffic spikes. Most apps under-invest in conversion testing relative to keyword optimisation.
Tags: app-audit, app conversion rate, ASO conversion, app listing page, custom product pages, A/B test app store
Fix
Fix the App Store keyword field (100c iOS). No duplicates, no plurals, comma-separated efficiency. Maximise unique keyword coverage in the hidden index.
1. The 100-character field rules
Format: comma-separated, no spaces "crm,lead,sales,pipeline,deal,tracker,manager" Apple algorithm: - Single-word indexing (combines title + subtitle + keyword field words) - Plurals auto-handled (don't add both "lead" and "leads") - Stop words stripped ("a", "and", "the" wasted) - Synonyms NOT auto-handled (add "manager" AND "tool" if both relevant) What NOT to waste characters on: - Spaces between keywords (use commas only) - Plurals (Apple stems) - Words already in title or subtitle - Your brand name (always indexed) - Category name (auto-indexed via category)
2. Word selection strategy
Pick words that: - Have search volume in your category - Aren't already covered in title + subtitle - Match user intent (informational, transactional) - Combine well with title words for multi-word queries Example: Title: "Acme: CRM & Lead Tracker" Subtitle: "Sales pipeline manager" Already covered: acme, crm, lead, tracker, sales, pipeline, manager Keyword field (100c): "deal,prospect,contact,follow,task,reminder,team,small,business,b2b,saas" → New coverage: deal, prospect, contact, follow, task, reminder, etc. → Combined with title: "small business CRM", "B2B sales tool", "deal tracker"
3. Localisation = multiplier
Each App Store locale has its OWN 100-character keyword field. You're not limited to 100c global — you have 100c × number of locales. UK English, US English, German, Spanish, French each independent. Aggressive ASO: research keywords per locale, fill all 100c per locale This can 3-5x your keyword footprint across markets.
4. Apple algorithm specifics
Stems plurals: "lead" covers "leads" Does NOT stem variations: "manage" doesn't cover "manager", "managing" Combines fields: any combination of words from title + subtitle + keyword field matches multi-word queries No fuzzy match: "tracking" doesn't cover "tracker"
5. Google Play long description equivalent
Play has no keyword field. Instead Play indexes the long description (4000c). Best practice: use target keywords 2-3 times in long description, in natural prose. Don't keyword stuff — Play's algorithm penalises density above ~3-4%.
6. Iterate based on ranking data
Monthly: - Check rank for each target keyword - Identify keywords where you're 50+ position (probably won't rank) - Replace with higher-potential keywords - Track 12-month rank trends Long-tail keywords often outperform popular ones: "small business CRM" (rank 5, decent traffic) beats "CRM" (rank 80, no traffic) <div
Tip: 💡 The keyword field is invisible to users but heavily weighted. Comma-separated efficiency, no duplicates with title/subtitle, single forms (Apple stems plurals). Per-locale population multiplies your reach across markets.
Tags: app-audit, App Store keywords, ASO keyword field, 100 character keywords, ASO keyword optimization
Fix
Fix App Preview Video. 15-30 seconds, hook in first 3s, narrate value, no voiceover by default. The preview-video pattern that drives install conversion.
1. Length and format
App Store: - 15 to 30 seconds maximum - Up to 3 preview videos per device size - Portrait or landscape (consistent with screenshots) - 30fps, H.264, M4V/MP4/MOV - Footage must be recorded in-app (Apple guidelines) Google Play: - 30 seconds to 2 minutes - One promotional video - Hosted on YouTube (paste link) - More flexible content (marketing acceptable)
2. First 3 seconds = hook
Users scroll past in 1-3 seconds. Your first frame must communicate value instantly. Bad: animated logo, "intro" sequence, brand reveal Good: immediate value proposition with motion - "Track 10x faster" caption + UI demo - User pain → solution - Visible result/outcome from the start The hook is your single most important video decision.
3. Muted by default — design for silent viewing
App Store autoplays muted. Most users never enable sound. Design accordingly: Captions for every key message (large, on-screen) Don't rely on voiceover for the message — supplementary at best Visual motion drives attention — static screens lose users Avoid background music alone — silent users miss it
4. Show outcomes, not features
Feature-focused (weak): - "Use our dashboard" - "Try our advanced filters" Outcome-focused (strong): - "Find your hot leads in 3 taps" - "Close more deals this week" - "From mess to organised in 60 seconds" Users care about what THEY get, not what the app DOES.
5. Structure: 3-act mini-narrative
0-3s: HOOK — value prop, motion, caption 3-15s: DEMO — show 2-3 key features in action 15-25s: PROOF — social proof, results, transformation 25-30s: CTA — final caption + branding For 30s videos, this is roughly 3-12s per section. Keep cuts fast — 1-2 seconds per shot maintains attention.
6. Localisation
Each App Store locale can have its own preview videos. Translate captions, swap UI screenshots to localised versions, adjust pricing/currency callouts. Same principle as screenshots but multiplied — videos cost more to localise so prioritise top markets.
7. Test against no-video baseline
Counterintuitive but real: poorly-made videos hurt conversion. A/B test your video version vs no video (screenshots only). If videoless converts higher, redo the video or remove it. Measure: impression → product page → install. Video plays in the impression — its job is to lift product-page → install. <div
Tip: 💡 Preview video is high-cost, high-variance. A great one beats screenshots alone significantly; a mediocre one underperforms them. If you can't invest properly in design and testing, screenshots-only is the safe option. If you can, the lift is meaningful.
Tags: app-audit, app preview video, ASO video, app store video, app trailer, app preview pattern
Fix
Recover low app ratings. Fix issues from low-star reviews, respond professionally, prompt happy users at the right moment. Rating recovery playbook.
1. Read your 1-2 star reviews systematically
Don't dismiss them. Categorise: - Crashes / bugs (technical — fix in next release) - Missing features (roadmap input) - Pricing complaints (positioning, not bug) - Account / billing issues (support process problem) - Onboarding confusion (UX problem) - Marketing mismatch (your store listing oversells) Each category needs a different response: - Bugs: fix and respond to reviewers explaining the fix shipped - Features: roadmap acknowledgment - Pricing: re-evaluate positioning, perhaps tier additions - Billing: improve support flow - Onboarding: improve in-app first-run experience - Mismatch: adjust marketing or improve fit
2. Respond to negative reviews professionally
Pattern that works: - Acknowledge the specific issue - Explain what's being done - Provide a support contact - Update the response when fix ships Tone: - Empathetic, not defensive - Specific, not boilerplate - Brief (2-4 sentences typical) Example: "Sorry the sync issue caused data loss for you. We've identified the cause in version 2.3.1 — fix shipped. Please email support@acme.com with your account ID and we'll restore data from backup." Apple and Play both let you respond. Don't ignore — silence signals neglect to other users reading reviews.
3. Prompt at the right moment
iOS SKStoreReviewController and Android Play Core in-app review API both throttle prompts. Apple allows ~3 per year per user. Use them carefully: Bad timing (low yield, often negative): - First app launch - Right after error or crash - During free trial signup - Immediately after onboarding Good timing (high yield, positive): - After user completes meaningful goal (closed a deal, saved a file, hit a streak) - After 3rd or 4th session (engagement signal) - After successful transaction completion - After positive in-app feedback signal (NPS, thumbs up) - Never during error states
4. NPS-style filter before review prompt
Ask in-app first: "How likely to recommend Acme?" 1-10 Score 9-10 (promoters): show review prompt Score 7-8 (passives): ask "what would make it 10?" Score 0-6 (detractors): "what went wrong?" → support flow This raises the average store rating because you only prompt satisfied users for store reviews. Detractors get a private support channel where you can fix issues before they leave negative reviews publicly. Important: this is selective routing, not gating. Users can always leave reviews voluntarily.
5. Rating recovery timeline
Old reviews don't auto-expire. Recovery requires NEW reviews diluting old: Current rating: 3.8 Goal: 4.4 If you have 1,000 lifetime reviews averaging 3.8 (3,800 stars): Need new reviews averaging 4.7+ stars to drift up At 100 new 4.7-star reviews per month: - Month 3: ~4.0 - Month 6: ~4.2 - Month 12: ~4.4 The math is unforgiving. Rating recovery is slow. Best to prevent low ratings via UX/bug work than to recover.
6. App Store ratings reset
Apple allows resetting your rating with each major version update. Use carefully — you reset GOOD ratings too. Worth resetting only if: - Old rating was very low (< 3.5) - Major version is genuinely substantially different - You're confident the new version will earn higher ratings - You can drive prompt volume in first 30 days post-launch Reset wrongly = lose hard-won 4.5-star history. <div
Tip: 💡 The cycle that works: ship genuine improvements, prompt happy users at meaningful moments, respond professionally to critical reviews, iterate fixes from review themes. Sites that try to game ratings (bought reviews, fake accounts) get caught and penalised. Real quality + smart prompting wins over 6-12 months.
Tags: app-audit, app reviews ASO, app rating recovery, app review responses, prompt for review, App Store rating
Fix
Fix app screenshots: caption-led design, value-prop first, social proof, localisation. Drive higher install conversion from impression to download.
1. Visible slots per store
App Store (iOS): - Up to 10 screenshots per device size - First 3 visible without scroll (most important) - 6.7" (iPhone Pro Max) is reference; other sizes auto-scaled - Portrait or landscape (consistency within app) Google Play: - Up to 8 screenshots - First 2-3 visible in listing preview - Phone, 7" tablet, 10" tablet versions
2. The story structure
Slot 1: HEADLINE BENEFIT Strongest value prop, brief caption "Track every lead automatically" Slot 2: KEY FEATURE 1 Specific capability with caption "AI prioritises hot prospects" Slot 3: KEY FEATURE 2 Second capability "Drag and drop pipeline" Slot 4-6: SECONDARY FEATURES Other capabilities, social proof Slot 7-10: TRUST & DEPTH Awards, reviews, integrations, security
3. Caption-led design
Raw screenshots without captions convert poorly — users don't decode app UI in 1-2 seconds. Captions explain the value: Bad: [pure app screenshot] Good: "TRACK EVERY LEAD" [headline] [app screenshot showing leads] "Never lose a prospect again" [subhead] Caption layout: - Headline: 4-6 words, biggest text - Subhead: 8-12 words, supporting - Use brand colours - Background colour matching brand - 60-70% screen for caption, 30-40% for screenshot
4. Localisation matters here too
Each App Store / Play locale gets its own screenshots. Don't just translate captions — adjust visuals for cultural context. Numeric formats, currency symbols, character sets all matter: US: "$29/month" English caption UK: "£24/month" English caption (different price possibly) DE: "29€/Monat" German caption JP: "¥3,200/月" Japanese caption + JP-localised app UI in screenshot
5. Social proof in screenshots
Slots 4-6 often include: Star ratings ("4.8/5 from 12,000 reviews") Awards (App Store Editor's Choice, etc.) Press quotes ("Best CRM of 2025 — TechCrunch") Customer logos (B2B apps) User testimonials with photos
6. A/B test
App Store Connect (iOS 15+) Custom Product Pages allow split tests. Google Play Store Listing experiments too. Test: Slot 1 caption variation (headline alternatives) Caption colour / contrast Portrait vs landscape Order of slots 2-4 Inclusion of social proof early vs late <div
Tip: 💡 If you fix one ASO element first, fix screenshots. Title affects discoverability (impressions); screenshots affect conversion (impressions → installs). Good title with bad screenshots produces lots of unrewarded discovery. Good screenshots even with mediocre title still convert browsers into users.
Tags: app-audit, app screenshots, ASO screenshots, app store conversion, captioned screenshots, app screenshot design
Fix
Fix App Store subtitle (30c iOS, short description 80c Play). Secondary keywords, benefit clarity, no repeating title words. The ASO subtitle pattern.
1. Limits and visibility
App Store subtitle: 30 characters shown below app name on listing indexed for search Google Play short desc: 80 characters shown in expandable section indexed for search
2. The complementary keyword pattern
Title: "Acme: CRM & Lead Tracker" Subtitle: "Sales pipeline manager" ← different keywords than title NOT: Subtitle: "CRM and lead tracker" ← duplicates title (wasted) The subtitle should add secondary keywords, not repeat title ones. Together title + subtitle should cover 3-4 primary search terms.
3. Benefit clarity
Subtitle also affects conversion. Users see it before tapping. Bad subtitles look spammy; good ones promise concrete value: Bad: "Best app ever, top-rated, #1 sales tool" Good: "Track leads, close deals faster" Best: "Track leads. Close deals faster." (period adds emphasis)
4. Patterns by app type
Productivity: "[function] for [audience]" "Notes for teams" Utility: "[verb] [object]" "Convert any PDF" Entertainment: "[mood/genre] + verb" "Calm puzzle games" Health/Fitness: "[goal] in [time]" "Lose weight in 30 days" Finance: "[outcome] [audience]" "Invest smarter, easier"
5. Test variations
Subtitle A/B testing same as title — App Store Connect and Play Console both support. Run minimum 7 days. Watch conversion rate (installs ÷ impressions) and search ranking changes simultaneously. <div
Tip: 💡 The subtitle is your secondary-keyword slot — don't waste it duplicating title content. Combined keyword coverage across title + subtitle covers the long tail your category searches reach.
Tags: app-audit, app subtitle ASO, App Store subtitle, Play short description, ASO subtitle pattern, secondary keywords
Fix
Fix app title (30c iOS, 50c Play) for App Store SEO. Brand + primary keyword pattern, localisation, avoid spammy keyword stacking that triggers rejection.
1. Character limits by store
App Store: 30 characters Google Play: 50 characters Both: brand + benefit keyword pattern works symbols (• – :) eat characters; use sparingly
2. The brand + keyword formula
Bad: "Acme" (brand only, no keyword) Bad: "Acme - Best CRM Sales Pipeline Lead Tracker Tool" (stacking, rejection risk) Good (iOS): "Acme: CRM & Lead Tracker" (30c) Good (Play): "Acme: CRM, Lead Tracker & Sales Pipeline" (50c) Pattern: Brand : Primary Keyword + Secondary The primary keyword is what users search for your category.
3. Find your primary keyword
Tools to research (varies by region): - App Store Connect (Apple) — search popularity - Google Play Console — keyword reports - Third-party: Sensor Tower, Data.ai, AppTweak, AppFollow For each candidate: - Search popularity (volume) - Chance you rank (your authority vs competitors) - Conversion intent (does the keyword match what your app does) Pick the keyword that maximises (volume × chance × intent).
4. Avoid Apple rejection patterns
Don't use competitor brand names (trademark risk) Don't stack 5+ keywords with dashes Don't claim "#1" or "Best" without evidence Don't use platform names ("for iPhone") unless required Don't reuse same keyword in title and subtitle (waste) Don't include pricing ("Free", "Premium")
5. Localisation
Each locale gets its own title (App Store / Play both support). Don't use machine translation — keyword research differs per market: US: "Lead Tracker" common, high-volume UK: "Lead Tracker" common but lower volume DE: "Vertriebs-Tracker" would research locally ES: "Gestor de leads" would research locally Per-locale ASO drives non-English market growth 2-4x.
6. A/B test title changes
App Store Connect (iOS 15+) and Google Play Console both support title experiments. Run for minimum 7 days to clear weekly variance. Look at install-per-impression conversion, not just downloads. Title changes can have negative impact even when the change "looks better". <div
Tip: 💡 Title carries the highest keyword weight in both stores. One well-researched keyword in the title beats 5 in the keyword field. Pick the one keyword you most need to rank for; build the title around it; localise; test.
Tags: app-audit, app title ASO, app store title optimization, app name SEO, App Store title length, Google Play title
Fix
Fix ARIA errors from the HTML Checker. Use roles correctly, manage states, follow ARIA design patterns, prefer native HTML semantics over manual ARIA.
Run the HTML Checker
Filter to ARIA findings. Common errors: role="presentaton" (typo for presentation ) role="button" on a <button> (redundant) aria-label repeating visible text (overrides visible label) Missing required ARIA: aria-expanded on disclosure widgets, aria-controls on tab triggers aria-labelledby pointing at non-existent ID <!-- WRONG --> <div role="checkbox" onclick="toggle(this)">Subscribe</div> <!-- RIGHT (native) --> <label> <input type="checkbox" name="subscribe"> Subscribe </label> <!-- IF YOU MUST use a custom checkbox (e.g. for styling) --> <div role="checkbox" tabindex="0" aria-checked="false" onclick="toggle(this)" onkeydown="if(event.key === ' ' || event.key === 'Enter') toggle(this)"> Subscribe </div> <!-- RIGHT --> <button type="button" aria-expanded="false" aria-controls="advanced-options"> Advanced options </button> <div id="advanced-options" hidden> ...options... </div> <!-- Toggle aria-expanded and the hidden attribute together --> <!-- WRONG: reinventing nav --> <div role="navigation" aria-label="Main">...</div> <!-- RIGHT --> <nav aria-label="Main">...</nav> <!-- For dynamic content that updates --> <div role="status" aria-live="polite"> 3 items added to cart </div> <!-- For urgent updates --> <div role="alert" aria-live="assertive"> Error: payment declined </div> <!-- Icon-only button --> <button aria-label="Close"> <svg>...x icon...</svg> </button> <!-- Link that just says "More" - use aria-label to disambiguate --> <a href="/aipageseo-demo-pages/how-to-fix-aria-errors.html" aria-label="Read more about pricing"> More → </a>
1. The first rule of ARIA
If a native HTML element exists with the semantics and behaviour you need, use the native element. <!-- WRONG: reinventing button --> <div role="button" tabindex="0" onclick="...">Submit</div> <!-- RIGHT: use the native button --> <button type="button" onclick="...">Submit</button> The native <button> handles keyboard (Enter, Space), focus management, screen-reader announcement, and disabled state — all for free. The div role="button" needs all of this re-implemented manually, and people get it wrong.
2. Audit your ARIA
Step 1 Run the HTML Checker Filter to ARIA findings. Common errors: role="presentaton" (typo for presentation ) role="button" on a <button> (redundant) aria-label repeating visible text (overrides visible label) Missing required ARIA: aria-expanded on disclosure widgets, aria-controls on tab triggers aria-labelledby pointing at non-existent ID
3. Common ARIA patterns and their fixes
Pattern: Custom checkbox <!-- WRONG --> <div role="checkbox" onclick="toggle(this)">Subscribe</div> <!-- RIGHT (native) --> <label> <input type="checkbox" name="subscribe"> Subscribe </label> <!-- IF YOU MUST use a custom checkbox (e.g. for styling) --> <div role="checkbox" tabindex="0" aria-checked="false" onclick="toggle(this)" onkeydown="if(event.key === ' ' || event.key === 'Enter') toggle(this)"> Subscribe </div> Pattern: Disclosure widget (expand/collapse) <!-- RIGHT --> <button type="button" aria-expanded="false" aria-controls="advanced-options"> Advanced options </button> <div id="advanced-options" hidden> ...options... </div> <!-- Toggle aria-expanded and the hidden attribute together --> Pattern: Navigation landmark <!-- WRONG: reinventing nav --> <div role="navigation" aria-label="Main">...</div> <!-- RIGHT --> <nav aria-label="Main">...</nav> <nav> implies role="navigation" . Adding it explicitly is redundant. Pattern: Live region <!-- For dynamic content that updates --> <div role="status" aria-live="polite"> 3 items added to cart </div> <!-- For urgent updates --> <div role="alert" aria-live="assertive"> Error: payment declined </div>
4. Fix aria-label misuse
⚠️ aria-label overrides the visible text for screen readers. If a button says "Submit" and has aria-label="Submit form" , sighted users see "Submit" but screen-reader users hear "Submit form". Worse, when copy changes the aria-label drifts. Only use aria-label when there's NO visible text: <!-- Icon-only button --> <button aria-label="Close"> <svg>...x icon...</svg> </button> <!-- Link that just says "More" - use aria-label to disambiguate --> <a href="/aipageseo-demo-pages/how-to-fix-aria-errors.html" aria-label="Read more about pricing"> More → </a>
5. ARIA design patterns reference
For complex widgets, follow the ARIA Authoring Practices Guide exactly. The patterns cover: Tabs Accordion Combobox Dialog (modal) Tree view Carousel Date picker Half-implementing a pattern is worse than not using ARIA at all. The patterns include required keyboard handling, focus management, and specific ARIA attributes — all of which must be present together.
6. Test with assistive technology
HTML validation passing isn't the same as accessibility working. Test with: VoiceOver (macOS / iOS) — Cmd-F5 or Settings → Accessibility NVDA (Windows) — free download from nvaccess.org TalkBack (Android) — Settings → Accessibility Keyboard only — tab through every interactive element, activate with Enter/Space <div
Tip: 💡 Many ARIA "errors" are actually correct ARIA used with elements that don't need it. The fix is often removing ARIA, not adding more.
Tags: html-checker, fix ARIA errors, ARIA roles, aria-label, aria-labelledby, ARIA design patterns, accessibility, semantic HTML, role landmark
Fix
Fix aria-label and aria-labelledby errors from the Accessibility audit. When to use each, when to use native labels, common misuse patterns.
Run the Accessibility audit
Run the Accessibility audit . Filter to ARIA-label findings. Categories typically include: missing accessible name on interactive element, aria-label conflicts with visible text, aria-labelledby references non-existent id, aria-label on element with no role.
Inventory existing aria-label across templates
grep -rE "aria-label" src/templates/ Most existing aria-labels in templates were either added correctly (icon button) or added wrongly (decorative duplicate of visible text). Each needs review. <button aria-label="Close dialog"> <svg><!-- x icon --></svg> </button> <a href="/pricing.html" aria-label="View cart, 3 items"> <svg><!-- cart icon --></svg> <span>3</span> </a> <h3>Article about pricing</h3> <a href="/pricing" aria-label="Read more about pricing">Read more</a> <input type="search" aria-label="Search products" placeholder="Search..."> <nav aria-label="Main">...</nav> <nav aria-label="Footer">...</nav> <nav aria-label="Breadcrumb">...</nav> <!-- WRONG --> <button aria-label="Submit">Submit</button> <!-- RIGHT --> <button>Submit</button> <!-- WRONG --> <button aria-label="Submit form">Submit</button> <!-- RIGHT — pick one --> <button>Submit form</button> <!-- WRONG (ignored by most screen readers) --> <div aria-label="Important warning">Read this</div> <!-- RIGHT (use semantic HTML) --> <div role="alert">Read this</div> <!-- BETTER (no ARIA needed) --> <p><strong>Important warning:</strong> Read this</p> <!-- WRONG — abuse --> <a href="/" aria-label="Acme Co home page best products lowest prices"> <img src="/logo.svg" alt=""> </a> <!-- RIGHT --> <a href="/" aria-label="Acme Co home"> <img src="/logo.svg" alt=""> </a> <!-- WRONG --> <label for="email">Email</label> <input id="email" aria-label="Your email address"> <!-- Screen reader announces "Your email address", hiding the visible label --> <!-- RIGHT --> <label for="email">Email</label> <input id="email"> <section aria-labelledby="related-heading"> <h2 id="related-heading">Related products</h2> <ul>...</ul> </section> <span id="price-label">Maximum price</span> <input type="number" aria-labelledby="price-label"> <span id="amount">100</span> <span id="currency">USD</span> <button aria-labelledby="amount currency">Confirm</button> <!-- Announces: "100 USD, button" -->
Find broken references
# In a built page, get all referenced ids and verify they exist grep -oE 'aria-labelledby="[^"]+"' output.html | \ awk -F'"' '{print $2}' | tr ' ' '\n' | sort -u > referenced grep -oE 'id="[^"]+"' output.html | \ awk -F'"' '{print $2}' | sort -u > existing diff referenced existing | grep '^<' Anything in referenced but not in existing is a broken aria-labelledby reference.
Tip: 💡 The accessible-name algorithm priority: aria-labelledby → aria-label → native label (label element, input title, etc.) → visible text content. ARIA wins when present, so it's powerful and dangerous in equal measure.
Tags: accessibility, fix aria-label, aria-labelledby, ARIA labels, accessible name, icon buttons, ambiguous link text, ARIA best practices
Fix
Build credible author bios with credentials, expertise signals and external profile links to strengthen your site’s E-E-A-T score.
Run the E-E-A-T audit
Open the E-E-A-T Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your E-E-A-T audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: eeat, author, bios
Fix
Add author trust signals AI engines weight: visible bylines, dated bios with credentials, Person schema with sameAs links, author pages with E-E-A-T.
Rich Results Test
Run an article URL through search.google.com/test/rich-results . Should detect Article + Person, no warnings about missing author.
Ask an AI
Prompt ChatGPT or Claude: "Who wrote [article URL]?" Verified setup returns the author's name and role. Weak setup returns "couldn't determine" or hallucinates.
1. Visible byline at top
Bad: no author or generic <article> <h1>How to choose a CRM</h1> <p>Published Jan 2026</p> <!-- ... content ... --> </article> <!-- OR --> <p>By Admin</p> <p>By Editorial Team</p> Good: named, linked to author page <article> <h1>How to choose a CRM</h1> <div class="byline"> <img src="/authors/jane-baker.jpg" alt="Jane Baker" /> By <a href="/learning-hub.html">Jane Baker</a>, Sales Operations Lead, 12 years experience <time datetime="2026-01-15">Published 15 January 2026</time> <time datetime="2026-04-22">Updated 22 April 2026</time> </div> </article>
2. Build proper author pages
Every named author needs a dedicated page at a stable URL. Template: <!-- /authors/jane-baker --> <main> <h1>Jane Baker</h1> <img src="/authors/jane-baker.jpg" alt="Jane Baker" /> <p><strong>Sales Operations Lead at Acme Corp</strong></p> <p>12 years building sales processes for B2B SaaS. Previously at [named company]. MBA, [named school]. Certified Salesforce Administrator since 2014.</p> <h2>Credentials</h2> <ul> <li>Salesforce Certified Administrator (2014)</li> <li>HubSpot Sales Software Certification (2018)</li> <li>MBA in Marketing, [University] (2012)</li> </ul> <h2>Connect</h2> <ul> <li><a href="https://linkedin.com/in/janebaker">LinkedIn</a></li> <li><a href="https://twitter.com/janebaker">Twitter</a></li> <li><a href="mailto:jane@example.com">Email</a></li> </ul> <h2>Articles by Jane</h2> <!-- List of all articles authored --> </main>
3. Person schema with sameAs
<!-- On the author page --> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Person", "@id": "https://example.com/authors/jane-baker#person", "name": "Jane Baker", "image": "https://example.com/authors/jane-baker.jpg", "jobTitle": "Sales Operations Lead", "worksFor": { "@type": "Organization", "name": "Acme Corp", "url": "https://example.com" }, "alumniOf": [ { "@type": "EducationalOrganization", "name": "University Name" } ], "hasOccupation": { "@type": "Occupation", "name": "Sales Operations Lead", "qualifications": "Salesforce Certified Administrator, HubSpot Sales Certified" }, "url": "https://example.com/authors/jane-baker", "sameAs": [ "https://linkedin.com/in/janebaker", "https://twitter.com/janebaker", "https://github.com/janebaker", "https://orcid.org/0000-0000-0000-0000" ] } </script>
4. Reference Person from Article schema
<!-- On each article authored --> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Article", "headline": "How to choose a CRM", "datePublished": "2026-01-15", "dateModified": "2026-04-22", "author": { "@type": "Person", "@id": "https://example.com/authors/jane-baker#person", "name": "Jane Baker", "url": "https://example.com/authors/jane-baker" }, "publisher": { "@type": "Organization", "name": "Acme Corp", "logo": { "@type": "ImageObject", "url": "https://example.com/logo.png" } } } </script> The @id reference connects Article to canonical Person definition. Closed-graph pattern — AI engines and Google build a strong author entity from this.
5. External verification
sameAs links must work both ways for max weight: LinkedIn profile lists current employer matching your worksFor Twitter bio mentions current role GitHub profile shows real activity (if relevant) ORCID for academic / research authors Industry credential databases (Salesforce Trailblazer, AWS Certified, etc.)
6. Date discipline
<!-- Both dates explicit, ISO 8601 with timezone --> <time datetime="2026-01-15T09:00:00Z">Published 15 Jan 2026</time> <time datetime="2026-04-22T14:30:00Z">Updated 22 Apr 2026</time> <!-- In schema --> "datePublished": "2026-01-15T09:00:00Z", "dateModified": "2026-04-22T14:30:00Z" <!-- Don't fake updates — AI engines compare content hash --> <!-- to detect "updated" pages that didn't actually change. -->
7. Editorial process disclosure
For YMYL or fact-heavy content, an editorial-standards page raises trust: <!-- /editorial-standards --> <h1>Editorial standards</h1> <h2>How we research</h2> <h2>How we fact-check</h2> <h2>Our review process</h2> <h2>Corrections policy</h2> <h2>Author qualifications</h2> <h2>Conflicts of interest</h2> <!-- Link to this from every article footer --> <footer>Content reviewed per our <a href="/seo-auth/acceptable-use.html">editorial standards</a>.</footer>
8. Verify
Step 1 Rich Results Test Run an article URL through search.google.com/test/rich-results . Should detect Article + Person, no warnings about missing author. Step 2 Ask an AI Prompt ChatGPT or Claude: "Who wrote [article URL]?" Verified setup returns the author's name and role. Weak setup returns "couldn't determine" or hallucinates. <div
Tip: 💡 Author signals compound: one named author with a sparse author page beats "Admin" but underperforms a fully-built page with credentials and external sameAs links. Invest once in proper author pages; payoff lasts years as AI engines build entity knowledge.
Tags: agent-readiness, author trust signals, E-E-A-T author, Person schema, byline AI, author bio credentials, expertise signals
Fix
Build the authority signals (mentions, citations, expert backlinks) that strengthen E-E-A-T evaluation for your topic area.
Run the E-E-A-T audit
Open the E-E-A-T Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your E-E-A-T audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: eeat, authority, building
Fix
Detect and respond when your audit scores drift from your historical baseline: identify cause, rule out seasonality and act on regression.
Run the Score History audit
Open the Score History Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Score History audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: score-history, benchmark, drift
Fix
Identify the topics, queries and channels where competitors are invisible and capitalise on those blind spots.
Run the Competitor Analysis audit
Open the Competitor Analysis Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Competitor Analysis audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: competitor-analysis, blind, spots
Fix
Fix blog post internal linking: topical clusters, hub-and-spoke architecture, contextual links, related-posts widgets, link equity flow.
Identify topic clusters
Group existing content by topic. Example: 'SEO basics', 'AI search', 'local SEO', 'technical SEO'. Each cluster: 5-30 articles related to one umbrella topic.
Designate pillar pages
Each cluster: one pillar page — comprehensive guide to the topic (3,000-10,000 words). Covers all major sub-topics briefly with links to detailed cluster articles.
Link cluster articles to pillar
Every cluster article links back to pillar (1-2 times in content). Anchor text: pillar topic. Builds 'topical authority' signal — Google understands you have deep coverage.
Link pillar to all clusters
Pillar page: comprehensive links to every cluster article. Body text + 'related articles' section + navigation. Pillar acts as topical hub.
Cross-link cluster articles
Within a topic cluster, articles link to each other when contextually relevant. 'See also: [related article]'. Strengthens cluster cohesion.
Add contextual links naturally
Within body content, link to other relevant articles. Don't force; only where genuinely helpful for reader. Forced linking reads spammy.
Audit orphan pages
Articles not linked from anywhere internally are 'orphans' — invisible to search ranking. Audit (Screaming Frog inlinks report); link from relevant articles. Common cause of underperforming content.
Use related-posts widgets carefully
Auto-generated 'related posts' useful but often weak (keyword overlap, not semantic relevance). Manual curation of top-related content per article more effective.
Tags: internal-links, blog internal linking, topical authority, content cluster, hub and spoke SEO, blog SEO architecture
Fix
Fix weak brand-mention coverage for AI visibility. Reddit, Quora, podcasts, comparison sites — the strategy that scales without spam.
Monthly mention audit
Brand Mention Monitor weekly auto-tracks new mentions. Once a month, review: New mentions count vs previous month Sentiment distribution (positive / neutral / negative) Source quality (Tier-1 vs general vs low-quality) Topic context (what queries the mention would help citations for)
Cross-check AI Visibility Tracker
Are mention-frequency rises followed by AI-citation rises? Typically 4-8 week lag. If mentions rise but AI citations don't, investigate: content extractability , schema coverage, or technical SEO probably gating.
1. Audit current coverage
Run the Brand Mention Monitor for a baseline, or check manually: # Mentions count "brand name" -site:yourdomain.com # in Google "brand name" # in Reddit, Quora search "brand name" # in YouTube search # Compare to direct competitor "competitor name" -site:theirdomain.com # Identify gap: if competitor has 5x your mentions, that's the work
2. Channel-by-channel strategy
Reddit Highest-weight unlinked-mention channel. AI engines (especially Perplexity and ChatGPT) lean on Reddit heavily for category opinions: Find your subreddits: - 3-5 subreddits where your audience already discusses - Mix general (r/marketing) and specific (r/SaaS, r/SEO) - Avoid r/[your-category]-promo type subs (low signal) Participation pattern: Months 1-2: lurk, observe norms, answer questions WITHOUT mentioning brand Months 3-4: build karma to 500+, become a recognised contributor Months 5+: occasionally mention brand when genuinely relevant (1 in 20 comments at most) What NOT to do: - Drop links to your site in unrelated threads - Astroturf with sock-puppet accounts (Reddit detects, AI engines too) - Hire SEO agency to mention brand artificially Quora Lower weight than Reddit but still cited by AI engines for definitional queries: Strategy: - Find 20-30 questions in your category that get views - Write substantive answers (500+ words, original insight) - Once per answer (or once per 5 answers) reference your brand in context: "We've seen this pattern in 10,000+ audits..." - Real expertise wins on Quora; thin promotional fails Best for: definitional ("What is X?"), comparative ("X vs Y"), and "How does X work" queries — exactly what AI engines field Podcasts Each podcast appearance generates: show notes mention, episode description, transcript (when AI engines transcribe), social posts, and listener Twitter mentions: Outreach: - List 30-50 podcasts in your category by audience size - Filter: active (episode in last 60 days), reachable host - Personalised pitch: specific episode you'd add value to, your unique angle, 3 talking points - Lead with a story or data, not features Effort: 50 pitches → ~5-10 yeses → 5-10 episodes / year Each episode = mention on 5-10 platforms (show notes, transcript, social, YouTube, Spotify) Compounding: hosts of small podcasts often guest on larger ones, chains of cross-promotion build over 12-18 months YouTube Three paths: 1. Your own channel: video content with brand-rich descriptions and transcripts (AI engines extract from both) 2. Guest appearances: interviews, panel discussions 3. Mentions by others: reach out to reviewers, tutorial creators with free trial/demo for honest review YouTube descriptions are heavily AI-extracted — write them like mini-articles, not afterthoughts. Brand, links, timestamps, full topic description. Comparison & review sites B2B SaaS: G2, Capterra, TrustRadius, GetApp Consumer: Wirecutter, RTINGS, comparison roundups Local services: Yelp, TripAdvisor, industry-specific Developers: AlternativeTo, GitHub awesome-lists Strategy: - Claim your listings (where applicable) - Encourage real customers to review (don't fake) - Engage with reviews (especially negative) publicly - Pitch comparison-site editors when launching meaningful features Each review and feature update creates a mention. Reviews on G2 specifically heavily weighted by AI engines for B2B queries. Industry trade press Approach: - Identify 10-20 trade publications in your category - Pitch original data, expert commentary, contributed articles - HARO / Qwoted / SourceBottle for journalist inquiries - Don't pay-to-play "sponsored content" mills (low AI weight) One editorial mention in a Tier-1 trade publication is worth 50 mentions in low-quality directories.
3. The "earn, don't engineer" principle
AI engines are good at detecting artificial mention patterns: Mentions clustered in time (sudden surge) = suspicion Mentions from accounts with no other category activity = suspicion Mentions with identical phrasing across sites = suspicion Mentions only in low-quality forums/directories = low weight Mentions from accounts created recently = low weight The pattern that earns trust: mentions distributed across time, sources, contexts, and authors . Real participation creates this organically; manufactured campaigns don't.
4. Make your brand mentionable
Some brands earn mentions easily; others struggle even with great content. Mentionability drivers: Distinct positioning "The X for Y" is easier to mention than "another general-purpose Z". Sharp positioning = mention-friendly. Founder/team presence Named founders writing publicly, on podcasts, speaking at conferences accumulate personal mentions that flow to the brand. Author trust signals compound here. Memorable name Generic name = hard to mention without confusion. Memorable, brandable name = naturally cited. Hard to change post-launch but worth considering for sub-brands or products. Strong opinions Mild and balanced loses to definitive and provocative for mention-worthiness. People mention what makes them think, agree strongly, or disagree.
5. Track and iterate
Step 1 Monthly mention audit Brand Mention Monitor weekly auto-tracks new mentions. Once a month, review: New mentions count vs previous month Sentiment distribution (positive / neutral / negative) Source quality (Tier-1 vs general vs low-quality) Topic context (what queries the mention would help citations for) Step 2 Cross-check AI Visibility Tracker Are mention-frequency rises followed by AI-citation rises? Typically 4-8 week lag. If mentions rise but AI citations don't, investigate: content extractability , schema coverage, or technical SEO probably gating.
6. What to avoid
Mention-buying services — most produce low-quality, pattern-detectable mentions Reddit/Quora sock-puppets — accounts ban; AI engines downweight Fake reviews on G2/TrustPilot — detected, hurt reputation HARO spam (low-quality quotes) — journalists blacklist Mass podcast pitching with templates — low yield, can blacklist you Press release wires (Newswire, PRWeb) — mostly ignored by AI engines <div
Tip: 💡 The compound win: real category participation over 12-18 months. Year 1 feels slow — 30-50 mentions, modest AI-citation lift. Year 2 the same effort produces 200+ mentions and dramatic citation improvement because the cumulative entity signal has built. Most sites give up at month 6; the ones that don't dominate by month 18.
Tags: ai-visibility, brand mentions AI, unlinked mentions, Reddit brand mentions, Quora brand strategy, podcast brand mentions, AEO brand
Fix
Ensure your branded keywords rank reliably for navigational and informational variants: capture brand-related demand fully.
Run the Keywords audit
Open the Keywords Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Keywords audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: keywords, branded
Fix
Build a 72-hour GDPR breach response process: detection, containment, ICO notification and data-subject communication.
Run the GDPR audit
Open the GDPR Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your GDPR audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gdpr, breach, response
Fix
Recover broken backlinks. Inbound links pointing at 404s lose authority — redirect to relevant pages or restore content. Reclamation workflow.
1. Identify broken inbound links
Sources: - GSC → Coverage → Errors → 404s with backlinks - Ahrefs → "Best by links" with 404 status - Server logs → 404 hits from referers - aiwebpageseo backlinks audit broken-link report Filter: - Only URLs with inbound backlinks (no point fixing 404s with no value) - Sort by linking root domains (more linkers = higher priority) - High DA linkers first
2. Decide redirect or restore
Restore content at original URL when: - Original content was your idea, just got removed - Removal was a mistake - You can recreate or have backup 301 redirect to relevant page when: - Original content is obsolete - Better current page covers same intent - Restoration would be artificial 410 Gone when: - Original was deliberately removed (legal, irrelevant) - No equivalent current content - You don't want to confuse Google with redirect Don't 301 to homepage as default — Google detects this as "soft 404" pattern and de-credits the redirect.
3. Build the redirect map
For each broken URL with inbound links: /old-page-url → /relevant-current-page /deleted-blog-post → /related-current-post /removed-product → /closest-current-product OR /category-page Implementation depends on stack: - Apache: .htaccess RewriteRule - Nginx: rewrite directives - Express: redirect middleware - Next.js: redirects in config - WordPress: Redirection plugin Test each: curl -I shows 301 + correct destination + 200 ultimate.
4. Recover the most-linked broken pages first
Don't try to fix every 404 with one inbound link. Prioritise: Pages with 10+ root domains linking → immediate fix Pages with 3-10 root domains → fix in 2-4 weeks Pages with 1-2 root domains → batch monthly 80/20: 20% of broken URLs hold 80% of recoverable equity.
5. Implementation checklist
301 not 302 — 302 is temporary, doesn't pass equity Single hop — no 301 → 301 → 301 chains Destination returns 200, not another redirect Test in private browsing to avoid cache Resubmit affected URLs in GSC after deploying
6. Track recovery
After deploying: - GSC will crawl and update Coverage report (2-4 weeks) - Ahrefs / Semrush will update broken-link counts (varies) - Rankings may lift if recovered equity was substantial - Track ranking trend on destination pages Monthly: - New broken backlinks detected (often from site changes) - Existing fixes still working (verify destinations 200) - Update redirect map as needed <div
Tip: 💡 Broken backlinks are the lowest-hanging fruit in SEO. The work is already done — someone linked to you. Fixing the link target costs nothing but recovers measurable authority. Run a broken-backlink audit before any new link-building campaign — you may already have the links you need.
Tags: backlinks, broken backlinks, 404 backlink recovery, link reclamation, broken link redirect, inbound 404
Fix
Find and fix every broken internal link site-wide: bulk-replace via CMS, 301 where appropriate, prevent recurrence with CI link checks.
Run the Internal Links audit
Open the Internal Links Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Internal Links audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: internal-links, broken, internal, links
Fix
Fix broken internal links from Site Crawler. Diagnose 404s, replace bad links in bulk via CMS, prevent breakage in your CI pipeline.
Run the Site Crawler
Run a fresh Site Crawler on your domain. Export the broken-links report as CSV. You'll get columns for source URL, target URL, anchor text, and status code (404, 410, 500, etc.).
Group findings by target
Sort the CSV by target URL. You'll typically see that 50-200 broken links collapse into 10-30 unique broken targets. Fixing one target eliminates many findings at once.
Typo in the URL
/blog/post-titl/ instead of /blog/post-title/ . The destination exists; the link has a typo. Fix the source link, not the destination.
Trailing-slash mismatch
Site canonical is /path/ but the link omits the slash, and the server doesn't auto-redirect to the canonical. Either fix the server to 301 the non-canonical to canonical, or fix the links.
HTTP-vs-HTTPS mismatch
Old internal links still pointing at http:// after HTTPS migration. The server should 301 these but the redirect is a wasted hop. Find and replace http://yourdomain.com with https://yourdomain.com in CMS content.
Deleted destination
The destination page genuinely no longer exists. Two paths: 301 the old URL to the closest live replacement (and update internal links to point at the replacement to avoid the hop), or update internal links to point at a different page entirely.
Case-sensitivity mismatch
/Blog/Post/ vs /blog/post/ . Linux servers are case-sensitive; the URL with wrong case 404s. Force-lowercase via server rewrite, or fix the links.
Install Better Search Replace
WP Admin → Plugins → Add New → search "Better Search Replace" → Install + Activate. The plugin runs database-level find-and-replace.
Run a dry-run first
Tools → Better Search Replace : Search for: https://yourdomain.com/old-broken-path Replace with: https://yourdomain.com/new-path Tables: select wp_posts , wp_postmeta , wp_options Tick Run as dry run? Click Run Dry run reports how many rows would change. Verify the count looks right.
Backup, then run for real
Take a database backup (UpdraftPlus or via hosting panel). Untick Dry Run. Click Run. Changes apply immediately.
Search theme code
Shopify Admin → Online Store → Themes → Actions → Edit code . Use the search box at the top of the file list. Search for the broken URL fragment (e.g. old-broken-path ). Edit each occurrence in .liquid templates and .html snippets.
Search product descriptions
Theme code is only half. Products, collections, pages and blog posts contain HTML links too. Use Shopify's bulk-edit URL admin/bulk?resource_name=Product&edit=descriptionHtml to mass-edit. Or for very large catalogues use a CSV export, find/replace in spreadsheet, re-import.
Use the CMS API
Contentful, Sanity, Strapi, Storyblok all expose APIs to bulk-update content. Write a small script that queries documents containing the broken URL, replaces it, and updates. Test on staging first. # Example: Contentful Node.js const contentful = require('contentful-management'); const client = contentful.createClient({ accessToken: 'YOUR_TOKEN' }); const space = await client.getSpace('SPACE_ID'); const env = await space.getEnvironment('master'); const entries = await env.getEntries({ 'fields.body[match]': 'old-broken-path' }); for (const entry of entries.items) { entry.fields.body['en-US'] = entry.fields.body['en-US'].replace(/old-broken-path/g, 'new-path'); await entry.update(); }
Add a CI link-check step
Use linkinator (Node) or muffet (Go) in your CI pipeline: # GitHub Actions example - name: Check links run: npx linkinator https://staging.yourdomain.com --recurse --skip ^https?://(twitter|facebook) Fail the build on any broken internal links. Now new broken links never reach production.
Tip: 💡 Many CMS platforms offer "broken link checker" plugins that run continuously. They catch breakage caused by external changes (linked-to sites deleting their pages) faster than periodic crawls.
Tags: site-crawler, fix broken links, broken internal links, 404 fix, link audit, WordPress broken links, dead links, link rot, internal linking
Fix
Fix browser prefix issues from the CSS Checker. Drop legacy prefixes, configure Autoprefixer with Browserslist, ship modern unprefixed CSS automatically.
Find prefixes in source
grep -rE "(-webkit-|-moz-|-ms-|-o-)" src/ --include="*.css" --include="*.scss" | wc -l Typical numbers: thousands of prefix usages in legacy codebases. After Autoprefixer migration: zero in source.
Categorise findings
For each prefix found, check if it's still needed for your target browsers using caniuse.com . Most prefixes for flex , transform , transition , border-radius , animation , gradients haven't been needed since 2016.
Create .browserslistrc
Pick a baseline. Most modern sites use: # .browserslistrc > 0.5% last 2 versions not dead not IE 11 This targets browsers used by more than 0.5% of users globally, the last 2 versions of each, excluding browsers that no longer receive updates and explicitly excluding IE 11.
Or use package.json
// package.json { "browserslist": [ "> 0.5%", "last 2 versions", "not dead", "not IE 11" ] } Either location works. Pick one source of truth.
Check what you're targeting
npx browserslist Lists every browser version your config targets. Useful sanity check — make sure you're not excluding browsers your real users use, and not including ancient ones you don't need.
Add to your build
npm install --save-dev postcss autoprefixer Configure PostCSS: // postcss.config.js module.exports = { plugins: { autoprefixer: {}, } }
Integrate with your build tool
Most modern build tools (Vite, webpack with postcss-loader, Next.js, Tailwind) automatically run PostCSS if a config exists. Verify by checking build output for prefixes that ARE needed for your target browsers. /* BEFORE — hand-written prefixes */ .box { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); -webkit-transition: transform 0.3s; transition: transform 0.3s; display: -webkit-flex; display: -ms-flexbox; display: flex; } /* AFTER — clean source CSS */ .box { transform: translateY(0); transition: transform 0.3s; display: flex; } # Strip common dead prefixes from all CSS files (test on backup first!) find src -name "*.css" -exec sed -i -E ' /-webkit-(transform|transition|border-radius|animation|box-shadow|user-select|appearance|backface-visibility|perspective)/d; /-moz-(transform|transition|border-radius|animation|box-shadow|user-select|appearance)/d; /-o-(transform|transition|animation)/d; ' {} +
Local test
Run your build. Inspect generated CSS to verify Autoprefixer added prefixes you expected: cat dist/main.css | grep -E "(-webkit-|-moz-|-ms-)" | head -20 You should see prefixes only on properties that still need them per your Browserslist.
Cross-browser test
Use BrowserStack, Sauce Labs, or LambdaTest to test on the oldest browsers in your Browserslist. Confirm everything renders correctly. Focus on: Flexbox / Grid layouts Animations and transitions Form elements (appearance, user-select) Backdrop filters (if used) Sticky positioning
Keep browser data fresh
Autoprefixer relies on caniuse-lite data. Outdated data means decisions based on old browser stats. // In CI or as a periodic task npx browserslist@latest --update-db Or set up a renovate/dependabot rule to update caniuse-lite monthly.
Tip: 💡 Once Autoprefixer is integrated, you never write prefixes again. New CSS goes in unprefixed; build adds what's needed. Drop "always include -webkit-" from your team style guide; replace with "write modern CSS; let the build handle compatibility."
Tags: css-checker, fix browser prefixes, autoprefixer, browserslist, vendor prefixes, webkit prefix, moz prefix, legacy CSS, modern CSS
Fix
Identify and resolve keyword cannibalisation when multiple pages compete for the same query: consolidate, canonicalise or differentiate intent.
Run the Keywords audit
Open the Keywords Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Keywords audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: keywords, cannibalisation
Fix
Fix canonical tag conflicts confusing AI engines: self-reference mismatches, cross-domain canonicals, conflicting OG URL and rel=canonical signals.
Extract all signals per page
# Fetch and grep URL="https://example.com/blog/post-1" curl -sI "$URL" | grep -i "^link:" # HTTP Link header curl -s "$URL" | grep -oE 'rel="canonical" href="[^"]+"' curl -s "$URL" | grep -oE 'property="og:url" content="[^"]+"' curl -s "$URL" | grep -A1 '"@type":' | grep -oE '"url":"[^"]+"' curl -s https://example.com/sitemap.xml | grep -A1 "$URL"
Crawl-wide audit
Run the Site Crawler . Filter findings: "canonical mismatch", "og:url differs from canonical", "sitemap URL differs from canonical". Each is a discrete fix. <!-- Layout template, single source of truth --> {# Compute canonical from current request, applying policy #} {% set canon = build_canonical_url(request) %} <link rel="canonical" href="{{ canon }}" /> <meta property="og:url" content="{{ canon }}" /> <script type="application/ld+json"> { "@type": "WebPage", "url": "{{ canon }}" } </script> <!-- Sitemap generator uses same function --> # Test URL variants all resolve correctly for u in \ "http://example.com/Blog/Post-1/" \ "http://www.example.com/blog/post-1?utm_source=test" \ "https://example.com/blog/post-1/" \ "https://example.com/blog/post-1"; do echo "=== $u ===" curl -sIL "$u" | grep -iE "^(location|link):" curl -sL "$u" | grep -oE 'rel="canonical" href="[^"]+"' done # All should redirect (301) or resolve to the same canonical URL
1. Every canonical signal
1. <link rel="canonical" href="..."> in <head> 2. <meta property="og:url" content="..."> in <head> 3. <url><loc>...</loc></url> in sitemap.xml 4. Internal link href values throughout site 5. Schema.org url property in JSON-LD 6. HTTP Link header in response headers 7. 301 redirects from variant URLs All seven must agree. One disagreement degrades the signal.
2. Pick the canonical URL
Decide once, document, enforce: # Canonical URL policy 1. Protocol: https (never http) 2. Host: non-www (example.com, not www.example.com) — OR www, but pick one and stick 3. Trailing slash: yes on directories (/blog/) no on pages (/blog/post-1) — OR consistent the other way 4. Parameters: stripped unless they change content (?ref=, ?utm_= stripped, ?id= kept) 5. Fragments: stripped (#section never canonical) 6. Case: lowercase (/Products/ → /products/)
3. Self-referencing canonical on every page
<!-- /blog/post-1 --> <link rel="canonical" href="https://example.com/blog/post-1" /> <meta property="og:url" content="https://example.com/blog/post-1" /> <!-- Schema --> <script type="application/ld+json"> { "@type": "Article", "url": "https://example.com/blog/post-1", "mainEntityOfPage": "https://example.com/blog/post-1" } </script>
4. Common conflict patterns
Conflict 1: rel=canonical vs og:url <!-- WordPress + Yoast default --> <link rel="canonical" href="https://example.com/post-slug/" /> <meta property="og:url" content="https://example.com/post-slug" /> <!-- Trailing slash mismatch — fix template --> Conflict 2: rel=canonical vs sitemap <!-- Page header --> <link rel="canonical" href="https://example.com/products/widget" /> <!-- sitemap.xml --> <url> <loc>https://example.com/products/widget?utm_source=feed</loc> </url> <!-- Sitemap should match canonical, no tracking params --> Conflict 3: HTTP redirects vs canonical # nginx config return 301 https://www.example.com$request_uri; # But canonical in HTML says: <link rel="canonical" href="https://example.com/..." /> # After redirect, www is canonical per server but non-www per HTML # Result: AI engines cite mix of URLs Conflict 4: case-sensitivity <!-- /Products/Widget responds 200 with --> <link rel="canonical" href="https://example.com/products/widget" /> <!-- Good: canonical points to lowercase --> <!-- Better: 301 /Products/Widget → /products/widget -->
5. Cross-domain canonical (syndication)
<!-- Syndicated copy on partner.com --> <link rel="canonical" href="https://example.com/original-article" /> <meta property="og:url" content="https://example.com/original-article" /> <!-- Tells AI engines: cite example.com, not partner.com --> <!-- partner.com still indexes; ranking authority flows to example.com -->
6. Audit current state
Step 1 Extract all signals per page # Fetch and grep URL="https://example.com/blog/post-1" curl -sI "$URL" | grep -i "^link:" # HTTP Link header curl -s "$URL" | grep -oE 'rel="canonical" href="[^"]+"' curl -s "$URL" | grep -oE 'property="og:url" content="[^"]+"' curl -s "$URL" | grep -A1 '"@type":' | grep -oE '"url":"[^"]+"' curl -s https://example.com/sitemap.xml | grep -A1 "$URL" Step 2 Crawl-wide audit Run the Site Crawler . Filter findings: "canonical mismatch", "og:url differs from canonical", "sitemap URL differs from canonical". Each is a discrete fix.
7. Fix at the template layer
Don't fix one page at a time. Patch the layout / theme so every page emits consistent signals: <!-- Layout template, single source of truth --> {# Compute canonical from current request, applying policy #} {% set canon = build_canonical_url(request) %} <link rel="canonical" href="{{ canon }}" /> <meta property="og:url" content="{{ canon }}" /> <script type="application/ld+json"> { "@type": "WebPage", "url": "{{ canon }}" } </script> <!-- Sitemap generator uses same function -->
Tip: 💡 The fix that pays back most: implement one build_canonical_url() helper used by every template, sitemap generator, and redirect rule. Single source of truth eliminates conflicts permanently and saves hours of per-page audits later.
Tags: agent-readiness, canonical conflicts, rel canonical AI, canonical tag fix, self referencing canonical, cross domain canonical
Fix
Restore correct canonical tags after a site migration so equity flows to the new URLs, not stale legacy ones.
Run the Migration Audit audit
Open the Migration Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Migration Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: migration-audit, canonical, migration
Fix
Resolve canonical tag conflicts, missing canonicals and self-referential errors so Google indexes the right URL for every page.
Run the Site Audit audit
Open the Site Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Site Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: site-audit, canonicals
Fix
Optimise category pages with descriptive content, internal linking, faceted nav rules and schema markup that wins rankings.
Run the Ecommerce Audit audit
Open the Ecommerce Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-audit, category, pages
Fix
Fix CDN-vs-origin redirect conflicts from the Redirect Checker. Pick one layer to own redirects, disable at the other, prevent double-hops and loops.
curl with verbose layer identification
curl -IL -v http://example.com/affected-page 2>&1 | \ grep -E "< HTTP|< Location|< Server|< CF-Ray|< X-" # Sample output reveals each hop's source: # < HTTP/1.1 301 Moved Permanently # < Server: cloudflare ← hop 1 from Cloudflare # < Location: https://example.com/affected-page # < HTTP/2 301 # < Server: nginx ← hop 2 from origin nginx # < Location: https://www.example.com/affected-page # < HTTP/2 200 # < Server: nginx ← final 200 from origin
Identify the layer per hop
Cloudflare → Server: cloudflare , CF-Ray: ... Fastly → X-Served-By: cache-... , Server: ... AWS CloudFront → X-Cache: ...cloudfront , Via: ... CloudFront nginx origin → Server: nginx Apache origin → Server: Apache Application server → X-Powered-By: Express , Server: gunicorn # Cloudflare rule (KEEP) # SSL/TLS → Edge Certificates → Always Use HTTPS: ON # nginx origin (DISABLE): # Before: server { listen 80; return 301 https://$host$request_uri; } # After (comment or remove): # server { # listen 80; # return 301 https://$host$request_uri; # } # Note: must still listen on port 80 if Cloudflare connects via HTTP (Flexible SSL) # Switch Cloudflare SSL to Full/Full Strict so it connects via HTTPS instead # Cloudflare Bulk Redirect or Page Rule (KEEP) # example.com/* → https://www.example.com/$1 (301) # Origin .htaccess (DISABLE): # Before: RewriteCond %{HTTP_HOST} ^example\.com$ RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L] # After (remove these lines): # (deleted) # WordPress Redirection plugin (KEEP) # Manage redirects via WP admin UI # .htaccess (CHECK FOR CONFLICTS): # Look for Redirect or RewriteRule lines that duplicate plugin redirects # Comment out duplicates # <IfModule mod_rewrite.c> # # Redirect 301 /old-page /new-page (now managed by Redirection plugin) # </IfModule> # Bad origin config in Flexible SSL setup if ($scheme != "https") { return 301 https://$host$request_uri; } # Origin always sees HTTP from Cloudflare → always redirects → loop # Fix: use X-Forwarded-Proto header if ($http_x_forwarded_proto != "https") { return 301 https://$host$request_uri; } # Cloudflare sets this header to "https" when browser→Cloudflare was HTTPS # Better fix: upgrade Cloudflare SSL to Full or Full (Strict) # Then origin connection is also HTTPS, $scheme is correct Cloudflare Dashboard → Rules → Page Rules / Redirect Rules / Bulk Redirects SSL/TLS → Edge Certificates → Always Use HTTPS Crypto → HSTS settings List every active rule. Compare to origin config. Identify duplicates. grep -rE "return 30[12]|rewrite.*permanent|rewrite.*redirect" \ /etc/nginx/ /etc/nginx/sites-enabled/ /etc/nginx/conf.d/ # Each match is a potential redirect rule. Check if it's still needed. grep -rE "Redirect |RedirectMatch |RewriteRule.*\[R" \ /var/www/ --include=".htaccess" # Some rules date back years and are forgotten about. # redirect-ownership.md # # This site's redirect layers and what each handles: # # === Cloudflare (edge) === # - HTTPS upgrade: "Always Use HTTPS" page rule # - www canonicalisation: Bulk Redirect example.com/* → www.example.com/$1 # - Legacy URL moves: Bulk Redirect list (manually maintained, CSV in repo) # # === Origin nginx === # - Trailing slash: rewrite ^(.+[^/])$ $1/ permanent # - No HTTPS rules (handled by Cloudflare) # - No www rules (handled by Cloudflare) # # === WordPress (CMS) === # - Editorial URL changes: Redirection plugin # - No infrastructure redirects (handled at layer above) # # === Application === # - Login redirect: middleware handles authenticated routes # - A/B test routing: dynamic, based on user cookie # # DO NOT add infrastructure redirects (HTTPS, www, slash) at origin or below. # When adding new redirects, check this doc to identify the correct layer.
Single-hop test
curl -IL http://example.com/affected-page 2>&1 | grep -cE "^HTTP" # Should be 2: one 301 + final 200 # More than 2 = chain still exists
Run Redirect Checker site-wide
Zero loops, all chains 1 hop maximum. Compare to baseline before cleanup.
Performance test
Reduced hops → faster TTFB. WebPageTest first-byte metric typically drops 100-500ms per eliminated hop.
Tip: 💡 Most CDN-origin conflicts come from one team setting up Cloudflare and a different team configuring the server. When in doubt, audit Cloudflare's dashboard first — it's the most likely source of "I didn't know that rule was there" surprises. Then audit origin config. The conflict is usually obvious once you see both rule sets side by side.
Tags: redirect-checker, fix CDN origin redirect, Cloudflare origin conflict, double hop redirect, redirect authority layer, edge vs origin, redirect ownership
Fix
Investigate and respond when schema drift detects a schema type has silently changed (e.g. Recipe to HowTo) and rich results are at risk.
Run the Schema Drift audit
Open the Schema Drift Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Drift audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-drift, changed, schema, types
Fix
Diagnose why ChatGPT never cites your site and fix the underlying signals: authority, schema, content extractability and brand mentions.
Run the AEO audit
Open the AEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your AEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: aeo, chatgpt, invisibility
Fix
Fix sporadic AI citations into consistent ones. Become source-of-truth: original data, primary research, definitive guides on niche queries.
1. Map your current citation niches
Where AI engines DO cite you reveals what's working. Audit the AI Visibility Tracker results: For each prompt that returned a citation to your domain: - What was the underlying query intent? - Which page got cited? - What was extracted (number, quote, fact, definition)? - Which engine cited? Look for patterns: - Specific data ("47% of B2B teams...") → primary research wins - Specific definitions → authoritative explainer wins - Specific comparisons → comparison content wins - Specific case studies → unique original content wins The patterns are your seed niches. Expand from these, not from scratch.
2. Become a primary source
AI engines disproportionately cite primary sources. Aggregation pieces lose to original-data pieces every time. Three primary-source plays: Play 1: Survey-driven research Run quarterly surveys in your category. 500-1000 respondents minimum for credibility. Publish full methodology, sample, raw findings. Format: "2026 State of [Category]" annual report, + quarterly mini-reports between. Why it works: AI engines extract the headline statistics. Every citation says "according to [your brand]'s 2026 survey..." That phrase puts your brand in user's awareness. Play 2: Aggregate-and-analyse Collect public data (gov stats, industry filings, scrape competitor data) Apply original analysis (charts, comparisons, year-over-year) Publish with full methodology and raw data link Update quarterly Why it works: You become the canonical reference for the analysis even though the underlying data is public. "According to [your brand]'s analysis of FDA filings..." Play 3: Practitioner data Anonymise and publish data from your own product/customers. "We analysed 50,000 SEO audits in 2025..." "From 10M emails sent through our platform..." Why it works: Practitioner data is uniquely yours. Competitors can't replicate without similar customer base. AI engines treat it as proprietary primary source.
3. Build definitive guides for niche queries
For high-priority topic queries, build the canonical reference: Pick: a specific narrow query in your category where existing AI citations come from weak sources (Wikipedia, generic SaaS blogs, forum threads) Build: a 3000-5000 word definitive guide - Original framework or model - Worked examples - Comparison tables - Original diagrams (alt-text rich) - Named expert author with credentials - Dated with quarterly updates - FAQPage schema for atomic Q&A pairs - HowTo schema for procedural sections Promote: outreach to citation circle (industry blogs, podcasts) in first 90 days. AI engines need to see external signals that your guide is the authoritative source. One definitive guide per quarter, building over 12-18 months, becomes a portfolio of source-of-truth content. Each addition lifts overall citation frequency, not just on its own query but on related ones.
4. Maintain freshness
AI engines weight recency for time-sensitive queries. Stale content loses to fresh, even if the stale content was originally better: For each piece in your portfolio: - Quarterly review: is anything outdated? Update. - Visible "Last updated: April 2026" in header - dateModified in schema matches actual updates - dateModified should NOT update on every deploy — only real edits - Track citation frequency BEFORE and AFTER each update to validate the freshness signal is working What to actually update: - Statistics (always check) - Tool/product references (versions, prices, features) - Regulation references (laws change) - Date-sensitive examples ("as of [year]") - Links (some break naturally over time) - Add: any new development in the topic
5. Atomic answers within guides
Inside each long guide, structure micro-answers AI engines can lift. See content extractability for the extraction patterns. Short version: For every likely user query the guide covers: - H2 phrased as the question - First sentence below = complete answer - Following paragraphs expand This makes each section independently quotable. A long guide becomes 30 quotable atoms instead of one big block to summarise.
6. Cross-engine consistency
If you're cited heavily on Perplexity but absent on ChatGPT/Claude/Gemini, the gap reveals what's missing: Cited by Probable gap Fix Perplexity only Low training-data inclusion (newer site or content) Time + brand citation breadth ChatGPT not Claude Training-data presence but limited browse Improve real-time content signals Gemini missing Weak Google Search visibility Traditional SEO improvements All except Gemini Google-Extended blocked Check robots.txt for Google-Extended
7. Track at the query level, not just brand level
Brand-level citation share: "0% → 10% → 25%" over 6 months → tells you the overall direction, not what works Query-level citation share: "Query A: 60% cited / Query B: 5% cited" → tells you exactly which content is winning → invest more in the topic of Query A → audit Query B's competitor citations and copy the pattern Run weekly query-level tracking, monthly trend review, quarterly investment shift based on what's actually winning. <div
Tip: 💡 Pick one niche query where you're sporadically cited and double down for 90 days. New original data, refreshed definitive guide, brand-mention push, schema tightening. Track citation frequency on that exact query. If it rises from 10% to 40%, you've proven the pattern. Then replicate across 3-5 more queries. Concentration before breadth.
Tags: ai-visibility, AI citation frequency, source of truth AEO, primary research SEO, definitive guides, AI source authority
Fix
Cite authoritative sources properly in content to strengthen E-E-A-T: link to studies, name experts, link to original data.
Run the E-E-A-T audit
Open the E-E-A-T Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your E-E-A-T audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: eeat, citations
Fix
Eliminate layout shift caused by display ads: reserve ad slot height, lazy-load below the fold, audit ad networks for slow load.
Run the CLS Debugger audit
Open the CLS Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your CLS Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cls-debugger, cls, ads
Fix
Stop cookie banners, notification bars and announcement banners from causing layout shift: position fixed instead of pushing content.
Run the CLS Debugger audit
Open the CLS Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your CLS Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cls-debugger, cls, banner
Fix
Resolve layout shift in JavaScript components: skeleton screens, reserved dimensions, prevent SSR/hydration mismatch.
Run the CLS Debugger audit
Open the CLS Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your CLS Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cls-debugger, cls, components
Fix
Stop dynamic content (chat widgets, modals, late-loading carousels) from shifting layout: reserve space or render outside flow.
Run the CLS Debugger audit
Open the CLS Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your CLS Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cls-debugger, cls, dynamic
Fix
Eliminate font-driven layout shift with font-display: optional, size-adjust, and matching fallback metrics.
Run the CLS Debugger audit
Open the CLS Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your CLS Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cls-debugger, cls, fonts
Fix
Stop images from causing layout shift: width and height attributes on every image, aspect-ratio CSS, responsive image sources.
Run the CLS Debugger audit
Open the CLS Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your CLS Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cls-debugger, cls, images
Fix
Reduce Cumulative Layout Shift in React/Next.js: hydration shifts, next/image dimensions, font loading, dynamic imports, conditional rendering patterns.
Baseline measurement
PageSpeed Insights or Chrome DevTools Performance Insights → CLS measurement. Mobile field data via CrUX (Search Console Core Web Vitals report).
Use next/image with explicit dimensions
Next.js next/image requires width/height or fill prop. Auto-handles srcset, WebP/AVIF, lazy loading. Never use raw <img> for content images — CLS protection only via next/image.
Use next/font for fonts
Next.js next/font (Google Fonts or local) eliminates font-swap CLS by automatically including font-display: optional and sizing fallback fonts. Migrate from manual @import or <link> font loading.
Reserve space for client-only components
Components rendered only after hydration (e.g., dynamic({ ssr: false })) appear empty server-side, then populated after hydration — pushing content down. Fix: render a placeholder with the same dimensions during SSR, replace on hydration.
Fix hydration mismatch warnings
Console errors like 'Text content does not match' indicate server HTML differs from client render. Causes flash + CLS. Common: Date.now(), Math.random(), localStorage usage during render. Use useEffect for client-only data or suppressHydrationWarning sparingly.
Audit third-party scripts
Tag managers, chat widgets, analytics injected as <script> tags shift content. Use next/script with strategy='lazyOnload' for non-critical scripts, position them in reserved containers.
Monitor and iterate
Real User Monitoring (Vercel Analytics, Sentry Performance, SpeedCurve) tracks CLS from real users. Lab data (PageSpeed) often shows better CLS than field data — field is what Google ranks.
Tags: cls-debugger, React CLS, Next.js CLS, Next.js hydration shift, next/image CLS, React layout shift
Fix
Resolve layout shift specific to responsive breakpoints: viewport-relative units, media query collisions, conditional images.
Run the CLS Debugger audit
Open the CLS Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your CLS Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cls-debugger, cls, responsive
Fix
Eliminate layout shift on Shopify: image dimensions, sticky cart drawers, banner timers, app-injected widgets, font loading, currency switchers.
Audit baseline CLS
PageSpeed Insights on homepage, collection, product, cart pages. Mobile CLS is where most Shopify stores fail; under 0.1 is the target.
Force product image dimensions
Dawn and 2.0 themes include width/height. Older themes (Debut, Brooklyn) often don't. Audit theme image tags; add explicit dimensions in liquid: <img src='...' width='800' height='800'>.
Reserve announcement bar space
Announcement bars that appear post-render shift content down. Either: render in liquid template (no post-render shift) or pre-reserve a fixed-height container that the JS-injected bar fills.
Fix cart drawer expansion
Sticky cart drawer (right-side slide-out) shouldn't shift main content — it should overlay. Confirm CSS uses position: fixed with transform translate, not display: block. Some apps misconfigure this.
Control web fonts
Shopify themes typically load Google Fonts. Use font-display: optional in theme CSS to eliminate font-swap shift. Or self-host critical fonts and preload them.
Audit installed apps for CLS
Cookie banners (Pandectes default config causes shift), popup apps (Privy, Klaviyo timer popups), currency converters that re-render prices, related-product apps loading mid-page. Test by selectively disabling on dev store.
Re-test and monitor
PageSpeed after each fix batch. Set Search Console Core Web Vitals alerts. Shopify app updates regress CLS — re-test monthly.
Tags: cls-debugger, Shopify CLS, Shopify layout shift, Shopify Core Web Vitals, Dawn theme CLS, Shopify cart drawer
Fix
Stop YouTube, Vimeo and other video embeds from shifting layout: aspect-ratio wrappers, deferred loading, dimension attributes.
Run the CLS Debugger audit
Open the CLS Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your CLS Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cls-debugger, cls, video
Fix
Eliminate Cumulative Layout Shift on WordPress: image dimensions, ad slots, font loading, plugin injection, audit-fix-verify loop.
Step 1: Audit the current state
Open PageSpeed Insights at https://pagespeed.web.dev/ and run five representative URLs from your WordPress site: homepage, top-traffic blog post, a category archive page, a contact or about page, and (if applicable) a WooCommerce product page. Note the CLS score under mobile — that's where most WordPress sites fail. A score above 0.25 is poor; under 0.1 is good. Most WordPress sites running default themes with plugins score 0.15–0.40 untreated. Pair this with our CLS Debugger which records the actual shifting elements in the DOM so you can identify the source rather than guess. The Debugger flags the highest-impact shift first — fixing it usually halves your CLS in one change.
Step 2: Force image dimensions everywhere
Since WordPress 5.5, the core Image block automatically adds width and height attributes to img tags from the media library. But three common WordPress patterns skip this: Theme template images. Hero banners, featured-image fallbacks, and theme-default placeholders often output <img src="..."> without dimensions. Audit your theme's template files (header.php, single.php, archive.php) and add explicit attributes: <img src="<?php echo get_template_directory_uri(); ?>/img/hero.jpg" width="1200" height="600" alt="..." /> ACF (Advanced Custom Fields) images. When outputting an ACF image field manually, dimensions are often omitted. Use the array return type and output explicitly: <?php $img = get_field('hero_image'); ?> <img src="<?php echo esc_url($img['url']); ?>" width="<?php echo $img['width']; ?>" height="<?php echo $img['height']; ?>" alt="<?php echo esc_attr($img['alt']); ?>" /> Page builder content. Elementor, Divi and Beaver Builder sometimes inject inline images without dimensions, particularly in older templates or custom widget configurations. Run a site-wide HTML scan with our HTML Checker to find images missing dimensions, then edit the builder modules.
Step 3: Reserve space for ad slots
Late-loading ads are the single biggest CLS culprit on content WordPress sites. The fix is to reserve the exact slot dimensions before the ad request fires: <div class="ad-slot" style="min-height:250px;display:block"> <!-- AdSense / Ezoic / MediaVine code here --> </div> For AdSense Auto-ads (which inject ads dynamically without your control), the most reliable mitigation is to switch off Auto-ads and place manual ad units with reserved containers. AdSense Auto-ads on WordPress score CLS poorly almost unavoidably. For Ezoic and MediaVine, both publishers provide WordPress plugins. Enable the plugin's "preserve slot dimensions" or "CLS-safe mode" option (named differently in each). MediaVine specifically has the Trellis theme which is CLS-optimised; if you're on MediaVine and CLS-burdened, switching themes is the fastest path.
Step 4: Control web font loading
Font-swap CLS happens when a fallback font renders first, then swaps to your web font once it loads, shifting all surrounding text. Three fixes, in order of effectiveness: 1. Use font-display: optional in your @font-face declarations. This tells the browser to use the fallback for 100ms only — if the web font isn't ready, it sticks with the fallback for the whole session. Zero CLS, slight visual inconsistency on slow networks. @font-face { font-family: 'Inter'; src: url('/fonts/inter.woff2') format('woff2'); font-display: optional; } 2. Host Google Fonts locally. Loading from fonts.googleapis.com adds DNS, TLS and CSS request overhead. Use a plugin like OMGF (Optimize My Google Fonts) or download and self-host. Combine with font-display: optional for maximum effect. 3. Match the fallback font metrics. Use size-adjust , ascent-override and descent-override in @font-face so the fallback renders at the same height as your web font: @font-face { font-family: 'Inter-fallback'; src: local('Arial'); size-adjust: 107%; ascent-override: 90%; descent-override: 22%; }
Step 5: Audit plugin-injected content
This is the most painful step because the culprit is usually a plugin you "need." The systematic approach: On a staging copy of your site, disable plugins one by one and re-measure CLS after each. Plugins that commonly cause CLS in WordPress: Cookie consent banners (CookieYes, Complianz, Cookie Notice) — load late, push content down. Mitigation: serve the banner inline in HTML rather than injecting via JS. Sticky header plugins — activate on scroll. Mitigation: implement sticky CSS-only with position: sticky , not JS-toggled. Related-posts plugins (YARPP, Contextual Related Posts) — inject content above the fold. Mitigation: render server-side via a shortcode in template, not lazy-injected. Chat widgets (Tidio, Intercom, Crisp) — grow on init. Mitigation: reserve a fixed-size container; or load only on user click. Lazy-load optimisers without dimension preservation (WP Rocket, Smush, Autoptimize misconfigured) — strip dimensions then re-add them late. Re-enable "preserve image dimensions" in plugin settings.
Step 6: Re-test and verify
After each batch of fixes, re-run PageSpeed Insights. Lab data updates immediately. Field data (the 28-day rolling CrUX dataset that Google actually uses for Core Web Vitals ranking signals) takes 4-6 weeks to fully reflect changes. Track both: lab numbers daily during the fix programme, then watch field data trend over the following month. Verify with a real Chrome DevTools Performance recording on a throttled "Fast 3G" connection — that's closer to real mobile conditions than the default desktop. Look for shifts in the Layout Shift Regions visualisation. <div
Tip: 💡 Add CLS to your weekly monitoring. Plugin updates routinely regress CLS — a "minor" update to your ad plugin or cookie banner can wipe out months of work overnight.
Tags: cls-debugger, fix CLS WordPress, Cumulative Layout Shift WordPress, WordPress Core Web Vitals, WordPress layout shift, Gutenberg CLS, Elementor CLS
Fix
Diagnose and fix CLS across the site: identify shifting elements, reserve space, hit the 0.1 threshold for Core Web Vitals “good”.
Run the Core Web Vitals audit
Open the Core Web Vitals Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Core Web Vitals audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cwv, cls
Fix
Recover from schema breakage caused by CMS, theme or plugin updates: roll back, patch generator or override at template level.
Run the Schema Drift audit
Open the Schema Drift Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Drift audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-drift, cms, update, regression
Fix
Restructure collections and category hierarchy so crawlers and shoppers can navigate, and ranking equity flows correctly.
Run the Ecommerce Crawl audit
Open the Ecommerce Crawl Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Crawl audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-crawl, collection, architecture
Fix
Fix colour contrast failures from the Accessibility audit. Hit WCAG AA ratios, find low-contrast pairs in your design system, fix at the token level.
Run the Accessibility audit
Run the Accessibility audit with WCAG AA level set. Export findings to CSV. Each row reports: URL, element selector, foreground colour, background colour, calculated contrast ratio, and WCAG level it fails.
Group by colour pair
Sort findings by foreground/background pair. You'll often see hundreds of findings collapse to 5-10 unique failing pairs. Those pairs are your design-token problem. /* FAIL — #999 on #fff = 2.85:1 */ .muted { color: #999; } /* PASS — #767676 on #fff = 4.54:1 */ .muted { color: #767676; } /* FAIL — #4da6ff on #fff = 2.6:1 */ a { color: #4da6ff; } /* PASS — #1e6fcc on #fff = 5.1:1 */ a { color: #1e6fcc; } ::placeholder { color: #6b6b6b; opacity: 1; } input { border: 1px solid #767676; } /* 4.5:1 — passes both 3:1 and 4.5:1 */
Identify your token system
Most modern sites use design tokens — CSS variables or a Tailwind / Sass colour scale. Find where colour values originate: /* CSS variables */ :root { --text-muted: #999; /* failing */ } /* Tailwind config */ colors: { gray: { 400: '#9ca3af' } /* used as text-gray-400 */ }
Adjust the token
Change the failing token value to one that passes: :root { --text-muted: #6b6b6b; /* now 5.7:1 on white */ } Every usage of var(--text-muted) across the site now passes contrast. One change, site-wide fix. :root { --bg: #ffffff; --text: #1a1a1a; } @media (prefers-color-scheme: dark) { :root { --bg: #1a1a1a; --text: #ffffff; /* keep high contrast in dark mode too */ } }
Automated contrast checks per PR
Tools: pa11y , axe-core CLI, Lighthouse CI. Run against staging URLs, fail builds on new contrast violations. # GitHub Actions - name: Accessibility check run: npx pa11y https://staging.yourdomain.com --threshold 0
Tip: 💡 Defining 2-3 accessible variants per brand colour (bright for backgrounds, dark for text-on-white, light for text-on-dark) up-front prevents the "we need to fix our brand" recurring problem.
Tags: accessibility, fix colour contrast, WCAG AA contrast, contrast ratio, design tokens, accessibility colours, dark mode contrast, low contrast text
Fix
Fix unfavourable share of voice vs competitors. Diagnose imbalance type, prioritise battles, execute differential positioning. The competitive mention strategy.
Where do you have product fit?
The audience segments / use cases where your product is genuinely superior or at least equivalent. Mention strategy without product fit creates traffic that doesn't convert.
Where is the competitor weakest?
Look at competitor's negative mentions — what do their customers complain about? That gap is your positioning territory. Don't compete on their strengths; compete where they're vulnerable.
Where can you sustain investment?
Share of voice is built over years. Pick channels/topics/segments where you can credibly invest 18-36 months without abandoning. Don't pick battles that require sustained spend you can't maintain.
Where does conversion follow?
Mentions that don't drive trials/leads/revenue are vanity. Even winning share of voice in a low-converting channel is poor ROI. Tag mentions by attributed business impact where possible; weight strategic priority toward channels with measurable conversion. Bad positioning: "We're an alternative to BigCorp" (vague, defines you by them) Good positioning: "We're the [specific category] for [specific audience] that need [specific outcome BigCorp doesn't deliver]" Examples: - "The CRM for technical sales teams that need code-aware deal records" - "The email tool for newsletter operators above 100K subscribers who outgrew Mailchimp" - "The analytics platform for product teams that hate dashboards" The wedge is specific enough that "is this for us?" is answerable in seconds by the target audience. Vague positioning produces vague share of voice. Content programme for next 12 months: - 1-2 definitive pieces per quarter on the wedge topic (research, original frameworks, contrarian POVs) - 4-6 supporting pieces per quarter (case studies, comparison deep-dives, customer perspectives) - Weekly social presence from named executives on the wedge topic - Podcast appearances pitched on the wedge - HARO / Qwoted / Help a B2B Writer responses on wedge questions Every piece signals "we are the voice on this specific thing". Twelve months in, you ARE that voice in the audience's mind. When you have wedge positioning, comparison content is leverage: Good comparison content: - "[Your category] for [audience]: comparison of options" - Honest assessment of competitor strengths (don't strawman) - Specific scenarios where each option wins - Acknowledge cases where the competitor is the better choice - Frame your wedge use case as one of several legitimate paths Why this works: - Honest comparison gets shared as "balanced perspective" - Your wedge appears AS the lead option for matching cases - Audiences trust the source because you didn't fake the strawman - SEO bonus: ranks for "[your brand] vs [competitor]" queries Co-marketing / partnership patterns: - Adjacent (not competitive) tools your audience uses - Industry associations in your wedge segment - Influencers / analysts who cover the wedge - Conferences with wedge-aligned audiences - Joint case studies with wedge-aligned customers Partnership amplifies wedge positioning faster than solo content. Pick 3-5 strategic partners over 12 months; pursue depth not breadth. Monthly dashboard: Per priority dimension (the 3-5 you're competing in): - Your mentions (count) - Competitor mentions (count, per competitor) - Your share of voice within dimension (%) - Trend vs prior month, prior quarter, prior year - Sentiment composition (yours vs theirs) - Notable mentions worth amplifying or addressing Quarterly review: - Which priority dimensions moved? Which didn't? - Are you closing gap in target dimensions or just adding noise? - What's working that should expand to next quarter's focus? - What's not working that should stop? Annual review: - Strategic positioning still right? - New competitors emerged or old ones faded? - Wedge definition needs refinement based on market evolution?
Tip: 💡 The strategic clarity: 80% share of voice in a meaningful niche beats 10% share of the broad category every time, for both business impact and AI engine references. The dominant competitor's compound advantages are insurmountable in their territory; in YOUR territory — the wedge where you genuinely fit better — those advantages don't apply. Pick the territory carefully, commit to it for 18+ months, and the share-of-voice math reverses inside that specific surface. That's how challenger brands win.
Tags: brand-mention, share of voice, competitor mentions, competitive positioning, brand share, mention parity
Fix
Close content gaps where competitors rank for topics you don’t cover: prioritise by traffic potential and build the missing pages.
Run the Competitor Analysis audit
Open the Competitor Analysis Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Competitor Analysis audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: competitor-analysis, content, gaps
Fix
Implement a GDPR-compliant cookie consent banner: granular categories, prior consent, no dark patterns and clear rejection.
Run the GDPR audit
Open the GDPR Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your GDPR audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gdpr, cookie, consent
Fix
Recover from Google core update drops. Holistic site quality work: E-E-A-T signals, content depth, UX. When to wait it out vs when to act on specific findings.
1. Confirm the drop is core-update related
Checks: - Drop date matches a confirmed Google core update window - Drop is broad (many pages, not isolated to one section) - Drop is sudden (single-day shift), not gradual - GSC shows no manual action notice - No site-wide technical issue (uptime, indexing, redirects) - Competitors in same category often see opposing moves If those check out, it's likely core update. If drop is gradual, it's content decay or competitive pressure. If drop is isolated to one section, it might be technical.
2. Audit site-wide, not just dropped pages
Core updates assess the WHOLE site. Thin pages elsewhere drag down the assessment of your strongest pages: Audit dimensions: Content quality - Original research, expert-level depth, useful even without ranking - Pages added "for SEO" that don't serve real users → prune - AI-generated bulk content → high risk, audit harshly E-E-A-T - Named authors with credentials, see how-to-fix-author-trust-signals - About/Editorial-standards pages - Privacy, terms, contact pages present and quality User experience - Core Web Vitals across templates - Mobile usability - Intrusive interstitials, sticky popups Site structure - Logical hierarchy, breadcrumbs - Internal linking density and relevance - Indexable content/spam ratio
3. The pruning decision
Most sites need to remove or massively improve 20-40% of pages: For each page, ask: - Does this serve a real user need? - Is it the best answer for its query? - Would a topical expert be proud to share it? - Has it had organic traffic in the last 12 months? - Is it differentiated from competitor pages? If 2+ are "no": - 301 redirect to a relevant authoritative page - OR massively rewrite to actually meet need - OR remove and accept the loss Pruning isn't an admission of failure. Google's stance: fewer high-quality pages outperform many low-quality pages.
4. Strengthen E-E-A-T site-wide
Named authors on every content piece, with proper author trust signals Person schema linking to LinkedIn / industry profiles Editorial-standards or methodology page disclosed Corrections policy visible About page that shows real people, real history, real expertise Contact info visible (not just a form) For YMYL: medical/legal/financial reviewers with credentials
5. Refresh and deepen existing content
Don't just produce more — make what exists deeper: For your top 20 pages by importance: - Add original data, surveys, case studies - Update statistics, examples, references - Add expert quotes (HARO / Qwoted) - Expand thin sections with more depth - Update content extractability - Update visible "Last updated" date when actually updated - Set dateModified in schema This is where the recovery effort concentrates. 20 deeply-improved pages > 100 thinly-touched ones.
6. Wait for the next core update
Core update recovery typically arrives on the NEXT core update, not via incremental crawls. Calendar reality: Google releases 2-4 core updates per year. Recovery cycles: Month 0: Update hits, you drop Month 0-3: Audit and improvement work Month 3-6: Next core update — partial recovery if work was sufficient Month 6-12: Following update — fuller recovery Month 12+: Steady state if quality work continues Don't expect mid-cycle recovery from quality work alone. Technical fixes (broken links, indexing issues) can show faster, but those are usually side effects, not the main cause.
7. What NOT to do
Drastic site overhaul — confuses Google further; incremental quality work outperforms rebuilds Mass AI-generated content — exactly what core updates often target Buying backlinks — may trigger spam-update penalty on top Domain migration — doesn't reset core update assessment; carries forward Switching CMS / redesign — adds technical risk during recovery period Pestering Google publicly — official guidance is "improve content"; that's the answer
8. Read the SQRG
Google's Search Quality Rater Guidelines (publicly available, 175+ pages) describes what raters score. Core updates train on this rater data. Reading the guidelines reveals what to actually fix — vastly more useful than guessing from rank tracker fluctuations. Key sections to read: Page Quality (Lowest, Low, Medium, High, Highest Quality criteria), E-E-A-T evaluation, YMYL identification, Lowest Quality flags. These describe the assessment Google trains its algorithms to mimic. <div
Tip: 💡 Core updates feel personal but aren't. They're broad recalibrations. The recovery that works is the recovery you'd do regardless: better content by better authors, useful to real users, demonstrably trustworthy. Sites that "recover via tactics" usually had quality and made minor tactical fixes. Sites that lack quality stay down regardless of tactics.
Tags: algo-impact, Google core update recovery, ranking drop fix, E-E-A-T core update, site quality, holistic SEO recovery
Fix
Investigate and recover when Rich Snippet coverage drops in Search Console: identify cause, restore valid markup and request re-validation.
Run the Rich Snippets audit
Open the Rich Snippets Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Rich Snippets audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: rich-snippets, coverage, drops
Fix
Fix gaps where competitors get mentioned but you don't. Platform gaps, topic gaps, audience gaps. The systematic outreach to close coverage holes.
Pick comparison set
Select 3-5 direct competitors. Not adjacent players, not aspirational big-name brands — direct competition in your specific category and size tier. The point is realistic gap analysis, not benchmarking against unreachable references.
Count mentions per platform
# Bash-style queries per platform "your brand" site:reddit.com → X mentions "competitor 1" site:reddit.com → Y mentions "competitor 2" site:reddit.com → Z mentions Repeat for: site:twitter.com / x.com site:linkedin.com site:medium.com site:youtube.com site:g2.com (B2B SaaS) site:trustpilot.com (consumer) site:[your industry's main trade publication] Build matrix: your brand vs competitors per platform. The gaps reveal where to invest.
Audit topic coverage
For top 10 category topics, search "[topic] [competitor]" and check coverage. Where competitor appears prominently in topic conversations and you don't, that's a topic gap. Different from platform gap — you might be on Reddit but not in conversations about a specific topic.
Audit audience coverage
Your audience isn't monolithic. Segment by: role (developer, manager, executive), industry (SaaS, healthcare, finance), company size (SMB, mid-market, enterprise), geography. For each segment, check whether competitors appear in their channels and you don't. Some segments may show heavy competitor presence where you have none — audience gap. Symptom: Competitor has 500 Reddit mentions; you have 30. OR competitor has G2 reviews; you have none. OR competitor on every relevant podcast; you on zero. Response: per-platform strategy. Each platform has its own norms: Reddit: - Identify 5-10 subreddits where your audience exists - Build authentic presence (months of helpful, non-promotional) - Eventually mention your brand when contextually appropriate - Patience: 6-12 months to meaningful presence G2 / Trustpilot: - Encourage existing happy customers to leave reviews - Offer review incentives within platform TOS (some platforms ban incentives, others allow modest ones) - Respond to every review (positive and negative) Podcasts: - List 30-50 relevant podcasts ranked by audience - Pitch personalised guest appearances - Show notes mentions are coverage; episodes become assets - 50 pitches → ~5-10 appearances per year YouTube: - Your own channel + outreach to reviewers/tutorial creators - Free trial / demo / product for honest review Symptom: Competitor mentioned heavily in conversations about "[specific topic in your category]"; you absent. Response: become a voice on that topic across channels. - Original research on the topic (publish data, analysis) - Blog content series on the topic (8-12 pieces) - Pitch yourself as expert source to journalists covering it - HARO / Qwoted responses on the topic - Podcast / panel appearances on the topic - Definitive guide that becomes the canonical reference (see how-to-fix-citation-frequency for the source-of-truth play) 12-18 months of consistent topic ownership establishes you as a voice on it. Then mentions accumulate naturally. Symptom: Competitor has presence with [specific audience segment] (e.g. enterprise IT, healthcare CIOs, German market); you don't. Response: targeted to the audience's channels: - Industry publications they read - Conferences they attend - Communities they participate in - Associations they belong to - Influencers they follow - Native-language content if geographic gap This is slower than platform gaps. Audience gaps often reflect genuine product/positioning mismatch — your product may need adjustments to actually serve the audience before mention strategy works. Quarter focus: dominate one specific platform Month 1: presence-building (account setup, lurk, initial activity) Month 2: substantive contribution (high-quality content/comments) Month 3: emerging recognition (mentioned in others' content, profile traffic, follow-counts rising) Month 4+: maintain cadence; brand mentions accumulate Best for: platform gaps where the platform has clear norms (Reddit, LinkedIn, YouTube) Quarter focus: own one specific topic across all relevant channels Month 1: original research / data publication Month 2-3: content series, social distribution, outreach to journalists Month 4+: emerging citation as topic authority; mentions follow Best for: topic gaps where you have unique angle/data and competitor's coverage is shallow Quarter focus: become known to one specific audience segment Month 1-2: research where they consume content Month 2-3: create content / appearances specifically for them Month 3-6: build personal relationships with audience influencers Month 6+: organic mentions from audience members Best for: audience gaps where you have genuine product/positioning fit for the segment Per priority gap, per month: - Your mention count (delta from prior month) - Competitor mention count (theirs may also be growing) - Share of voice: your mentions / total category mentions on this gap - Qualitative: are mentions positive, neutral, or critical? - Conversion signal: do these mentions correlate with leads/trials? Quarterly review: - Which gaps closed? Which didn't? - Where did efforts produce no measurable lift? - What patterns transferred (e.g. Reddit approach worked for Discord too)? - Next quarter's priority gaps
Tip: 💡 The most common gap-closure mistake: spreading effort thinly across 15+ platforms hoping for broad lift. The reality: concentrated effort on 3-5 priority gaps in a quarter produces 3-5x the share-of-voice gain. Pick where you have audience fit and reachable proximity, dominate those, then move outward. Breadth without depth is the comfortable failure mode that produces measurable activity but minimal share-of-voice movement.
Tags: brand-mention, brand coverage gaps, mention gaps, platform coverage, audience coverage, competitive share of voice
Fix
Reclaim wasted crawl budget on faceted URLs, sort parameters, low-value PLPs and let Google focus on revenue-driving pages.
Run the Ecommerce Crawl audit
Open the Ecommerce Crawl Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Crawl audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-crawl, crawl, budget
Fix
Fix crawler-trap patterns from the Robots Tester. Faceted search, calendar pagination, parameter explosion, infinite session URLs. Cap exploration cost.
Search Console crawl stats
Search Console → Settings → Crawl stats. "Crawl requests" chart shows total. "By response" shows status code breakdown. "By file type" shows HTML vs CSS vs JS. "By Googlebot type" shows mobile vs desktop vs image.
Server log analysis
# Top crawled URLs by Googlebot in past week grep "Googlebot" /var/log/nginx/access.log | \ awk '{print $7}' | \ sort | uniq -c | sort -rn | \ head -30 # If trap patterns dominate top results, you have a budget problem
Audit indexed URLs
site:example.com inurl:?sort= site:example.com inurl:?page= site:example.com inurl:?utm_ # If results count is in the thousands and they're trap variants — Google has indexed your traps User-agent: * # Block all URLs with multiple filter parameters Disallow: /products?*&* # Or block specific parameter combinations Disallow: /products?*size=* Disallow: /products?*color=* Disallow: /products?*brand=* # Block years before 2000 and after 2030 Disallow: /events/19* Disallow: /events/204* Disallow: /events/205* # (better: fix the calendar to not generate impossible years) Disallow: /*?sort= Disallow: /*&sort= Disallow: /*?sessionid= Disallow: /*&sessionid= Disallow: /*?PHPSESSID= Disallow: /*?sid= Disallow: /*?utm_ Disallow: /*&utm_ // Apply at the application level function maybeNoindex(req, res) { const url = new URL(req.url, 'https://example.com'); // Multi-parameter URLs are trap variants if (url.search.split('&').length > 2) { res.setHeader('X-Robots-Tag', 'noindex, nofollow'); } // Sort/filter parameter combinations if (url.searchParams.has('sort') || url.searchParams.has('utm_source')) { res.setHeader('X-Robots-Tag', 'noindex, nofollow'); } } <!-- On filter pages, conditionally render --> <?php if (count($_GET) > 2): ?> <meta name="robots" content="noindex, follow"> <?php endif; ?> <!-- On /products?sort=price&page=2 --> <link rel="canonical" href="https://example.com/products" /> <!-- Or to the paginated version --> <link rel="canonical" href="https://example.com/products?page=2" /> <!-- (drops sort but keeps pagination) --> <!-- Filter form submit creates trap URLs — nofollow them --> <form action="/products" method="get"> <select name="sort">...</select> <button type="submit" rel="nofollow">Apply</button> </form> <!-- Print/email variant links --> <a href="/aipageseo-demo-pages/how-to-fix-crawler-traps.html" rel="nofollow">Print</a> <a href="/aipageseo-demo-pages/how-to-fix-crawler-traps.html" rel="nofollow">Share</a> // Don't render links to years with no events const eventsByYear = await getEventsByYear(); const yearsWithEvents = Object.keys(eventsByYear); // Only render pagination links for actual data yearsWithEvents.forEach(year => { // ...render link }); // Don't generate impossible years const minYear = 2010; const maxYear = new Date().getFullYear() + 2; if (year < minYear || year > maxYear) { return res.status(404).end(); } // Use cookies or headers, not URL params // BAD: ?sessionid=abc123 // RIGHT: Cookie: session=abc123 in HTTP header // Express example app.use(session({ secret: '...', cookie: { httpOnly: true, secure: true }, // No "saveUninitialized: true" — only set cookie when needed })); // Middleware to canonicalise query strings function normaliseQueryString(req, res, next) { const url = new URL(req.url, 'https://example.com'); const params = [...url.searchParams.entries()].sort(); const canonical = params.map(([k, v]) => `${k}=${v}`).join('&'); if (canonical !== url.search.slice(1)) { return res.redirect(301, `${url.pathname}?${canonical}`); } next(); }
Re-run Robots Tester
Crawler-trap findings cleared. Sample URLs from trap patterns confirmed blocked by robots.txt.
Search Console crawl stats
4-8 weeks after deployment, crawl requests per day should drop for trap patterns and concentrate on real content. Total HTML crawl requests may decrease while indexed URL count holds steady — good sign.
site: query reduction
site:example.com inurl:?sort= result count should drop to zero over 2-3 months as Google deindexes blocked variants.
Tip: 💡 The biggest crawl-budget wins come from one or two top trap patterns, not from blocking every edge case. Check your server logs — find which trap URLs Googlebot hits most, block those first. The long tail of minor traps matters less.
Tags: robots-sitemap, fix crawler trap, faceted URLs, calendar pagination, parameter explosion, session URLs, infinite URLs, crawl budget, robots disallow patterns
Fix
Improve cross-linking between PDPs, PLPs, collections and editorial content so equity flows to revenue-driving pages.
Run the Ecommerce Audit audit
Open the Ecommerce Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-audit, cross, links
Fix
Resolve discrepancies between CrUX field data and Lighthouse lab data so your Core Web Vitals improvements show up in Search Console.
Run the Core Web Vitals audit
Open the Core Web Vitals Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Core Web Vitals audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cwv, crux, mismatch
Fix
Fix oversized CSS bundles from the CSS Checker. Audit what is shipping, split per route, tree-shake utility frameworks, compress and cache.
Network tab measurements
DevTools → Network → reload. Filter by CSS. Note for each file: Transfer size (compressed, what user actually downloads) Resource size (decompressed) Time to download Whether served from cache
Bundle analysis
For webpack: webpack-bundle-analyzer . For Vite: rollup-plugin-visualizer . For Next.js: ANALYZE=true next build . Shows what's inside each bundle, where the bytes go. // vite.config.js export default { build: { cssCodeSplit: true, // default in Vite 4+ rollupOptions: { output: { manualChunks: { 'vendor-css': ['react', 'react-dom'], } } } } } // webpack.config.js with MiniCssExtractPlugin module.exports = { optimization: { splitChunks: { cacheGroups: { styles: { name: 'styles', type: 'css/mini-extract', chunks: 'async', /* async to enable splitting */ enforce: true, } } } } } // tailwind.config.js module.exports = { content: [ './src/**/*.{html,js,jsx,ts,tsx,vue}', './public/**/*.html', // EVERY file that uses Tailwind classes ], } // scss/main.scss @use 'bootstrap/scss/functions'; @use 'bootstrap/scss/variables'; @use 'bootstrap/scss/mixins'; @use 'bootstrap/scss/root'; @use 'bootstrap/scss/reboot'; @use 'bootstrap/scss/type'; @use 'bootstrap/scss/grid'; @use 'bootstrap/scss/buttons'; // Skip components you don't use: carousel, modal, etc. @use 'bulma/sass/utilities/all'; @use 'bulma/sass/base/minireset'; @use 'bulma/sass/elements/button'; @use 'bulma/sass/components/navbar'; // Cherry-pick only what you use // postcss.config.js module.exports = { plugins: [ require('cssnano')({ preset: ['default', { discardComments: { removeAll: true }, minifyFontValues: true, normalizeWhitespace: true, colormin: true, }] }) ] } http { brotli on; brotli_comp_level 6; brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # Also enable gzip as fallback gzip on; gzip_types same list as above; } <IfModule mod_brotli.c> AddOutputFilterByType BROTLI_COMPRESS text/css application/javascript </IfModule> <IfModule mod_deflate.c> AddOutputFilterByType DEFLATE text/css application/javascript </IfModule> // Build step: pre-compress CSS files npm install --save-dev compression-webpack-plugin new CompressionPlugin({ algorithm: 'brotliCompress', test: /\.(css|js)$/, filename: '[path][base].br', }); brotli_static on; # nginx location ~* \.(css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; }
Before/after Lighthouse
Capture Lighthouse Performance score, FCP, LCP, transfer sizes before any changes. After each change, re-measure. Document the gain per technique: PurgeCSS: usually 60-80% bundle reduction Code splitting: 30-50% reduction in per-page CSS Brotli vs gzip: 15-25% further reduction Caching: zero CSS download on repeat visits Cumulative effect: a 250 KB initial CSS bundle becomes 30-50 KB compressed per page, with repeat visits at 0 KB.
Tip: 💡 The CSS bundle size win cascades. Smaller CSS means faster First Paint, smaller HTML responses (less inlined critical CSS), better Core Web Vitals , lower mobile data costs. Few optimisations have this many downstream benefits.
Tags: css-checker, fix CSS bundle size, CSS code splitting, CSS compression, gzip brotli CSS, Tailwind bundle, CSS optimisation, smaller CSS
Fix
Debug React/Next.js CSS: CSS-in-JS performance, CSS Modules scoping, Tailwind purge, critical CSS, hydration flicker, dark mode patterns.
Identify your CSS approach
Check imports in components and global config. CSS Modules: .module.css files. CSS-in-JS: styled-components or @emotion/styled imports. Tailwind: tailwind.config.js + utility classes in JSX. Global: imported into _app.tsx or layout.tsx.
Audit CSS weight
DevTools Coverage tab. React apps often have 100-300KB CSS, 60-80% unused per page. Tailwind users: verify content array in tailwind.config.js includes all source paths — purge depends on this.
Fix hydration flicker (FOUC) in CSS-in-JS
styled-components and Emotion can show flash of unstyled content during hydration. Fix: configure SSR (styled-components: ServerStyleSheet in _document.tsx; Emotion: createCache + CacheProvider). Next.js 13+ App Router needs styled-components v6 with directive 'use client'.
Switch to CSS Modules or Tailwind for new projects
Modern Next.js (13+) defaults discourage CSS-in-JS due to RSC compatibility issues. CSS Modules (built-in) and Tailwind work cleanly with Server Components. For greenfield projects, choose CSS Modules or Tailwind.
Optimise Tailwind purge
tailwind.config.js content array must include all paths where Tailwind classes appear: ./app/**/*.{js,ts,jsx,tsx}, ./components/**/*.{js,ts,jsx,tsx}. Missing paths = unused classes shipped (CSS bloat). Run npx tailwindcss with --watch in dev; build with NODE_ENV=production for optimal purge.
Add critical CSS
Next.js extracts critical CSS automatically with experimental.optimizeCss in next.config.js. For other React setups, use critters or critical CLI to inline above-fold CSS.
Validate dark mode and theming
CSS variables (--bg, --text, etc.) updated via class toggle on html. Tailwind dark: prefix. CSS-in-JS theme object via ThemeProvider. Test mode-switch — content shouldn't flash wrong theme.
Tags: css-checker, React CSS, Next.js CSS, CSS-in-JS, CSS Modules, Tailwind Next.js, React styling
Fix
Debug Shopify theme CSS: bloated stylesheets, app CSS conflicts, custom CSS in theme editor, base.css editing, dark mode handling, critical CSS.
Audit CSS weight
DevTools → Coverage tab. Shopify stores often load 100-300KB CSS with 50-70% unused per page. Dawn ships with around 80KB base.css plus per-section CSS loaded conditionally.
Identify CSS sources
View source: theme CSS (base.css and section-*.css), app CSS (cdn.shopify.com/extensions/...), inline CSS in theme.liquid. Classify per file.
Edit base
css for theme tweaks. Online Store → Themes → Edit code → assets/base.css. For small changes, easier to add custom CSS via Theme settings → Custom CSS (in 2.0 themes) which lives in section settings.json.
Use theme
liquid for site-wide additions. Add <style>...</style> block in theme.liquid head for small site-wide CSS. For larger custom CSS, create assets/custom.css and load it via {{ 'custom.css' | asset_url | stylesheet_tag }}.
Audit app CSS impact
Apps inject CSS from CDN URLs. Use DevTools Coverage to see which app CSS is unused on which pages. Some apps have settings to load CSS only on relevant pages (cart apps only on cart, etc.).
Resolve specificity conflicts
Use DevTools Computed tab to see which CSS rule wins. Increase specificity in your custom CSS (chain selectors) rather than using !important. !important wars between apps and theme are common but resolvable with proper specificity.
Test theme settings
Shopify 2.0 themes have extensive theme settings. Test colour scheme, dark mode, font choices to ensure your custom CSS doesn't break under different settings.
Tags: css-checker, Shopify CSS, Shopify theme CSS, Dawn CSS, Shopify base.css, Shopify custom CSS
Fix
Reduce CSS bundle size: remove unused rules, split critical CSS, purge framework defaults that aren’t used.
Run the Page Speed audit
Open the Page Speed Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Page Speed audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: pagespeed, css, size
Fix
Fix CSS specificity issues from the CSS Checker. Tame runaway specificity, remove !important, refactor with CSS layers, build maintainable cascade.
Get the high-specificity list
Run the CSS Checker . Filter to specificity findings — selectors above (0,2,0,0) and any !important usage. Export to CSV with selector, specificity score, file location.
Use Specificity Calculator
For visual specificity charts of your entire stylesheet, use Specificity Graph . Plot every rule's specificity in source order. Healthy CSS stays low and flat; unhealthy CSS climbs steeply or zig-zags wildly. div /* (0,0,0,1) */ .button /* (0,0,1,0) */ .card .button /* (0,0,2,0) */ .card div.button /* (0,0,2,1) */ #header .nav a:hover /* (0,1,2,1) */ [data-state="active"] .button /* (0,0,2,0) */ :where(.card) .button /* (0,0,1,0) — :where adds 0 */ /* HIGH SPECIFICITY — hard to override */ #header { padding: 16px; } #header .logo { width: 120px; } /* FLAT — easier to override */ .header { padding: 16px; } .header__logo { width: 120px; } /* DEEP NESTING — fragile, high specificity */ .page .content .article .header .title { color: blue; } /* FLAT — robust */ .article-title { color: blue; } /* Each layer adds !important to overrride previous --> */ .button { background: blue !important; } .button.primary { background: red !important; } .modal .button { background: green !important; } /* New requirement: nobody can override anything without ALSO using !important */ /* FIX — restart with low-specificity selectors, no !important */ .button { background: blue; } .button--primary { background: red; } .modal .button { background: green; } /* MIXED */ .button { padding: 12px; } button.primary { padding: 16px; } /* CONSISTENT — class-only */ .button { padding: 12px; } .button--primary { padding: 16px; } .card { ... } /* Block */ .card__title { ... } /* Element */ .card__title--large { ... } /* Modifier */ .card--featured { ... } /* Block modifier */ <div class="p-4 bg-blue-500 text-white rounded">...</div> // Card.module.css .card { ... } .title { ... } // Card.jsx import styles from './Card.module.css'; <div className={styles.card}> <h3 className={styles.title}>...</h3> </div> // Generated HTML: <div class="Card_card_abc123"> @layer reset, base, components, utilities; @layer reset { * { margin: 0; padding: 0; } } @layer base { body { font-family: system-ui; line-height: 1.5; } } @layer components { .button { padding: 12px; } .card { background: white; } } @layer utilities { .text-center { text-align: center; } .hidden { display: none; } }
Add specificity rules
// .stylelintrc.json { "extends": "stylelint-config-standard", "rules": { "selector-max-specificity": "0,3,0", "selector-max-id": 0, "selector-max-compound-selectors": 3, "declaration-no-important": true } } Blocks new high-specificity selectors and !important. Existing violations need eslint-disable-style comments while you migrate. /* Specificity (0,0,3,0) — three classes */ .card .header .title { color: blue; } /* Specificity (0,0,1,0) — :where() adds zero, only .title counts */ :where(.card, .modal, .panel) .title { color: blue; } /* :is() takes the highest specificity of its arguments */ :is(.card, #special) .title { ... } /* :is() contributes (0,1,0,0) here */
Tip: 💡 Most specificity problems disappear with two changes: (1) flat methodology like BEM, utilities, or CSS Modules, (2) @layer for explicit cascade control. Together they eliminate 95% of !important needs and 99% of specificity wars.
Tags: css-checker, fix CSS specificity, important overuse, CSS cascade, CSS layers, BEM, specificity war, refactor CSS, maintainable CSS
Fix
Fix CSS validation errors from the CSS Checker. Invalid syntax, unknown properties, deprecated values, vendor prefixes and modern replacements.
Get the findings
Run the CSS Checker . Export findings to CSV. Categories include: syntax errors, unknown properties, invalid values, deprecated properties, vendor-prefix issues. /* Missing semicolon */ .button { background: blue color: white; /* This rule is now part of background's value */ } /* Unclosed brace */ .card { padding: 20px; /* missing } */ .button { ... } /* parser confused */ /* Typo in property name */ .text { colour: red; /* colour not color - unknown property */ } /* Extra characters */ .box { margin: 10px;; /* double semicolon */ } npm install --save-dev stylelint stylelint-config-standard echo '{"extends": "stylelint-config-standard"}' > .stylelintrc.json npx stylelint "**/*.css" /* IE 6 hack — drop it */ .box { *display: inline; zoom: 1; } /* Old prefixed properties no longer needed */ .flex { -ms-flex-direction: column; /* IE10 — only matters if you support IE10 */ -webkit-flex-direction: column; /* Old WebKit — Autoprefixer handles if needed */ flex-direction: column; } /* Modern unprefixed version */ .flex { flex-direction: column; } /* Invalid colour */ .text { color: bluergh; /* not a colour */ } /* Invalid unit */ .spacer { height: 10pixels; /* should be 10px */ } /* Out-of-range value */ .box { z-index: -99999999999999; /* exceeds integer range, browser-dependent */ } /* Wrong type for property */ .button { padding: red; /* padding takes length, not colour */ }
Use Autoprefixer with Browserslist
Define target browsers in .browserslistrc : > 0.5% last 2 versions not dead Autoprefixer reads this and adds ONLY the prefixes those browsers need. No more guessing whether to add -webkit- or -moz- .
Write unprefixed source CSS
Stop hand-writing prefixes: /* DON'T write this manually */ .box { -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; transition: all 0.3s; } /* WRITE this — Autoprefixer adds what's needed */ .box { transition: all 0.3s; }
Use source maps
Build tools with source maps enabled let DevTools map back to source. Open the rendered page → DevTools → Sources panel → find the line in the compiled CSS → DevTools shows the source location.
For Tailwind / utility CSS
Errors usually mean a custom class in a config or a plugin generates invalid CSS. Check tailwind.config.js theme extensions and plugins.
GitHub Actions example
name: CSS lint on: [pull_request] jobs: stylelint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20 } - run: npm ci - run: npx stylelint "**/*.css" "**/*.scss" Fail PRs that introduce new validation errors. Zero new errors policy is achievable; gradually reducing pre-existing errors is the way to fix legacy CSS over time.
Tip: 💡 Configure stylelint with rules that match your team's level. stylelint-config-standard is a sensible baseline. Add stylelint-order to enforce property ordering and stylelint-no-unsupported-browser-features to catch usage of CSS your target browsers don't support.
Tags: css-checker, fix CSS validation, invalid CSS, unknown property, CSS syntax error, stylelint, CSS validator, vendor prefix, deprecated CSS
Fix
Debug WordPress theme CSS: unused-CSS removal, critical CSS, plugin conflicts, custom CSS organisation, child theme patterns, page builder CSS bloat.
Step 1: Audit total CSS weight
Open DevTools → Coverage tab. Reload the page. See total CSS bytes loaded and percentage unused. WordPress sites often load 200-400KB CSS with 60-80% unused on any single page.
Step 2: Identify the CSS sources
In Sources tab, list every stylesheet by domain/path. Categorise: theme CSS, page builder CSS, plugin CSS, third-party CSS (Google Fonts, fontawesome).
Step 3: Remove unused CSS via plugin
WP Rocket's 'Remove Unused CSS' or Perfmatters' 'Optimize CSS Delivery' generate per-page CSS. Configure carefully — too-aggressive removal breaks layouts on logged-in or dynamic pages.
Step 4: Extract critical CSS
Critical CSS is the above-the-fold styles, inlined in so the page renders before the main stylesheet loads. Autoptimize, WP Rocket, and Perfmatters all generate it. Test on every template type.
Step 5: Use a child theme for customisations
Never edit the parent theme — updates will overwrite changes. Create a child theme via Generate Child Theme plugin or manually (style.css with Template: parent-theme-slug). Custom CSS goes in child theme style.css.
Step 6: Resolve plugin CSS conflicts
Browser DevTools → Elements → Computed tab shows which CSS rule wins. If a plugin's CSS overrides yours, increase specificity (add class chain) in child theme CSS, or use !important sparingly.
Step 7: Organise custom CSS
WordPress Customizer → Additional CSS for small tweaks. For substantial customisation, put it in child theme style.css. Avoid plugins that store CSS in the database (Simple Custom CSS) — harder to version-control.
Tags: css-checker, WordPress CSS, child theme, unused CSS WordPress, critical CSS WordPress, page builder CSS, WP CSS conflicts
Fix
Optimise Next.js and Nuxt Core Web Vitals: SSR strategy, next/image, next/font, code splitting, edge runtime, INP improvement, Vercel Analytics.
Baseline measurement
Vercel Analytics or Sentry Performance for real field data. PageSpeed Insights for lab. Map current LCP, INP, CLS against thresholds (2.5s / 200ms / 0.1).
Choose right rendering strategy
Static pages: getStaticProps / SSG (Next.js) or asyncData with static mode (Nuxt). Dynamic pages: SSR. Mixed: Next.js ISR or on-demand revalidation. CSR (client-only) only for authenticated dashboards — never for marketing/content pages.
Use next/image (Next
js) or NuxtImage (Nuxt). Both handle: format conversion (WebP, AVIF), responsive sizing, lazy loading, dimensions for CLS protection. Never use raw <img>.
Use next/font or @nuxtjs/google-fonts
Eliminates font-swap CLS. next/font also self-hosts Google Fonts removing third-party connection.
Reduce client component scope
Next.js App Router: most components should be Server Components (no 'use client'). Only mark client when you need useState, useEffect, event handlers. Reduces client JS bundle dramatically.
Code split aggressively
Dynamic imports for heavy components used conditionally: const Chart = dynamic(() => import('./Chart'), { ssr: false }). Reduces initial bundle, improves INP.
Audit third-party scripts
Use next/script with appropriate strategy (afterInteractive, lazyOnload, beforeInteractive). Move analytics, chat, marketing scripts to lazyOnload. Each third-party can cost 100+ms INP.
Tags: cwv, Next.js Core Web Vitals, Nuxt CWV, Next.js LCP, Next.js INP, Next.js performance
Fix
Improve Shopify LCP, INP and CLS: theme choice, app audit, image optimisation, JS deferral, cart drawer fixes, monitoring in Search Console.
Baseline LCP, INP, CLS
PageSpeed Insights on homepage, collection, product, cart. Mobile field data is what Google ranks on. Note where you stand vs targets (LCP <2.5s, INP <200ms, CLS <0.1).
Audit theme age and architecture
Pre-2.0 themes (Debut, Brooklyn, Supply) are CWV-disadvantaged structurally. Dawn and other 2.0 themes (Sense, Studio, Crave, Symmetry) score CWV better. If on a pre-2.0 theme, migration is highest-leverage single change.
Prune apps aggressively
Every Shopify app adds JS. Audit installed apps: which are essential? Most stores have 8-15 apps installed but use 4-6. Uninstall unused. For essential apps with CWV impact, look for lighter alternatives.
Optimise images
Shopify auto-converts to WebP, applies responsive sizing, and serves from CDN. Verify it's working (view source — image URLs should include _500x or similar dimension suffixes). Compress further with apps like Crush.pics, Image Optimizer.
Defer non-critical JS
Apps like Booster: Page Speed Optimizer or AVADA SEO can defer non-critical app scripts. Test carefully — deferring some checkout scripts breaks purchase flow. Test purchase end-to-end after enabling deferral.
Fix cart drawer and overlay layout
Cart drawer, mini-cart, related products should overlay not shift. Audit CSS positioning.
Monitor and iterate
Search Console Core Web Vitals report. Weekly review during fix programme; monthly thereafter. App updates regress CWV often.
Tags: cwv, Shopify Core Web Vitals, Shopify LCP, Shopify INP, Shopify CLS, Shopify page experience
Fix
Fix LCP, INP and CLS together on WordPress: hosting, caching, image optimisation, JavaScript reduction, layout reservation, font loading, the unified audit.
Step 1: Baseline all three metrics
PageSpeed Insights on 5 representative URLs. Record LCP, INP, CLS under mobile (where most CWV failures live). Note field data vs lab data — Google ranks on field.
Step 2: Audit hosting and TTFB
If TTFB exceeds 600ms, LCP is fundamentally capped. Test with a managed WP host (Kinsta, WP Engine) trial or move to a tuned VPS. This is the highest-leverage single change for LCP.
Step 3: Install and configure caching
WP Rocket or WP Fastest Cache. Enable page caching, browser caching, GZIP, lazy load below-fold images. Caching alone usually moves LCP by 30-50%.
Step 4: Optimise images comprehensively
Bulk compress, convert to WebP, ensure responsive srcset, enable native lazy loading, add CDN. Detailed in 'Fix Images in WordPress'.
Step 5: Reduce JavaScript execution time (INP)
Use Query Monitor to identify slow plugins. Remove or replace JS-heavy plugins. Defer non-critical JS via WP Rocket. Move analytics and ad scripts to load after user interaction where possible.
Step 6: Reserve layout space (CLS)
Image dimensions on every img tag, fixed-height containers for ad slots, font-display: optional, audit plugin-injected content. Detailed in 'Fix CLS in WordPress'.
Step 7: Add a CDN
Cloudflare or BunnyCDN. Improves LCP for international visitors, takes load off origin server.
Step 8: Monitor field data over time
Set up Search Console → Core Web Vitals report. Watch for regressions weekly. Plugin updates routinely cause CWV regressions — catch them in the first 28 days before field data is fully affected.
Tags: cwv, WordPress Core Web Vitals, WordPress LCP, WordPress INP, WordPress CLS, CWV WordPress, WordPress page experience
Fix
Fix dark mode issues from the CSS Checker. Use design tokens, hit contrast in dark mode, handle images, persist user preference, prevent flash.
Check every text token
For each text colour on each background colour combination, check ratio: --text on --bg : should be 4.5:1+ --text-muted on --bg : 4.5:1+ for body text, 3:1+ for large --text on --bg-elevated : same checks for card surfaces --accent on --bg : 4.5:1+ for links
Use a contrast tool
WebAIM Contrast Checker or browser DevTools colour picker shows ratio in real time. The Chrome DevTools colour picker displays a horizontal line on the colour gradient showing the AA threshold — colours above it pass, below it fail. <!-- Use currentColor so logo adapts to text colour --> <svg fill="currentColor"> <path d="..."/> </svg> <picture> <source srcset="/logo-dark.png" media="(prefers-color-scheme: dark)"> <img src="/logo-light.png" alt="Brand"> </picture> // Markup <button id="theme-toggle" aria-label="Toggle theme"> <span class="theme-icon"></span> </button> // JS — apply chosen theme via data attribute const stored = localStorage.getItem('theme') || 'system'; applyTheme(stored); function applyTheme(mode) { if (mode === 'system') { document.documentElement.removeAttribute('data-theme'); } else { document.documentElement.setAttribute('data-theme', mode); } localStorage.setItem('theme', mode); } document.getElementById('theme-toggle').addEventListener('click', () => { const current = localStorage.getItem('theme') || 'system'; const next = current === 'system' ? 'light' : current === 'light' ? 'dark' : 'system'; applyTheme(next); }); :root { /* light defaults */ } @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { /* dark values — applies when OS is dark AND user hasn't overridden to light */ } } :root[data-theme="dark"] { /* dark values — applies when user explicitly chose dark */ } <head> <script> // INLINE in head — runs before any render (function() { const stored = localStorage.getItem('theme'); if (stored === 'dark') { document.documentElement.setAttribute('data-theme', 'dark'); } else if (stored === 'light') { document.documentElement.setAttribute('data-theme', 'light'); } // 'system' or null: let prefers-color-scheme media query work })(); </script> <link rel="stylesheet" href="/main.css"> </head>
Both OS modes
Test with system dark mode on AND off. Reload pages with each setting. Confirm no flash on either. Confirm contrast holds on both.
User toggle override
Set OS to light, force dark via toggle, reload. App should remain dark. Same in reverse. Toggle to System and reload — should follow OS.
Edge cases
Test: form inputs (browser default styles differ in dark mode), images with transparent PNG, video posters, embedded iframes (Twitter, YouTube — they have their own theme handling).
Tip: 💡 Dark mode quality is a brand signal. Apple, Apple-adjacent products, developer tools, and most modern SaaS expect polished dark mode. Get it right and users notice. Get it wrong (flash, broken contrast, missing logos) and the rough edges feel cheap.
Tags: css-checker, fix dark mode, prefers-color-scheme, dark mode CSS, dark mode contrast, design tokens dark mode, flash of light, dark mode toggle
Fix
Build a GDPR-compliant process for handling data subject requests (access, deletion, portability) within the required 30-day window.
Run the GDPR audit
Open the GDPR Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your GDPR audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gdpr, data, subject, rights
Fix
Fix decorative image markup from the Image Optimisation audit. Use empty alt, ARIA role presentation, CSS backgrounds, and skip screen reader announcements.
Re-run the Image Optimisation audit
"Missing alt" findings on decorative images should clear (they now have alt=""). "Empty alt on important image" findings should clear (informative images now have descriptive alt). The audit distinguishes properly-marked decoratives from missing-alt errors.
Screen reader pass
Navigate through the page with VoiceOver or NVDA. Decorative images should pass silently. You shouldn't hear "image divider" or any filename announcement.
1. Identify decorative images
The test: would the user lose any information if this image was removed entirely? If no — decorative. If yes — informative or functional. Typical decorative examples Horizontal divider between sections Background pattern or texture Hero background image with overlay text (the image is decoration, the text carries info) Bullet point icons Icon next to a button labelled "Search" (icon + label say the same thing) Profile photo decoration around a testimonial Loading spinner NOT decorative Product photo on a product page Icon without text label (must describe the action) Chart, graph, infographic Photo illustrating an article point Logo with brand name (describes the brand)
2. The empty-alt pattern
<!-- Correct: explicit empty alt --> <img src="divider.svg" alt=""> <!-- Wrong: missing alt attribute --> <img src="divider.svg"> The difference matters. With alt="" , screen readers skip the image silently. Without any alt attribute, screen readers fall back to reading the filename — turning divider.svg into "image divider dot s v g", noise users hate. Even simpler: aria-hidden <img src="divider.svg" alt="" aria-hidden="true"> Defensive double-marking. Empty alt is sufficient for screen readers; aria-hidden is belt-and-braces and also hides the image from assistive technology that might be confused by empty alt in older code.
3. Use CSS backgrounds for truly decorative content
For images that exist solely for visual styling, CSS background-image is cleaner — the image lives in the styling layer, not the document layer, so there's no accessibility consideration at all. <!-- Before: img tag with empty alt --> <section class="hero"> <img src="hero-bg.jpg" alt="" class="hero-bg"> <h1>Welcome</h1> </section> <!-- After: CSS background --> <section class="hero"> <h1>Welcome</h1> </section> <style> .hero { background: url('/hero-bg.jpg') center/cover no-repeat; } </style> Trade-offs Pro: No accessibility question. Image is purely styling. Pro: Easier to swap or remove without touching HTML. Con: Harder to make responsive (need media-queries with image-set() or multiple CSS rules). Con: No native lazy-loading via loading="lazy" attribute. Con: Search engines don't index background images for image search. If the image needs lazy-loading or might benefit from image-search indexing, use img with empty alt. If it's pure decoration, CSS background is cleaner.
4. ARIA presentation role
<img src="..." alt="" role="presentation"> <!-- or --> <img src="..." alt="" role="none"> Removes any semantic meaning the element might have. Belt-and-braces with empty alt. Both role="presentation" and role="none" work; "none" is the modern preferred value. Not required if you have alt="" . Just defensive.
5. Icons paired with text
Icon + visible text label → icon is decorative <!-- Search button with icon + label --> <button> <img src="search.svg" alt="" aria-hidden="true"> Search </button> <!-- Screen reader announces "Search button" once, not "search icon Search button" --> Icon alone → NOT decorative <!-- Icon-only button --> <button aria-label="Search"> <img src="search.svg" alt="" aria-hidden="true"> </button> <!-- aria-label on the button describes the function --> The icon image is still marked decorative (empty alt + aria-hidden), but the parent button has an aria-label describing what clicking does. Screen reader announces "Search button" because of the aria-label. Or use inline SVG with title <button> <svg role="img" aria-labelledby="search-label"> <title id="search-label">Search</title> <path d="..."> </svg> </button>
6. Common mistakes
Mistake 1: Filename in alt <!-- Bad --> <img src="ornament.png" alt="ornament.png"> <img src="bg-pattern.jpg" alt="background pattern"> <!-- Right --> <img src="ornament.png" alt=""> <img src="bg-pattern.jpg" alt=""> Mistake 2: "spacer" alt <!-- Bad --> <img src="spacer.gif" alt="spacer"> <!-- Right --> <img src="spacer.gif" alt=""> <!-- Or better: use CSS margin/padding instead of spacer images --> Mistake 3: Decorative-but-still-keyworded alt <!-- Bad — keyword stuffing on a decorative divider --> <img src="divider.svg" alt="best SEO tools website divider"> <!-- Right --> <img src="divider.svg" alt="">
7. CMS-level enforcement
WordPress: filter for decorative // Allow editors to mark images as decorative function add_decorative_field($form_fields, $post) { $form_fields['decorative'] = [ 'label' => 'Decorative (skip in screen readers)', 'input' => 'html', 'html' => '<input type="checkbox" name="attachments[' . $post->ID . '][decorative]" ' . checked(get_post_meta($post->ID, '_decorative', true), '1', false) . '>' ]; return $form_fields; } add_filter('attachment_fields_to_edit', 'add_decorative_field', 10, 2); // Output empty alt when marked decorative function filter_decorative_alt($attr, $attachment) { if (get_post_meta($attachment->ID, '_decorative', true) === '1') { $attr['alt'] = ''; $attr['aria-hidden'] = 'true'; } return $attr; } add_filter('wp_get_attachment_image_attributes', 'filter_decorative_alt', 10, 2); React: a Decorative component function DecorativeImage({ src, ...rest }) { return <img src={src} alt="" aria-hidden="true" {...rest} />; } // Usage <DecorativeImage src="/divider.svg" className="my-section-divider" /> Forces explicit decoration choice at the component level. Linters can flag any bare <img> without alt.
8. Verify
Step 1 Re-run the Image Optimisation audit "Missing alt" findings on decorative images should clear (they now have alt=""). "Empty alt on important image" findings should clear (informative images now have descriptive alt). The audit distinguishes properly-marked decoratives from missing-alt errors. Step 2 Screen reader pass Navigate through the page with VoiceOver or NVDA. Decorative images should pass silently. You shouldn't hear "image divider" or any filename announcement. <div
Tip: 💡 The rule that catches 90% of cases: if an image only exists for visual reasons and removing it would lose no information, give it alt="" (empty quotes, attribute present). Don't remove the alt attribute. Don't write descriptive alt for a decoration. Don't keyword-stuff. Just empty quotes.
Tags: image-optimisation, fix decorative images, empty alt, role presentation, CSS background image, ARIA decorative, screen reader skip, icon accessibility
Fix
Bring important pages closer to the homepage in click-depth: hub pages, breadcrumb improvements and internal link audits.
Run the Internal Links audit
Open the Internal Links Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Internal Links audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: internal-links, deep, pages
Fix
Restore schema markup that disappeared after a plugin update, theme change or content edit: identify regression and rebuild.
Run the Schema Drift audit
Open the Schema Drift Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Drift audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-drift, deleted, schema
Fix
Fix deprecated HTML elements from the HTML Checker. Replace center, font, marquee, frame and other dead tags with modern CSS and semantic alternatives.
Run the HTML Checker
Run the HTML Checker filtered to deprecated-element warnings. Export the CSV. Each row identifies the URL, line, and which deprecated element appears.
Determine source: template or content
View source on a few flagged pages. Is the deprecated tag in template markup (header, footer, sidebar — fix once in the template) or in CMS-stored content (need bulk replacement in database)? Most are in content from old WYSIWYG editors.
Search templates for deprecated patterns
SSH into the site or use SFTP: grep -rE "<(center|font|marquee|big|strike|tt|frame|blink)" /var/www/yoursite/wp-content/themes/your-theme/ Each match is a template file that needs updating. Apply the replacement table above.
Run targeted database replacements
WP Admin → Tools → Better Search Replace . Take a database backup first. Then for each deprecated pattern: Search: <center> Replace: <div style="text-align:center"> Tables: wp_posts, wp_postmeta Dry run: yes (first), then real run Repeat for the closing tag and each deprecated element. The plugin handles serialised data correctly.
Apply replace filters in templates
In theme.liquid or specific section templates, filter rich-text output: {{ product.description | replace: '<center>', '<div style="text-align:center">' | replace: '</center>', '</div>' | replace: '<font color="red">', '<span style="color:red">' }} Quick fix; the real cleanup is editing each product's description in the admin panel.
Direct database replace
BACKUP FIRST. Then: UPDATE wp_posts SET post_content = REPLACE(post_content, '<center>', '<div style="text-align:center">'); UPDATE wp_posts SET post_content = REPLACE(post_content, '</center>', '</div>');
Configure WYSIWYG editor to strip deprecated tags
TinyMCE in WP: filter via tiny_mce_before_init with valid_elements excluding deprecated tags. Custom editors: configure allowed-element schema to omit deprecated tags.
CI linting
Add a build-time check that fails the deploy if templates contain deprecated tags: ! grep -rE "<(center|font|marquee|big|strike|tt|frame|blink)" ./src/templates/
Tags: html-checker, fix deprecated HTML, center tag, font tag, marquee, frameset, HTML5 replacements, semantic HTML, modern HTML
Fix
Reduce Google’s rewrite rate of your meta descriptions: write specific, query-matched, length-appropriate copy.
Run the SERP Preview audit
Open the SERP Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your SERP Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: serp-preview, description, rewrite
Fix
Build a Google Disavow file correctly. Domain vs URL syntax, when to use it, upload to GSC, processing timeline. The disavow workflow for toxic backlinks.
1. File format
Plain UTF-8 text file, max 100,000 lines, max 2MB. One directive per line. Comments with #. Example: # Disavow file for example.com - April 2026 # Last updated: 2026-04-15 # Spam network discovered Q1 2026 domain:spammy-network.com domain:link-farm.net # Individual toxic pages http://otherwise-ok-site.com/spam-link-page https://random-blog.com/post-with-bad-link # PBN cluster domain:pbn-site-1.org domain:pbn-site-2.org domain:pbn-site-3.org
2. Domain vs URL scope
Use domain: when: - Entire site is spam (PBN, link farm, low-quality directory) - All links from the domain are toxic - You want to ignore future links from that domain too Use URL when: - Otherwise-legitimate site has ONE bad page linking to you - You don't want to disavow other links from the domain - Comment-spam scenarios (one comment thread on otherwise-ok site) When in doubt: - Spammy patterns: domain: - Single bad page on real site: URL - Most real-world cases: domain: (PBN clusters)
3. Common syntax mistakes
Trailing slashes : domain:example.com/ wrong; should be domain:example.com Including http/https : domain:https://example.com wrong Wildcards : domain:*.example.com wrong; just use domain:example.com (covers subdomains) Multiple URLs per line : one directive per line Wrong file format : must be plain text, not docx or rtf BOM characters : save as UTF-8 without BOM
4. Submission
1. Google Search Console → no direct link, go to: https://search.google.com/search-console/disavow-links-tool/ 2. Select the property 3. Click "Disavow links" (replaces any existing file) 4. Upload disavow.txt 5. Confirm The new file fully REPLACES any previous disavow. Don't upload partial files — include EVERYTHING you want disavowed.
5. Processing and effect
Timeline: - Upload: immediate - Google processes: 2-6 weeks - Effect: Google treats disavowed links as nofollow - Algorithmic recovery: 4-12 additional weeks Effect on rankings: - Removes toxic signal (good) - Doesn't restore prior rankings automatically - Best paired with other quality work (content, real backlinks)
6. When NOT to use disavow
You haven't tried removal via outreach yet You don't have manual action OR strong algorithmic evidence Links are merely low-quality, not actively toxic Out of "just in case" defensiveness Without auditing what you're disavowing
7. Maintain over time
Quarterly review: - Re-audit backlink profile - Add new toxic patterns found - Verify removed disavowed entries don't reappear - Update the dated comment line Keep historical versions in case you need to revert. <div
Tip: 💡 Disavow is the nuclear option. Most sites don't need it. If you do, build the file carefully, audit honestly, prefer domain-level for true spam networks, prefer URL-level for isolated cases on legitimate sites. Update quarterly.
Tags: backlinks, disavow file, Google disavow tool, link disavow syntax, disavow.txt, GSC disavow
Fix
Make your links render properly in Discord: og:image, og:title, og:description and content-type headers that Discord respects.
Run the Social Preview audit
Open the Social Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Social Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: social-preview, discord, preview
Fix
Fix DOCTYPE errors from the HTML Checker. Set the correct HTML5 DOCTYPE, escape quirks mode, fix CMS injection bugs and prevent recurrence.
View page source
Right-click → View Page Source (or Ctrl+U). The very first line should be: <!DOCTYPE html> If the first line is blank, contains a BOM character, contains an XHTML DOCTYPE, or contains anything else — you have a problem.
Check the rendering mode
Open browser DevTools → Console. Type: document.compatMode Expected: "CSS1Compat" (standards mode). If you get "BackCompat" , the page is rendering in quirks mode and your DOCTYPE is wrong or missing.
Edit header.php
In WP Admin → Appearance → Theme File Editor → header.php . The file should start with: <!DOCTYPE html> <html <?php language_attributes(); ?>> <head> No blank lines, no whitespace, no PHP echo before the DOCTYPE line. If you're using a child theme, edit the child theme's header.php, not the parent.
Edit theme.liquid
Online Store → Themes → Actions → Edit code → layout/theme.liquid . First line should be: <!DOCTYPE html> <html lang="{{ request.locale.iso_code }}"> Shopify rarely has DOCTYPE issues since theme.liquid is tightly controlled, but custom themes sometimes inject Liquid output before the DOCTYPE.
Next handles DOCTYPE automatically
Next.js emits the correct HTML5 DOCTYPE in its server output. You don't need to add it in your app/layout.tsx . If you see a DOCTYPE issue on a Next site, the cause is usually middleware or a custom server response that prepends content before Next renders. Audit middleware.ts and any custom response handlers.
PHP closing tags with trailing whitespace
A plugin file ends with: function my_function() { // ... } ?> [BLANK LINE] That blank line after ?> echoes to the response before the DOCTYPE. Fix: remove the closing ?> entirely. PHP doesn't require it and modern coding standards recommend omitting it in PHP-only files.
UTF-8 BOM in template files
Some editors save files with a UTF-8 Byte Order Mark (BOM) — three invisible bytes at the start of the file. The BOM echoes to the response before DOCTYPE. Re-save the template file as "UTF-8 without BOM" in your editor.
Plugin output buffering before theme
A plugin echoes diagnostic output (debug logs, admin notices to non-admins) before WordPress reaches header.php. Disable plugins one by one to identify the offender, then file a bug or replace the plugin.
Test via curl
curl -s https://yourdomain.com/ | head -c 100 Expected: response starts with <!DOCTYPE html> immediately. Any preceding character is a bug.
Re-check in browser
In DevTools console: document.compatMode should return "CSS1Compat" .
Re-run the HTML Checker
Run the HTML Checker again. DOCTYPE finding should be cleared. While you're at it, fix any other validation warnings the checker reports.
Tip: 💡 If you maintain multiple sites, add a CI lint step that checks every template file for: (a) no BOM, (b) no closing ?> in PHP-only files, (c) DOCTYPE as first non-whitespace content. Catches issues before deploy.
Tags: html-checker, fix DOCTYPE errors, HTML5 DOCTYPE, quirks mode, missing DOCTYPE, DOCTYPE WordPress, DOCTYPE injection, standards mode, HTML validation
Fix
Step-by-step fix for duplicate content from the Site Crawler. Identify duplicates, choose canonicals, consolidate or 301, prevent recurrence.
Run the Site Crawler with similarity scoring
Run a fresh Site Crawler with the option Detect near-duplicates enabled. The crawler scores every page pair for content similarity and groups pages above the threshold (default 80%) into clusters. Export the cluster CSV.
Sort by similarity
Highest-similarity clusters (95-100%) are exact duplicates from CMS quirks — fix first. 80-95% clusters are near-duplicates that need editorial decisions. Below 80%, generally fine unless intent overlaps significantly.
Add self-referencing canonical to chosen primary
In the <head> of the primary URL: <link rel="canonical" href="https://yourdomain.com/products/widget"> Self-referencing canonicals are best practice on every indexable page. They prevent accidental duplication from tracking parameters and similar URL variants.
Add cross-referencing canonical on duplicates
If duplicate URLs must stay accessible (e.g. parameter URLs needed for filtering), add canonical on each duplicate pointing at the primary: <!-- on /products/widget?utm_source=email --> <link rel="canonical" href="https://yourdomain.com/products/widget">
Decide the winner per duplicate cluster
For each cluster, the winner is the URL with the most external backlinks (check Search Console Links report) or the most current Search Console clicks. If both are similar, choose the cleaner URL or the one matching your current URL conventions.
301 the losers to the winner
Apply 301 via your platform — Redirection plugin (WordPress), URL Redirects (Shopify), nginx rewrite, Apache Redirect 301. See the redirect chains guide for syntax per platform.
Update internal links
Internal links still pointing at the loser URL now go through a 301 hop. Update them to point directly at the winner. Use bulk find-and-replace techniques from the broken links guide .
Add noindex meta
In the <head> of these pages: <meta name="robots" content="noindex, follow"> follow lets Google still discover links from these pages; noindex keeps them out of search results.
Tags: site-crawler, fix duplicate content, canonical tags, content consolidation, duplicate pages, near-duplicate, content cannibalisation, 301 duplicates
Fix
Fix duplicate HTML IDs from the HTML Checker. Audit conflicts, rename safely, fix JS selectors and form labels, prevent recurrence in templates.
Get the duplicate list
Run the HTML Checker . Filter to duplicate-ID errors. Each finding shows the ID value, how many times it appears on the page, and the element types.
Group by component
Duplicate IDs almost always come from a reusable component rendered multiple times on the same page. Example: a "product-card" template with id="add-to-cart" on its button. Render 12 product cards on a category page and you have 12 elements with that ID.
Search for the ID across the codebase
# Find every JS reference grep -rE 'getElementById.{0,3}("|'"'"')add-to-cart\1' src/ # Find every CSS reference grep -rE '#add-to-cart' src/ # Find every label-for reference grep -rE 'for="add-to-cart"' src/ # Find anchor references grep -rE 'href="#add-to-cart"' src/ Note every file and line. These all need updating when you rename. <!-- WordPress / PHP --> <button id="add-to-cart-<?php echo $product->id; ?>">Add to cart</button> <!-- Liquid (Shopify) --> <button id="add-to-cart-{{ product.id }}">Add to cart</button> <!-- React JSX --> <button id={`add-to-cart-${product.id}`}>Add to cart</button> import { useId } from 'react'; function ProductCard() { const id = useId(); return ( <form> <label htmlFor={id}>Quantity</label> <input id={id} type="number" /> </form> ); }
Update JS selectors
Replace document.getElementById('add-to-cart') with event delegation or a class-based selector: // BEFORE document.getElementById('add-to-cart').addEventListener('click', handleClick); // AFTER (event delegation) document.addEventListener('click', (e) => { if (e.target.matches('.add-to-cart')) handleClick(e); });
Update CSS selectors
Replace #add-to-cart { ... } with .add-to-cart { ... } . Classes work for styling; IDs aren't needed.
Update form labels
Forms must use unique IDs because label for= points at a specific element. If you parameterised by instance: <label for="quantity-<?php echo $product->id; ?>">Quantity</label> <input id="quantity-<?php echo $product->id; ?>" type="number">
Run the HTML Checker
Duplicate-ID count should drop to zero. Any remaining duplicates indicate a component you missed or content with hardcoded IDs in CMS rich-text.
Test interactive behaviour
Click every button that previously used the duplicate ID. Submit every form. Test anchor links. Renaming usually catches most issues but visual testing catches what greps miss.
Tip: 💡 For new templates and components, default to classes for styling and JS hooks. Use IDs only when uniqueness is structurally required ( form labels , in-page anchors). Prevention beats audit-after.
Tags: html-checker, fix duplicate IDs, HTML ID conflicts, getElementById, form label for, anchor links, ID validation, unique identifier
Fix
Consolidate duplicate product pages from variants, parameters or accidental duplicates with canonical tags and 301s.
Run the Ecommerce Crawl audit
Open the Ecommerce Crawl Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Crawl audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-crawl, duplicate, products
Fix
Shopify SEO: product page optimisation, collection structure, Product schema, /products/ URL handling, blog SEO, app dependencies, sitemap.
Audit product page structure
5 representative products. Each should have: unique descriptive title (not just brand+SKU), unique long description (not duplicate from supplier), product images with alt text, breadcrumbs, related products, customer reviews.
Optimise collection (category) pages
Shopify collections are your category pages. Each needs: descriptive title and meta, intro paragraph above products, manual product ordering for best-sellers first, internal links from blog/homepage.
Verify Product schema
View source on product page. Should have Product JSON-LD with name, image, description, sku, brand, offers (price, currency, availability), aggregateRating (from review app).
Handle /products/ URL prefix
Shopify forces /products/ in URLs (immutable). Live with it; redirect any old non-/products/ URLs from migration. Don't try to remove it via Cloudflare tricks — breaks Shopify admin.
Optimise Shopify blog
Shopify Blog is functional but limited. Each post needs: SEO title + meta description (not just defaults), featured image with alt, internal links to relevant products, author bio. Schedule posts; Shopify supports scheduling natively.
Manage app SEO dependencies
Review apps, FAQ apps, page builder apps (PageFly, Shogun) all affect SEO output. Audit what each app injects (JSON-LD, meta tags, content). Resolve conflicts where multiple apps target the same element.
Submit sitemap
Shopify auto-generates sitemap.xml. Submit to Search Console at yourstore.com/sitemap.xml. Resubmit after major site changes (theme migration, structure changes).
Tags: ecommerce-audit, Shopify SEO, Shopify product page, Shopify collection SEO, Shopify Product schema, Shopify URL structure
Fix
WooCommerce SEO: product page optimisation, category structure, Product schema, faceted-nav handling, sitemap configuration, speed and review snippets.
Step 1: Audit product page structure
Check 5 product pages. Each should have: descriptive H1 (not 'Product Title'), unique long-form description (not duplicate from supplier), product images with alt text, breadcrumbs, related products, customer reviews.
Step 2: Configure category hierarchy and URLs
WooCommerce → Settings → Permalinks → set product permalink to /product/ or /shop/ with the category base. Avoid deep nested categories (3+ levels) which dilute internal link equity.
Step 3: Add Product schema correctly
Yoast WooCommerce SEO, Rank Math WooCommerce, or AIOSEO add Product schema automatically. Verify it includes: name, image, description, sku, brand, offers (with price, priceCurrency, availability), aggregateRating.
Step 4: Handle faceted navigation
Variation pages (size, colour) can create duplicate-content issues. Use canonical URLs to consolidate to the parent product, OR use parameter handling in Search Console, OR use AJAX-only filters that don't change URLs.
Step 5: Configure sitemap exclusions
Exclude cart, checkout, my-account from sitemap and robots.txt. Include product, category, brand archive URLs. Yoast/Rank Math do this automatically; verify in /sitemap_index.xml.
Step 6: Fix WooCommerce-specific speed issues
WooCommerce loads cart fragments and product scripts globally even on non-shop pages. Use 'Disable WooCommerce Bloat' or Perfmatters to dequeue them on non-shop pages.
Step 7: Enable customer reviews and review schema
WooCommerce → Settings → Products → Reviews → enable. Configure 'Verified owner label' to add trust signal. Reviews automatically appear in Product schema as aggregateRating once you have 1+ reviews.
Tags: ecommerce-audit, WooCommerce SEO, WooCommerce product page, Product schema WooCommerce, WooCommerce sitemap, WooCommerce speed
Fix
Fix E-E-A-T in blog posts: author byline placement, Person schema, citation patterns, last-updated dates, trust signals throughout post.
Add visible author byline at top
Name, role, link to author profile. Top of post, not buried at bottom. 'By [Author Name], [Role] — [Date]'. Avoid generic 'Admin' or 'Editor' bylines. Real author with real credentials.
Build author bio at bottom
Photo (real photo, not stock). Bio: credentials, experience, contact, sameAs links (LinkedIn, Twitter, GitHub, professional bodies). 3-5 sentences. Establishes the human behind the content.
Add Person schema in JSON-LD
Article schema includes author Person object. name, url (author profile), image, jobTitle, worksFor, sameAs array (LinkedIn, Twitter URLs). Strong entity signal for Google.
Cite sources inline
When you make factual claims, link to authoritative sources. 'According to [linked source], X is Y'. Don't quote without attribution. Citations build trust + signal good research.
Add last-updated date
Visible date on post: 'Last updated: [Date]'. Plus dateModified in Article schema. Fresh content ranks better; stale content loses ranks. Update meaningfully (new info, updated stats) not cosmetically.
Build trust-page presence
About Us page with team, mission, contact. Privacy Policy and Terms. Contact page with multiple methods (email, form, phone if applicable, physical address if applicable). Trust pages aren't directly about blog content but build site-wide E-E-A-T.
Iterate over months
E-E-A-T improvements take months to influence rankings. Implement systematically, monitor quarterly, expect 60-180 days for visible impact on traffic.
Tags: eeat, E-E-A-T blog, blog post E-E-A-T, author byline SEO, Person schema blog, blog trust signals
Fix
Fix E-E-A-T in product reviews: hands-on testing photos, specific scenarios, measured data, comparison context, named reviewers with credentials.
Include real testing photos
Hands-on review needs evidence: photos of you using the product, setup, unboxing, in-context use, wear-and-tear over time. Stock photos signal: 'didn't actually test'. Reviewer-taken photos with timestamps build trust.
Document specific use cases
Don't generic-describe: 'I used this laptop for video editing — specifically, editing 4K footage in DaVinci Resolve for client X over 3 months. Render times: [specific numbers]. Stability: [specific incidents].' Specifics demonstrate real use.
Provide measured data
Battery life: actual hours measured in specific scenarios. Weight: measured. Sound levels: decibel readings. Performance: benchmark scores from actual testing. Numbers signal first-hand testing; vague descriptors signal lack of testing.
Comparison context
'Compared to [Competitor X] which I also tested', highlighting specific differences. Direct comparisons with real testing across products build authority. Don't just list features; assess how products actually differ in use.
Transparent affiliate disclosure
'As an affiliate, I earn from qualifying purchases. This doesn't affect my reviews — products tested before commission consideration.' Visible disclosure. Following FTC requirements (US), CMA (UK) is mandatory; helps E-E-A-T even where not legally required.
Name reviewer with relevant background
'Reviewed by [Name], [credential relevant to category]'. Photography expert reviewing cameras. Audio engineer reviewing headphones. Real names with real credentials. Generic 'expert reviewers' less effective.
Cover negatives honestly
Best reviews mention drawbacks. 'Battery short for power users — managed only 5 hours in my testing.' Honest negatives signal genuine assessment, not affiliate cheerleading. Counter-intuitively, mentioning negatives often improves conversion (perceived honesty).
Tags: eeat, product review E-E-A-T, review SEO, hands-on review, product review Google, review trust signals
Fix
Fix E-E-A-T in YMYL (Your Money Your Life) content: medical reviewer lines, professional disclaimers, evidence-based statements, named expert reviewers.
Identify YMYL pages
Google's YMYL categories: medical/health, financial/legal, news/civic, safety. Your content covering: medications, financial decisions, legal advice, mental health, safety practices = YMYL. Treat with highest E-E-A-T rigour.
Add named expert reviewer line
Medical content: 'Medically reviewed by [Doctor Name], [Specialisation, Credentials], [Date]'. Financial content: 'Reviewed by [Name], CFP, [Date]'. Legal: 'Reviewed by [Name], [Jurisdiction]-licensed attorney, [Date]'. Real reviewers with verifiable credentials.
Display credentials prominently
Reviewer name with credentials visible (degrees, licences, registration numbers where applicable). Link to verifiable profile (LinkedIn, professional registry). Generic 'medical professional' doesn't suffice; specific named expert does.
Cite peer-reviewed sources
Medical: PubMed, Cochrane Reviews, WHO publications. Financial: SEC filings, Federal Reserve, OECD. Legal: court records, statutes, bar association publications. Inline citations to authoritative sources, not aggregator sites.
Include professional disclaimers
Medical: 'This article is for informational purposes only and is not a substitute for professional medical advice. Always consult a qualified healthcare provider.' Financial: 'Not financial advice. Consult a licensed financial advisor.' Visible at start and/or end.
Transparent funding/conflict disclosure
If sponsored, disclose. If author has conflicts (employed by mentioned product company, owns stock in mentioned company), disclose. Top YMYL publications (NYT, Wirecutter) have detailed disclosure policies; emulate.
Build site-wide YMYL credibility
Editorial standards page (how content is researched, reviewed, updated). Corrections policy (how errors are addressed). Editorial team page (visible team with credentials). About page with mission and methodology. These site-wide signals reinforce per-page YMYL trust.
Tags: eeat, YMYL E-E-A-T, medical content SEO, financial content SEO, legal content SEO, YMYL trust signals
Fix
Diagnose why pages eligible for rich results aren’t showing them: content quality, page authority, schema-content match.
Run the Rich Snippets audit
Open the Rich Snippets Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Rich Snippets audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: rich-snippets, eligible, not, shown
Fix
Fix character encoding errors from the HTML Checker. Set UTF-8 charset correctly, remove BOM, fix mojibake, configure database and server consistently.
Check meta charset
View page source. The <head> should contain near the top: <meta charset="UTF-8"> If you see charset="ISO-8859-1" , charset="windows-1252" , or no charset at all — that's a problem.
Check HTTP Content-Type header
curl -sI https://yourdomain.com/ | grep -i content-type Expected: content-type: text/html; charset=UTF-8 . If charset is missing or different from meta, that's where mojibake comes from.
Check file encoding
file -i /path/to/template.php Expected: charset=utf-8 . charset=iso-8859-1 means the file is saved wrong. charset=utf-8 with BOM means there's a Byte Order Mark to remove.
Check database charset
MySQL: SHOW VARIABLES LIKE 'character_set%'; Want all entries set to utf8mb4 . utf8 (MySQL's mistake — only 3 bytes) doesn't support emoji or rare characters.
Put it first in head
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Page Title</title> ... Browsers must see the charset declaration within the first 1024 bytes of the document. Putting it first in <head> guarantees that. http { charset utf-8; charset_types text/html text/css text/plain application/javascript application/json application/xml; } # In httpd.conf or .htaccess AddDefaultCharset UTF-8 AddCharset UTF-8 .html .css .js .json .xml // At the top of any PHP file or in a global bootstrap header('Content-Type: text/html; charset=UTF-8');
Find files with BOM
find . -type f \( -name "*.php" -o -name "*.html" \) -exec grep -l $'^\xef\xbb\xbf' {} + Lists every file starting with a UTF-8 BOM.
Strip BOM in bulk
find . -type f \( -name "*.php" -o -name "*.html" \) -exec sed -i '1s/^\xef\xbb\xbf//' {} + Removes the BOM from the first line of each matching file. Test on a backup first.
Backup first
mysqldump -u user -p database > backup-before-utf8mb4.sql
Convert per database, table, column
ALTER DATABASE mydatabase CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; ALTER TABLE wp_posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- repeat for every table In WordPress, set DB_CHARSET in wp-config.php to 'utf8mb4' after migration.
Fix the connection charset
Tables in utf8mb4 + connection in utf8 = silently truncated data on insert. Set connection charset on connect: // PHP / MySQLi $mysqli->set_charset('utf8mb4'); // PHP / PDO new PDO('mysql:host=localhost;dbname=db;charset=utf8mb4', ...); // WordPress: handled by wp-config DB_CHARSET
Re-run the HTML Checker
Encoding-related findings should clear. Spot-check problem characters (em-dash, smart quotes, accented characters, emoji) render correctly across pages.
Tags: html-checker, fix encoding errors, UTF-8 charset, BOM removal, mojibake, character encoding HTML, MySQL utf8mb4, response encoding
Fix
Comply with ePrivacy Directive for tracking, analytics and cookies: prior consent, named purposes, opt-out preserved.
Run the GDPR audit
Open the GDPR Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your GDPR audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gdpr, eprivacy, tracking
Fix
Make Event rich snippets eligible and appear in Google: required fields, date format, location structure, schema validation.
Run the Rich Snippets audit
Open the Rich Snippets Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Rich Snippets audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: rich-snippets, event, snippets
Fix
Strengthen “Experience” (the first E in E-E-A-T): first-hand testing screenshots, methodology, original data.
Run the E-E-A-T audit
Open the E-E-A-T Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your E-E-A-T audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: eeat, experience, signals
Fix
Demonstrate author expertise with named credentials, qualifications, published research and visible bylines.
Run the E-E-A-T audit
Open the E-E-A-T Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your E-E-A-T audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: eeat, expertise, credentials
Fix
Make links render correctly on Facebook with proper og:image sizing, debug tool refresh and cached preview busting.
Run the Social Preview audit
Open the Social Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Social Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: social-preview, facebook, preview
Fix
Stop faceted nav from generating 100,000+ indexable URLs: allow valuable facets, disallow low-value ones, canonicalise the rest.
Run the Ecommerce Crawl audit
Open the Ecommerce Crawl Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Crawl audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-crawl, faceted, explosion
Fix
Design faceted nav so high-value filter combinations rank and low-value ones don’t waste crawl budget or split equity.
Run the Ecommerce Audit audit
Open the Ecommerce Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-audit, faceted, nav
Fix
Restore or enable FAQ rich snippets: required schema, visible Q&A content match, request indexing.
Run the Rich Snippets audit
Open the Rich Snippets Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Rich Snippets audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: rich-snippets, faq, rich, result
Fix
Resolve FAQPage schema validation errors: question/answer pairs, content match to page and Google’s eligibility rules.
Run the Schema Debugger audit
Open the Schema Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-debugger, faq, schema
Fix
Improve FCP: reduce render-blocking resources, preload critical assets, defer non-critical CSS and JS.
Run the Core Web Vitals audit
Open the Core Web Vitals Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Core Web Vitals audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cwv, fcp
Fix
Improve Flesch Reading Ease score by shortening sentences, simplifying words and breaking dense paragraphs.
Run the Readability audit
Open the Readability Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Readability audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: readability, flesch, score
Fix
Fix missing or weak focus indicators from the Accessibility audit. Use focus-visible properly, hit WCAG 2.4.7 visibility, style without removing.
Search stylesheets for outline removal
grep -rE "outline:\s*(none|0)" your-css/ Common offenders: legacy CSS resets (Eric Meyer's reset, normalize.css versions before 8.0), button styles that "clean up" the default outline, frameworks that override focus per component. /* GOOD modern pattern */ :focus { outline: none; /* hide default for everyone */ } :focus-visible { outline: 2px solid #1e3a8a; outline-offset: 2px; } :focus-visible { outline: 2px solid currentColor; /* at least 2px */ outline-offset: 2px; /* gap between control and outline */ } :focus-visible { outline: 2px solid #fff; box-shadow: 0 0 0 4px #000; /* outer ring contrasts against light backgrounds */ } button:focus-visible, a:focus-visible { outline: 2px solid #7c3aed; outline-offset: 2px; border-radius: 4px; } input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px solid #7c3aed; outline-offset: 1px; border-color: #7c3aed; /* also change border colour */ } [role="button"]:focus-visible, [role="link"]:focus-visible, [tabindex="0"]:focus-visible { outline: 2px solid #7c3aed; outline-offset: 2px; } <a href="#main" class="skip-link">Skip to main content</a> <style> .skip-link { position: absolute; top: -40px; left: 0; background: #1e3a8a; color: white; padding: 8px 16px; z-index: 100; } .skip-link:focus { top: 0; } </style>
Tab through every page
Put your mouse away. Use Tab/Shift+Tab/Enter only. Try to use every feature of the site: Open menus, navigate dropdowns Fill forms, submit them Open modals, close them Add items to cart, checkout At every step, you should see a clear focus indicator. If you ever lose track of where focus is, the indicator failed there.
Tab order check
While you're tabbing, also check tab ORDER matches visual order. Skipping around (down then back up) signals layout/DOM disconnect that hurts comprehension.
axe-core / Playwright accessibility tests
// Playwright test import { test } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; test('no a11y violations on homepage', async ({ page }) => { await page.goto('/'); const results = await new AxeBuilder({ page }).analyze(); expect(results.violations).toEqual([]); }); Runs in CI, catches regressions before merge.
Tip: 💡 If you maintain a design system, define the focus style once at the system level. Every consumer of the system gets accessible focus for free. Defining per-component is how you end up with 12 different focus styles, half of them failing WCAG.
Tags: accessibility, fix focus indicators, focus-visible, focus outline, WCAG 2.4.7, keyboard focus, accessibility focus, focus ring
Fix
Fix missing or wrong form labels from the Accessibility audit. Associate labels programmatically, avoid placeholder-as-label, fix group labels for radios.
Run the Accessibility audit
Run the Accessibility audit . Filter findings to form-label issues. Common errors: input with no associated label, label for pointing at non-existent id, placeholder used in place of label, label text that's empty or non-descriptive. <label for="email">Email address</label> <input id="email" type="email" name="email"> <label> Email address <input type="email" name="email"> </label> <input type="search" placeholder="Search products" aria-label="Search products"> <h2 id="search-heading">Find what you need</h2> <input type="search" aria-labelledby="search-heading"> <label for="search" class="sr-only">Search products</label> <input id="search" type="search" placeholder="Search..."> <style> .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } </style> <fieldset> <legend>Shipping method</legend> <label> <input type="radio" name="shipping" value="standard"> Standard (3-5 days) </label> <label> <input type="radio" name="shipping" value="express"> Express (next day) </label> </fieldset> <!-- WRONG --> <div class="label">Email</div> <input type="email"> <!-- RIGHT --> <label for="email">Email</label> <input id="email" type="email"> <!-- WRONG --> <input type="email" placeholder="Email"> <!-- RIGHT --> <label for="email">Email</label> <input id="email" type="email" placeholder="you@example.com"> <!-- WRONG (typo in id, association broken) --> <label for="emial">Email</label> <input id="email" type="email"> <!-- RIGHT --> <label for="email">Email</label> <input id="email" type="email"> <!-- WRONG (* only visible, not announced) --> <label for="email">Email *</label> <input id="email" type="email"> <!-- RIGHT --> <label for="email">Email <span aria-hidden="true">*</span></label> <input id="email" type="email" required aria-required="true"> <div class="float-label"> <input id="email" type="email" placeholder=" "> <label for="email">Email</label> </div> <style> .float-label { position: relative; } .float-label input { padding: 18px 12px 6px; } .float-label label { position: absolute; top: 16px; left: 12px; transition: all 0.2s; pointer-events: none; } .float-label input:focus + label, .float-label input:not(:placeholder-shown) + label { top: 4px; font-size: 12px; color: #7c3aed; } </style>
1. Audit existing forms
Step 1 Run the Accessibility audit Run the Accessibility audit . Filter findings to form-label issues. Common errors: input with no associated label, label for pointing at non-existent id, placeholder used in place of label, label text that's empty or non-descriptive.
3. Hidden-label patterns
Sometimes the design genuinely doesn't have room for a visible label — a search box in the nav, a single-field newsletter signup, an inline filter input. Three valid patterns: Pattern A: aria-label <input type="search" placeholder="Search products" aria-label="Search products"> Screen reader announces "Search products, edit text". Sighted users see the placeholder. Less ideal than a real label but acceptable when design demands. Pattern B: aria-labelledby <h2 id="search-heading">Find what you need</h2> <input type="search" aria-labelledby="search-heading"> The input borrows the heading as its label. Useful when a nearby heading already describes the field's purpose. Pattern C: visually-hidden label <label for="search" class="sr-only">Search products</label> <input id="search" type="search" placeholder="Search..."> <style> .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } </style> Best of both worlds: a real label element (still clickable, programmatically associated) that's hidden visually.
4. Grouped controls: fieldset + legend
Radio buttons and related checkboxes need a group label in addition to per-control labels. Fieldset and legend provide it. <fieldset> <legend>Shipping method</legend> <label> <input type="radio" name="shipping" value="standard"> Standard (3-5 days) </label> <label> <input type="radio" name="shipping" value="express"> Express (next day) </label> </fieldset> Screen reader announcement: "Shipping method, group. Standard, 3-5 days, radio button, 1 of 2."
5. Common label mistakes
⚠️ Five anti-patterns to fix: Mistake 1: Div styled to look like a label <!-- WRONG --> <div class="label">Email</div> <input type="email"> <!-- RIGHT --> <label for="email">Email</label> <input id="email" type="email"> Mistake 2: Placeholder as label <!-- WRONG --> <input type="email" placeholder="Email"> <!-- RIGHT --> <label for="email">Email</label> <input id="email" type="email" placeholder="you@example.com"> Note placeholder still has a role — but as an EXAMPLE format, not as the label itself. Mistake 3: Label for pointing at wrong/missing id <!-- WRONG (typo in id, association broken) --> <label for="emial">Email</label> <input id="email" type="email"> <!-- RIGHT --> <label for="email">Email</label> <input id="email" type="email"> Mistake 4: Generic labels "Name" alone is ambiguous in a form that asks for several names. "First name" / "Last name" / "Company name" each have their own role. Generic labels make voice control and screen readers ambiguous. Mistake 5: Required indicator only visual <!-- WRONG (* only visible, not announced) --> <label for="email">Email *</label> <input id="email" type="email"> <!-- RIGHT --> <label for="email">Email <span aria-hidden="true">*</span></label> <input id="email" type="email" required aria-required="true"> The required attribute is what screen readers announce. The asterisk is decorative for sighted users.
6. Float-label pattern (clean design + accessibility)
Want labels that look like placeholders but stay accessible? The float-label pattern: <div class="float-label"> <input id="email" type="email" placeholder=" "> <label for="email">Email</label> </div> <style> .float-label { position: relative; } .float-label input { padding: 18px 12px 6px; } .float-label label { position: absolute; top: 16px; left: 12px; transition: all 0.2s; pointer-events: none; } .float-label input:focus + label, .float-label input:not(:placeholder-shown) + label { top: 4px; font-size: 12px; color: #7c3aed; } </style> Label visible initially. When user focuses or types, label animates up to make room. Real <label> element preserved — programmatic association intact.
7. Test with a screen reader
Open VoiceOver (Cmd+F5 on macOS) or NVDA (Windows free download). Tab through your form. Every field should announce: Field name (from the label) Type (edit text, checkbox, radio button, etc.) Required state if applicable Current value if any Anything that announces just "edit text, blank" is a missing or broken label.
Tags: accessibility, fix form labels, label for, aria-labelledby, fieldset legend, placeholder label, accessible forms, form accessibility, label association
Fix
Fix form validation HTML errors. Add labels, required attributes, correct input types, autocomplete, accessible error messages and constraint validation.
Run the HTML Checker
Filter to form-related findings. Common errors: missing label, missing name attribute, input type=text where type=email/tel/url is appropriate, missing autocomplete, form lacking action attribute. <label> Email <input type="email" name="email" required> </label> <label for="email">Email</label> <input id="email" type="email" name="email" required> type="email" - email validation + email keyboard on mobile type="tel" - numeric keyboard on mobile type="url" - URL keyboard, .com key on mobile type="number" - numeric input with up/down spinners type="date" - native date picker type="time" - native time picker type="search" - search-styled input with clear button type="password" - obscured input <input type="email" name="email" autocomplete="email"> <input type="text" name="given_name" autocomplete="given-name"> <input type="text" name="family_name" autocomplete="family-name"> <input type="tel" name="phone" autocomplete="tel"> <input type="text" name="street_address" autocomplete="street-address"> <input type="text" name="postcode" autocomplete="postal-code"> <input type="password" name="password" autocomplete="current-password"> <input type="password" name="new_password" autocomplete="new-password"> <input type="email" required minlength="3"> <input type="number" min="0" max="100" step="1"> <input type="text" pattern="[A-Z]{2}[0-9]{4}" title="2 letters then 4 digits">
Associate errors with their field
<label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email-error"> <span id="email-error" role="alert"> Please enter a valid email address. </span> aria-describedby links the error to the field; aria-invalid marks the field as invalid; role="alert" announces the error to screen readers when it appears.
Use the Constraint Validation API
const email = document.querySelector('input[type=email]'); email.addEventListener('blur', () => { if (!email.checkValidity()) { email.setAttribute('aria-invalid', 'true'); document.getElementById('email-error').textContent = email.validationMessage; } else { email.removeAttribute('aria-invalid'); document.getElementById('email-error').textContent = ''; } }); checkValidity() uses the same rules HTML5 native validation applies. Build on it instead of replacing it.
Tip: 💡 autocomplete="new-password" on sign-up forms tells password managers to suggest a strong password. autocomplete="current-password" on login tells them to fill the existing one. Distinct tokens for distinct intent.
Tags: html-checker, fix form validation, HTML form errors, label for, required attribute, input types, autocomplete, ARIA, constraint validation API
Fix
Audit and complete every relevant GBP attribute for richer Maps listings and improved local visibility.
Run the Google Business Profile audit
Open the Google Business Profile Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Google Business Profile audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gbp, attributes
Fix
Pick the right primary category and secondary categories on your Google Business Profile for maximum local ranking eligibility.
Run the Google Business Profile audit
Open the Google Business Profile Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Google Business Profile audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gbp, categories
Fix
Optimise GBP for multi-location businesses: bulk verification, location-specific content, brand consistency, reporting at scale.
Use Business Profile Manager for bulk management
Google's Business Profile Manager (for 10+ locations) lets you manage all locations in one interface. Bulk verification, bulk updates, organisation-level reporting.
Standardise on naming convention
Brand name + location format. 'Acme Plumbing — Birmingham' or 'Starbucks Reserve Roastery — Seattle'. Consistent format helps Google understand locations relate to one brand.
Maintain brand consistency
Brand name (exact), logo, brand colour cover photos. Don't let individual locations modify these — protects brand identity.
Allow location-specific content
Per-location: address, phone, hours (locations vary), photos (each location should have own photos), reviews (location-specific). Don't centralise photos — each location should look like itself.
Configure per-location categories
Some chains have variant offerings per location (e.g., a coffee chain with some locations roasting on-site). Reflect in per-location category configuration.
Bulk review management
Multi-location chains face volume — hundreds or thousands of reviews monthly. Use review management platforms (BrightLocal, Birdeye, Podium) to manage at scale. Set response SLAs.
Centralised reporting
GBP Insights at organisation level. Plus tools like BrightLocal, Whitespark, Moz Local for aggregated multi-location reporting. Compare locations against each other; identify outliers (very strong or very weak).
Tags: gbp, multi-location GBP, franchise GBP, chain Google Business Profile, bulk GBP management, multi-location local SEO
Fix
Upload, optimise and refresh GBP photos: cover, profile, products, interior, exterior and team photos that move local rankings.
Run the Google Business Profile audit
Open the Google Business Profile Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Google Business Profile audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gbp, photos
Fix
Establish a sustainable GBP Posts cadence: what to post, weekly minimum, conversion-tracking, expired post cleanup.
Run the Google Business Profile audit
Open the Google Business Profile Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Google Business Profile audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gbp, posts
Fix
Manage the GBP Q&A section: seed common questions, monitor for new ones, respond fast and remove inaccurate answers.
Run the Google Business Profile audit
Open the Google Business Profile Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Google Business Profile audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gbp, qa
Fix
Optimise restaurant Google Business Profile: menu integration, photo strategy, reservation links, review management, post strategy.
Complete every profile field
Cuisine type, price range, dining options (dine-in, takeaway, delivery), service options (vegetarian, vegan, gluten-free, kid-friendly, dog-friendly), accessibility. Every field affects how Google matches your restaurant to user queries.
Integrate menu
Menu in GBP directly or via integration (UrbanSpoon, Yelp, DoorDash). Updated menu (not stale 2019 version). Photos of signature dishes. Pricing visible. Menu drives ordering decision and Google's understanding of what your restaurant offers.
Photo strategy
Restaurants need many photos: food (every signature dish), interior (atmosphere, seating, lighting), exterior (street view, signage), staff (humanises). 100+ photos baseline; refresh monthly with new dishes/events. Google rewards active photo updating.
Reservation link integration
Link OpenTable, Resy, SevenRooms, or your own system. Direct booking from Google = higher conversion. Without it, customers may call, drift, lose.
Manage reviews actively
Respond to every review within 24-48 hours. Thank positive reviewers; address concerns professionally on negative. Restaurant reviews influence rankings and bookings. Solicit reviews from happy diners (no incentives — that violates policy).
Post regularly
GBP Posts — events, specials, new dishes, holiday hours. Updates Google Maps profile freshness signal. Restaurants posting weekly outperform those posting monthly. Use Posts for: weekly specials, events, holiday hours, new menu items.
Monitor performance
GBP Insights — searches, profile views, actions (calls, directions, website visits), photos viewed. Compare against previous periods. Direct correlation between GBP optimisation and footfall.
Tags: gbp, restaurant GBP, Google Business Profile restaurant, restaurant local SEO, restaurant Google Maps, restaurant reviews
Fix
Build a sustainable GBP review acquisition flow: ask consistently, respond to every review, hit category-typical velocity.
Run the Google Business Profile audit
Open the Google Business Profile Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Google Business Profile audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gbp, reviews
Fix
Optimise GBP for service-area businesses (plumbers, electricians, landscapers): service area configuration, no-storefront setup, lead handling.
Set up service-area configuration
GBP → Edit Profile → Service Area. Set hide-address (you don't want customers showing up at home office). Define areas served by city, zip code, or radius. Up to 20 areas. Be honest about service coverage.
List every service
GBP → Services. Enumerate every service offered with descriptions. Plumbing might have: emergency repair, water heater installation, drain cleaning, pipe replacement, fixture installation. Each service is a potential ranking entry.
Add service-specific photos
Before/after photos for visible services (landscaping, painting, renovation). Equipment/vehicle photos for trust signals. Team photos. Avoid stock photos — customers want to see actual work.
Configure Local Services Ads (LSA) if applicable
For US service businesses, LSA = Google's vetted-pro program. Verified background check, insurance, licence. LSA listings appear above organic GBP results for many service queries. Significant lead source where available.
Manage reviews aggressively
Service-area businesses depend on reviews. Post-job review request (text or email link to GBP review). Aim: 50+ reviews within first year, 4.5+ stars. Respond to every review.
Post regularly
GBP Posts on services, project case studies, seasonal offers, emergency-availability messaging. Service businesses with weekly posts rank better than those with stale profiles.
Track lead source
GBP Insights — calls from GBP, direction requests (less relevant for SABs), website clicks. Plus CRM/lead-tracking attribution — what % of leads come from GBP? Direct correlation between GBP optimisation and lead volume.
Tags: gbp, service area business GBP, plumber GBP, electrician GBP, service area Google Business Profile, SAB local SEO
Fix
Populate the GBP services section with all service categories you offer for richer service-based search visibility.
Run the Google Business Profile audit
Open the Google Business Profile Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Google Business Profile audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gbp, services
Fix
Fix GDPR for content sites: cookie consent, Google Analytics compliance, ad targeting, newsletter consent, comment data, simple compliance approach.
Implement cookie consent
Tools: Cookiebot, Iubenda, Complianz (WordPress plugin). Block non-essential cookies until consent. Common cookies on content sites: Google Analytics, ad networks (Google AdSense, Mediavine, AdThrive), social embeds, comments.
Configure Google Analytics for GDPR
GA4 (replaces deprecated UA): configure IP anonymisation, disable ads data sharing (or get consent first), set data retention to 14 months. Or: use privacy-friendly analytics (Plausible, Fathom, Matomo) — collect less data, simpler compliance.
Manage ad-network consent
Ad networks (AdSense, Mediavine, AdThrive) require GDPR-compliant consent for EU traffic. Each network has specific integration. Most: integrate with Cookiebot/similar via IAB Transparency and Consent Framework (TCF v2).
Newsletter signup compliance
Double opt-in (confirmation email) for EU subscribers. Lawful basis: consent. Clear unsubscribe in every email. Privacy policy linked. ESP (Mailchimp, ConvertKit) handles double opt-in setup.
Comment system GDPR
WordPress comments collect name, email, IP. Privacy policy should disclose. Gravatar shows email-hash to remote service (potential GDPR concern for some). Alternatives: Disqus (separate privacy policy), GraphComment, native WordPress comments with privacy adjustments.
Write simple privacy policy
Content sites need privacy policy covering: what data collected, why, who shared with (analytics provider, ad networks, ESP), user rights, contact for privacy issues. Generators: Termly, iubenda. Don't copy-paste from another site (each site's data flow differs).
Set up data subject request handling
Users can request: access (data export), deletion. Workflow: email contact, verify identity, process within 30 days. Most content sites: low volume, manual handling fine.
Tags: gdpr, content site GDPR, blog GDPR, publisher GDPR, Google Analytics GDPR, newsletter GDPR
Fix
Fix GDPR compliance for ecommerce: cookie consent, marketing data, checkout data minimisation, customer accounts, data retention.
Implement cookie consent banner
Visitors from EU need explicit consent before non-essential cookies fire. Tools: OneTrust, Cookiebot, Iubenda — managed consent platforms. Set up: categorise cookies (essential, analytics, marketing), block until consent, log consent records.
Audit current data collection
Map every data point you collect: at landing (IP, device), at signup (name, email, phone), at checkout (payment, address), via cookies (analytics, advertising), via marketing tools. Each needs lawful basis.
Minimise data collection at checkout
Only collect what's needed. 'Date of birth' for age-restricted products: needed. 'Date of birth' for general retail: not needed, remove. Less data = less compliance burden, less breach risk, better conversion.
Document lawful basis
Each data category needs lawful basis: consent (cookies, marketing), contract (checkout to fulfil order), legitimate interest (fraud prevention), legal obligation (tax records). Document; train team.
Build user rights workflow
Users can request: access (copy of data), portability (machine-readable export), erasure (delete data), restriction (stop processing), objection (opt-out of certain processing). 30-day response window. Process documented and team-trained.
Set data retention policies
Customer account data: keep while account active + reasonable retention after. Marketing data: until opt-out. Order data: 6+ years for tax (UK), varies by jurisdiction. Document retention periods; enforce automated deletion.
Plan breach response
If data breach occurs: notify ICO (UK) or DPA (EU) within 72 hours, notify affected users if high risk to rights. Document breach response plan before needed. Test annually.
Tags: gdpr, ecommerce GDPR, GDPR ecommerce, ecommerce cookie consent, ecommerce data protection, GDPR checkout
Fix
Fix GDPR for B2B SaaS: Data Processing Agreements (DPAs), sub-processor management, processor responsibilities, customer-data isolation, enterprise compliance.
Understand processor vs controller distinction
Your customer (the business) controls the data they put in your SaaS. You process on their instructions. Different obligations: processor must follow controller's instructions, notify of breaches, allow audits, support data subject requests.
Create standard DPA
Data Processing Agreement: contract between you (processor) and customer (controller). Defines: data types processed, processing purposes, security measures, sub-processors, breach notification, audit rights. Template: EU Standard Contractual Clauses (SCCs) for international transfers.
Maintain sub-processor list
Sub-processors: any vendor that processes customer data on your behalf (AWS, Sentry, Postmark, Mixpanel). Publish current list. Notify customers of new sub-processors with right to object.
Implement customer data isolation
Multi-tenant architecture: customer data logically separated, never cross-contaminated. Test: customer A's queries cannot access customer B's data. Common breach vector if architecture fails.
Build data subject request support
Customer's end-user requests: access, deletion, portability. Customer needs ability to fulfil — provide tools/APIs allowing customers to export, delete end-user data on demand. Document workflow.
Plan breach notification
If breach occurs: notify customer within 24-72 hours (faster than GDPR's 72-hour to regulators). Customer needs time to assess + notify regulator + notify their end-users. Document plan; test.
Prepare for customer audits
Enterprise customers conduct vendor audits. SOC 2 Type II report (annual, $30K-200K) common deliverable. Plus ISO 27001 for security. Audit-ready documentation: privacy policy, security policy, incident response, sub-processor list, DPAs.
Tags: gdpr, B2B SaaS GDPR, SaaS DPA, Data Processing Agreement, sub-processor GDPR, SaaS compliance
Fix
Diagnose why Google Gemini doesn’t cite your site and fix the schema, structure and authority signals it relies on.
Run the AEO audit
Open the AEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your AEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: aeo, gemini, invisibility
Fix
Build neighbourhood and city-specific content pages that rank for local intent without thin or duplicate content.
Run the Local SEO audit
Open the Local SEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Local SEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: local-seo, geo, content
Fix
Reduce Google’s rewrite rate of your title tags: clear, length-appropriate, query-matched, branded-at-end titles.
Run the SERP Preview audit
Open the SERP Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your SERP Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: serp-preview, google, title, rewrite
Fix
Lower the reading grade level of your content: shorter sentences, simpler vocabulary, less jargon, more transition words.
Run the Readability audit
Open the Readability Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Readability audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: readability, grade, level
Fix
Diagnose and reverse a slow downward trend in your audit score: identify the page-type or category leading the erosion.
Run the Score History audit
Open the Score History Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Score History audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: score-history, gradual, erosion
Fix
Fix heading hierarchy errors from the Accessibility audit. One H1 per page, no skipped levels, logical outline, fix CMS auto-generation patterns.
Get the findings
Run the Accessibility audit . Filter to heading-related findings: pages with no H1, pages with multiple H1s, skipped levels (H2 jumps to H4), and out-of-order sequences.
View the heading outline
Browser DevTools extensions like "headingsMap" show the outline of any page. Compare the outline to the visual structure — they should match. <!-- Wrong: site name as H1 on every page --> <h1> <a href="/">Acme Co</a> </h1> <!-- Right: logo is just an image link, H1 is the page topic --> <a href="/" aria-label="Acme Co home"> <img src="/logo.svg" alt=""> </a> <!-- ...later, in <main> --> <h1>Industrial valves for water treatment</h1> <!-- Wrong --> <h1>Article title</h1> <h4>Subsection</h4> <!-- skipped H2 and H3 --> <!-- Right --> <h1>Article title</h1> <h2>Subsection</h2> h2 { font-size: 1.5rem; } /* normal */ h2.smaller { font-size: 1.2rem; } /* style a subset, don't downlevel the tag */ // TinyMCE config block_formats: 'Paragraph=p; Heading 2=h2; Heading 3=h3; Heading 4=h4'
1. Run the audit
Step 1 Get the findings Run the Accessibility audit . Filter to heading-related findings: pages with no H1, pages with multiple H1s, skipped levels (H2 jumps to H4), and out-of-order sequences. Step 2 View the heading outline Browser DevTools extensions like "headingsMap" show the outline of any page. Compare the outline to the visual structure — they should match.
2. Fix the H1
Exactly one H1 per page. The H1 should describe the page's main topic. <!-- Wrong: site name as H1 on every page --> <h1> <a href="/">Acme Co</a> </h1> <!-- Right: logo is just an image link, H1 is the page topic --> <a href="/" aria-label="Acme Co home"> <img src="/logo.svg" alt=""> </a> <!-- ...later, in <main> --> <h1>Industrial valves for water treatment</h1> Per page type, the H1 typically maps to: Homepage: value proposition or site purpose Article page: article title Product page: product name Category page: category name Search results: "Results for [query]"
4. Common template issues
Sidebar widgets using H3 Many themes hardcode H3 for sidebar widget titles. If main content uses H1-H2, this creates a skip. Either restructure to use H2 in sidebars, or accept H2 + sidebar H3 as a valid structure (sidebar visually parallel to main, semantically can be too). Footer headings "Quick Links", "Contact Us", "Follow Us" — common footer column titles often coded as H2. If main content uses H1, then footer H2s are at the same level as main subsections, suggesting they're peer content. Fix: either step footer headings down (H3 if main has H2), or remove headings entirely and use styled strong instead. Card components Product cards on a category page often have product names as H3 or H4. If the page has H1 (category) → H2 (filters/section) → H3 (per product), the structure works. But if cards jump straight to H4 because designers wanted smaller text, that's a skip.
5. Fix content-editor mistakes
Content editors often pick headings for visual size, not semantic structure: ⚠️ "I want this to look medium, so I'll use H4" — wrong reasoning. H4 has semantic meaning regardless of how it's styled. Editor training Heading levels are semantic, not stylistic Pages have one H1 already (the page title) — content body should use H2 and below Never skip levels for visual reasons — use CSS classes for size variation Treat headings as a table of contents — does the outline make sense without the visual layout? CMS configuration Some CMS rich-text editors expose H1 in the dropdown when the page template already has an H1. Remove H1 from the editor's allowed heading options: // TinyMCE config block_formats: 'Paragraph=p; Heading 2=h2; Heading 3=h3; Heading 4=h4'
6. Tools to verify
Browser extensions headingsMap (Chrome/Firefox) — shows page outline, highlights skips WAVE — flags heading errors in-page axe DevTools — includes heading-order rule Screen reader heading navigation With a screen reader on, press H to jump between H1s and adjacent levels. The flow should match the visible content order. If it's confusing or skips around, the hierarchy is broken. <div
7. Re-run the audit
After fixes, re-run the Accessibility audit . Heading findings should drop to zero. Spot-check a few pages with a screen reader to confirm the heading navigation works.
Tip: 💡 Treat the heading outline as a table of contents. A screen-reader user can pull up just the headings (Insert+F7 in NVDA, VO+U in VoiceOver) and see your page as an outline. That outline should make sense as a standalone navigation aid.
Tags: accessibility, fix heading hierarchy, H1 H2 H3, heading skip level, document outline, accessibility headings, SEO headings, semantic structure
Fix
Restore proper heading hierarchy (H1—H6) so screen readers and Google understand content structure.
Run the Readability audit
Open the Readability Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Readability audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: readability, headings
Fix
Headless ecommerce SEO: Shopify Hydrogen, BigCommerce headless, commercetools, custom SSR for products, schema in headless, structured-data ownership.
Verify SSR/SSG for product pages
Product pages must render real HTML server-side. View-source on a product URL — should see product name, description, price, image, schema. Headless without SSR = invisible to Google and AI agents.
Implement Product schema explicitly
Headless platforms typically don't auto-generate Product schema (unlike standard Shopify). Build it in your frontend code: fetch product data from commerce API, serialise to JSON-LD Product, render in <script type='application/ld+json'>. Include name, image, description, sku, brand, offers, aggregateRating.
Manage category/collection SEO
Category pages need: descriptive title, meta description, intro content above products, BreadcrumbList schema, ItemList of products. All require custom implementation in headless. Build category SEO templates as you would product templates.
Handle URL structure
Hydrogen defaults to /products/SLUG (Shopify-like). Custom builds: design clean URLs (/products/SLUG or /shop/category/product). Avoid: parameter-heavy URLs (?id=12345), deep nesting (/store/category/sub/sub/product).
Configure sitemaps
Headless = manual sitemap. Generate from commerce API: fetch all products, categories, content pages → build sitemap.xml at build time (SSG) or runtime (SSR). Submit to Search Console.
Implement Organization schema site-wide
Layout-level: Organization schema with name, logo (600x60 min), sameAs links, contactPoint. Critical for brand entity recognition by Google and AI agents.
Monitor and iterate
Headless gives flexibility — also gives ways to break things. Re-validate every key schema after deployments. Search Console enhancements report catches regressions.
Tags: ecommerce-audit, headless ecommerce SEO, Hydrogen SEO, headless Shopify, commercetools SEO, headless product schema
Fix
Migrate monolith CMS to headless: WordPress to Next.js, Shopify to Hydrogen, redirect mapping, SSR readiness, structured data, recovery monitoring.
Pre-migration baseline
Crawl old monolith with Screaming Frog. Export URLs, titles, meta, schema. Export Search Console queries and clicks. Save as before-state.
Map URLs
Old WordPress /sample-post/ → new headless /blog/sample-post. Old Shopify /products/SLUG → new Hydrogen /products/SLUG (if mirroring structure) or /shop/SLUG (if changing). Build redirect spreadsheet — old URL → new URL → status.
Plan content carry-over
Headless requires data in a CMS. Options: keep WordPress as headless source (most common — WordPress as CMS, Next.js frontend), migrate to dedicated headless CMS (Contentful, Sanity, Strapi), or static markdown files. Each has migration tooling.
Verify SSR/SSG on new build
Headless frontends MUST render content server-side for SEO. Disable JS, reload pages. If empty, fix rendering before launch. Common failure: developers shipped pure SPA, then surprised by SEO loss.
Implement structured data explicitly
Headless = no automatic schema. You build Product, Article, FAQPage, Organization etc. from scratch in the frontend. Don't launch without schema — losing it is a major SEO regression.
Set up redirects
301 from every old URL with traffic or backlinks. Implementation depends on hosting: Vercel via vercel.json or middleware.ts; Netlify via _redirects file or netlify.toml; Cloudflare via Page Rules.
Monitor post-launch
Daily for first week: Search Console errors, organic traffic delta, indexation counts. Weekly for 90 days. Expect 14-30 day dip in traffic; full recovery in 60-90 days. If still down at 90 days, identify gaps via audit.
Tags: migration-audit, headless migration, WordPress to Next.js, Shopify to Hydrogen, headless SEO, monolith to headless
Fix
Recover from Helpful Content Update. Prune thin content, add first-hand experience, build page-level E-E-A-T. The recovery path for HCU-impacted sites.
1. Identify HCU-flagged content
For each page, score honestly: - Was this written for users or for SEO? - Does it show first-hand experience or just summarise? - Does it have a unique angle vs the top-ranking competitor? - Did the author actually do/use/test the thing they describe? - Is the depth proportional to the topic's complexity? - Does it have value beyond ranking? If most answers are "no", it's likely HCU-flagged content. Be ruthless. Hopeful self-assessment is what got you flagged.
2. Prune decisively
Don't try to save what shouldn't exist: Decision tree per flagged page: Page serves a real user need + has some value: → Rewrite from scratch with first-hand experience → New URL not required; keep equity Page has no user need (made for keyword): → 410 Gone (clean signal to deindex) → OR 301 to relevant authoritative content Page has user need but content is irredeemable: → 301 to better page covering same intent → OR replace with completely new content at same URL Don't: → Noindex and leave (still drags site quality assessment) → Hope for incremental improvement to thin pages → Mass-edit with AI tools to "improve" en masse
3. Add first-hand experience
The "E" in E-E-A-T (experience) is what HCU specifically weights: Original photos of products, processes, places — not stock images Original screenshots from software you actually use Testing notes : "Used for 6 weeks, found that..." not "Users report..." Named contributor with documented experience in the topic Before/after data from real implementations Mistakes you made — content that admits failure carries authenticity Process documentation with timestamps, screenshots, decision points
4. Build page-level E-E-A-T
For each retained page: - Named author with bio link - "How we tested" or methodology section - "Why trust us on this" with specific credentials - Date of original publication AND last review - Update log if it's an evolving guide - Reviewer (separate person from author for fact-check) - Sources cited with links to primary research - Disclosure of any affiliate, sponsorship, or COI
5. Content production going forward
HCU shifts what to produce: STOP producing: - Bulk AI-generated pieces, even if "edited" - Aggregations of what other sites say - Tutorials that just rephrase official docs - List posts with no original insight - "X tips" content without the X being earned START producing: - In-depth pieces from genuine experience - Original data, surveys, case studies - Process documentation with screenshots - Failure post-mortems - Practitioner debates with named contributors Volume drops. Depth rises. Quality compounds. HCU rewards this pattern over 12-24 months.
6. Recovery timeline
HCU is now integrated into core update assessments. Recovery follows core update cycle: Month 0: HCU impact identified Month 0-2: Audit, prune ~30-50% of pages, rewrite ~20% Month 2-4: Add first-hand experience to surviving content Month 3-6: Next core update — partial recovery Month 9-12: Following core update — fuller recovery Year 2: Steady state if production discipline maintained Sites that prune but don't rebuild stay flat. Sites that prune AND add first-hand experience recover. <div
Tip: 💡 HCU is the only Google algorithm that explicitly penalises "content that wasn't written for humans". The recovery path is exactly what it says: write for humans, demonstrate that you did. Sites trying to game this with AI-edited bulk content compound the problem. Authentic content + named expertise + visible work is the only path back.
Tags: algo-impact, helpful content update, HCU recovery, thin content prune, first-hand experience, page-level E-E-A-T
Fix
Fix homepage internal linking: distribute PageRank, top-level navigation, footer links, contextual links, anchor text strategy.
Audit current homepage links
Count and categorise: navigation links (primary nav), footer links, body content links, sidebar/widget links. Most homepages: 50-150 internal links. Identify which pages get linked from homepage.
Prioritise top-of-funnel pages
Homepage should link prominently to your most important pages — those driving most revenue or strategic priority. Audit: do your top revenue pages get homepage links? If not, restructure.
Use descriptive anchor text
Anchor text signals link target's topic. 'Click here' wastes the signal. 'Sustainable wedding flowers' as anchor text helps Google understand the linked page. Mix: brand anchors, descriptive anchors, partial-match anchors.
Limit total links
Old rule: <100 links per page. Modern: practical limit (Google can crawl thousands but prioritises early links). Homepage 80-150 links typical. Beyond that, individual link equity dilutes.
Structure primary navigation
Top-level navigation: 5-9 items (cognitive load limit). Each item links to important hub or category. Dropdown sub-navigation acceptable but reduces individual sub-link equity slightly.
Add contextual body links
Beyond nav: body content with contextual links to key articles, products, categories. Editorial homepage layouts (think Wirecutter, NYT) win on this.
Footer for utility, not strategy
Footer: utility links (contact, about, privacy, terms). Some sites stuff footer with category links for SEO — works modestly, but cluttered. Strategic links belong in body and navigation; footer for utility.
Tags: internal-links, homepage internal linking, internal link strategy, site architecture, PageRank flow, homepage SEO
Fix
Restore HowTo rich result eligibility where it still applies (mobile, certain regions): schema, image steps and Google policy.
Run the Rich Snippets audit
Open the Rich Snippets Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Rich Snippets audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: rich-snippets, howto, carousel
Fix
Resolve HowTo schema validation errors: step images, names, total time and Google’s current policy on HowTo eligibility.
Run the Schema Debugger audit
Open the Schema Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-debugger, howto, schema
Fix
Fix hreflang pointing at blocked or noindex targets. Disallowed by robots.txt, noindex meta, password-protected. Audit and resolve per pattern.
Run the Hreflang Checker
Blocked targets categorised by block type with specific URL list.
Manual robots.txt check
# For each hreflang URL, check if robots.txt allows it URL="https://example.com/fr/about" ROBOTS_URL="https://example.com/robots.txt" python3 -c " from urllib.robotparser import RobotFileParser rp = RobotFileParser() rp.set_url('$ROBOTS_URL') rp.read() print('Crawlable:' if rp.can_fetch('Googlebot', '$URL') else 'BLOCKED:', '$URL') "
Manual noindex check
# Check meta robots and X-Robots-Tag header curl -sI https://example.com/fr/about | grep -i "x-robots-tag" curl -s https://example.com/fr/about | grep -oE '<meta[^>]*robots[^>]*>' # Both should be absent or set to "index, follow" # If "noindex" appears, target is blocked // BAD: production page references staging 'fr': 'https://staging.example.com/fr/about' // robots-blocked // FIX: update cluster to production URL 'fr': 'https://example.com/fr/about' # BAD: robots.txt blocks legitimate hreflang targets User-agent: * Disallow: /fr/internal/ # OK Disallow: /fr/ # TOO BROAD — catches everything # FIX: tighten the rule User-agent: * Disallow: /fr/internal/ # Public /fr/* now crawlable <!-- Some sites add noindex to all pages in a soft-launch locale --> <meta name="robots" content="noindex"> <!-- If launched, remove the noindex --> <!-- Until launched, keep noindex AND remove variant from hreflang config --> // WPML offers "complete translation" workflow. // Posts marked draft/incomplete shouldn't appear in hreflang. // Confirm WPML setting: "Include only complete translations in hreflang" // Conditionally include locales based on env const locales = process.env.NODE_ENV === 'production' ? ['en', 'fr', 'de'] : ['en', 'fr', 'de', 'es-beta']; module.exports = { i18n: { locales } }; // Beta locale doesn't appear in production hreflang
Re-run Hreflang Checker
Blocked target findings clear. All targets crawlable and indexable.
Search Console
For previously blocked variants now unblocked, request re-crawl. Indexing should follow within 1-3 weeks.
Tip: 💡 The decision tree: is this URL meant to be public and indexed? If yes, remove the block. If no, remove from hreflang. The wrong outcome is keeping the block AND the hreflang — that contradicts itself and Google ignores both signals.
Tags: hreflang, hreflang noindex, hreflang robots blocked, hreflang blocked target, blocked locale variant, hreflang accessibility
Fix
Fix hreflang pointing at 404s or redirects from the Hreflang Checker. Targets must return 200, not redirect. CI pipeline check prevents broken clusters.
Run the Hreflang Checker
Each annotation target gets a status probe. Findings grouped by status code: 404, 301/302, 410, 5xx.
Bulk-check yourself
# Extract every unique hreflang URL on the site URLS=$(curl -s https://example.com/sitemap.xml | \ grep -oE 'hreflang="[^"]+" href="[^"]+"' | \ grep -oE 'href="[^"]+"' | \ awk -F'"' '{print $2}' | sort -u) # Check status of each for url in $URLS; do status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url") if [ "$status" != "200" ]; then echo "$status $url" fi done // Cluster before { 'en': 'https://example.com/en/discontinued-product', 'fr': 'https://example.com/fr/produit-arrete', // 404 'de': 'https://example.com/de/eingestellt' // 404 } // Two options: // A) Remove the cluster entirely if all variants gone // B) Remove deleted locales, keep remaining variants { 'en': 'https://example.com/en/discontinued-product' // fr and de removed since they no longer exist } // Even then, consider whether single-variant clusters need hreflang at all // Target redirects: 'fr': 'https://example.com/fr/old-slug' → 301 → 'https://example.com/fr/new-slug' // FIX: update the cluster to point at the final URL directly 'fr': 'https://example.com/fr/new-slug' // locales-config.js — update the source of truth export const clusters = { about: { 'en': 'https://example.com/en/about', 'fr': 'https://example.com/fr/a-propos', 'de': 'https://example.com/de/uber-uns' // No 'es' here because Spanish page was deleted }, // ... }; // Redeploy all variants — every page now reflects the cleaned cluster // scripts/validate-hreflang-targets.js const fetch = require('node-fetch'); const { clusters } = require('./locales-config'); async function validate() { const allUrls = new Set(); for (const cluster of Object.values(clusters)) { for (const [lang, url] of Object.entries(cluster)) { if (lang !== 'x-default') allUrls.add(url); } } const errors = []; for (const url of allUrls) { try { const r = await fetch(url, { method: 'HEAD', redirect: 'manual' }); if (r.status !== 200) { errors.push(`${r.status} ${url}`); } } catch (e) { errors.push(`ERR ${url} — ${e.message}`); } } if (errors.length) { console.error('Hreflang target validation failed:'); errors.forEach(e => console.error(' ' + e)); process.exit(1); } console.log(`Validated ${allUrls.size} hreflang targets — all 200`); } validate(); name: Validate hreflang targets on: [push, deployment_status] jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' } - run: npm ci - run: node scripts/validate-hreflang-targets.js
Re-run Hreflang Checker
Broken target findings clear. Every annotation target returns 200.
CI passes
The validation script runs green on every pre-deploy.
Tip: 💡 The most-forgotten step: when launching or removing a locale, update the cluster config BEFORE the content change. Avoids the window where existing variants advertise URLs that don't exist yet, or that no longer exist.
Tags: hreflang
Fix
Fix hreflang vs canonical conflicts from the Hreflang Checker. Each variant must canonical to itself, not across locales. Detection and repair patterns.
Run the Hreflang Checker
Canonical conflict findings list every variant whose canonical doesn't match its served URL.
Manual spot check
# Check canonical on each variant for url in https://example.com/en/about https://example.com/fr/about https://example.com/de/about; do echo "=== $url ===" curl -s "$url" | grep -oE '<link[^>]*rel="canonical"[^>]*>' done # Each canonical href should equal the URL it's served from
Search Console URL Inspection
Inspect each variant. "User-declared canonical" should equal the URL. "Google-selected canonical" should also equal it. If Google-selected differs from User-declared, Google overrode your signal. // Confirm WPML self-canonicals translated posts // WPML → SEO → "Set hreflang and canonical tags" // - Mode: "Same URL per language" → each gets self-canonical // - Avoid "Use default language URL" if duplicate handling // Polylang handles self-canonicals correctly with Yoast/Rank Math // If issues, check that: // - Each translated post has Yoast canonical set to its OWN permalink (not source) // - Yoast → Translation tab → confirm canonical settings // app/[locale]/about/page.tsx export async function generateMetadata({ params }) { const url = `https://example.com/${params.locale}/about`; return { alternates: { canonical: url, // self-canonical — uses the served locale languages: { 'en': 'https://example.com/en/about', 'fr': 'https://example.com/fr/about', 'de': 'https://example.com/de/about', 'x-default': 'https://example.com/en/about' } } }; } --- // pages/[locale]/about.astro const { locale } = Astro.params; const canonical = `https://example.com/${locale}/about`; --- <link rel="canonical" href={canonical} /> <!-- hreflang as before --> // In a partial/component function getCanonical(currentLocale, slug) { return `https://example.com/${currentLocale}/${slug}`; } <link rel="canonical" href="${getCanonical('fr', 'about')}" /> // NEVER pick a default-locale URL here <!-- en.example.com/about --> <link rel="canonical" href="https://en.example.com/about" /> <!-- fr.example.com/about --> <link rel="canonical" href="https://fr.example.com/about" /> <!-- example.co.uk/about --> <link rel="canonical" href="https://example.co.uk/about" /> <!-- example.fr/about --> <link rel="canonical" href="https://example.fr/about" />
Re-run Hreflang Checker
Canonical conflict findings clear. Every variant reports self-canonical.
Search Console index recovery
For previously-canonicaled-away variants, request reindex via URL Inspection. Search Console "Indexed, though canonicalised to other URL" findings decrease over 2-4 weeks.
site: query confirms recovery
site:example.com/fr/ now returns the French variants in results. Before the fix, only English versions appeared.
Tip: 💡 The single rule: each locale variant canonicals to itself, regardless of which language it's translated from. Hreflang declares the cross-locale relationship separately. The two signals work together — canonical says "this URL is the right one for this content in this locale", hreflang says "and here are the other locales".
Tags: hreflang, hreflang canonical conflict, cross-locale canonical, self-canonical, hreflang cluster, canonical hreflang interaction
Fix
Fix invalid hreflang language codes from the Hreflang Checker. ISO 639-1 for language, ISO 3166-1 alpha-2 for region. Common mistakes and the right codes.
Re-run Hreflang Checker
Invalid code findings clear. Each annotation parses as valid BCP 47.
Search Console International Targeting
Legacy International Targeting report (if accessible) should show no "Incorrect hreflang values" errors.
1. The format
Hreflang values follow BCP 47 but in practice use one of two shapes: <!-- Language only --> <link rel="alternate" hreflang="en" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <link rel="alternate" hreflang="fr" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <!-- Language + region --> <link rel="alternate" hreflang="en-GB" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <link rel="alternate" hreflang="en-US" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <link rel="alternate" hreflang="fr-CA" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <link rel="alternate" hreflang="zh-CN" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <!-- Special fallback --> <link rel="alternate" hreflang="x-default" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> Convention Language code: lowercase, 2 letters (ISO 639-1) Region code: uppercase, 2 letters (ISO 3166-1 alpha-2) Separator: hyphen - (not underscore) Order: language-REGION (not REGION-language)
2. Common language codes (ISO 639-1)
Language Code Common mistake English en — French fr — German de Some use ge — wrong Spanish es Some use sp — wrong Portuguese pt — Italian it — Chinese zh cn is a region code, not language Japanese ja Some use jp — that's region Korean ko Some use kr — that's region Arabic ar — Russian ru — Dutch nl Some use du — wrong Swedish sv Some use se — that's region Hebrew he Some use iw (deprecated) Hindi hi — Polish pl — Turkish tr —
3. Common region codes (ISO 3166-1 alpha-2)
Region Code Common mistake United Kingdom GB UK is NOT an ISO code United States US — Germany DE — France FR — Spain ES — Italy IT — China CN — Japan JP — Brazil BR br is also Breton language — case matters in tooling Canada CA — Mexico MX — Australia AU — India IN — Hong Kong HK — Taiwan TW — Netherlands NL —
4. Common combined patterns
en — English, any region en-GB — English, United Kingdom (NOT en-UK) en-US — English, United States en-AU — English, Australia en-CA — English, Canada fr — French, any region fr-FR — French, France fr-CA — French, Canada fr-BE — French, Belgium fr-CH — French, Switzerland es — Spanish, any region es-ES — Spanish, Spain es-MX — Spanish, Mexico es-AR — Spanish, Argentina zh — Chinese, any region (rarely used alone) zh-CN — Chinese, China (simplified) zh-TW — Chinese, Taiwan (traditional) zh-HK — Chinese, Hong Kong zh-Hans — Chinese, simplified script (BCP 47 extension) zh-Hant — Chinese, traditional script pt — Portuguese pt-BR — Portuguese, Brazil pt-PT — Portuguese, Portugal x-default — Fallback for unmatched locales
5. Validation
Programmatic check // JavaScript example const VALID_LANGUAGES = new Set([ 'aa','ab','ae','af','ak','am','an','ar','as','av','ay','az', 'ba','be','bg','bh','bi','bm','bn','bo','br','bs', 'ca','ce','ch','co','cr','cs','cu','cv','cy', 'da','de','dv','dz', 'ee','el','en','eo','es','et','eu', // ... full ISO 639-1 list ]); const VALID_REGIONS = new Set([ 'AD','AE','AF','AG','AI','AL','AM','AO','AQ','AR','AS','AT','AU','AW','AX','AZ', 'BA','BB','BD','BE','BF','BG','BH','BI','BJ','BL','BM','BN','BO','BQ','BR','BS', // ... full ISO 3166-1 alpha-2 list ]); function validateHreflang(code) { if (code === 'x-default') return true; const parts = code.split('-'); if (parts.length === 1) { return VALID_LANGUAGES.has(parts[0].toLowerCase()); } if (parts.length === 2) { return VALID_LANGUAGES.has(parts[0].toLowerCase()) && VALID_REGIONS.has(parts[1].toUpperCase()); } return false; } Node libraries npm install bcp-47 const { parse } = require('bcp-47'); const result = parse('en-GB'); console.log(result.language); // 'en' console.log(result.region); // 'GB' // Returns null for invalid tags
6. Common mistakes and fixes
<!-- WRONG: en-UK --> <link rel="alternate" hreflang="en-UK" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <!-- RIGHT: en-GB --> <link rel="alternate" hreflang="en-GB" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <!-- WRONG: cn for Chinese language --> <link rel="alternate" hreflang="en" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <!-- RIGHT: zh for language, optionally zh-CN for China region --> <link rel="alternate" hreflang="zh-CN" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <!-- WRONG: underscore separator --> <link rel="alternate" hreflang="en_GB" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <!-- RIGHT: hyphen --> <link rel="alternate" hreflang="en-GB" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <!-- WRONG: REGION-language order --> <link rel="alternate" hreflang="GB-en" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <!-- RIGHT: language-REGION --> <link rel="alternate" hreflang="en-GB" href="/aipageseo-demo-pages/hreflang-fixes-index.html" />
7. Verify the fix
Step 1 Re-run Hreflang Checker Invalid code findings clear. Each annotation parses as valid BCP 47. Step 2 Search Console International Targeting Legacy International Targeting report (if accessible) should show no "Incorrect hreflang values" errors. <div
Tip: 💡 The most common mistake is en-UK. UK isn't an ISO country code — Great Britain is GB. Almost every site that targets the UK starts with en-UK and silently fails until the audit catches it. Always en-GB.
Tags: hreflang, fix hreflang language codes, ISO 639-1, ISO 3166-1, hreflang format, en-GB vs en-UK, zh-CN, language region code, BCP 47
Fix
Configure hreflang in Next.js and Nuxt: i18n routing patterns (subpath, subdomain, domain), automatic vs manual hreflang, sitemap integration, validation.
Choose URL pattern
Subpath (recommended): example.com/en/page, example.com/fr/page. Subdomain: en.example.com, fr.example.com. Domain: example.com, example.fr. Subpath is simplest in Next.js/Nuxt and works for most use cases.
Configure Next
js App Router i18n. Manual approach with middleware.ts handling locale detection and routing. Or @next-intl or next-i18next libraries. App Router doesn't have built-in i18n config like Pages Router did — implementation is library-driven.
Configure Pages Router i18n
next.config.js with i18n key: { locales: ['en', 'fr', 'de'], defaultLocale: 'en' }. Next.js handles locale routing automatically. Output hreflang via metadata API or next-seo.
Configure Nuxt @nuxtjs/i18n
Add module, configure locales array with iso codes, route generation. @nuxtjs/i18n automatically generates hreflang link tags.
Verify hreflang output
View source on a multilingual page → check for <link rel='alternate' hreflang='X' href='Y' /> tags. Should include all active locales plus x-default. Self-referencing tag also required.
Generate locale-aware sitemap
next-sitemap with i18n config outputs per-locale URLs with hreflang alternates. Or @nuxtjs/sitemap with i18n integration. Submit one sitemap to Search Console; it covers all locales.
Validate
hreflang.xyz, Aleyda Solis's validator, or Search Console International Targeting report. Common errors: missing return tags, mismatched language codes, x-default missing.
Tags: hreflang, Next.js hreflang, Next.js i18n, Nuxt i18n, Next.js internationalization, Next.js multilingual
Fix
Fix missing hreflang return tags from the Hreflang Checker. Bidirectional reciprocity, auto-generation patterns, cluster-wide audit, CI validation.
Run the Hreflang Checker
Findings list each annotation lacking a return tag. Groups by cluster so you fix related URLs together.
Manual spot check
# Extract hreflang from page A curl -s https://example.com/en/about | grep -oE '<link[^>]*hreflang[^>]*>' # Then for each href found, check if it links back to A # Each target should mention https://example.com/en/about in its own hreflangs // locales-config.js export const aboutCluster = { en: 'https://example.com/en/about', fr: 'https://example.com/fr/a-propos', de: 'https://example.com/de/uber-uns', es: 'https://example.com/es/sobre-nosotros', 'x-default': 'https://example.com/en/about' }; // In template/component function renderHreflang(cluster) { return Object.entries(cluster).map(([lang, href]) => `<link rel="alternate" hreflang="${lang}" href="${href}" />` ).join('\n'); } // On /en/about, /fr/a-propos, /de/uber-uns, /es/sobre-nosotros // All output the SAME cluster — automatic reciprocity // functions.php — WPML outputs hreflang automatically in <head> // If they're missing, check: // WPML → Languages → SEO options → "Add alternate language link..." → enabled // next.config.js module.exports = { i18n: { locales: ['en', 'fr', 'de', 'es'], defaultLocale: 'en' } }; // app/[locale]/about/page.tsx import { generateMetadata } from '@/lib/hreflang'; export async function generateMetadata({ params }) { return { alternates: { canonical: `https://example.com/${params.locale}/about`, languages: { 'en': 'https://example.com/en/about', 'fr': 'https://example.com/fr/a-propos', 'de': 'https://example.com/de/uber-uns', 'es': 'https://example.com/es/sobre-nosotros', 'x-default': 'https://example.com/en/about' } } }; } # scripts/validate-hreflang.js const cheerio = require('cheerio'); const fetch = require('node-fetch'); const clusters = require('./locales-config.js'); async function getHreflangs(url) { const html = await (await fetch(url)).text(); const $ = cheerio.load(html); const tags = {}; $('link[rel="alternate"][hreflang]').each((i, el) => { tags[$(el).attr('hreflang')] = $(el).attr('href'); }); return tags; } async function validateCluster(cluster) { const issues = []; for (const [lang, url] of Object.entries(cluster)) { if (lang === 'x-default') continue; const tags = await getHreflangs(url); // Each page must link to all others (and itself) for (const [otherLang, otherUrl] of Object.entries(cluster)) { if (tags[otherLang] !== otherUrl) { issues.push(`${url} missing/wrong hreflang for ${otherLang}`); } } } return issues; } (async () => { const allIssues = []; for (const cluster of clusters) { allIssues.push(...await validateCluster(cluster)); } if (allIssues.length) { console.error('Hreflang issues:', allIssues); process.exit(1); } })(); <!-- On /en/about — missing self --> <link rel="alternate" hreflang="fr" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <link rel="alternate" hreflang="de" href="/aipageseo-demo-pages/hreflang-fixes-index.html" /> <!-- Where's hreflang="en"? -->
Re-run Hreflang Checker
Return tag findings clear. All clusters report bidirectional.
Search Console International Targeting
Search Console → Legacy tools → International Targeting (if available in your account). Lists hreflang errors detected by Google. Should drop to zero.
Tip: 💡 The only sustainable approach: generate hreflang from a single config object that lists every variant. Each page outputs the full cluster. Reciprocity is automatic. Hand-coding hreflang per page guarantees drift.
Tags: hreflang, fix hreflang return tags, reciprocal hreflang, bidirectional hreflang, hreflang cluster, return tag audit, hreflang reciprocity
Fix
Fix missing self-referencing hreflang from the Hreflang Checker. Every cluster page must include itself. Generation patterns and audit approach.
Run the Hreflang Checker
Self-reference findings list every page that doesn't include its own URL in its hreflang tags.
Spot check
# Get hreflang from a page URL="https://example.com/en/about" curl -s "$URL" | grep -oE '<link[^>]*hreflang[^>]*>' # Does any of the href attributes equal $URL? If yes — self-reference present. # If no — missing. // BAD pattern in template {cluster.entries().filter(([lang, url]) => lang !== currentLang).map(...)} // "Don't render the current language" — wrong intent // Self-reference must always be present WPML → Languages → SEO options - "Add alternate links" enabled - "Include current language" enabled (some versions hide this) Polylang → Languages → Settings - hreflang generation enabled - Defaults include self-reference // app/[locale]/page.tsx export async function generateMetadata({ params }) { return { alternates: { languages: { 'en': 'https://example.com/en/', 'fr': 'https://example.com/fr/', 'de': 'https://example.com/de/', // Even if currentLocale === 'fr', the 'fr' entry must be present 'x-default': 'https://example.com/en/' } } }; } --- const allLocales = { 'en': '/en/about', 'fr': '/fr/a-propos', 'de': '/de/uber-uns' }; const SITE = 'https://example.com'; --- {Object.entries(allLocales).map(([lang, path]) => ( <link rel="alternate" hreflang={lang} href={`${SITE}${path}`} /> ))} <link rel="alternate" hreflang="x-default" href={`${SITE}${allLocales.en}`} /> <!-- Output identical on every locale page — self always present --> // scripts/check-self-ref.js const cheerio = require('cheerio'); const fetch = require('node-fetch'); async function checkSelfReference(url) { const html = await (await fetch(url)).text(); const $ = cheerio.load(html); const hreflangs = []; $('link[rel="alternate"][hreflang]').each((i, el) => { hreflangs.push($(el).attr('href')); }); if (!hreflangs.includes(url)) { throw new Error(`Missing self-reference at ${url}. Found: ${hreflangs.join(', ')}`); } } // Run on every variant for (const url of allLocaleUrls) { await checkSelfReference(url); }
Each variant lists itself
curl + grep each variant. Self URL should appear in the hreflang list.
Re-run Hreflang Checker
Missing self-reference findings clear. All cluster pages report complete annotations.
Tip: 💡 The cluster pattern fixes self-reference by construction. If every page outputs the FULL cluster (the same data on every variant), self-reference is automatic. The variant for any locale always includes its own entry because the data structure includes every entry.
Tags: hreflang, hreflang self reference, hreflang self link, cluster self entry, every variant lists itself, hreflang missing self
Fix
Configure hreflang on Shopify: Shopify Markets, Translate & Adapt, multi-store setups, subdirectory vs subdomain, validation and Search Console.
Choose strategy
Markets or multi-store. Shopify Markets (since 2022): single store, multiple regional/language versions via subdirectories. Multi-store: separate Shopify stores per region (e.g., yourstore.com, yourstore.fr). Markets is easier and recommended for most; multi-store gives more independence but more complexity.
Configure Shopify Markets
Settings → Markets → add markets per country/region. Enable Translate & Adapt for language translations within each market. Configure subdirectory pattern (/fr-fr/, /en-gb/) or subdomain.
Verify hreflang output
Visit a translated page. View source → look for link rel='alternate' hreflang='X' tags. Markets should generate these automatically once configured. Should include all your active markets plus x-default.
Set x-default
Markets automatically sets x-default to your primary market. Verify in Markets settings → primary market designation. Usually your largest market or English-language version.
Validate
hreflang.xyz, Aleyda Solis validator, or Ahrefs hreflang checker. Common errors: missing return tags (Markets should prevent these but worth verifying), mismatched language codes, x-default pointing wrong.
Handle currency separately
Currency conversion is independent of language/hreflang. Markets handles currency too — set per-market currency. Don't confuse currency display with hreflang language targeting.
Monitor in Search Console
Search Console → International Targeting → Language. Reports hreflang errors. Watch 'No return tags' count.
Tags: hreflang, Shopify hreflang, Shopify Markets, Shopify multilingual, Shopify geo-targeting, Translate Adapt
Fix
Fix hreflang declared in both sitemap and HTML inconsistently. Pick one source of truth, generate from cluster config, verify single declaration per cluster.
Check HTML head
curl -s https://example.com/en/about | grep -E "hreflang|alternate" # Lists every hreflang annotation in the HTML head
Check sitemap
curl -s https://example.com/sitemap.xml | grep -E "xhtml:link" # If empty, sitemap doesn't declare hreflang — you're using HTML head only # If present, sitemap declares hreflang — check whether HTML head also does
Compare clusters
For one representative page, extract the cluster from HTML head and from sitemap. Compare. If they differ in any URL, alt text , or hreflang value, you have a mismatch. <!-- Sitemap reduced to bare loc entries --> <url> <loc>https://example.com/en/about</loc> <lastmod>2024-01-15</lastmod> </url> <!-- No xhtml:link tags --> <head> <link rel="canonical" href="https://example.com/en/about" /> <!-- No hreflang here; sitemap handles it --> </head> // locales-config.js (single source of truth) export const clusters = { about: { 'en': 'https://example.com/en/about', 'fr': 'https://example.com/fr/a-propos', 'de': 'https://example.com/de/uber-uns', 'x-default': 'https://example.com/en/about' }, contact: { 'en': 'https://example.com/en/contact', 'fr': 'https://example.com/fr/contact', // ... } }; // If HTML head method: // Each page template imports clusters[currentPage] and renders <link> tags // If sitemap method: // Build script imports all clusters and renders sitemap.xml with xhtml:link entries // scripts/generate-hreflang-sitemap.js const { clusters } = require('./locales-config'); const fs = require('fs'); function buildSitemap(clusters) { let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`; xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n`; xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">\n`; for (const [pageId, cluster] of Object.entries(clusters)) { for (const [lang, url] of Object.entries(cluster)) { if (lang === 'x-default') continue; xml += ` <url>\n`; xml += ` <loc>${url}</loc>\n`; // Each URL declares its full cluster as alternates for (const [altLang, altUrl] of Object.entries(cluster)) { xml += ` <xhtml:link rel="alternate" hreflang="${altLang}" href="${altUrl}"/>\n`; } xml += ` </url>\n`; } } xml += `</urlset>\n`; return xml; } fs.writeFileSync('./public/sitemap.xml', buildSitemap(clusters)); console.log('Sitemap regenerated');
Confirm single source
Either HTML head OR sitemap declares hreflang — never both. Verify by curl + grep on representative pages and on sitemap.
Re-run Hreflang Checker
Sitemap mismatch findings clear. Clusters validate against a single declared source.
Tip: 💡 Sitemap approach scales better but HTML approach debugs easier. Pick based on your site size and team workflow, not because one is "more SEO-friendly". Google handles both equally. The mistake is running both at once and letting them drift.
Tags: hreflang, hreflang sitemap xml, hreflang html head, hreflang source of truth, sitemap hreflang generator, hreflang mismatch
Fix
Configure hreflang on WordPress: WPML, Polylang, TranslatePress patterns, x-default, regional targeting, common errors, validation.
Step 1: Decide on URL structure
Three options: subdirectory (example.com/fr/), subdomain (fr.example.com), or ccTLD (example.fr). Subdirectory is easiest in WordPress; subdomain requires separate WP installs or multisite; ccTLD requires separate domains.
Step 2: Install and configure a translation plugin
WPML ($39+/year, most powerful, complex), Polylang (free + Pro from €99, simpler, faster), or TranslatePress (free + Pro from €89, visual editor). All three handle hreflang automatically once configured.
Step 3: Configure language URLs
WPML/Polylang → Languages → URL structure. Match your decided pattern (subdirectory etc.). Confirm permalinks update — visit a post in each language.
Step 4: Verify hreflang output
View source on a translated page. Look for <link rel='alternate' hreflang='X' href='Y' /> tags. Should include: every language variant, x-default pointing to a generic version (often the English/main version).
Step 5: Set x-default correctly
x-default is for users whose locale doesn't match any of your languages. Usually points to your main-language URL. Configure in plugin: WPML → Languages → SEO options; Polylang → Settings → Languages → set default URL.
Step 6: Validate hreflang implementation
Use hreflang.xyz, Ahrefs hreflang validator, or Aleyda Solis's tool. Common errors: missing return tags (page A says alternate is B, B doesn't list A back), missing self-reference, mismatched language codes (use ISO 639-1 like 'en' not 'eng').
Step 7: Monitor in Search Console
Search Console → International Targeting → Language. Reports hreflang errors. Watch the 'No return tags' count; should be zero for a healthy implementation.
Tags: hreflang, WordPress hreflang, WPML hreflang, Polylang hreflang, WordPress multilingual SEO, WordPress geo-targeting, hreflang validator
Fix
Fix HTML validation in React/Next.js: nested invalid HTML (forms in forms, p in p), JSX-vs-HTML quirks, hydration errors, W3C validation workflow.
Run W3C validator on rendered HTML
Critical: validate AFTER rendering, not on JSX source. validator.w3.org → enter URL. JSX components might compile to invalid HTML even if JSX looks correct.
Fix nested form elements
JSX allows nesting <form> inside <form>; HTML doesn't. Common in component hierarchies where a parent wraps everything in form for state management and a child component also renders form. W3C flags this. Fix: lift the form to a single level.
Fix p-inside-p
JSX allows <p>{children}</p> where children includes <p>; HTML spec doesn't allow nested <p>. Common when rendering rich text into paragraphs. Fix: use <div> for nested rich content or restructure.
Fix table semantic errors
React's table rendering quirks (whitespace text nodes between <tr>) sometimes produce HTML W3C flags. Use <tbody> explicitly; avoid conditional rendering directly between <tr> tags.
Resolve hydration mismatch warnings
React console: 'Hydration failed because the initial UI does not match what was rendered on the server.' Causes: time-dependent values, browser-only APIs in render, conditional rendering based on undefined-checks. Each mismatch can produce invalid HTML.
Audit ARIA attributes
JSX doesn't validate ARIA semantics. Use ESLint plugin jsx-a11y to catch invalid ARIA at lint time. W3C validator also reports ARIA issues.
Re-validate after each deploy
Add Lighthouse CI or a periodic W3C check to your CI pipeline. Catches regressions automatically.
Tags: html-checker, React HTML validation, Next.js HTML errors, JSX HTML errors, React W3C, hydration error
Fix
Validate Shopify HTML output: theme template errors, app-injected invalid HTML, liquid syntax problems, deprecated elements, W3C validation workflow.
Run W3C validator
validator.w3.org → 5 representative URLs (homepage, collection, product, blog post, contact). Note error counts and types.
Categorise errors by source
View source. Theme errors: in liquid-rendered HTML. App errors: in app-injected sections. Admin content errors: in product descriptions/blog posts edited in admin. Each fixes differently.
Fix theme errors
Online Store → Themes → Edit code. Find the section with invalid HTML in template files (sections/*.liquid). Common: unclosed div in custom sections, missing alt attributes, deprecated elements (rare in 2.0 themes).
Fix app errors
If an app outputs invalid HTML, options: update app, contact app vendor, or replace app. Don't try to filter app output yourself.
Fix admin content errors
Product descriptions or blog posts containing pasted-from-Word HTML often have invalid markup. Re-edit in Shopify's rich text editor (handles cleanup automatically) or paste as plain text and reformat.
Address deprecated elements
Old themes sometimes have <font> or <center>. Replace with semantic + CSS. Audit theme codebase: grep for deprecated tags.
Re-validate and monitor
Re-run W3C. Set monthly checks via the API or as part of broader accessibility scans (WAVE covers most HTML validation as a by-product).
Tags: html-checker, Shopify HTML validation, Shopify theme HTML, Shopify liquid errors, Shopify W3C, Shopify HTML errors
Fix
Fix HTML validation in static-site generators: Astro, Hugo, Eleventy, Gatsby. Template-output errors, Markdown-to-HTML quirks, build-time validation in CI.
Build the site
npm run build (Astro, Gatsby, Eleventy) or hugo (Hugo). Output goes to dist/, public/, or _site/ depending on framework.
Run html-validate on built output
npm install --save-dev html-validate. Configure .htmlvalidate.json with desired rules. Run: npx html-validate 'dist/**/*.html'. Reports invalid HTML per file.
Identify template-source errors
Most SSG HTML errors come from templates with bugs (Hugo .html templates, Astro .astro files, Eleventy .njk templates). Trace error from built file back to its template source.
Fix Markdown-to-HTML quirks
Markdown content rendered to HTML sometimes produces unexpected structure: lists not wrapped in <ul>, paragraphs interrupted by HTML blocks, rough <br> handling. Configure your Markdown processor (marked, remark, goldmark) to handle edge cases.
Fix layout/wrapper errors
Common: layouts wrap content in <p> tags, then content includes block elements (becomes <p><div>...</div></p> — invalid). Audit layout templates: use <div> not <p> for content wrappers.
Add html-validate to CI
GitHub Actions or other CI: build site → run html-validate → fail build if errors. Catches regressions before deployment.
Periodic W3C validation
Beyond CI: monthly W3C validator runs on production URLs. Catches edge cases CI missed.
Tags: html-checker, static site generator HTML, Astro HTML errors, Hugo HTML validation, Eleventy HTML, Gatsby HTML
Fix
Validate WordPress HTML output: unclosed tags, invalid attributes, deprecated elements, theme issues, plugin injection problems, W3C validator workflow.
Step 1: Run W3C validator
validator.w3.org → enter 5 representative URLs (homepage, blog post, category, product if WooCommerce, contact form). Note error counts and types per page.
Step 2: Categorise errors by source
For each error, view source and identify the source: theme template, plugin output, post content, or page builder. Most errors come from 1-2 sources, not scattered.
Step 3: Fix theme template errors
Edit child theme template files (parent theme edits get overwritten on update). Common theme errors: unclosed div tags in widget areas, missing alt attributes on theme images, deprecated or tags in old themes.
Step 4: Fix plugin output errors
If a plugin outputs invalid HTML, check for plugin updates first. If still broken, file an issue with the plugin author. If unmaintained, replace the plugin. Don't try to filter and fix plugin HTML output yourself — fragile.
Step 5: Fix content-level errors
Page builders (Elementor, Divi) sometimes produce nested-block errors. Re-saving the page in the builder usually fixes it. Manually-edited HTML in Gutenberg's Custom HTML block can introduce errors; switch to a structured block.
Step 6: Address deprecated elements
Replace with + CSS. Replace with text-align CSS. Replace deprecated img attributes (align, hspace) with CSS. Old themes are the usual source.
Step 7: Re-validate and set up monitoring
Re-run W3C validator. Set up monthly checks via the validator's API or via WAVE accessibility scans which include HTML validation. Re-test after every plugin/theme update.
Tags: html-checker, WordPress HTML validation, W3C validator WordPress, WordPress HTML errors, invalid HTML WordPress, WordPress theme HTML
Fix
Debug Node.js and Next.js HTTP errors: 404 handling, 500 error pages, API route errors, edge function errors, Vercel logs, monitoring.
Audit current error pages
Visit /this-page-doesnt-exist on your Next.js site. Should return 404 with custom page. Visit a known broken page or trigger an error — should return 500 with custom page. If you see framework defaults, customise.
Customise 404 (App Router)
Create app/not-found.tsx → exports default NotFound function returning JSX. Triggered when notFound() called or no route matches. Customise messaging, links to popular content, search.
Customise 500 (App Router)
Create app/error.tsx → 'use client' directive, exports default Error function receiving error and reset props. Renders for uncaught errors in client components. Add app/global-error.tsx for layout-level errors.
Handle API route errors
Next.js API routes (app/api/*/route.ts or pages/api/*.ts): wrap handler logic in try/catch, return Response with proper status code: return new Response(JSON.stringify({error: 'message'}), {status: 500, headers: {'Content-Type': 'application/json'}}).
Audit edge function failures
Edge runtime (middleware.ts, edge API routes) has restrictions: no Node.js APIs, limited memory, timeout. Failures often silent. Vercel logs → Edge Functions tab. Self-hosted: add monitoring (Sentry edge runtime support).
Add error monitoring
Sentry, Rollbar, LogRocket. Wraps Next.js with error capture. Catches client errors, SSR errors, API route errors, edge function errors. Per-deployment release tracking matches errors to deploys.
Monitor production
Vercel deployment URL → Logs tab → Errors filter. Self-hosted: pm2 logs, journalctl, or wherever your stdout/stderr ships. Track 5xx rate; spike investigation immediately.
Tags: http-error-audit, Node.js HTTP errors, Next.js 404, Next.js 500, Next.js API errors, Vercel error logs
Fix
Fix HTTP errors on Plesk and cPanel servers: 500 errors, 502 bad gateway, 503 service unavailable, error log access, configuration patterns.
Access error logs
Plesk: Domain → Logs → error_log. cPanel: Errors section in cPanel home, or Metrics → Errors. Latest entries at top. Filter by time range when investigating a specific incident.
Identify error pattern
500 = server error (often PHP fatal error or .htaccess issue). 502 = bad gateway (upstream like PHP-FPM not responding). 503 = service unavailable (overload or maintenance mode). 504 = gateway timeout (slow upstream). 403 = forbidden (permission or .htaccess deny). 404 = not found (file missing or rewrite rule wrong). Each error type points to different fixes.
Check PHP error log
Plesk: Tools & Settings → PHP Settings → error_log path. Common location: /var/www/vhosts/yoursite.com/logs/error_log. Fatal errors, timeouts, memory limit exceeded all logged here. Many 500 errors trace to PHP.
Verify resource limits
Plesk: Subscription → Hosting Settings → resource limits. PHP memory_limit, max_execution_time, max_input_vars. cPanel: PHP Selector. Inadequate limits cause 500/502 errors under load.
Check
htaccess. Bad RewriteRule patterns cause 500. Test by renaming .htaccess temporarily — if 500 disappears, the file is the issue. Iterate: remove sections until error stops, restoring lines until error returns. Identifies the offending rule.
Fix 502/503 patterns
502 often: PHP-FPM down (restart via Plesk → Services or cPanel WHM → Services). 503 often: server overloaded, traffic spike. Check load average: 'top' or 'uptime'. Above core count = overloaded.
Set up monitoring
Plesk: Statistics → HTTP errors. cPanel: Cron job grepping recent errors. External monitoring (UptimeRobot, StatusCake) catches errors users see but you might miss in logs. Email/Slack alerts on 5xx spikes.
Tags: http-error-audit, Plesk HTTP errors, cPanel HTTP errors, Plesk 500 error, Plesk 502 error, cPanel error logs
Fix
Diagnose WordPress HTTP errors: 404, 500, 502, 503, 504. Plugin conflicts, memory limits, .htaccess, server timeouts, debug logging.
Step 1: Enable WordPress debug logging
Edit wp-config.php: define('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false). Errors log to /wp-content/debug.log. Critical first step — diagnosis without logs is guessing.
Step 2: Diagnose 404 errors
Check Settings → Permalinks → click Save (re-flushes rewrite rules — fixes most 404s). For broken inbound links, install Redirection plugin to catch and 301 them. Update internal links pointing to dead URLs.
Step 3: Diagnose 500 errors
Look at debug.log for PHP fatal errors. Common causes: plugin update broke compatibility, theme conflict, exhausted memory limit, corrupted .htaccess. Disable all plugins via /wp-content/plugins rename, re-enable one by one to identify culprit.
Step 4: Diagnose 502/504 errors
Server-level errors. Check PHP-FPM status (managed hosts have logs). Often caused by: long-running queries, external API timeouts, exhausted PHP workers. Increase PHP timeout (php.ini max_execution_time), increase memory_limit, kill stuck queries via Query Monitor or hosting dashboard.
Step 5: Diagnose 503 errors
WordPress 'Briefly unavailable for scheduled maintenance' page that doesn't go away: delete .maintenance file from root via FTP. True 503 (server-level): check hosting account for resource limits hit. Increase plan or optimise resource-heavy plugins.
Step 6: Fix memory limit issues
WordPress 'Allowed memory size exhausted' messages. wp-config.php: define('WP_MEMORY_LIMIT', '256M');. If still failing on admin actions: define('WP_MAX_MEMORY_LIMIT', '512M');. If host caps below this, contact host or upgrade plan.
Step 7: Set up monitoring
Uptime Robot, Better Uptime, or hosting-built-in monitoring. Alert on 5xx errors immediately. Track 404 rate via Search Console; spikes indicate broken links or removed pages.
Tags: http-error-audit, WordPress 500 error, WordPress 404 error, WordPress 502, WordPress 503, WordPress white screen, WordPress memory limit
Fix
Fix HTTPS upgrade redirect chains from the Redirect Checker. Force HTTPS at the edge, enable HSTS, collapse three-hop patterns to single hop.
Test the chain length
curl -IL http://example.com 2>&1 | grep -E "^HTTP|^location:|^Location:" Should show 1-2 status code lines maximum. If 3+, you have a chain to collapse.
Identify each hop's source
Run Redirect Checker. Findings list each hop with the source rule (CDN page rule, .htaccess, nginx config, etc). Rules → Redirect Rules → Create rule When: HTTP scheme equals "http" Then: Static redirect Target URL: https://www.example.com${http.request.uri} Status code: 301 Preserve query string: enabled This single rule handles: - HTTP → HTTPS upgrade - non-www → www canonicalisation - Path preservation - Query string preservation server { listen 80; listen [::]:80; server_name example.com www.example.com; return 301 https://www.example.com$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; server_name example.com; # non-canonical https → canonical https return 301 https://www.example.com$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; server_name www.example.com; # actual content serving root /var/www/html; } # Force HTTPS + www in one rule RewriteEngine On RewriteCond %{HTTPS} !=on [OR] RewriteCond %{HTTP_HOST} !^www\. [NC] RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L] # Check for redundant rules in: # - .htaccess (if Cloudflare handles HTTPS) # - WordPress functions.php (force_ssl_admin etc) # - Application middleware (Express/Django HTTPS forcing) # - Load balancer health check configs # - DNS A records pointing to non-CDN IPs server { listen 443 ssl; server_name www.example.com; # Initial conservative HSTS: 1 hour add_header Strict-Transport-Security "max-age=3600" always; # After 1 week of HTTPS stability: # add_header Strict-Transport-Security "max-age=31536000" always; # 1 year # After 1 month confident: # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Ready for preload: # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; } Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS) → Enable Max Age: 12 months Include subdomains: enabled (only if ALL subdomains have HTTPS) Preload: enabled (only if confident — see warning below) No-Sniff: enabled # Fix: trust X-Forwarded-Proto header instead of $scheme if ($http_x_forwarded_proto = "http") { return 301 https://$host$request_uri; } # Or: use Cloudflare "Full" mode so origin connection is HTTPS
curl trace
curl -IL http://example.com/page 2>&1 | grep -E "^HTTP|^location:" -i # Expected (single hop): # HTTP/1.1 301 Moved Permanently # location: https://www.example.com/page # HTTP/2 200
HSTS header check
curl -I https://www.example.com | grep -i "strict-transport" # Should show: strict-transport-security: max-age=...
SSL Labs scan
SSL Labs server test grades HTTPS configuration. Aim for A or A+. HSTS earns points; preload gets the highest grade.
Tip: 💡 Order of operations matters: get HTTPS working solidly first, then add HSTS with a short max-age (1 hour), test thoroughly, then ramp max-age to 1 year, then add includeSubDomains, then preload. Don't preload until certain.
Tags: redirect-checker, fix HTTPS redirect, HSTS, force HTTPS, edge upgrade, single hop redirect, HTTPS chain, http to https, nginx https redirect
Fix
Fix image alt text from the Image Optimisation audit. Write descriptive, contextually relevant alt that helps SEO and accessibility simultaneously.
Run the Image Optimisation audit
Listings group images by issue: missing alt entirely, empty alt with non-decorative content, alt text that's just a filename, alt longer than 200 characters (usually keyword-stuffed), duplicate alt across many images.
Quick site-wide scan
# Count images missing alt on the homepage curl -s https://yoursite.com/ | grep -oE '<img[^>]*>' | grep -v 'alt=' | wc -l # Find duplicate alt values curl -s https://yoursite.com/ | grep -oE 'alt="[^"]+"' | sort | uniq -c | sort -rn | head <img src="divider.svg" alt=""> <img src="check-icon.svg" alt=""> Available now <!-- Or for CSS-decorative: --> <img src="hero-bg.jpg" alt="" role="presentation"> <img src="widget-blue.jpg" alt="Blue ceramic widget with brushed steel base"> <img src="conversion-chart.png" alt="Conversion rate rose from 2.1% to 4.8% after redesign"> <a href="/pricing.html"> <img src="cart-icon.svg" alt="View cart"> </a> <!-- NOT alt="shopping cart icon" — describes appearance, not function --> <button> <img src="search.svg" alt="Search"> </button> <figure> <img src="quarterly-revenue.png" alt="Quarterly revenue from 2022 to 2024, detailed breakdown below"> <figcaption> Revenue grew steadily through 2022 ($1.2M to $1.8M), accelerated in 2023 ($2.1M to $3.4M), and reached $4.1M in Q4 2024. </figcaption> </figure> <img src="..." alt="image"> <img src="..." alt="photo"> <img src="..." alt="IMG_2342"> <img src="..." alt="blue product"> <img src="widget.jpg" alt="best blue widget cheap blue widget buy blue widget online blue widget review blue widget for sale"> <img src="widget.jpg" alt="Blue ceramic widget with brushed steel base, 12cm tall"> <img src="team.jpg" alt="Five-person engineering team standing in front of whiteboard"> <img src="exercise.jpg" alt="Demonstration of proper deadlift form, hips hinged, back straight"> <!-- Correct: screen reader skips --> <img src="divider.svg" alt=""> <!-- Wrong: screen reader reads 'image divider dot S V G' --> <img src="divider.svg"> <!-- Bad --> <img src="/uploads/IMG_2342.jpg" alt="..."> <img src="/uploads/DSC_0001.jpg" alt="..."> <img src="/uploads/screenshot 2024-01-15 at 14.32.55.png" alt="..."> <!-- Good --> <img src="/uploads/blue-ceramic-widget.jpg" alt="..."> <img src="/uploads/2024-conference-keynote.jpg" alt="..."> <img src="/uploads/dashboard-revenue-view.png" alt="..."> // functions.php — flag images missing alt function require_image_alt($attachment_ID) { $alt = get_post_meta($attachment_ID, '_wp_attachment_image_alt', true); if (empty($alt)) { // Use post title as fallback $title = get_the_title($attachment_ID); update_post_meta($attachment_ID, '_wp_attachment_image_alt', $title); } } add_action('add_attachment', 'require_image_alt'); // .eslintrc.js { "rules": { "jsx-a11y/alt-text": "error" } } // TypeScript — alt is required, not optional interface ImageProps { src: string; alt: string; // not alt?: string width?: number; height?: number; } // Decorative images explicitly opt-in interface DecorativeImageProps { src: string; decorative: true; // forces explicit decoration choice width?: number; height?: number; } // Example: Vision API call const response = await openai.chat.completions.create({ model: 'gpt-4o', messages: [{ role: 'user', content: [ { type: 'text', text: 'Describe this image in 8-12 words for alt text.' }, { type: 'image_url', image_url: { url: imageUrl } } ] }], max_tokens: 60 });
Re-run the Image Optimisation audit
All "missing alt" and "empty alt on non-decorative" findings should clear. Remaining decoratives should still show empty alt — that's correct, not a bug.
Test with a screen reader
macOS: VoiceOver (Cmd+F5). Windows: NVDA (free download). Tab through your page; each meaningful image should be announced descriptively, decorative images should be skipped silently.
Tip: 💡 The accessibility and SEO communities sometimes recommend conflicting things — accessibility wants concise functional descriptions, SEO wants keyword-rich descriptions. Resolve in favour of accessibility. Search engines don't penalise concise good alt; they DO penalise keyword stuffing.
Tags: image-optimisation, fix image alt text, descriptive alt, alt for SEO, accessibility alt, alt text best practice, image SEO, decorative images alt
Fix
Fix image delivery from the Image Optimisation audit. Use image CDN for on-the-fly resize, format conversion, edge caching. Cut latency 50-80%.
Run the Image Optimisation audit
Flags images served directly from origin with high TTFB, no edge caching headers, no format negotiation.
Check headers manually
curl -sI -H "Accept: image/avif,image/webp,*/*" https://yoursite.com/hero.jpg # Look for: # Content-Type: image/jpeg → format not negotiated # Cache-Control: max-age=... → caching present # X-Cache: HIT/MISS → CDN presence # Server: cloudfront/cloudflare/etc → CDN identity https://res.cloudinary.com/<cloud_name>/image/upload/ <transformations>/ <public_id>.<format> <!-- Original Cloudinary asset --> <img src="https://res.cloudinary.com/demo/image/upload/sample.jpg" alt="..."> <!-- Resize to 800px wide --> <img src="https://res.cloudinary.com/demo/image/upload/w_800/sample.jpg" alt="..."> <!-- Resize + auto format + auto quality --> <img src="https://res.cloudinary.com/demo/image/upload/w_800,f_auto,q_auto/sample.jpg" alt="..."> <!-- f_auto: AVIF/WebP/JPEG based on browser q_auto: optimal quality based on content w_800: 800px wide --> <!-- Full responsive set --> <img src="https://res.cloudinary.com/demo/image/upload/w_800,f_auto,q_auto/sample.jpg" srcset="https://res.cloudinary.com/demo/image/upload/w_400,f_auto,q_auto/sample.jpg 400w, https://res.cloudinary.com/demo/image/upload/w_800,f_auto,q_auto/sample.jpg 800w, https://res.cloudinary.com/demo/image/upload/w_1200,f_auto,q_auto/sample.jpg 1200w, https://res.cloudinary.com/demo/image/upload/w_2400,f_auto,q_auto/sample.jpg 2400w" sizes="(max-width: 800px) 100vw, 1200px" width="1200" height="675" alt="..."> <!-- Simple resize --> <img src="https://ik.imagekit.io/yourkey/sample.jpg?tr=w-800,f-auto,q-auto" alt="..."> <!-- Or with helper SDK --> import IKImage from 'imagekitio-react'; <IKImage urlEndpoint="https://ik.imagekit.io/yourkey" path="/sample.jpg" transformation={[{ width: 800, format: 'auto', quality: 'auto' }]} loading="lazy" /> <!-- Bunny Optimizer URL parameters --> <img src="https://your-zone.b-cdn.net/hero.jpg?width=800&optimizer=image" alt="..."> <!-- With multiple params --> <img src="https://your-zone.b-cdn.net/hero.jpg?width=800&aspect_ratio=16:9&quality=80&optimizer=image" alt="..."> // next.config.js module.exports = { images: { formats: ['image/avif', 'image/webp'], remotePatterns: [ { protocol: 'https', hostname: 'cdn.example.com' } ] } }; // Component import Image from 'next/image'; <Image src="https://cdn.example.com/hero.jpg" alt="..." width={1200} height={675} priority /> // Outputs srcset, sizes, format negotiation automatically // Served via Vercel's image optimisation when hosted on Vercel # Check cache HIT curl -sI https://res.cloudinary.com/yourcloud/image/upload/w_800,f_auto/hero.jpg | \ grep -i "x-cache\|cf-cache\|age\|cache-control" # Expected: # X-Cache: HIT # Age: 12345 (seconds since cached) # Cache-Control: public, max-age=31536000 # Check format negotiation curl -sI -H "Accept: image/avif" https://res.cloudinary.com/yourcloud/image/upload/f_auto/hero.jpg | \ grep -i "content-type" # Expected: Content-Type: image/avif curl -sI -H "Accept: image/webp" https://res.cloudinary.com/yourcloud/image/upload/f_auto/hero.jpg | \ grep -i "content-type" # Expected: Content-Type: image/webp
Tip: 💡 Pick simplicity over features for your first image CDN. Bunny CDN Optimizer or ImageKit deliver 90% of the value of Cloudinary at 10-20% of the price. Switch to advanced features (AI cropping, video CDN, etc.) only when you have a specific need.
Tags: image-optimisation, fix image CDN, Cloudinary, ImageKit, Bunny CDN, image edge cache, on-the-fly resize, image delivery, image latency
Fix
Fix image-caused layout shift from the Image Optimisation audit. Set dimensions, reserve space with aspect-ratio, prevent CLS from late-loading images.
Lighthouse CLS audit
Lighthouse Performance → expand CLS metric → "Avoid large layout shifts". Each shift listed with its score contribution and the offending element.
DevTools layout shift visualisation
DevTools Performance tab → record load → look for blue rectangles in the Experience track. Each represents a layout shift event. Click to see which element shifted and by how much.
Web Vitals extension
Web Vitals extension shows live CLS during browsing. Visit your pages, watch the number rise; pinpoint which interactions or scrolls trigger shifts. <!-- Wrong --> <img src="/hero.jpg" alt="..."> <!-- Right --> <img src="/hero.jpg" alt="..." width="1200" height="675"> img { max-width: 100%; height: auto; } <div class="image-frame"> <img src="/uploads/abc.jpg" alt="..."> </div> <style> .image-frame { aspect-ratio: 16 / 9; background: #f0f0f0; overflow: hidden; } .image-frame img { width: 100%; height: 100%; object-fit: cover; } </style> <img src="/hero.jpg" width="1200" height="675" alt="..." style="background: #f0f0f0;"> <img src="data:image/jpeg;base64,iVBORw0KGgoA..." data-src="/hero.jpg" width="1200" height="675" alt="..."> <!-- Modern: BlurHash or thumbhash --> <div class="placeholder" style="background-image: url(blur.svg)"> <img src="/hero.jpg" width="1200" height="675"> </div> import Image from 'next/image'; import heroImage from './hero.jpg'; <Image src={heroImage} alt="..." placeholder="blur" /> // Auto-generates blurred placeholder, served inline as base64 <!-- Ad slot reserves 300x250 before ad loads --> <div class="ad-slot" style="width: 300px; height: 250px; background: #f5f5f5"> <script> googletag.cmd.push(function() { googletag.display('ad-slot-1'); }); </script> </div> <!-- YouTube embed with reserved space --> <div style="aspect-ratio: 16/9; background: #000"> <iframe src="..." loading="lazy" width="100%" height="100%"></iframe> </div> <!-- Bad: CSS won't prevent CLS --> <img src="/hero.jpg" style="width: 1200px; height: 675px"> // WordPress example — some themes strip dimensions function add_image_dimensions($content) { // Bad theme code might do this return preg_replace('/width="\d+"\s+height="\d+"/', '', $content); } // Don't strip them. Browsers need them. <!-- Bad: mobile variant has different ratio --> <img srcset="/mobile-400x600.jpg 400w, /desktop-1200x675.jpg 1200w" width="1200" height="675"> <!-- Browser reserves 16:9 but mobile image is 2:3 --> <!-- Image renders cropped or distorted -->
Lab CLS in Lighthouse
Run Lighthouse Mobile on representative pages. CLS should be under 0.1. If still high, check the listed contributors — often a fonts issue or a single missed image.
Field CLS in Search Console
Real-user CLS data takes 2-4 weeks to reflect changes. Search Console → Core Web Vitals . Track percentage of URLs in Good vs Needs Improvement vs Poor. Successful CLS work moves URLs from Poor to Good.
PageSpeed Insights live test
pagespeed.web.dev shows both lab and field CLS per URL. Run it on key pages before and after deployment.
Tip: 💡 The 5-minute fix: add width and height attributes to the image-rendering helper or component in your codebase. One template change patches every image site-wide. Most sites with CLS problems are missing this one thing.
Tags: image-optimisation, fix image CLS, image layout shift, aspect ratio CSS, reserve space images, cumulative layout shift, prevent CLS, image dimensions
Fix
Fix missing image dimensions from the Image Optimisation audit. Set width and height attributes, prevent CLS, use aspect-ratio CSS, fix responsive scaling.
Run the Image Optimisation audit
Lists every img tag without width and/or height attributes. Grouped by page so you can fix templates rather than individual instances.
Quick scan in source
# Count img tags missing width or height curl -s https://yoursite.com/ | \ grep -oE '<img[^>]*>' | \ grep -vE 'width="[^"]+"' | wc -l curl -s https://yoursite.com/ | \ grep -oE '<img[^>]*>' | \ grep -vE 'height="[^"]+"' | wc -l <!-- Wrong: no dimensions, causes CLS --> <img src="/hero.jpg" alt="Description"> <!-- Right: intrinsic dimensions --> <img src="/hero.jpg" alt="Description" width="1200" height="675"> <img src="/hero.jpg" alt="..." width="1200" height="675" style="max-width: 100%; height: auto;"> <style> img { max-width: 100%; height: auto; } </style> <!-- User-uploaded image, dimensions vary --> <div class="user-image-wrapper"> <img src="/uploads/abc123.jpg" alt="..."> </div> <style> .user-image-wrapper { aspect-ratio: 16 / 9; background: #f0f0f0; } .user-image-wrapper img { width: 100%; height: 100%; object-fit: cover; } </style> <!-- Bad: bare img --> <img src="<?php echo $featured_image_url; ?>" alt="..."> <!-- Good: helper function --> <?php echo wp_get_attachment_image($attachment_id, 'large'); ?> <!-- Outputs: <img width="1200" height="675" src="..." alt="..." srcset="..." sizes="..." /> --> import Image from 'next/image'; import heroImage from './hero.jpg'; <Image src={heroImage} alt="..." /> // width and height inferred from import metadata const sharp = require('sharp'); async function getImageDimensions(filePath) { const meta = await sharp(filePath).metadata(); return { width: meta.width, height: meta.height }; } // At build time, pre-compute dimensions const { width, height } = await getImageDimensions('./public/hero.jpg'); // Store in JSON, inject into templates const probe = require('probe-image-size'); const result = await probe('https://example.com/image.jpg'); console.log(result.width, result.height); // Downloads just enough bytes to read dimensions, then aborts <img src="/hero-1200.jpg" srcset="/hero-400.jpg 400w, /hero-800.jpg 800w, /hero-1200.jpg 1200w, /hero-2400.jpg 2400w" sizes="(max-width: 800px) 100vw, 1200px" width="1200" height="675" alt="..."> <!-- BAD: CSS won't prevent CLS --> <img src="/hero.jpg" style="width: 1200px; height: 675px"> <!-- GOOD: HTML attributes --> <img src="/hero.jpg" width="1200" height="675"> <!-- BAD --> <img width="1200px" height="675px"> <!-- GOOD --> <img width="1200" height="675"> <!-- BAD: different aspect ratios for different sizes --> <img src="/hero-1200.jpg" srcset="/hero-mobile-400x600.jpg 400w, /hero-1200x675.jpg 1200w" width="1200" height="675"> <!-- Mobile variant is 2:3 portrait, desktop is 16:9 landscape --> <!-- Browser uses 16:9 for space reservation regardless --> <!-- Mobile image then renders distorted or cropped --> <picture> <source media="(max-width: 600px)" srcset="/hero-mobile-400x600.jpg" width="400" height="600"> <source media="(min-width: 601px)" srcset="/hero-desktop-1200x675.jpg" width="1200" height="675"> <img src="/hero-desktop-1200x675.jpg" alt="..." width="1200" height="675"> </picture>
Lighthouse CLS metric
CLS under 0.1 is Good per Core Web Vitals . Pages with missing dimensions commonly score 0.2-0.5. After adding width/height, expect CLS to drop to 0.05 or below for most pages.
Search Console field CLS
Search Console → Core Web Vitals. Real-user CLS data. Improvements visible 2-4 weeks after deployment as Google reprocesses field data.
Tip: 💡 Audit and fix at the template level, not per image. The biggest CLS wins come from finding the 3-5 templates that emit dimensionless img tags and patching them. One template fix corrects thousands of pages at once.
Tags: image-optimisation, fix image dimensions, width height attributes, aspect ratio CSS, prevent CLS images, image layout shift, intrinsic size
Fix
Fix legacy image formats from the Image Optimisation audit. Serve WebP and AVIF with picture element, cut bytes 40-70%, keep JPEG fallback.
Run the Image Optimisation audit
Lists images by format with potential savings. Big JPEG hero images get the most benefit from AVIF.
DevTools quick check
DevTools → Network tab → filter "Img" → sort by size. Biggest images first. Compare Content-Type header to see what's being served. If Content-Type is image/jpeg or image/png and the file is over 100 KB, modern format conversion saves significant bytes. <picture> <source type="image/avif" srcset="/img/hero.avif"> <source type="image/webp" srcset="/img/hero.webp"> <img src="/img/hero.jpg" alt="Description" width="1200" height="675" loading="lazy"> </picture> <picture> <source type="image/avif" srcset="/img/hero-400.avif 400w, /img/hero-800.avif 800w, /img/hero-1200.avif 1200w, /img/hero-2400.avif 2400w" sizes="(max-width: 800px) 100vw, 800px"> <source type="image/webp" srcset="/img/hero-400.webp 400w, /img/hero-800.webp 800w, /img/hero-1200.webp 1200w, /img/hero-2400.webp 2400w" sizes="(max-width: 800px) 100vw, 800px"> <img src="/img/hero-1200.jpg" srcset="/img/hero-400.jpg 400w, /img/hero-800.jpg 800w, /img/hero-1200.jpg 1200w, /img/hero-2400.jpg 2400w" sizes="(max-width: 800px) 100vw, 800px" alt="Description" width="1200" height="675" loading="lazy"> </picture> const sharp = require('sharp'); async function convertImage(input) { await sharp(input) .webp({ quality: 80 }) .toFile(input.replace(/\.(jpg|png)$/, '.webp')); await sharp(input) .avif({ quality: 60 }) .toFile(input.replace(/\.(jpg|png)$/, '.avif')); } npm install -g @squoosh/cli squoosh-cli --webp '{"quality": 80}' \ --avif '{"cqLevel": 33}' \ ./images/*.jpg // next.config.js — automatic AVIF + WebP module.exports = { images: { formats: ['image/avif', 'image/webp'] } }; // Component handles negotiation automatically import Image from 'next/image'; <Image src="/hero.jpg" alt="..." width={1200} height={675} /> <img src="https://yoursite-cdn.com/auto/hero.jpg" alt="..."> <!-- CDN reads request Accept: image/avif and returns AVIF --> # nginx — rewrite to .webp if browser supports it location ~* \.(jpg|jpeg|png)$ { if ($http_accept ~* "webp") { set $webp_uri $uri.webp; } try_files $webp_uri $uri =404; } npm install -g svgo svgo input.svg -o output.svg
Before/after image weight
# Total image bytes on a page, before curl -s https://yoursite.com/page | \ grep -oE 'src="[^"]+\.(jpg|png)"' | \ awk -F'"' '{print $2}' | \ xargs -I{} curl -sI https://yoursite.com{} | \ grep -i content-length Sum the byte counts. Compare to AVIF-served totals after deployment.
Lighthouse before/after
Run Lighthouse on a representative page before and after. Expect: "Serve images in next-gen formats" audit: pass LCP: drop 200-1500ms depending on image weight Total bytes: down 30-60% on image-heavy pages
Tip: 💡 Most impact from least work: convert your biggest 10 images (hero, product photos, blog headers) to AVIF + WebP first. They account for most of your image weight. Tail of small icons and thumbnails can convert later or get left as JPEG/PNG.
Tags: image-optimisation, fix image format, WebP, AVIF, picture element, modern image formats, JPEG to WebP, image bytes savings, responsive images
Fix
Fix image lazy-loading from the Image Optimisation audit. Use loading=lazy for below-fold, eager for LCP, decoding=async, native browser support.
Run the Image Optimisation audit
Findings categorise as: Below-fold images without loading=lazy — wasted bandwidth LCP image with loading=lazy — delayed first paint All images marked lazy (including hero) — major LCP impact Old data-src lazy-load pattern still in use — switch to native <!-- All images that aren't visible on initial page load --> <img src="/gallery-photo.jpg" loading="lazy" decoding="async" width="800" height="600" alt="..."> // React example: Image component defaults to lazy function ResponsiveImage({ src, alt, width, height, priority = false }) { return <img src={src} alt={alt} width={width} height={height} loading={priority ? 'eager' : 'lazy'} decoding="async" fetchpriority={priority ? 'high' : 'auto'} />; } // Usage <ResponsiveImage src="/hero.jpg" alt="..." width={1200} height={675} priority /> // LCP <ResponsiveImage src="/below-fold.jpg" alt="..." width={800} height={600} /> // lazy default <!-- Hero / LCP image --> <img src="/hero.jpg" loading="eager" fetchpriority="high" decoding="async" width="1200" height="675" alt="..."> <link rel="preload" as="image" href="/hero.jpg" imagesrcset="/hero-400.jpg 400w, /hero-1200.jpg 1200w, /hero-2400.jpg 2400w" imagesizes="(max-width: 800px) 100vw, 1200px" fetchpriority="high">
DevTools Performance recording
DevTools → Performance → record page load → stop. Timings panel shows LCP marker pointing at the largest content element. Usually a hero image, sometimes a large block of text.
Web Vitals Chrome extension
Install Web Vitals extension . Real-time LCP, CLS, INP displayed per page. Click LCP to highlight which element is being measured. <img src="..." decoding="async" alt="..."> <!-- OLD: lazysizes pattern --> <img data-src="/photo.jpg" class="lazyload" alt="..."> <script src="lazysizes.min.js"></script> <!-- NEW: native --> <img src="/photo.jpg" loading="lazy" alt="..."> <!-- No JS needed --> <!-- Was background-image, now img --> <img src="/section-bg.jpg" alt="" loading="lazy" class="full-bleed"> <style> .full-bleed { width: 100%; height: 400px; object-fit: cover; } </style> <div class="lazy-bg" data-bg="/section-bg.jpg"></div> <script> const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.style.backgroundImage = `url(${entry.target.dataset.bg})`; observer.unobserve(entry.target); } }); }); document.querySelectorAll('.lazy-bg').forEach(el => observer.observe(el)); </script> <iframe src="https://www.youtube.com/embed/..." loading="lazy" width="560" height="315" title="..."> </iframe>
Network tab: deferred downloads
DevTools → Network → reload. Below-fold images should NOT appear in the initial download list. Scroll the page; they appear as you approach them.
Lighthouse LCP improvement
Re-run Lighthouse. Expected: "Defer offscreen images" audit: pass "Largest Contentful Paint image was not lazily loaded" audit: pass LCP: 200-800ms faster if hero was previously lazy-loaded Initial page weight: smaller
Tip: 💡 The single biggest mistake: applying loading=lazy uniformly to all images including the hero. That makes LCP worse, not better. Audit your LCP image specifically — it gets eager + fetchpriority=high. Everything else gets lazy.
Tags: image-optimisation, fix image lazy load, loading lazy, decoding async, native lazy load, LCP image eager, fetchpriority, below fold images
Fix
Fix oversized images from the Image Optimisation audit. Resize to display size, compress, use srcset for responsive delivery, cut image bytes 60-80%.
Run the Image Optimisation audit
Findings ranked by waste: actual image dimensions vs displayed dimensions. A 4000x3000 image displayed at 800x600 wastes ~93% of its bytes.
DevTools quick check
DevTools → right-click any image → "Inspect". Shows the natural dimensions (full image size) vs rendered dimensions (display size). Ratio over 1.5x means resizing helps. // Display size: 800x600 CSS pixels // Standard display (1x DPR): serve 800x600 // Retina display (2x DPR): serve 1600x1200 // Modern phones (3x DPR): serve 2400x1800 (overkill, 2x usually fine) // Rule of thumb: serve at 2x DPR for retina compatibility, // don't waste bytes serving 3x — barely visible difference const sharp = require('sharp'); async function generateVariants(input, basename) { const sizes = [400, 800, 1200, 1600, 2400]; for (const width of sizes) { await sharp(input) .resize({ width, withoutEnlargement: true }) .jpeg({ quality: 82, mozjpeg: true }) .toFile(`./public/img/${basename}-${width}.jpg`); await sharp(input) .resize({ width, withoutEnlargement: true }) .webp({ quality: 80 }) .toFile(`./public/img/${basename}-${width}.webp`); } } add_image_size('hero-small', 400); add_image_size('hero-medium', 800); add_image_size('hero-large', 1200); add_image_size('hero-xl', 1600); add_image_size('hero-2x', 2400); <img src="https://cdn.example.com/hero.jpg?w=800&q=82" alt="..."> <!-- Returns 800px-wide JPEG at quality 82 --> // Sharp sharp(input).jpeg({ quality: 82, mozjpeg: true }); // mozjpeg encoder produces smaller files than libjpeg at same quality // quality 82 visually indistinguishable from 100 for most photos // quality 100 typically 40-60% larger for no visible benefit # pngquant for lossy PNG-8 pngquant --quality=70-90 --strip image.png -o image-optimised.png # Typically 60-80% size reduction # Visible quality loss minimal for photos # Keeps transparency unlike JPEG conversion // Removes EXIF, ICC profile, GPS data // Saves bytes AND privacy (no GPS in publicly shared photos) sharp(input).jpeg({ quality: 82 }).toFile(output); // Sharp strips by default — verify with `exiftool output.jpg` <img src="/img/hero-1200.jpg" srcset="/img/hero-400.jpg 400w, /img/hero-800.jpg 800w, /img/hero-1200.jpg 1200w, /img/hero-1600.jpg 1600w, /img/hero-2400.jpg 2400w" sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 1200px" alt="Description" width="1200" height="675" loading="lazy"> <picture> <source type="image/avif" srcset="/img/hero-400.avif 400w, /img/hero-800.avif 800w, /img/hero-1200.avif 1200w, /img/hero-2400.avif 2400w" sizes="(max-width: 800px) 100vw, 1200px"> <source type="image/webp" srcset="/img/hero-400.webp 400w, /img/hero-800.webp 800w, /img/hero-1200.webp 1200w, /img/hero-2400.webp 2400w" sizes="(max-width: 800px) 100vw, 1200px"> <img src="/img/hero-1200.jpg" srcset="/img/hero-400.jpg 400w, /img/hero-800.jpg 800w, /img/hero-1200.jpg 1200w, /img/hero-2400.jpg 2400w" sizes="(max-width: 800px) 100vw, 1200px" width="1200" height="675" alt="Description" loading="lazy"> </picture> <img src="/img/hero-1200.jpg" srcset="..." sizes="..." width="1200" height="675" alt="..." fetchpriority="high" loading="eager"> <img src="/img/gallery-item.jpg" loading="lazy" decoding="async" width="400" height="300" alt="..."> import Image from 'next/image'; <Image src="/hero.jpg" alt="..." width={1200} height={675} priority // for LCP image sizes="(max-width: 800px) 100vw, 1200px" /> // Generates AVIF, WebP, multiple sizes, sets srcset, lazy by default // Astro import { Image } from 'astro:assets'; <Image src={heroImage} alt="..." widths={[400, 800, 1200]} formats={['avif', 'webp', 'jpg']} />
Total page weight before vs after
DevTools → Network tab → reload → bottom right shows total transferred bytes. Should drop 40-70% on image-heavy pages.
Lighthouse LCP improvement
Mobile Lighthouse pre-fix vs post-fix. Heroes dropping from 4MB to 250KB typically improve LCP by 1.5-3 seconds on 3G/4G connections.
Tip: 💡 The 80/20 of image-size fixes: identify your 5 biggest images by byte size, optimise them, redeploy. That covers 70-80% of total image weight on most sites. The long tail of small icons can wait.
Tags: image-optimisation, fix image size, resize images, compress images, srcset responsive, image bytes, oversized images, image weight, LCP image
Fix
Optimise Next.js images: next/image component, Vercel Image Optimization API, custom loaders, AVIF/WebP, fill vs width/height, priority and lazy loading.
Replace raw <img> with next/image
Audit codebase for <img> tags. Replace each with <Image from 'next/image' /> with required width/height (or fill for parent-sized). Build will fail if dimensions missing — forces correctness.
Configure remote image hostnames
next.config.js → images.remotePatterns: [{protocol: 'https', hostname: 'cdn.yoursite.com'}]. Next.js won't optimise images from undeclared hosts (security).
Set priority on LCP image
The largest image in the initial viewport (LCP candidate) should have priority prop: <Image priority />. Disables lazy loading, preloads. Improves LCP score by 200-500ms typically.
Choose fill vs width/height
width/height: known dimensions, simpler. fill: image fills relative-positioned parent (responsive layouts where parent has known size). Don't mix — pick per use case.
Configure formats and quality
next.config.js → images.formats: ['image/avif', 'image/webp']. AVIF first (smaller, supported in modern browsers); WebP fallback; original as last resort. Quality default 75 is good — only override per image when needed.
Verify Vercel Image Optimization or custom loader
Vercel hosting: Image API runs at edge, fast and free up to limits. Other hosts: configure loader (Cloudinary, imgix, custom). Self-hosted Next.js can use sharp-based optimisation but requires more setup.
Monitor
Vercel Analytics → image metrics. Or DevTools Network tab: verify served format is AVIF/WebP, served size matches displayed size (no over-fetching).
Tags: image-optimisation, Next.js images, next/image, Vercel Image API, Next.js AVIF, Next.js image optimization
Fix
Optimise Shopify images: bulk compression, WebP/AVIF (auto by Shopify CDN), responsive variants, lazy loading, alt text, app choices.
Audit image weight
DevTools Network → filter images. Note total image weight per page. Shopify CDN auto-resizes and converts; if images are still heavy, originals are over-large.
Bulk-compress large originals
Apps: Crush.pics, TinyIMG, AVADA SEO Image Optimizer, Plug in SEO. Compress all product images, especially originals over 1MB. Save up to 70% bandwidth without visual degradation.
Verify WebP delivery
View source on a product page. Image src URLs include format conversion via Shopify CDN (look for .webp or _500x_crop_center suffixes). All modern Shopify themes and CDN serve WebP to supporting browsers automatically.
Configure theme image sizes
Dawn and 2.0 themes use srcset with multiple sizes. Verify in view-source on product page — should see srcset='url 200w, url 400w, url 800w, url 1200w'. Older themes may serve single large image.
Enable lazy loading correctly
Shopify themes use loading='lazy' by default on below-fold images. Hero images and first product image should NOT be lazy. Verify in source — first product image should have loading='eager' or no loading attribute.
Backfill alt text
Use bulk alt-text app (Image Optimizer, AVADA, Crush.pics) or manual workflow. Priority: top 100 highest-traffic products first. Decorative images get alt='' (empty), never omit attribute.
Monitor
After every theme update, re-test 3-5 product pages for image loading correctness. Theme updates can break lazy-loading or srcset configuration.
Tags: image-optimisation, Shopify images, Shopify image optimization, Shopify WebP, Shopify image alt text, Shopify image compression
Fix
Reduce image-driven LCP: WebP/AVIF, resize to display dimensions, lazy-load below fold, preload the hero image.
Run the Page Speed audit
Open the Page Speed Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Page Speed audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: pagespeed, images, speed
Fix
Optimise WordPress images: bulk compression, WebP and AVIF conversion, responsive srcset, lazy loading, CDN delivery, alt text and the audit-fix-verify cycle.
Step 1: Audit existing images
Before optimising, measure. Run PageSpeed Insights on 5 representative pages and look at the "Opportunities" section — it lists "Properly size images", "Efficiently encode images", "Serve images in next-gen formats" with specific URLs and predicted savings in KB. Also use our Image Optimisation tool for a full site-wide scan that returns every image with size, format, dimensions and alt-text status. Note the worst offenders. Typically you'll find: a 4000×3000px hero image displayed at 1200px wide (2-3MB when it should be 200KB), a series of uncompressed PNG screenshots in blog posts (5MB+ each), and dozens of media-library uploads missing alt text.
Step 2: Bulk-compress the media library
Install one image optimisation plugin (don't run multiple — they conflict). The three main choices: ShortPixel — best compression ratios. Free tier: 100 images/month. Paid plans from $4.99/month for 5,000 images. Configure: lossy compression, 80% quality, keep originals as backup, generate WebP automatically. Smush — easiest UI. Free version: unlimited compression but lossless only. Smush Pro ($7/month) adds lossy and CDN. Good for non-technical users. Imagify — by WP Rocket. Free tier: 20MB/month. Paid plans from $9.99/month. Tightest integration with WP Rocket caching. Whichever plugin you pick: enable "keep original backup" before running bulk compression. Without backup, compression is destructive. Run bulk-compress on a quiet day — the queue can take hours for large libraries.
Step 3: Convert to WebP and AVIF
WebP is 25-35% smaller than JPEG at equivalent quality with 98% browser support. AVIF is another 20% smaller again with 92% support. The serving stack you want: Browser sends Accept header listing supported formats Server checks: AVIF? Serve .avif. WebP? Serve .webp. Otherwise serve .jpg Done via .htaccess rules or a plugin that handles content-negotiation Easiest implementation: ShortPixel's "Deliver WebP" toggle + Cloudflare Polish for AVIF. Or use a CDN like BunnyCDN with image optimisation enabled — it negotiates format at the edge.
Step 4: Verify srcset for responsive images
WordPress automatically generates multiple image sizes on upload and outputs them as srcset attributes. View source on a blog post and confirm your img tags look like this: <img src="image-1024x768.jpg" srcset="image-300x225.jpg 300w, image-768x576.jpg 768w, image-1024x768.jpg 1024w, image-1536x1152.jpg 1536w" sizes="(max-width: 1024px) 100vw, 1024px" width="1024" height="768" alt="..." /> If srcset is missing: your theme is stripping it (audit theme functions.php for filters like wp_calculate_image_srcset ), or your page builder is outputting raw URLs without WordPress's image function. Edit theme/builder output to use wp_get_attachment_image() which always produces srcset.
Step 5: Enable native lazy loading correctly
WordPress 5.5+ automatically adds loading="lazy" to img tags below the fold. Verify in view-source. Three common breakage patterns: Hero images marked lazy. Above-the-fold hero images should NOT be lazy — they delay LCP. WordPress's auto-detection sometimes mislabels them. Override by adding loading="eager" manually on the hero img tag, or use the wp_lazy_loading_enabled filter to disable lazy for above-fold contexts. Page builder override. Elementor and Divi sometimes output their own lazy-loading attribute that conflicts with WP's native one. Check the builder image module settings — usually a "Native lazy load" toggle. Plugin double-lazy. A3 Lazy Load and similar plugins were essential before WP 5.5; now they conflict with native lazy. Disable third-party lazy plugins; let WP core handle it.
Step 6: Add CDN delivery
Images served from your origin server take 200-400ms longer to load than images from a global CDN, particularly for international visitors. Easy wins: Cloudflare (free) — sign up, change nameservers, enable Polish for image optimisation, enable WebP. Done. Cloudflare Mirage further optimises images for mobile. BunnyCDN ($1/month base) — install the BunnyCDN WordPress plugin, point at your origin, enable image optimisation. Faster than Cloudflare in some regions, cheaper at scale. KeyCDN — similar to BunnyCDN. Plugin handles URL rewriting.
Step 7: Backfill alt text
Alt text matters for accessibility (screen readers), SEO (image search ranking) and AI visibility (engines extract alt text as image context). Audit your media library — most WordPress sites have 30-60% missing alt text. Tools: Use a bulk alt-text plugin like "Auto Alt Text for Images" which applies AI-generated descriptions, or work through manually for higher quality. Either way, prioritise: featured images and in-content blog images first (highest SEO value), then media library backfill. Decorative images get alt="" (empty), never omit the attribute entirely. <div
Tip: 💡 After every WordPress core update, re-test 3-5 image URLs in view-source. WordPress 5.5, 5.7, 6.0 and 6.4 all changed image-handling defaults — your plugin configuration may need adjusting after each major update.
Tags: image-optimisation, WordPress image optimisation, WebP WordPress, AVIF WordPress, WordPress lazy load, WordPress image compression, srcset WordPress
Fix
Make links render correctly in iMessage: required Open Graph tags, image format and content-type headers.
Run the Social Preview audit
Open the Social Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Social Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: social-preview, imessage, preview
Fix
Recover indexation after a site migration: submit new sitemap, request indexing for top URLs, fix any noindex regressions.
Run the Migration Audit audit
Open the Migration Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Migration Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: migration-audit, indexation, migration
Fix
Diagnose and fix indexation problems: noindex tags, robots blocks, canonical conflicts, sitemap mismatches.
Run the Site Audit audit
Open the Site Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Site Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: site-audit, indexation
Fix
Improve INP: profile slow interactions, break up long tasks, optimise event handlers, defer non-critical JS.
Run the Core Web Vitals audit
Open the Core Web Vitals Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Core Web Vitals audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cwv, inp
Fix
Bulk-update internal links after a migration so every link points at the new URL, not via 301 hops.
Run the Migration Audit audit
Open the Migration Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Migration Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: migration-audit, internal, links, migration
Fix
Comply with GDPR Article 44+ for international transfers: SCCs, adequacy decisions, transfer impact assessments.
Run the GDPR audit
Open the GDPR Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your GDPR audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gdpr, international, transfers
Fix
Remove unnecessary jargon, replace technical terms with plain language and explain unavoidable industry terms inline.
Run the Readability audit
Open the Readability Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Readability audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: readability, jargon
Fix
Fix oversized JavaScript bundles from the JS Checker. Tree-shake unused exports, split routes, replace heavy libraries, compress with Brotli.
Install a bundle analyser
# webpack npm install --save-dev webpack-bundle-analyzer # Rollup / Vite npm install --save-dev rollup-plugin-visualizer # Next.js ANALYZE=true npm run build
Read the visualisation
Bundle analysers produce treemap views: each block is a module, sized proportionally to its bytes in the bundle. Largest blocks are your biggest opportunities. Common offenders: lodash, moment, jQuery duplicated by dependencies, large UI libraries. // Bad: imports all of lodash (24 KB+ per chunk) import _ from 'lodash'; _.debounce(...); // Better: named import (still imports lodash internally, doesn't tree-shake) import { debounce } from 'lodash'; // Best: lodash-es supports tree-shaking import { debounce } from 'lodash-es'; // Or: per-function import import debounce from 'lodash/debounce'; // Bad: moment.js — 232 KB minified+gzipped with all locales import moment from 'moment'; moment().format('YYYY-MM-DD'); // Good: date-fns — only the functions you use, ~5-15 KB total import { format } from 'date-fns'; format(new Date(), 'yyyy-MM-dd'); // package.json { "sideEffects": false, // your code has no side-effect imports // OR list specific files that DO have side effects "sideEffects": ["*.css", "./src/polyfills.js"] } // Static import: everything in initial bundle import Dashboard from './pages/Dashboard'; import Settings from './pages/Settings'; import Reports from './pages/Reports'; // Dynamic import: each becomes its own chunk const Dashboard = lazy(() => import('./pages/Dashboard')); const Settings = lazy(() => import('./pages/Settings')); const Reports = lazy(() => import('./pages/Reports'));
Find unused npm packages
npx depcheck # Shows unused dependencies, missing dependencies, unused devDependencies Manually verify before removing — depcheck can miss dynamic imports.
Audit dependency tree depth
npm ls --depth=2 Look for: same library at multiple versions (caused by transitive dependencies), unexpected huge packages installed indirectly. # nginx brotli on; brotli_types text/css application/javascript application/json text/html; brotli_comp_level 6; # Pre-compress at build time for static files npm install --save-dev compression-webpack-plugin new CompressionPlugin({ algorithm: 'brotliCompress', test: /\.(js|css)$/ }); location ~* \.(js|css)$ { expires 1y; add_header Cache-Control "public, immutable"; } # Lighthouse CI { "ci": { "assert": { "assertions": { "resource-summary:script:size": ["error", { "maxNumericValue": 200000 }] } } } }
Tip: 💡 Bundle size optimisation has diminishing returns. Cut the big stuff first (heavy libraries, untree-shaken imports, missing splitting), then stop. Spending hours saving 2 KB is rarely the best use of time when there are other performance fixes to ship.
Tags: js-checker, fix JS bundle size, tree shaking, code splitting, webpack bundle analyzer, JavaScript optimization, smaller bundle, dependency audit
Fix
Fix JavaScript console errors from the JS Checker. Diagnose TypeError, ReferenceError, undefined access, async failures, monitor real-user errors.
Run the JS Checker
The JS Checker reports console errors observed during synthetic page loads. Useful for catching errors that fire on initial render.
Cross-reference real-user data
Most production errors fire only under specific conditions: certain inputs, async race conditions, third-party script timing. Sentry / LogRocket / Datadog catches these in real-user sessions with stack traces, browser context, replay (LogRocket).
Sort by frequency
Don't fix every error individually. Group errors by stack trace; fix the top 3 most-frequent first. Often one bug accounts for 50% of all errors. // Bug: user.profile may not exist function showName(user) { return user.profile.name; // TypeError if no profile } // Fix 1: Optional chaining function showName(user) { return user?.profile?.name; // returns undefined gracefully } // Fix 2: Default with nullish coalescing function showName(user) { return user?.profile?.name ?? 'Anonymous'; } // Fix 3: Explicit guard function showName(user) { if (!user || !user.profile) return 'Anonymous'; return user.profile.name; } // Bug: missing import function process() { return moment().format(); // ReferenceError if moment not imported } // Fix: import the dependency import moment from 'moment'; function process() { return moment().format(); } // Bug: typo const userName = 'jane'; console.log(usrName); // ReferenceError - typo // Fix: ESLint catches these at build time const userName = 'jane'; console.log(userName); // Bug: no error handling async function loadUser(id) { const res = await fetch(`/api/users/${id}`); return res.json(); // rejects on network failure } loadUser(1); // unhandled rejection if fetch fails // Fix: handle the rejection async function loadUser(id) { try { const res = await fetch(`/api/users/${id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } catch (err) { console.error('Failed to load user', err); return null; // or appropriate fallback } } window.addEventListener('unhandledrejection', (event) => { Sentry.captureException(event.reason); }); // Wrap third-party initialisation try { window.someThirdParty.init({ key: 'X' }); } catch (err) { console.warn('Third party init failed', err); } Sentry.init({ dsn: '...', beforeSend(event) { // Ignore errors from third-party scripts if (event.exception?.values?.[0]?.stacktrace?.frames?.some( f => f.filename?.includes('third-party-domain.com') )) { return null; } return event; } }); // Bug: component unmounted before async completes async function loadAndSet() { const data = await fetch('/api/data'); setState(data); // setState on unmounted component } // Fix: signal abort or component-aware function MyComponent() { useEffect(() => { const controller = new AbortController(); fetch('/api/data', { signal: controller.signal }) .then(setState) .catch(err => { if (err.name !== 'AbortError') console.error(err); }); return () => controller.abort(); }, []); } // Sentry quick setup npm install --save @sentry/browser // At app entry import * as Sentry from '@sentry/browser'; Sentry.init({ dsn: 'YOUR_DSN_HERE', environment: process.env.NODE_ENV, release: process.env.GIT_SHA, // Optional: session replay for context integrations: [new Sentry.Replay()], replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, });
Watch error frequency drop
After deploying fixes, error monitoring should show the top error types dropping to zero (or near-zero) within 24 hours as your deploy reaches all users.
Set up alerts
Configure Sentry / similar to alert on: new error types appearing for the first time, error rate spiking above threshold, errors affecting more than X users per hour. Catches regressions immediately.
Tip: 💡 The 80/20 rule applies hard to JS errors. Most production sites have 5-10 errors that account for 80% of total error volume. Fix those first. The long tail of one-off errors can be ignored until the top issues are clean.
Tags: js-checker, fix JS console errors, TypeError, ReferenceError, undefined, null reference, error monitoring, Sentry, browser console errors
Fix
Fix JavaScript crawler rendering issues from the JS Checker. Server-side render critical content, prerender static routes, fix hydration mismatches.
URL Inspection in Search Console
Search Console → URL Inspection → paste a URL → "Test Live URL" → "View Rendered HTML". Shows the HTML Googlebot actually rendered. Compare to what users see.
Mobile-Friendly Test
search.google.com/test/mobile-friendly shows rendered HTML and screenshot. Faster than URL Inspection for quick checks.
Curl to see raw HTML
curl -A "Mozilla/5.0 (compatible; Googlebot/2.1)" https://yoursite.com/page | grep -i "<title>" Raw HTML from the server. This is what Googlebot indexes BEFORE rendering. Title, meta description, structured data should be present here. // pages/product/[id].js export async function getServerSideProps({ params }) { const product = await fetchProduct(params.id); return { props: { product } }; } export default function ProductPage({ product }) { return ( <> <Head> <title>{product.name} - Brand</title> <meta name="description" content={product.description} /> </Head> <h1>{product.name}</h1> <p>{product.description}</p> </> ); } <script setup> const route = useRoute(); const { data: product } = await useFetch(`/api/products/${route.params.id}`); useHead({ title: () => `${product.value.name} - Brand`, meta: [{ name: 'description', content: product.value.description }] }); </script> <template> <h1>{{ product.name }}</h1> <p>{{ product.description }}</p> </template> // +page.server.js export async function load({ params }) { return { product: await fetchProduct(params.id) }; } // +page.svelte <svelte:head> <title>{data.product.name} - Brand</title> <meta name="description" content={data.product.description} /> </svelte:head> <h1>{data.product.name}</h1> <p>{data.product.description}</p> export async function getStaticPaths() { const products = await fetchAllProducts(); return { paths: products.map(p => ({ params: { id: p.id } })), fallback: 'blocking' }; } export async function getStaticProps({ params }) { const product = await fetchProduct(params.id); return { props: { product }, revalidate: 3600 }; } // nuxt.config.ts export default defineNuxtConfig({ nitro: { prerender: { routes: ['/', '/about', '/pricing'] } } }); --- // Astro file: pages/product/[id].astro export async function getStaticPaths() { const products = await fetchAllProducts(); return products.map(p => ({ params: { id: p.id }, props: { product: p } })); } const { product } = Astro.props; --- <html> <head> <title>{product.name}</title> </head> <body> <h1>{product.name}</h1> </body> </html> // Mismatch: Date.now() differs between server (render time) and client (hydration time) function Footer() { return <p>Year: {new Date().getFullYear()}</p>; } // Fix: render on client only, or pass server value down import { useEffect, useState } from 'react'; function Footer() { const [year, setYear] = useState(null); useEffect(() => setYear(new Date().getFullYear()), []); if (!year) return null; return <p>Year: {year}</p>; } // Mismatch: window/document not available on server function Component() { const isMobile = window.innerWidth < 768; // ReferenceError on server return isMobile ? <Mobile /> : <Desktop />; } // Fix: check for typeof window OR use a hook function Component() { const [isMobile, setIsMobile] = useState(false); useEffect(() => { setIsMobile(window.innerWidth < 768); }, []); return isMobile ? <Mobile /> : <Desktop />; } // nginx — detect crawler User-Agent and proxy to prerender service map $http_user_agent $is_crawler { default 0; ~*Googlebot 1; ~*Bingbot 1; ~*facebookexternalhit 1; } server { location / { if ($is_crawler = 1) { proxy_pass https://service.prerender.io; } try_files $uri /index.html; } }
Re-test in URL Inspection
Critical content should now appear in raw HTML view, not just rendered HTML view. "Indexing allowed" should be green, no rendering issues reported.
Request re-indexing
URL Inspection → "Request Indexing" for important pages. Google re-fetches and re-renders. Within days, the updated indexing reflects in search.
Tip: 💡 The frameworks designed for SSR (Next.js, Nuxt, SvelteKit, Astro) make crawler-friendly rendering automatic. Sticking with create-react-app or pure SPA approaches is a choice to fight this battle manually. If SEO matters, pick a framework with SSR built in.
Tags: js-checker, fix JS rendering, SSR, prerender, hydration, Googlebot, JavaScript SEO, dynamic rendering, client-side rendering
Fix
Fix content invisible to AI agents that skip JavaScript. Server-side render, pre-render for bots, hybrid SSR/CSR. Ensure critical content in initial HTML.
curl raw HTML
curl -s https://example.com/your-page | grep -i "your-main-content-keyword" # Or save and inspect: curl -s https://example.com/your-page > raw.html wc -l raw.html grep -c " " raw.html # If content keyword absent and <p> count is tiny, page is JS-rendered
Check for SPA shell pattern
curl -s https://example.com/ | head -50 # Bad sign: <div id="root"></div> and nothing else # Good sign: full content visible in <main>, <article>, <section> # Astro npm create astro@latest # Next.js (SSG) export async function generateStaticParams() { const posts = await getPosts(); return posts.map(post => ({ slug: post.slug })); } # Hugo, Eleventy, Jekyll — all SSG // Next.js App Router — SSR by default export default async function Page({ params }) { const data = await fetchData(params.id); return <article>{data.content}</article>; } // Nuxt <script setup> const { data } = await useFetch(`/api/posts/${slug}`); </script> <template> <article>{{ data.content }}</article> </template> # Prerender.io middleware (Express) const prerender = require('prerender-node'); app.use(prerender.set('prerenderToken', 'YOUR_TOKEN')); # Or self-hosted with Rendertron # Nginx config detects bot UA and proxies to renderer location / { if ($http_user_agent ~* "GPTBot|ClaudeBot|PerplexityBot|Googlebot") { proxy_pass http://localhost:3001/render/$scheme://$host$request_uri; } try_files $uri /index.html; } // Next.js: SSR the public-facing pages, CSR the app // /blog/[slug] — server component (SSR) // /app/dashboard — client component (CSR) // Astro: islands architecture — SSR by default, hydrate only what needs JS <Header /> <!-- pure HTML --> <ArticleBody /> <!-- pure HTML --> <CommentSystem client:visible /> <!-- hydrates on view --> # 1. Install Next.js alongside existing app npx create-next-app@latest my-app-next # 2. Move pages to app/ directory as server components # Old: src/pages/About.jsx (CSR) # New: app/about/page.tsx (SSR) # 3. Convert client-only hooks/state # - useState, useEffect → require 'use client' directive # - Keep top-level page as server component # - Mark interactive subcomponents 'use client' # 4. Replace API calls # Old: fetch from useEffect # New: await fetch in async server component # 5. Verify with curl after deploy curl -s https://example.com/about | grep "Welcome to About" # Should show content in raw HTML
curl as agent
for ua in "GPTBot/1.0" "ClaudeBot/1.0" "Googlebot/2.1"; do echo "=== $ua ===" curl -s -A "$ua" https://example.com/your-page | \ grep -o "your main content phrase" done # Each should output the phrase. If empty, content still JS-only.
Lighthouse SEO score
Run Lighthouse → SEO category. "Document does not have a title" or "Page has no meta description" indicate JS-rendered metadata still failing.
Tip: 💡 If you can only migrate one page to SSR, pick your most-cited / most-trafficked content page. AI agents disproportionately fetch a small set of pages; rendering those server-side captures most of the visibility gain at minimum effort.
Tags: agent-compat, fix JS-only content, SSR for agents, server-side rendering, pre-render, agent content access, JavaScript SEO
Fix
Debug React/Next.js JS: bundle size analysis, INP optimisation, hydration errors, code splitting, third-party impact, error monitoring.
Analyse bundle size
Install @next/bundle-analyzer. Add to next.config.js. Build with ANALYZE=true npm run build. Visualises every chunk — find the biggest and ask: is it needed on this page? Common heavy chunks: moment.js (use date-fns or dayjs), lodash full vs lodash-es modular, large icon libraries shipping all icons.
Reduce 'use client' scope
Next.js App Router: every 'use client' component ships to the browser. Mark only what needs interactivity. Layouts, page wrappers, static content sections: leave as Server Components. Pages with mostly static content + small interactive islands: massive bundle reduction.
Code split heavy components
Charts, rich editors, video players, modal libraries: dynamic import. const Chart = dynamic(() => import('./Chart'), { ssr: false, loading: () => <Skeleton /> }). Loads only when needed.
Fix hydration errors
React console: 'Hydration failed' warnings. Each represents a server/client render mismatch. Common: Math.random() in render, Date during render, browser API access in render. Fix by moving non-deterministic logic into useEffect.
Optimise INP
Long tasks block interaction. Identify via DevTools Performance → record interaction → flame chart shows long tasks. Reduce: break long synchronous work into chunks (setTimeout with 0), use Web Workers for heavy compute, defer non-critical work.
Audit third-party scripts
Next/script with strategy='lazyOnload' for non-critical (analytics, chat, marketing). strategy='beforeInteractive' only for critical (auth, framework deps). Most stores have 5-15 third-party scripts; each can cost 50-200ms.
Add error monitoring
Sentry, LogRocket, Rollbar. Capture client errors, hydration errors, unhandled promises. Per-deploy release tracking. Real-user data far more useful than dev testing alone.
Tags: js-checker, React JS, Next.js bundle, Next.js INP, React errors, React code splitting
Fix
Debug Shopify JS: app conflicts, render-blocking scripts, INP debugging, theme JS organisation, cart drawer issues, console error tracking.
Open DevTools console
F12 → Console. Load homepage, collection, product, cart. Note all errors and warnings. Most Shopify stores have at least one JS error in console.
Identify error source
Each error shows the source file (typically from cdn.shopify.com for theme JS or extensions.shopifycdn.com for app JS). Trace back which app or theme section.
Disable apps to isolate
Shopify's dev store / staging: disable apps one-by-one, retest. Find conflict. Most JS errors come from 1-2 specific apps interacting badly with theme.
Defer analytics and tracking JS
Most analytics (Google Analytics, Facebook Pixel, TikTok) can defer without breaking. Theme.liquid: add 'defer' attribute to script tags or move to footer. Test purchase flow end-to-end after deferring.
Audit theme
js. Online Store → Themes → Edit code → assets/theme.js (or global.js in Dawn). Look for known patterns: deprecated jQuery, conflicts with apps. Update theme if outdated.
Fix cart drawer conflicts
Most common Shopify JS issue. Cart drawer apps (UpCart, Rebuy, Slide Cart) replace the theme's default cart drawer. Theme JS still tries to control cart state. Result: errors or unexpected behaviour. Fix: pick one — theme cart drawer OR cart drawer app, not both.
Add error tracking
Sentry, Rollbar, or LogRocket. Track JS errors from real users. Many errors only occur on specific browsers or in specific user flows.
Tags: js-checker, Shopify JavaScript, Shopify app conflicts, Shopify INP, Shopify cart drawer, Shopify theme JS
Fix
Fix JavaScript syntax errors from the JS Checker. ESLint, build pipeline errors, browser console syntax issues, transpile target alignment.
Run the JS Checker
Run the JS Checker on your domain. Filter to syntax errors. Each finding shows: source URL, error message, file name, line number, affected browser/version if available.
Cross-reference with error monitoring
If you run Sentry, LogRocket or similar, cross-reference syntax errors against real-user sessions. You'll see which browsers and OS versions actually hit them. Often shows older iOS Safari, Samsung Internet, or legacy desktop browsers. npm install --save-dev eslint @eslint/js // Modern flat config: eslint.config.js import js from '@eslint/js'; export default [ js.configs.recommended, { rules: { // Common rules to add 'no-unused-vars': 'warn', 'no-undef': 'error', } } ]; // Run npx eslint src/ // babel.config.js module.exports = { presets: [ ['@babel/preset-env', { // Reads .browserslistrc automatically useBuiltIns: 'usage', corejs: 3 }] ] }; // .swcrc { "jsc": { "target": "es2017" // or use .browserslistrc } } # .browserslistrc > 0.5% last 2 versions not dead // SyntaxError: Unexpected token function process(items { // missing ) return items.map(i => i * 2); } // Fix: complete the bracket function process(items) { return items.map(i => i * 2); } // Works in modern browsers, syntax error in Safari < 13.1 const name = user?.profile?.name; // Fix: transpile via Babel/SWC OR use logical && const name = user && user.profile && user.profile.name; // Module scope only const data = await fetchData(); // SyntaxError if not in module // Fix: wrap in async IIFE (async () => { const data = await fetchData(); })(); // Modern syntax — needs transpilation for older browsers class User { #password; setPassword(p) { this.#password = p; } } // Babel handles it. Verify your build targets the right browsers. // ES2017+ — older browsers error fn(a, b, c,); // Fix: transpile OR remove trailing comma fn(a, b, c); # GitHub Actions name: Lint on: [pull_request] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20 } - run: npm ci - run: npx eslint src/ --max-warnings 0 // Sentry browser SDK import * as Sentry from '@sentry/browser'; Sentry.init({ dsn: 'YOUR_DSN', environment: 'production', });
Build with verbose output
Run your build with verbose flag. Verify Babel/SWC is processing your source files and the output JS doesn't contain unsupported syntax for your target browsers.
Test on target browsers
BrowserStack or Sauce Labs lets you test on real older browsers. Load your built page on Safari 14, iOS 14, etc. Check console for syntax errors.
Tip: 💡 The cheapest way to prevent syntax errors is keeping your Browserslist target current and rebuilding when caniuse-lite updates. Old transpile targets ship outdated polyfills; the newest browsers might have new features that need adjusted targets.
Tags: js-checker, fix JS syntax errors, ESLint, JavaScript SyntaxError, transpile errors, Babel, build errors, modern JS syntax, browser compatibility
Fix
Fix WordPress JavaScript issues: plugin conflicts, render-blocking JS, jQuery dependencies, INP debugging, defer/async patterns, error tracking.
Step 1: Open DevTools console
F12 → Console tab. Load 5 representative URLs. Note all errors and warnings. Most WordPress sites have at least one JS error in console even on the homepage.
Step 2: Identify error source
Each console error shows the source file. Trace back: which plugin or theme owns that file? View source: search for the file path.
Step 3: Disable plugins one-by-one
On staging copy, disable plugins individually and re-test. Find the conflict. Usually one plugin's JS calls a function that another removed or shadowed.
Step 4: Defer non-critical JS
WP Rocket → File Optimization → Load JavaScript deferred. Or Async JavaScript plugin. Move analytics, chat widgets, ad scripts to defer. Don't defer scripts needed before user interaction (Gutenberg editor scripts, payment processor scripts on checkout).
Step 5: Audit jQuery dependencies
WordPress bundles jQuery 3.x since WP 5.5. Old plugins/themes calling deprecated jQuery patterns ($('foo').load()) will fail. Update plugins; if can't, install jQuery Migrate Helper (was bundled until 5.5, now optional).
Step 6: Optimise heavy plugin JS
Page builders (Elementor, Divi) and ad managers add significant JS. Use Asset CleanUp or Perfmatters to dequeue these scripts on pages where they're not used. Major INP improvements.
Step 7: Add error tracking
Sentry, Rollbar, or LogRocket. Install via plugin or manual JS snippet. Track JS errors from real users — many errors only occur in specific browsers or for logged-in users.
Tags: js-checker, WordPress JavaScript, WordPress JS conflicts, WordPress jQuery, defer JavaScript WordPress, WordPress INP
Fix
Implement JSON-LD in Next.js: Server Components render, metadata API limitations, dynamic schema from data, validation, common errors.
Render JSON-LD from Server Components
In page.tsx or layout.tsx: const jsonLd = {...}; return (<><script type='application/ld+json' dangerouslySetInnerHTML={{__html: JSON.stringify(jsonLd)}} />...</>). Renders server-side, ships in initial HTML — perfect for crawlers.
Build dynamic schema from data
Fetch data in Server Component → build JSON-LD object using fetched data → render. For an Article page: const article = await getArticle(slug); const ld = { '@context': 'https://schema.org', '@type': 'Article', headline: article.title, ... }.
Centralise schema builders
src/lib/schema.ts: functions like buildArticleSchema(article), buildOrganizationSchema(), buildBreadcrumbSchema(crumbs). Reuse across pages. Type-safe with TypeScript.
Add to layout for site-wide schema
Organization, WebSite belong in root layout.tsx — appears on every page. Article, Product, FAQPage belong on respective pages.
Validate
Rich Results Test on representative URLs. Search Console Enhancements after deploy. Both should be clean.
Fix common Next
js JSON-LD errors. Common: not stringifying object (passing object directly to dangerouslySetInnerHTML — needs JSON.stringify). Missing @context. Wrong @type. Date fields not in ISO 8601. Audit each via Rich Results Test errors.
Monitor
Search Console weekly. Schema updates affect rich result eligibility — catch regressions fast.
Tags: json-checker, Next.js JSON-LD, Next.js schema, Next.js structured data, Next.js Article schema, Next.js metadata
Fix
Debug Shopify JSON-LD: duplicate schemas from theme + apps, missing required fields, validator errors, theme code edits, Search Console enhancements.
Audit JSON-LD output
View source on product page. Search 'application/ld+json'. Count blocks. Most Shopify stores output 2-4 blocks: theme default + 1-2 app additions.
Run Rich Results Test
Each page type: homepage, collection, product, blog post. Note detected schemas, errors, warnings.
Resolve theme + app duplicates
If theme outputs Product and an app also outputs Product, you have duplicates. Pick authority: usually app (more flexible). Disable theme's product schema by editing product template (remove existing JSON-LD block from product.liquid) or disable in app settings.
Fix missing required fields
Shopify theme Product schema sometimes misses brand (theme reads vendor field for brand — empty for some products). Audit and populate vendor field on all products. Or edit theme to use product type as fallback.
Fix Organization schema
Theme outputs Organization but often without sameAs (social profile links) or logo. Edit theme.liquid: add complete Organization JSON-LD in head with required fields.
Validate after every plugin update
Apps that modify schema update frequently. Each update can introduce errors. After any schema-affecting app update, re-run Rich Results Test on representative pages.
Monitor Search Console
Enhancements → Product, FAQPage, etc. Errors appear within days of schema regressions.
Tags: json-checker, Shopify JSON-LD, Shopify schema, Shopify structured data, Shopify schema conflicts, Shopify schema errors
Fix
Debug WordPress JSON-LD output: plugin conflicts producing duplicate schemas, invalid syntax, missing required fields, @id collisions, validator workflow.
Step 1: Audit current JSON-LD output
View source on a published post. Search for 'application/ld+json'. Count blocks (should be 1-3 maximum). List @type values present.
Step 2: Run Rich Results Test
search.google.com/test/rich-results. Enter URL. Note: detected schemas, errors, warnings. Most WordPress sites have 1-3 schema types detected and 0-5 warnings.
Step 3: Identify plugin conflicts
If you have multiple SEO/schema plugins (Yoast + Schema Pro, Rank Math + AIOSEO), they're likely both outputting overlapping schemas. Choose one as the schema authority and disable schema output in the others (each plugin has a toggle).
Step 4: Fix invalid syntax
Validator errors like 'invalid JSON' usually mean: HTML inside JSON-LD (extra , unencoded quotes), wrong @type values, or missing required fields. Locate the source plugin and either configure it correctly or replace.
Step 5: Resolve @id collisions
When multiple plugins output schemas with the same @id, search engines reject them. Fix: pick one plugin, disable others' schema output. Or set unique @id prefixes per plugin if you must run both.
Step 6: Add missing required fields
Each schema type has required fields. Article needs: headline, image, datePublished, author. Product needs: name, image, offers. Recipe needs: name, image, recipeIngredient, recipeInstructions. Validate per-type via Schema.org docs.
Step 7: Monitor in Search Console
Search Console → Enhancements → per schema type. Errors and warnings reported there. Watch for new errors after plugin updates.
Tags: json-checker, WordPress JSON-LD, WordPress structured data, JSON-LD validation, schema plugin conflicts, WordPress schema errors
Fix
Fix JSON-LD syntax errors from the JSON Checker. Validate every block, escape quotes correctly, lint at build time, prevent template-injection bugs.
Run the JSON Checker
Run the JSON Checker . Each finding shows: URL, error message, the line/column position of the failure, and a snippet of the surrounding JSON.
Cross-check with Schema.org Validator
validator.schema.org and Google's Rich Results Test both highlight JSON syntax errors before semantic checks. The error messages from these tools are typically clearer than the JSON Checker's.
Pattern-match the errors
Most syntax errors cluster around a few patterns: Unescaped double quotes in string values Trailing commas Newlines inside string values Single quotes instead of double Closing </script> within a string value Fix the pattern in the template; many pages get fixed at once. <!-- BROKEN: title contains double quote --> <script type="application/ld+json"> { "@type": "Article", "headline": "Apple's "iPhone" announcement" } </script> <!-- FIXED: escape inner quotes --> <script type="application/ld+json"> { "@type": "Article", "headline": "Apple's \"iPhone\" announcement" } </script> "headline": "Apple's “iPhone” announcement" <!-- BROKEN --> <script type="application/ld+json"> { "description": "Read about </script> tag use in HTML" } </script> <!-- FIXED — escape the forward slash --> <script type="application/ld+json"> { "description": "Read about <\/script> tag use in HTML" } </script> JSON.stringify(data).replace(/<\/script/gi, '<\\/script') <!-- Liquid template — fragile --> <script type="application/ld+json"> { "@type": "Product", "name": "{{ product.title }}", "description": "{{ product.description }}" } </script> <!-- If product.title contains a quote, this breaks --> <!-- Build the object in code, serialise once --> <script type="application/ld+json"> {{ product_schema | json }} </script> # In your controller / data layer: product_schema = { '@context': 'https://schema.org', '@type': 'Product', 'name': product.title, 'description': product.description } function ProductPage({ product }) { const schema = { '@context': 'https://schema.org', '@type': 'Product', name: product.title, description: product.description, image: product.images[0], offers: { '@type': 'Offer', price: product.price, priceCurrency: 'USD', availability: product.inStock ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock' } }; return ( <> <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schema).replace(/<\/script/g, '<\\/script') }} /> <ProductContent product={product} /> </> ); } # scripts/validate-json-ld.js const fs = require('fs'); const path = require('path'); const cheerio = require('cheerio'); const errors = []; const htmlFiles = require('glob').sync('dist/**/*.html'); for (const file of htmlFiles) { const $ = cheerio.load(fs.readFileSync(file, 'utf8')); $('script[type="application/ld+json"]').each((i, el) => { try { JSON.parse($(el).html()); } catch (e) { errors.push(`${file}: ${e.message}`); } }); } if (errors.length) { console.error('Invalid JSON-LD found:'); errors.forEach(e => console.error(' ', e)); process.exit(1); } console.log('All JSON-LD valid');
Tip: 💡 Treat JSON-LD as data, not text. Build a typed model (or schema-DTS in TypeScript), populate it from your CMS data, serialise once at the boundary. Most JSON syntax errors disappear because the serialiser handles edge cases correctly.
Tags: json-checker, fix JSON-LD syntax, structured data errors, schema markup syntax, JSON validator, escape quotes JSON-LD, schema.org validation
Fix
Fix JSON-LD coverage gaps from the Agent Readiness audit. Per page-type schema, entity closure with @graph, the schema types AI agents extract reliably.
Run Agent Readiness
Lists schema coverage per template/section. "No schema on article pages" or "Missing Organization on homepage" surface here.
Run Schema Debugger on representative pages
Per-page schema validation. Catches incomplete schema, hallucinated properties, broken @id references. // BAD { "@type": "Article", "author": "Jane Doe" } // GOOD { "@type": "Article", "author": { "@type": "Person", "name": "Jane Doe", "url": "https://example.com/authors/jane-doe", "sameAs": ["https://twitter.com/janedoe"] } } // BAD: Article with no publisher { "@type": "Article", "headline": "..." } // GOOD: Publisher reference { "@type": "Article", "publisher": { "@type": "Organization", "name": "Example", "logo": { "@type": "ImageObject", "url": "..." } } }
Schema Debugger validation
Each schema type validates against schema.org and Google rich results.
Re-run Agent Readiness
Coverage score improves. Per-page-type schema present. Entity graph closed.
1. Per page-type schema
Articles, blog posts, news { "@context": "https://schema.org", "@type": "Article", "headline": "Article title", "description": "Brief description", "image": "https://example.com/cover.jpg", "datePublished": "2024-01-15T08:00:00+00:00", "dateModified": "2024-01-20T10:00:00+00:00", "author": { "@type": "Person", "name": "Author Name", "url": "https://example.com/authors/author-name" }, "publisher": { "@type": "Organization", "name": "Example Publisher", "logo": { "@type": "ImageObject", "url": "https://example.com/logo.png" } } } Products { "@context": "https://schema.org", "@type": "Product", "name": "Product Name", "image": ["https://example.com/product.jpg"], "description": "Product description", "sku": "SKU-12345", "brand": { "@type": "Brand", "name": "Brand Name" }, "offers": { "@type": "Offer", "url": "https://example.com/product", "priceCurrency": "GBP", "price": "29.99", "availability": "https://schema.org/InStock", "itemCondition": "https://schema.org/NewCondition" }, "aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.5", "reviewCount": "127" } } Local business { "@context": "https://schema.org", "@type": "LocalBusiness", "name": "Business Name", "image": "https://example.com/storefront.jpg", "address": { "@type": "PostalAddress", "streetAddress": "123 High Street", "addressLocality": "London", "postalCode": "SW1A 1AA", "addressCountry": "GB" }, "geo": { "@type": "GeoCoordinates", "latitude": 51.5074, "longitude": -0.1278 }, "telephone": "+44-20-7946-0958", "openingHoursSpecification": [{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"], "opens": "09:00", "closes": "18:00" }] } FAQ pages { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [ { "@type": "Question", "name": "What does X do?", "acceptedAnswer": { "@type": "Answer", "text": "X does this and that." } }, { "@type": "Question", "name": "How does it work?", "acceptedAnswer": { "@type": "Answer", "text": "Step-by-step answer." } } ] } How-to content { "@context": "https://schema.org", "@type": "HowTo", "name": "How to do X", "description": "Step-by-step guide to X.", "totalTime": "PT30M", "step": [ { "@type": "HowToStep", "name": "First step", "text": "Description of step 1" }, { "@type": "HowToStep", "name": "Second step", "text": "Description of step 2" } ] }
2. Close the entity graph with @graph
Multiple entities on one page should be linked via @id references. This forms a connected graph that AI engines can traverse. { "@context": "https://schema.org", "@graph": [ { "@type": "Article", "@id": "https://example.com/article#article", "headline": "Article Title", "datePublished": "2024-01-15", "author": { "@id": "https://example.com/article#author" }, "publisher": { "@id": "https://example.com/#org" } }, { "@type": "Person", "@id": "https://example.com/article#author", "name": "Jane Doe", "url": "https://example.com/authors/jane-doe", "worksFor": { "@id": "https://example.com/#org" }, "sameAs": [ "https://twitter.com/janedoe", "https://linkedin.com/in/janedoe" ] }, { "@type": "Organization", "@id": "https://example.com/#org", "name": "Example Publishing", "url": "https://example.com", "logo": "https://example.com/logo.png", "sameAs": [ "https://en.wikipedia.org/wiki/Example_Publishing", "https://www.wikidata.org/wiki/Q12345", "https://twitter.com/examplepub", "https://linkedin.com/company/example-publishing" ] }, { "@type": "BreadcrumbList", "itemListElement": [ {"@type":"ListItem","position":1,"name":"Home","item":"https://example.com/"}, {"@type":"ListItem","position":2,"name":"Blog","item":"https://example.com/blog"}, {"@type":"ListItem","position":3,"name":"Article","item":"https://example.com/article"} ] } ] } Article references Person (author) by @id. Person references Organization (worksFor) by @id. The graph closes — every entity connects to others. AI engines extract richer context from this than from a flat Article.
3. Organization sameAs for entity disambiguation
sameAs tells AI engines "this Organization on my site is the same as this entity on Wikipedia / Wikidata / social". Helps disambiguate against companies with similar names. { "@type": "Organization", "name": "Apple", "sameAs": [ "https://en.wikipedia.org/wiki/Apple_Inc.", "https://www.wikidata.org/wiki/Q312", "https://twitter.com/Apple", "https://www.linkedin.com/company/apple/" ] } // Disambiguates Apple Inc from Apple Records, Apple Bank, Apple fruit, etc
4. Audit coverage
Step 1 Run Agent Readiness Lists schema coverage per template/section. "No schema on article pages" or "Missing Organization on homepage" surface here. Step 2 Run Schema Debugger on representative pages Per-page schema validation. Catches incomplete schema, hallucinated properties, broken @id references.
5. Common gaps
Gap: No author Person, just a string // BAD { "@type": "Article", "author": "Jane Doe" } // GOOD { "@type": "Article", "author": { "@type": "Person", "name": "Jane Doe", "url": "https://example.com/authors/jane-doe", "sameAs": ["https://twitter.com/janedoe"] } } Gap: No publisher Organization // BAD: Article with no publisher { "@type": "Article", "headline": "..." } // GOOD: Publisher reference { "@type": "Article", "publisher": { "@type": "Organization", "name": "Example", "logo": { "@type": "ImageObject", "url": "..." } } } Gap: No Organization sameAs Organization without sameAs is an unverified entity. AI can't confirm "this is the actual company". sameAs to Wikipedia/Wikidata is the strongest signal. Gap: BreadcrumbList missing Add to every page. Helps both Google rich results and AI understanding of site structure.
6. Coverage targets
Aim for full coverage across major page types: Homepage → WebSite + Organization About → Organization + Person entries for key staff Article/blog → Article + Person (author) + Organization (publisher) + BreadcrumbList Product → Product + Offer + AggregateRating + BreadcrumbList FAQ-style content → FAQPage How-to content → HowTo Local pages → LocalBusiness Author archive → Person + ItemList of authored Articles
7. Verify
Step 1 Schema Debugger validation Each schema type validates against schema.org and Google rich results. Step 2 Re-run Agent Readiness Coverage score improves. Per-page-type schema present. Entity graph closed. <div
Tip: 💡 The single most impactful addition for AI visibility: Organization with sameAs to Wikipedia/Wikidata, referenced from every page via @graph. That single link gives AI engines a verified entity to attribute claims to. Without it, your brand name is just a string they hope refers to you.
Tags: agent-readiness, JSON-LD coverage, schema gaps, entity graph, Organization Person schema, @graph closure, AI schema extraction
Fix
Fix keyboard navigation failures from the Accessibility audit. Restore tab order, eliminate focus traps, add skip links, handle custom widgets.
Put the mouse away
Open your site. Use Tab/Shift+Tab/Enter/Space/Arrows only. Try to perform every common task: navigate menus, complete a purchase, submit a form, dismiss a notification.
Note every failure
Watch for: elements you can't reach by Tab, controls that don't respond to Enter/Space, focus jumping unpredictably, focus disappearing after an action, modals that won't close with Escape, dropdowns that won't open with keyboard. <!-- WRONG --> <div onclick="openMenu()">Menu</div> <!-- RIGHT --> <button onclick="openMenu()">Menu</button> <!-- IF YOU MUST use a div --> <div role="button" tabindex="0" onclick="openMenu()" onkeydown="if(event.key === 'Enter' || event.key === ' ') openMenu()"> Menu </div> <!-- WRONG — puts these at the START of tab order before everything else --> <input tabindex="1"> <input tabindex="2"> <input tabindex="3"> <!-- RIGHT — natural DOM order --> <input> <input> <input> // Correct modal focus management function openModal(modal) { const previouslyFocused = document.activeElement; // Move focus into modal modal.querySelector('h2').focus(); // Trap Tab within modal modal.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(modal, previouslyFocused); if (e.key === 'Tab') { const focusables = modal.querySelectorAll('a, button, input, [tabindex="0"]'); const first = focusables[0]; const last = focusables[focusables.length - 1]; if (e.shiftKey && document.activeElement === first) { last.focus(); e.preventDefault(); } else if (!e.shiftKey && document.activeElement === last) { first.focus(); e.preventDefault(); } } }); } function closeModal(modal, previouslyFocused) { modal.setAttribute('hidden', ''); previouslyFocused.focus(); // restore focus to opener } <body> <a href="#main" class="skip-link">Skip to main content</a> <header>...nav...</header> <main id="main" tabindex="-1">...content...</main> </body> <style> .skip-link { position: absolute; top: -100px; left: 8px; background: #1e3a8a; color: white; padding: 12px 16px; z-index: 1000; text-decoration: none; } .skip-link:focus { top: 8px; } </style>
Manual keyboard test
Tab through every page completing every workflow. Mouse-free.
Automated tests with Playwright
test('can navigate menu with keyboard', async ({ page }) => { await page.goto('/'); await page.keyboard.press('Tab'); // skip link await page.keyboard.press('Tab'); // logo await page.keyboard.press('Tab'); // first nav link await page.keyboard.press('Enter'); await expect(page).toHaveURL(/about/); });
Tip: 💡 The HTML <dialog> element with showModal() handles focus trap automatically. Use it when browser support allows. Saves implementing the trap logic yourself.
Tags: accessibility, fix keyboard navigation, tab order, focus trap, skip links, tabindex, keyboard accessibility, modal focus management, WCAG 2.1.1
Fix
Stop targeting keywords too hard for your site’s authority. Find achievable long-tail variants and build authority over time.
Run the Keywords audit
Open the Keywords Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Keywords audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: keywords, keyword, difficulty
Fix
Identify and close keyword gaps where competitors rank top-10 and you don’t: prioritise by volume and difficulty.
Run the Keywords audit
Open the Keywords Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Keywords audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: keywords, keyword, gaps
Fix
Use keywords effectively in Shopify: product titles, meta strategy, collection naming, tag taxonomy, blog SEO, avoiding cannibalisation.
Research per-product target keyword
Each top product needs one primary keyword. Use Search Console 'Performance' to find queries you rank for but not in top 10. Pair with keyword tools (Ahrefs, Semrush, Mangools) for search volume and difficulty.
Configure SEO title and description
Each product: SEO title (60 chars max) and Meta description (160 chars max) in the product editor. Don't leave at default — defaults are weak. Include focus keyword, brand, value proposition.
Optimise product title
Product title in Shopify drives both the H1 and the SEO title (unless overridden). Front-load primary keyword: 'Red Leather Chelsea Boot — Acme Boots' not 'Acme Boots — Style 1234'.
Write unique product descriptions
First 100 words: what it is, who it's for, key benefit. Then expand with features, specs, materials. Never use unmodified supplier descriptions — Google penalises duplicate content across stores.
Structure collection pages around keywords
Collection title = category keyword ('Women's Leather Boots'). Intro paragraph above products explains the category with keyword variations. Collection meta description targets the search query.
Use tags for taxonomy not keywords
Tags in Shopify create filter facets, not direct SEO benefit. Use tags for product organisation; don't keyword-stuff tags.
Monitor for cannibalisation
Multiple products targeting the same primary keyword cannibalise rankings. Search Console Performance → filter by query → see if multiple URLs compete. Consolidate: rewrite secondary products to target related but distinct keywords.
Tags: keywords, Shopify keywords, Shopify product SEO, Shopify keyword research, Shopify SEO tags, Shopify meta optimisation
Fix
Use keywords effectively in WordPress: title strategy, heading hierarchy, placement, plugin tools (Yoast, Rank Math), avoiding over-optimisation.
Step 1: Decide focus keyword per post
Pick one primary keyword per page. Use Search Console 'Performance' to find queries you already rank for but not in top 10 — these are easiest wins. Avoid targeting the same keyword across multiple pages (cannibalisation).
Step 2: Set focus keyword in SEO plugin
Yoast → Focus keyphrase field. Rank Math → Focus keyword. AIOSEO → SEO Title and Description. The plugin then audits the page against the keyword.
Step 3: Place keyword in critical positions
Title tag (first 60 chars). H1 (page heading). First paragraph (within 150 words). One or two H2 subheadings. URL slug (use the slug field). Avoid forcing it; prioritise readability.
Step 4: Expand semantic coverage
Use related queries from Google's 'People also ask' and 'Related searches'. Cover synonyms, related concepts, and natural variations. Modern algorithms reward topical depth, not keyword repetition.
Step 5: Optimise meta description
Include focus keyword once in the meta description, but write for click-through (compelling, accurate, specific). Meta description doesn't directly affect ranking but affects CTR which does.
Step 6: Internal link by topic cluster
Link to related posts using descriptive anchor text. WordPress plugins like Yoast Premium or Link Whisper suggest internal links based on content analysis. Build topic clusters: pillar page links to subtopic pages and vice versa.
Step 7: Monitor and iterate
Search Console Performance report weekly. Watch position changes for your focus keyword and related queries. If rankings plateau, expand content depth; if rankings drop, audit for over-optimisation or thin content.
Tags: keywords, WordPress keywords, Yoast focus keyword, Rank Math keyword, WordPress SEO content, keyword research WordPress
Fix
Set the correct <html lang> attribute for accessibility, screen readers and search engines.
Run the Meta Analyzer audit
Open the Meta Analyzer Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Meta Analyzer audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: meta-analyzer, lang, attribute
Fix
Document the lawful basis for each data-processing activity (consent, contract, legitimate interest) per GDPR Article 6.
Run the GDPR audit
Open the GDPR Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your GDPR audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gdpr, lawful, basis
Fix
Fix CSS-caused layout shift from the CSS Checker. Set image dimensions, reserve space for fonts and ads, use aspect-ratio, hit CLS under 0.1.
Lab measurement: Lighthouse
DevTools → Lighthouse → Generate report. CLS appears in the Core Web Vitals section. Lab measurements catch obvious shifts but miss user-interaction-triggered ones.
Field measurement: Search Console
Search Console → Core Web Vitals report. Real-user 75th-percentile CLS from Chrome User Experience Report. Field data reflects actual user devices, networks, scroll behaviour — lab data doesn't.
Visualise shifts in DevTools
DevTools → Performance tab → record a page load. The Experience track shows layout shift events with rectangles highlighting what moved and when. <!-- WRONG — zero-size until load --> <img src="/hero.jpg"> <!-- RIGHT — slot reserved before download --> <img src="/hero.jpg" width="1200" height="630" alt="..."> <img src="/hero.jpg" width="1200" height="630" style="width:100%; height:auto" alt="..."> .media-card { aspect-ratio: 16 / 9; background: var(--bg3); /* placeholder colour */ } .media-card img { width: 100%; height: 100%; object-fit: cover; } @font-face { font-family: 'Inter'; src: url('/fonts/inter.woff2') format('woff2'); font-display: swap; size-adjust: 107%; /* match fallback metric */ ascent-override: 90%; descent-override: 22%; line-gap-override: 0%; } @font-face { /* ... */ font-display: optional; } /* Ad slot — reserve 300x250 even before ad loads */ .ad-slot-300x250 { display: block; width: 300px; height: 250px; background: var(--bg3); /* placeholder */ } /* Responsive ad slot with aspect-ratio */ .ad-responsive { aspect-ratio: 6 / 5; background: var(--bg3); } .ad-slot { min-height: 250px; display: flex; align-items: center; justify-content: center; } .cookie-banner { position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; } .product-grid-loading { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; } .product-grid-loading::before { content: ''; aspect-ratio: 3 / 4; background: var(--bg3); border-radius: 8px; } <img src="/photo.jpg" width="800" height="600" loading="lazy" alt="..."> /* Triggers layout shift */ .element { animation: slide 0.3s; } @keyframes slide { from { left: -100px; } to { left: 0; } } /* No layout shift */ .element { animation: slide 0.3s; } @keyframes slide { from { transform: translateX(-100px); } to { transform: translateX(0); } }
Lab re-measure
Run Lighthouse again. CLS should drop significantly. Aim for under 0.1.
Wait for field data
Search Console Core Web Vitals updates every 28 days based on real-user data. After 28 days of fixed code, the field CLS should match the lab improvement.
Tip: 💡 CLS fixes are usually quick wins. A few hours adding width/height to images and font-display fixes can drop site-wide CLS by 50-80%. Few performance interventions have this return on time.
Tags: css-checker, fix layout shift CSS, CLS, cumulative layout shift, image dimensions, font swap shift, aspect ratio, skeleton screen, web font shift
Fix
Improve LCP: identify the LCP element, preload it, reduce TTFB, eliminate render-blocking resources.
Run the Core Web Vitals audit
Open the Core Web Vitals Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Core Web Vitals audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cwv, lcp
Fix
Build topic clusters with hub-and-spoke internal linking so pillar pages get equity from related supporting content.
Run the Internal Links audit
Open the Internal Links Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Internal Links audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: internal-links, link, clusters
Fix
Improve internal link equity flow so ranking signal concentrates on high-value pages, not buried deep ones.
Run the Internal Links audit
Open the Internal Links Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Internal Links audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: internal-links, link, equity, flow
Fix
Close link gaps where competitors have backlinks from sites that don’t link to you: outreach, content and PR.
Run the Competitor Analysis audit
Open the Competitor Analysis Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Competitor Analysis audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: competitor-analysis, link, gaps
Fix
Reclaim links from existing brand mentions. Identify unlinked mentions, prioritise by domain authority, outreach for link insertion. Highest ROI link building.
1. Identify unlinked mentions
The Brand Mention Monitor surfaces mentions across the web. Cross-reference with your backlink data (Search Console, Ahrefs, Majestic) to identify mentions WITHOUT corresponding backlinks: Workflow: 1. Export all brand mentions from last 90-180 days 2. Export all backlinks pointing to your site 3. For each mention's source URL, check if it's in the backlink set 4. Mentions not in backlink set = unlinked mentions 5. Filter out: - Social media (links work differently; rarely indexed for SEO) - Sites with rel=nofollow policies (Reddit, Wikipedia citations) - Sites that link competitors but rarely include outbound links generally (their editorial policy) - PDF / image-only mentions (can't add links anyway) What's left: unlinked text mentions on sites that DO publish external links editorially. These are your prospects.
2. Prioritise by domain authority
Not all links worth the same outreach effort. A link from DA 80 publication is worth 50 links from DA 20 blogs. Sort prospects by domain quality: Tier DA / authority Outreach approach 1 — Top priority DA 70+ major publications, recognised industry sites Personalised, multiple touchpoints if needed, sometimes phone follow-up 2 — High priority DA 40-70 mid-size publications, established blogs Personalised email outreach 3 — Standard DA 20-40 smaller blogs, niche sites Personalised email with templated frame 4 — Low priority DA under 20 small blogs, new sites Batch email outreach OR skip entirely depending on ROI Skip Spam / link farms / low-trust sites Don't pursue — you don't want links from these anyway For most B2B sites, focus quarterly outreach on top 20-40 prospects rather than spreading across hundreds.
3. Find the right contact
The wrong contact = wasted outreach. Generic contact@ emails rarely reach editorial. Right contact varies by site type: Major publications (Forbes, TechCrunch, industry trade) Target: The author of the specific piece Find: Author byline → LinkedIn → professional email Pattern: [first].[last]@publication.com is common Tools: Hunter.io, Apollo, ContactOut to verify email Backup: Editorial email IF the post is older and author may have moved on Authors care about article accuracy; appeal to that. Mid-size blogs / sites Target: Author OR editor depending on structure Find: About page, contact page, LinkedIn outreach Pattern: Often [name]@domain.com or specific editorial address Tools: hunter.io for domain pattern discovery If unclear, send to general contact with subject line specific enough to route correctly: "Editorial: small correction request for [article title]" Niche / small blogs Target: Site owner / operator (often a single person) Find: About page, footer contact, social media Pattern: Personal email or contact form Smaller sites often respond better than large ones — single decision maker, personal investment in their content, faster cycle time. Trade publications / industry sites Target: Specific section editor or industry-specific journalist Find: Masthead, contributor pages Pattern: Often individual emails of staff/contributors Trade pubs often have editorial calendars and welcome corrections — the angle "your article would be more useful with this link" works.
4. Outreach templates
Template 1: Author of recent article (Tier 1-2) Subject: Quick correction request — your piece on [topic] Hi [first name], I really enjoyed your article "[exact title]" on [publication] — the section on [specific point they made] was especially useful, and I shared it with our team. Quick note: you mentioned us ([your company]) in this paragraph: "[exact paragraph or sentence containing your mention]" Wondered if it would be possible to link our name to [specific URL]? The [specific page] page has more context on what we do that your readers might find useful. Either way, thanks for the thoughtful piece. Best, [Your name] [Your role at company] [Direct contact if appropriate] --- Key elements: - Specific mention of their article (proves you read it) - Specific point they made (genuine engagement, not flattery) - Quote the exact paragraph (shows you're attentive, makes their job easy) - Specific URL suggestion (don't make them choose) - Light, no pressure Template 2: Older article / editorial team (Tier 2-3) Subject: Small correction for [article title] Hi there, I came across your article "[exact title]" while researching [related topic] — it's still the best overview I've found of [specific theme]. I noticed you mentioned [your company] in the section about [topic]: "[exact paragraph]" Wondered if you'd be open to adding a link to our [specific page]? It would help readers find the [specific information they'd want] you reference. Happy to provide any additional context if useful. Thanks for considering, [Your name] [Your role] --- For older articles where the original author may not be available, this works better than reaching out to author specifically. Editorial team can make the update. Template 3: Niche blog / personal site (Tier 3) Subject: Loved your [topic] post Hi [first name if known], Came across your post on [specific topic] and have to say it's one of the best treatments of [specific aspect] I've seen. I noticed you mentioned [your company] briefly. Would you mind adding a link to [URL] when you have a chance? It would help your readers find [specific value they'd get]. Either way — thanks for writing such a useful piece. Mind if I share it in our newsletter? [Your name] --- Smaller sites respond to genuine appreciation. The "mind if I share" offer adds value back. Don't make promises you won't keep on the sharing offer. What to avoid Generic templates — "I noticed your great article" with no specifics reads as bot Multiple link requests — one ask per email; don't try to get 5 links in one ask Aggressive follow-up — one polite follow-up after 10-14 days; no further Reciprocal link demands — "we'll link to you if..." is link scheme territory Payment offers — paid editorial links are against guidelines Fake personas — "Sarah from outreach team" when there's no Sarah; identifiable as fake Mass-personalisation tools that look mass — better fewer truly personal emails than 200 templated
5. Outreach workflow at scale
For 30-50 prospects per quarter: Week 1: Research and personalisation - Read each article thoroughly - Identify the specific paragraph mentioning your brand - Identify what specific URL you'd want linked - Find author / editor contact - Verify email deliverability Week 2-3: Outreach - Send 5-10 emails per day, personalised - Track sends in spreadsheet / CRM - Note any auto-responses, bounces Week 4-5: Follow-ups - One polite follow-up after 10-14 days for non-responders - Update tracker with responses Week 6+: Track results - Which links went live - Which had partial wins (different URL than requested) - Conversion rate per tier - Patterns: which template variants worked better
6. Track conversion and improve
Per outreach campaign: - Prospects identified - Emails sent - Responses received (positive, negative, no-response) - Links added (per response) - Conversion rate (links / sent) - Tier-specific conversion rates Pattern recognition over multiple campaigns: - Best-converting subject lines - Best-converting opening lines - Best-converting closing requests - Best-converting time-of-day / day-of-week - Which industries / publication types respond best A/B test subject lines, opening sentences, specific asks across campaigns. After 3-4 campaigns you'll know what works for your specific category/audience.
7. Maintain link health after acquisition
Reclaimed links can be lost again — site redesigns, content updates, editorial purges. Monthly link monitoring catches losses early: Track all reclaimed links in a dedicated list Monthly check: are they still live and pointing to correct URL? For lost links: re-contact the publication briefly For redirect chains: ask for direct URL update For URL changes on your side: send updated URL proactively See lost backlinks for the recovery workflow when reclaimed links go missing.
8. Connection to AEO
Reclaimed links carry both classical SEO weight AND AI engine visibility benefits: AI engines crawl linked content with more depth — a mention with link gets more inspection than text-only Linked mentions in high-DA publications signal entity authority to AI systems Internal linking from your site to itself doesn't count; external editorial links do Same conversion improves both Google ranking signals and AI engine reference quality The investment in link reclamation pays both traditional and AEO dividends — same outreach, double the surface impact. <div
Tip: 💡 Link reclamation is the most under-utilised link building tactic. The conversion rate (15-30%) dwarfs cold outreach (1-3%) because you're starting from established context — the publication already chose to mention you, so adding a link is a small enhancement rather than a new editorial decision. Sites running quarterly reclamation campaigns typically generate 8-30 high-quality links per year from existing mentions, often DA 50+ in their categories. Compare to the months of cold outreach work needed to land equivalent links from scratch.
Tags: brand-mention, link reclamation, unlinked brand mentions, mention to link conversion, link building outreach, brand link recovery
Fix
Fix vague link text from the Accessibility audit. Replace Click Here and Read More with descriptive text, fix duplicate anchors, write for context.
Run the Accessibility audit
Run the Accessibility audit . Filter to link-text findings. Categories: vague text (Click Here, Read More), duplicate anchor text pointing at different URLs, identical text within same context, empty link text, image link with no alt.
Pull up the link list yourself
With VoiceOver: Cmd+VO+U → choose "Links" rotor. With NVDA: Insert+F7 → Links. Scan the list. If you can't tell where each link goes from just its text, the text needs fixing. <!-- Visible Read More button, but accessible name is descriptive --> <article> <h3>Q3 sales analysis</h3> <p>Revenue grew 23% led by EMEA region performance...</p> <a href="/learning-hub.html" aria-label="Read more about Q3 sales analysis"> Read More </a> </article> <!-- WordPress --> <a href="<?php the_permalink(); ?>" aria-label="Read more about <?php the_title_attribute(); ?>"> Read More </a> <!-- Shopify Liquid --> <a href="{{ article.url }}" aria-label="Read more about {{ article.title | escape }}"> Read More </a> <!-- React --> <a href={article.url} aria-label={`Read more about ${article.title}`}> Read More </a> <!-- WRONG --> <a href="/support.html">Contact us</a> <a href="/support.html">Contact us</a> <a href="/support.html">Contact us</a> <!-- RIGHT --> <a href="/support.html">General contact</a> <a href="/support.html">Contact support</a> <a href="/support.html">Contact sales</a> <!-- Social icons in footer --> <a href="https://twitter.com/yourbrand" aria-label="Follow us on Twitter"> <svg><!-- twitter icon --></svg> </a> <a href="https://linkedin.com/company/yourbrand" aria-label="Connect on LinkedIn"> <svg><!-- linkedin icon --></svg> </a> <!-- Vague --> <a href="/docs/whitepaper.pdf">Download</a> <!-- Descriptive --> <a href="/docs/whitepaper.pdf"> Download 2025 industry whitepaper (PDF, 2.4 MB) </a>
Tip: 💡 The "list of links" test: imagine your page's links pulled into a flat list with no surrounding context. Can you tell what each link does? If not, rewrite for context-independence.
Tags: accessibility, fix link text, click here, read more, descriptive links, link accessibility, anchor text SEO, ambiguous links, WCAG 2.4.4
Fix
Build natural link velocity. Avoid sudden spike (manipulation signal) and crash patterns. Sustained acquisition pace that signals legitimate growth.
1. Healthy velocity patterns
Natural-looking velocity: - Gradual upward trend over months/years - Occasional spikes from PR moments (acceptable) - Steady baseline activity even between campaigns - Slow decay rather than sudden drops Suspicious patterns: - Sudden spike then complete silence ("link blast") - Periodic spikes at exact monthly intervals (paid campaigns) - Huge losses in a short window (link removal scandal) - Multi-year flat zero then sudden activity
2. The "link blast" anti-pattern
Buying 100 links in one week, then zero for 6 months: - Pattern visible in any backlink monitoring tool - Triggers Google's velocity-based filters - Often paired with low-quality sources (compounded penalty) - Recovery takes 6-18 months at best If you've inherited this pattern (previous SEO agency): - Audit the spike-period sources (likely toxic) - See how-to-fix-toxic-backlinks for cleanup - Build sustained genuine activity going forward
3. Sustained acquisition pace
For a mid-sized site (10K-100K pages): Realistic monthly acquisition: 5-20 new referring domains 50-200 new individual backlinks Sourced through: - PR / press mentions: 2-5/month - Resource page listings: 1-3/month - Guest content: 1-2/month - Earned organic: 3-10/month - Other (tools, citations): variable Stay consistent month-to-month. Quality matters more than absolute count.
4. Handling PR moments (legitimate spikes)
When you launch a product, publish original research, win an award: - Expect a spike (10-100 new links in days) - This is FINE — it's natural - The signal: spike followed by sustained activity - Not the signal: spike followed by zero After PR spike: - Continue normal outreach - Make sure the sustained baseline returns - Don't try to artificially "smooth" the spike
5. Handling losses gracefully
Some loss is normal (4-8% per quarter). Sudden mass-loss is dangerous: - Lost 30% of backlinks in 2 weeks → investigation needed - Likely cause: linking site went offline, redesign, or content purge - Or: you removed/disavowed bad links (good loss) Track loss rate alongside acquisition: Net velocity = new - lost Net should remain positive over rolling 90 days.
6. Track velocity over time
Quarterly: - New referring domains per month (last 12 months) - Lost domains per month - Net change - Any spikes or troughs — verify cause - Velocity vs competitors (similar size) Tools showing velocity charts: - Ahrefs → Overview → "New & lost referring domains" - Semrush → Backlink Analytics → "New & lost referring domains" - Majestic → Trust Flow trend <div
Tip: 💡 Velocity is about consistency over time. Sites with the strongest profiles built links steadily for years, not in bursts. If you've been silent, restart with modest acquisition (3-5/month) and ramp gradually. Sudden post-silence activity looks suspicious; sustained low-level resumption looks like recovery.
Tags: backlinks, link velocity, link acquisition rate, sudden link spike, link building pace
Fix
Make links render correctly on LinkedIn: og tags, debug tool refresh and cached preview busting.
Run the Social Preview audit
Open the Social Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Social Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: social-preview, linkedin, preview
Fix
Improve the content of your llms.txt so AI engines find the most useful URLs and skip the noise.
Run the llms.txt audit
Open the llms.txt Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your llms.txt audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: llms, content, llms-txt
Fix
Resolve llms.txt formatting errors so AI engines can parse it: H1 site name, blockquote summary, well-formed sections.
Run the llms.txt audit
Open the llms.txt Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your llms.txt audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: llms, format, llms-txt
Fix
Add llms-full.txt to give AI engines expanded context: 1–2 paragraph summary per linked page improves citation rate.
Run the llms.txt audit
Open the llms.txt Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your llms.txt audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: llms, full, llms-txt
Fix
Write llms.txt in clear English (or your primary language) without jargon or marketing copy that confuses AI engines.
Run the llms.txt audit
Open the llms.txt Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your llms.txt audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: llms, language, llms-txt
Fix
Fix broken links in llms.txt so AI engines don’t hit 404s when following references from your discovery file.
Run the llms.txt audit
Open the llms.txt Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your llms.txt audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: llms, links, llms-txt
Fix
Configure llms.txt on Shopify: theme code, dedicated files via Pages, manual upload, content selection, validation and AI bot monitoring.
Decide content for llms
txt. Curated list of your highest-value content: top product collections, comprehensive guides on Shopify Blog, About/FAQ pages. Not full sitemap. 20-100 entries.
Choose serving approach
Option A: Theme template. Create templates/llms.liquid in theme; create a page with that template. URL becomes /pages/llms. Use URL redirect to map /llms.txt → /pages/llms. Option B: Static page approach — create a Page in admin, paste markdown content, accept the /pages/llms URL. Option C: Shopify app that handles llms.txt (a few emerging — 'LLM Crawler Optimizer').
Implement via theme template
Online Store → Themes → Edit code → templates/llms.liquid. Set content-type and output markdown directly: {%- layout none -%}{%- comment -%}...generate markdown content here using liquid loops over products/collections/pages...{%- endcomment -%}. Create a page in admin and assign this template.
Set up redirect
Online Store → Navigation → URL Redirects → from '/llms.txt' to '/pages/llms'. Now visitors to yourstore.com/llms.txt get redirected (301) to the page that serves the content.
Verify file is accessible
Visit yourstore.com/llms.txt. Should redirect to /pages/llms which displays markdown content. Test from curl: 'curl -L https://yourstore.com/llms.txt' should show the markdown content.
Add an llms-full
txt variant (optional). Some AI agents look for llms-full.txt with complete content (not just links). Same approach as llms.txt but include actual page content not just links. Larger file, more useful for AI agents.
Monitor AI agent traffic
Shopify Plus: Analytics → Live View → filter by user-agent. Standard plans: limited bot visibility. Cloudflare-fronted stores: Cloudflare analytics shows AI bot requests.
Tags: llms, Shopify llms.txt, llms.txt Shopify, Shopify AI optimisation, Shopify GPTBot, Shopify AI knowledge file, llms-txt
Fix
Configure llms.txt in static-site generators: Astro, Hugo, Eleventy, Gatsby. Build-time generation, content selection, hosting, validation.
Pick content to include
Curated list of your most valuable content: top blog posts, comprehensive guides, About/FAQ. 20-100 entries. Not full sitemap (different file).
Astro: create src/pages/llms
txt.ts. Astro supports custom endpoints. Create src/pages/llms.txt.ts that exports a GET function returning Response with text/plain content. Build collects all posts via getCollection() → builds markdown output.
Hugo: create layouts/_default/llms
txt and content/llms.md. Hugo: content/llms.md with content. layouts/_default/llms.txt template loops over .Site.RegularPages → outputs markdown lines. Configure mediatypes in config.toml to add text/plain. Output goes to public/llms.txt at build.
Eleventy
create llms.njk template. src/llms.njk: --- permalink: /llms.txt --- followed by Nunjucks template iterating collections.all → outputting markdown lines. Set permalink to ensure file generates at /llms.txt root.
Gatsby
create gatsby-node.js custom file. gatsby-node.js → onPostBuild hook → use fs.writeFileSync to write public/llms.txt with content built from GraphQL queries.
Validate file at /llms
txt. After build: visit yoursite.com/llms.txt. Should be text/plain (not text/html). Should contain markdown content. Test from curl: curl -I yoursite.com/llms.txt → Content-Type should be text/plain.
Monitor
Periodically (monthly) review llms.txt output is current with site content. Static generation means file is rebuilt on every deploy — stale only if you forget to deploy.
Tags: llms, llms.txt static site, Astro llms.txt, Hugo llms.txt, Eleventy llms.txt, llms.txt SSG, llms-txt
Fix
Configure llms.txt on WordPress to guide AI agents to your best content. Plugin options, manual configuration, content selection, validation and monitoring.
Step 1: Decide what to include in llms.txt
Curated list of your most valuable, evergreen content. Not your full sitemap. Aim for 20-100 entries. Categories: documentation, key blog posts, FAQ pages, about/contact, pricing, integration pages.
Step 2: Choose an llms.txt approach
Three options: (a) plugin: 'LLMs.txt for WordPress' or 'Website LLMs.txt' — auto-generated from posts. (b) Manual: upload static llms.txt via FTP to /public_html/. (c) Programmatic: register custom rewrite via theme functions.php that serves dynamic content.
Step 3: Install and configure plugin (option a)
LLMs.txt for WordPress plugin → Settings → choose content types to include → choose post statuses → set max entries → save. Plugin auto-generates llms.txt at yoursite.com/llms.txt.
Step 4: Create static file (option b)
Use a markdown editor. Structure: H1 (site name), short H2 description, then sections (H2) with bullet links. Example: '# Acme Corp' / '## Documentation' / '- [API reference](https://...) - REST API docs'. Upload via FTP to public_html/llms.txt.
Step 5: Verify file is accessible
Visit yoursite.com/llms.txt. Should serve raw markdown text, content-type text/markdown or text/plain. If 404: rewrite rules not generated (Settings → Permalinks → Save), or file in wrong directory.
Step 6: Also serve llms-full.txt (optional)
Some AI agents look for llms-full.txt with complete content (not just links). Generate by concatenating full markdown of listed pages. Larger file but gives agents complete context without crawling links.
Step 7: Monitor AI agent traffic
Server logs: search for requests to /llms.txt. Track which AI agents fetch it. Useful tools: Cloudflare analytics, WP Activity Log, custom log filters via plugin like Simple History.
Tags: llms, WordPress llms.txt, llms.txt plugin, AI knowledge file WordPress, GPTBot WordPress, AI optimisation WordPress, llms-txt
Fix
Fix missing or broken llms.txt from the Agent Readiness audit. Acts as an AI sitemap pointing at your best content. Format, location, generation patterns.
List your most useful pages
Not every URL. Just the content you'd want AI to recommend or cite. Documentation, primary guides, core product pages, pricing, key reference content.
Group into sections
Logical H2 sections like Documentation / Guides / Pricing / Reference. End with an Optional section for content that's nice-to-have but lower priority.
Write one-line descriptions
After each link, brief description of what's there. AI uses these as signal for what each URL is about. Keep concise — 1 sentence max.
Place at site root
https://example.com/llms.txt . Must be at root, not in a subdirectory. Serve as text/markdown or text/plain . location = /llms.txt { default_type "text/markdown; charset=utf-8"; add_header Content-Type "text/markdown; charset=utf-8" always; } <Files "llms.txt"> ForceType "text/markdown; charset=utf-8" </Files> # aiwebpageseo — Full Content ## Site Audit [Full content of /seo-audit-platform.html in markdown] --- ## Schema Debugger [Full content of /schema-tools.html#debugger in markdown] --- ## AEO Guide [Full content of /aipageseo-demo-pages/aeo-guide.html in markdown] // scripts/generate-llms-full.js const fs = require('fs'); const TurndownService = require('turndown'); const td = new TurndownService(); const pages = [ { title: 'Site Audit', file: 'public/seo-audit-platform.html' }, { title: 'AEO Guide', file: 'public/aipageseo-demo-pages/aeo-guide.html' }, // ... ]; let out = `# Site Name — Full Content\n\n`; for (const p of pages) { const html = fs.readFileSync(p.file, 'utf-8'); // Extract main content (skip nav/footer) const main = extractMain(html); out += `## ${p.title}\n\n${td.turndown(main)}\n\n---\n\n`; } fs.writeFileSync('public/llms-full.txt', out); # robots.txt — comment for AI agent discoverability # llms.txt: https://example.com/llms.txt # llms-full.txt: https://example.com/llms-full.txt User-agent: * Allow: / // package.json "scripts": { "build": "next build && node scripts/generate-llms.js", "predeploy": "npm run build" }
Fetch and confirm
curl -I https://example.com/llms.txt # Should return 200 OK with content-type: text/markdown curl https://example.com/llms.txt | head -30 # Should show your markdown structure
Re-run Agent Readiness
llms.txt findings clear. Structure parses, links resolve, content is meaningful.
Tip: 💡 llms.txt is low-effort, low-risk, potential-upside as the standard gains adoption. Even if AI engines don't fully consume it yet, having it in place costs almost nothing and positions you well as the ecosystem develops. List your 20-50 best URLs, not every page.
Tags: agent-readiness, llms.txt, AI sitemap, llms full txt, AI content map, agent readiness llms, markdown sitemap AI, llms-txt
Fix
Audit and fix NAP inconsistency across local directories so your business name, address and phone are byte-identical everywhere.
Run the Local SEO audit
Open the Local SEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Local SEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: local-seo, local, citations
Fix
Add neighbourhood and city modifiers to relevant pages so you rank for “service + location” queries with real local intent.
Run the Keywords audit
Open the Keywords Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Keywords audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: keywords, local
Fix
Add valid LocalBusiness schema with NAP, hours, geo coordinates and sameAs links for stronger local search eligibility.
Run the Local SEO audit
Open the Local SEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Local SEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: local-seo, local, schema
Fix
Brick-and-mortar local SEO: storefront optimisation, GBP, reviews, citation building, Maps visibility, in-store conversion tracking.
Optimise GBP comprehensively
Address verified, hours accurate (including holidays), services listed, photos current. Detailed in 'Fix GBP for Restaurants' etc. — most patterns apply to all brick-and-mortar.
Maximise Maps visibility
Local Falcon grid analysis. Identify zones of weak visibility around your store. Address: more reviews, more photos, more citation building, posts.
Build local citations
Industry-relevant directories (Yelp, Foursquare, BBB), local-chamber directories, Google's My Business validators. NAP consistent everywhere.
Manage in-store reviews
QR code at checkout linking to review URL. Receipt-printed prompt. Post-visit email follow-up. Aim for steady review velocity (e.g., 5-10 new per month for a typical retail store).
Optimise website for local
Local landing page (if multi-location) or homepage emphasising location. LocalBusiness schema. Embedded Google Map. NAP visible in footer. Local-context content (community events, area expertise).
Track in-store conversion attribution
Footfall counting (door sensors, beacon technology). UTM-tagged Maps direction links to track Maps→store visits. Customer surveys at checkout ('how did you find us').
Engage local community
Local sponsorships, partnerships, events. Each creates linkable content (community group websites, local press). Local link-building is harder than national but more impactful for local SEO.
Tags: local-seo, brick and mortar local SEO, storefront SEO, physical store SEO, brick mortar Google Business, retail local SEO
Fix
Multi-location local SEO: per-location optimisation at scale, brand-consistency, local landing pages, bulk reporting.
Build per-location landing pages
Each location: own page on owned site. Unique content (local context, manager bio, location-specific photos). Consistent template + custom local content. /locations/london-bridge/, /locations/manchester-central/, etc.
Manage all GBPs centrally
Business Profile Manager for 10+ locations. Standardise brand elements; customise local content. Bulk verification, bulk updates, organisation-level reporting.
Bulk citation management
Yext, BrightLocal, Moz Local — push consistent NAP across major directories. NAP inconsistency at scale is harder to fix retroactively.
Bulk review management
Birdeye, Podium, BrightLocal — review monitoring and response at scale. Per-location SLAs. Route reviews to appropriate location manager.
Train per-location managers
Local managers should engage with GBP (post regularly, respond to reviews promptly, add local photos). Provide training, templates, monthly performance reports.
Aggregate reporting
Total visibility across locations. Identify outliers: top performers (replicate) and bottom performers (intervene). Compare against multi-location competitors.
Strategic content
Multi-location chains often invest in content marketing (blog, guides). Drives top-of-funnel awareness benefiting all locations. Plus localised content (manager spotlights, location stories, community involvement) builds local relevance per area.
Tags: local-seo, multi-location local SEO, franchise local SEO, chain local SEO, bulk local SEO, multi-location strategy
Fix
Service-area local SEO: multi-area visibility, area-specific landing pages, review velocity per area, citations, Maps optimisation.
Build per-service-area landing pages
Unique content per area: local case studies, area-specific service variations, local testimonials. Avoid template-with-find-replace. /service-areas/birmingham/, /service-areas/manchester/, etc.
Configure GBP service areas accurately
Up to 20 areas. Honest coverage (not aspirational). Each area you actually serve. Detailed in 'Fix GBP for Service Area'.
Build citations across areas
Major directories (Yelp, BBB) + area-specific (local Chamber of Commerce sites, regional industry directories). NAP exactly consistent.
Drive reviews from each area
Customer in Manchester leaves Google review = Manchester local SEO signal. Distribute review-solicitation requests across areas. Don't concentrate; otherwise concentrated-area visibility, sparse elsewhere.
Earn local backlinks per area
Local chamber of commerce websites, area trade associations, local press, community partnerships. Each link strengthens area-specific local SEO.
Track per-area Maps visibility
Local Falcon multi-area. Identify under-performing areas. Diagnose: lack of reviews, lack of citations, content depth, or competitive saturation. Address per-area weakness.
Lead-source attribution
CRM tracks which areas generate which leads. Compare to local SEO visibility per area. Strong correlation indicates SEO work paying off; weak correlation indicates other lead channels (referral, ads) dominate.
Tags: local-seo, service area local SEO, SAB local SEO, plumber local SEO, electrician local SEO, landscaper local SEO
Fix
Break long sentences into shorter, scannable ones for better reading ease and Flesch score improvement.
Run the Readability audit
Open the Readability Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Readability audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: readability, long, sentences
Fix
Build content targeting long-tail keyword variants for faster ranking wins and higher commercial intent.
Run the Keywords audit
Open the Keywords Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Keywords audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: keywords, long, tail
Fix
Fix long JavaScript tasks from the JS Checker. Reduce Total Blocking Time, hit INP under 200ms, break up tasks, use scheduler API and Web Workers.
DevTools Performance recording
DevTools → Performance tab → record button → reload the page → stop recording. Long tasks appear as red-flagged rectangles in the Main track. Click each to see the function call stack responsible.
Lighthouse audit
"Avoid long main-thread tasks" audit in Lighthouse lists each long task with duration. "Reduce JavaScript execution time" audit shows which scripts contribute most.
Field INP in Search Console
Search Console Core Web Vitals report shows real-user 75th-percentile INP. Pages with INP over 200ms have responsiveness issues from long tasks in production. // Before: 200ms task processing 10,000 items function processItems(items) { for (const item of items) { expensiveOperation(item); } } // After: yields every 50ms async function processItems(items) { for (let i = 0; i < items.length; i++) { expensiveOperation(items[i]); if (i % 100 === 0) { // Yield to browser await new Promise(resolve => setTimeout(resolve, 0)); } } } async function processItems(items) { for (let i = 0; i < items.length; i++) { expensiveOperation(items[i]); if (i % 100 === 0) { if ('scheduler' in window && 'yield' in scheduler) { await scheduler.yield(); } else { await new Promise(resolve => setTimeout(resolve, 0)); } } } } // Available in Chrome 94+, Edge 94+ if ('scheduler' in window && 'postTask' in scheduler) { scheduler.postTask(() => { // Background work, low priority refreshCache(); }, { priority: 'background' }); scheduler.postTask(() => { // User-blocking work, runs first updateUI(); }, { priority: 'user-blocking' }); } // main.js const worker = new Worker(new URL('./parser-worker.js', import.meta.url)); worker.postMessage({ csv: largeCsvString }); worker.onmessage = (event) => { console.log('Parsed:', event.data); }; // parser-worker.js (runs in separate thread) import Papa from 'papaparse'; self.onmessage = (event) => { const result = Papa.parse(event.data.csv, { header: true }); self.postMessage(result.data); }; // Defer analytics and prefetch until idle requestIdleCallback(() => { initAnalytics(); prefetchLikelyPages(); warmCaches(); }, { timeout: 2000 }); // safety: run within 2s regardless // Slow: recalculates on every render function expensiveCalc(data) { return data.reduce((acc, item) => acc + heavyMath(item), 0); } // Fast: cache by input const cache = new Map(); function expensiveCalc(data) { const key = JSON.stringify(data); if (cache.has(key)) return cache.get(key); const result = data.reduce((acc, item) => acc + heavyMath(item), 0); cache.set(key, result); return result; } // Bad: reading and writing to DOM in a loop forces multiple layouts items.forEach(item => { const width = item.offsetWidth; // forces layout item.style.width = width * 2 + 'px'; // invalidates layout }); // Good: batch reads, then batch writes const widths = items.map(item => item.offsetWidth); // batch reads items.forEach((item, i) => { item.style.width = widths[i] * 2 + 'px'; // batch writes });
Lighthouse TBT
TBT under 200ms is Good per Core Web Vitals. Re-run Lighthouse after fixes; TBT should drop noticeably.
Real-user INP via web-vitals library
import { onINP } from 'web-vitals'; onINP(metric => { console.log('INP:', metric.value); // Send to analytics navigator.sendBeacon('/analytics', JSON.stringify(metric)); }); Field data shows the real picture. Pages where INP drops from 400ms to 150ms after long-task fixes have noticeably better responsiveness for users.
Tip: 💡 The 80/20 of long-task fixes: find the top 3 tasks contributing most to TBT, fix them, ignore the rest. Heavy initialisation in third-party scripts, framework hydration, and synchronous data parsing on load account for most long-task time in typical sites.
Tags: js-checker, fix long tasks, total blocking time, TBT, INP, scheduler API, web workers, JS main thread, requestIdleCallback, yield
Fix
Recover lost backlinks. Detect drops via monitoring, identify cause (page deleted, link removed, link rewritten), outreach to restore. The lost-link workflow.
1. Detect lost backlinks
Sources: - Ahrefs / Semrush / Majestic "Lost backlinks" reports - GSC → Links → compare month-over-month - Your aiwebpageseo backlinks watch (automated alerts) Weekly review: - Newly lost links since last check - Total backlink delta - High-value losses (DA 50+) need urgent action
2. Classify cause for each loss
Cause Action ───────────────────────────────────────────────────────────── Linking page 404 Find new equivalent page, request relink Page exists, link removed Outreach to publisher Link rewritten to competitor Critical — competitive intelligence Site no longer exists Lost permanently, archive only Link nofollowed Less critical; some authority lost Anchor changed to brand Usually fine, less authority impact Site redesign / migration Outreach during transition window
3. Outreach for restoration
Pattern that works: - Identify the publisher contact (author, editor) - Reference the specific old page where link existed - Explain the link was helpful for their readers (why) - Note that the link now goes to (404 / removed) - Offer the current correct URL - Acknowledge their time Success rate: 30-50% on legitimate publishers. Higher when you reference specific reader value. Lower for sites that auto-purge old pages.
4. Prioritise by link value
Don't chase every lost link. Prioritise: - DA 50+ → outreach within 1 week - DA 30-50 → outreach within 2-4 weeks - DA < 30 → batch monthly outreach, skip if no contact - Toxic / spammy → don't try to recover; let them stay gone
5. Prevent future loss
Some causes are preventable: Maintain target URLs : 301 redirect if you must change URL of a linked page 301 internal moves : never break inbound link targets Migration audits : redirect every linked URL during site moves Update content : refresh linked-to pages so editors don't replace your link Build relationships : editors who know you keep your links during refreshes
6. Track restoration rate
Per month: - Lost links detected - Outreach attempts - Restorations confirmed - Restoration rate Healthy: 30-50% restoration on actively-outreach'd losses. Below 20%: outreach quality low, or targets are auto-purgers. Above 50%: well-built relationships and good outreach. <div
Tip: 💡 Lost-link recovery has higher ROI than new link-building because the relationship already exists. A 30-minute outreach often restores DA-50 link that would take weeks to earn fresh. Make it a monthly habit not an annual event.
Tags: backlinks, lost backlinks, link recovery, link loss detection, broken backlink restoration
Fix
Investigate and restore schema properties that silently disappeared after CMS or plugin updates.
Run the Schema Drift audit
Open the Schema Drift Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Drift audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-drift, lost, schema, properties
Fix
Recover URLs that ranked before migration but now 404 or redirect to homepage: build the missing 301 map.
Run the Migration Audit audit
Open the Migration Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Migration Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: migration-audit, lost, urls
Fix
Fix Magento crawl issues: faceted-URL explosion, crawl-budget consumption, layered navigation, robots.txt patterns, sitemap submission for large catalogues.
Audit current crawl situation
Search Console → Settings → Crawl stats. Note: total URLs crawled per day, % of crawls returning 200 vs 3xx/4xx, % of crawl budget on /catalogsearch/, /catalog/category/view/, filter URLs. Magento sites commonly burn 40-80% of crawl budget on non-canonical filter URLs.
Identify faceted URL patterns
Layered navigation generates URLs like ?cat=23&color=red&size=large&price=50-100. Each combination is a crawlable URL. Catalogue with 5 filters × 4 options each = 1024 combinations per category. Multiply by category count: easily millions of URLs.
Configure robots
txt. Block crawler from non-canonical filter patterns: 'Disallow: /*?p=', 'Disallow: /*?cat=', 'Disallow: /*?color=', 'Disallow: /catalogsearch/', 'Disallow: /customer/'. Allow only canonical category and product URLs.
Set canonical URLs on faceted pages
Magento layered navigation should output rel=canonical pointing to the parent category. Verify in HTML source. If missing, use Magento SEO extensions (Mageworx SEO Suite, Mirasvit SEO Suite) or custom module. Without canonicals, even blocked URLs can fragment ranking signals.
Configure XML sitemap
Magento generates sitemap via admin → catalog → google sitemap. Configure: products in chunks of 50,000 (Google's limit per file). Include only canonical product and category URLs. Exclude filter URLs entirely. Submit via Search Console.
Disable layered nav for crawlers
Some Magento sites detect crawler user-agents and serve simpler navigation (no filter links). Reduces crawl explosion. Verify cloaking concerns: if implementation differs significantly between user and crawler view, Google may flag. Conservative: use robots.txt + canonical + nofollow on filter links instead.
Monitor
Search Console crawl stats weekly. Index Coverage report → identify newly indexed filter URLs (should be zero) and dropping non-canonical URLs (good signal). Goal: 90%+ of crawl budget on canonical product and category URLs.
Address performance
Magento can be slow under crawl load. Cache configuration (Varnish, Redis), database tuning, CDN for static assets. Slow crawl response triggers Google to reduce crawl rate, which reduces freshness on legitimate product pages.
Tags: ecommerce-crawl, Magento crawl, Magento SEO, large catalogue crawl, faceted navigation crawl, Magento crawl budget
Fix
Diagnose why you’re not appearing in the Google Map Pack and fix the proximity, prominence and relevance signals.
Run the Local SEO audit
Open the Local SEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Local SEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: local-seo, map, pack
Fix
Complete every relevant Google Maps attribute (women-led, wheelchair-accessible, etc.) for richer Maps listings.
Run the Maps Audit audit
Open the Maps Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Maps Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: maps-audit, maps, attributes
Fix
Pick the right Google Maps primary category for ranking eligibility across your most important local query patterns.
Run the Maps Audit audit
Open the Maps Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Maps Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: maps-audit, maps, category
Fix
Set accurate Maps hours including special hours (holidays, events) and verify with the Maps preview.
Run the Maps Audit audit
Open the Maps Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Maps Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: maps-audit, maps, hours
Fix
Audit and refresh Maps photos: cover, interior, exterior, team, products and updated weekly to signal activity.
Run the Maps Audit audit
Open the Maps Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Maps Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: maps-audit, maps, photos
Fix
Correct an inaccurate Google Maps pin: edit via GBP, request fix from Google if pin is locked, verify with appeals process.
Run the Maps Audit audit
Open the Maps Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Maps Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: maps-audit, maps, pin
Fix
Set up a sustainable Maps posts cadence: weekly minimum, conversion-tracked offers, refreshed before they expire.
Run the Maps Audit audit
Open the Maps Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Maps Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: maps-audit, maps, posts
Fix
Manage Maps reviews: respond to every review, dispute fake reviews, build a sustainable acquisition cadence.
Run the Maps Audit audit
Open the Maps Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Maps Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: maps-audit, maps, reviews
Fix
Populate the Google Maps services list with every service category you offer for richer service-based search visibility.
Run the Maps Audit audit
Open the Maps Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Maps Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: maps-audit, maps, services
Fix
Fix JavaScript memory leaks from the JS Checker. Take heap snapshots, find detached DOM, fix listener cleanup, closures, timers and SPA route leaks.
Memory profile in DevTools
DevTools → Memory tab → "Allocations on timeline" → start recording → perform the user flow several times → stop. Memory chart shows allocations; healthy apps see allocations followed by garbage collection drops. Leaks show stair-step growth that never drops.
Identify the leaking flow
Common leak triggers: Navigation between SPA routes Opening and closing modals Infinite scroll lists WebSocket connections Inserting/removing dynamic components
Three-snapshot technique
Memory tab → "Heap snapshot" → take snapshot 1 (baseline) Perform the leaking action and undo it (e.g., open modal, close modal) Take snapshot 2 Repeat the action and undo Take snapshot 3 Switch view: "Comparison" → comparing snapshot 3 to snapshot 1 Objects that exist in snapshot 3 but not 1 — and grow proportionally to the repetitions — are leaking.
Filter for Detached DOM
In the snapshot, filter Class by "Detached". Lists DOM nodes JS still holds references to but aren't in the document. Click a node → "Retainers" panel shows what holds it alive — usually a closure, array, or property. // Leak: listener references the element forever class Modal { constructor() { this.el = document.createElement('div'); document.body.addEventListener('keydown', this.handleKey); // No cleanup — handleKey holds 'this' forever } handleKey = (e) => { /* ... */ }; } // Fix: store reference, remove on destroy class Modal { constructor() { this.el = document.createElement('div'); this.handleKey = (e) => { /* ... */ }; document.body.addEventListener('keydown', this.handleKey); } destroy() { document.body.removeEventListener('keydown', this.handleKey); this.el.remove(); } } // Leak: interval runs forever, retains reference to callback closure function startPolling() { setInterval(() => { fetch('/api/status').then(updateUI); }, 5000); } // Fix: save the id, allow clearing let pollingId; function startPolling() { pollingId = setInterval(() => { fetch('/api/status').then(updateUI); }, 5000); } function stopPolling() { clearInterval(pollingId); pollingId = null; } // Leak: array keeps removed DOM nodes alive const cache = []; function addCard(data) { const card = document.createElement('div'); cache.push(card); // never removed from cache return card; } // Fix: clean up references when DOM is removed function removeCard(card) { const idx = cache.indexOf(card); if (idx > -1) cache.splice(idx, 1); card.remove(); } // Leak: setInterval callback captures 'data' forever function processLater(data) { setInterval(() => { // uses something else, but data is still in scope console.log('tick'); }, 1000); // Even though data isn't used in the callback, it's in scope // and the engine may retain it. } // Fix: scope only what's needed function processLater() { setInterval(() => { console.log('tick'); }, 1000); } // Process data separately, let it be GC'd // Leak: useEffect missing cleanup function ChatRoom({ roomId }) { useEffect(() => { const socket = new WebSocket(`wss://chat/${roomId}`); socket.onmessage = handleMessage; // No cleanup — socket stays open }, [roomId]); } // Fix: return cleanup function function ChatRoom({ roomId }) { useEffect(() => { const socket = new WebSocket(`wss://chat/${roomId}`); socket.onmessage = handleMessage; return () => socket.close(); // runs on unmount and before next effect }, [roomId]); } // Leak: stale state from async work after unmount function Profile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { fetchUser(userId).then(setUser); // setUser may fire after unmount }, [userId]); } // Fix: AbortController function Profile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { const controller = new AbortController(); fetch(`/api/users/${userId}`, { signal: controller.signal }) .then(r => r.json()) .then(setUser) .catch(err => { if (err.name !== 'AbortError') console.error(err); }); return () => controller.abort(); }, [userId]); } // Leak: Map retains keys even after they're removed elsewhere const cardMeta = new Map(); function addMeta(card, meta) { cardMeta.set(card, meta); // card never freed even if removed from DOM } // Fix: WeakMap allows GC of unused entries const cardMeta = new WeakMap(); function addMeta(card, meta) { cardMeta.set(card, meta); // freed when card has no other references } # Install npm install -g memlab # Define a scenario file: scenario.js module.exports = { url: () => 'https://yoursite.com', action: async (page) => { await page.click('.open-modal'); await page.click('.close-modal'); }, back: async (page) => { // return to baseline state } }; # Run memlab run --scenario scenario.js // performance.memory (Chrome-only, deprecated, but useful) if ('memory' in performance) { setInterval(() => { const used = performance.memory.usedJSHeapSize; if (used > 100_000_000) { // 100MB threshold // Report to monitoring Sentry.captureMessage(`High memory: ${used}`); } }, 60_000); }
Tip: 💡 Most SPA memory leaks come from three patterns: missed listener cleanup, missed interval/subscription cleanup, and arrays/maps holding references to deleted DOM. Focus diagnosis on those three first; they cover 90% of cases.
Tags: js-checker, fix JS memory leaks, heap snapshot, detached DOM, event listener cleanup, SPA memory leak, closure leak, timer leak, garbage collection
Fix
Write meta descriptions for every page: query-matched, 140–160 chars, action-oriented, no boilerplate.
Run the Meta Analyzer audit
Open the Meta Analyzer Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Meta Analyzer audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: meta-analyzer, meta, descriptions
Fix
Configure meta tags in Next.js: Metadata API in App Router, generateMetadata for dynamic, OG and Twitter, robots, canonical, validation.
Export static metadata
page.tsx: export const metadata: Metadata = { title: 'Page Title', description: '...' }. Next.js renders the appropriate <head> tags. Cleanest for static pages.
Use generateMetadata for dynamic
Blog post or product page where metadata depends on data: export async function generateMetadata({ params }): Promise<Metadata> { const data = await fetch(...); return { title: data.title, description: data.description, openGraph: { ... } }; }
Configure metadataBase
layout.tsx: export const metadata: Metadata = { metadataBase: new URL('https://example.com'), ... }. Required for relative URLs in OG/Twitter to resolve to absolute.
Set OG, Twitter explicitly
openGraph: { title, description, url, type: 'article', images: [{url: ...}] }. twitter: { card: 'summary_large_image', title, description, images: [...] }. Don't rely on inheritance — set per page.
Set canonical
alternates: { canonical: '/path' }. Always set; without canonical, duplicate-content risks. Self-referencing canonical is fine and recommended.
Set robots
robots: { index: true, follow: true, googleBot: { index: true, follow: true, maxSnippet: -1, maxImagePreview: 'large' } }. Set in root layout for site-wide; override per page for noindex pages (legal, internal-search, thank-you).
Validate
View source on representative pages. Use a meta tag analyser or our tool. Verify completeness, lengths (title ≤60, description ≤160), no duplicates.
Tags: meta-analyzer, Next.js meta tags, Next.js Metadata API, Next.js generateMetadata, Next.js canonical, Next.js robots
Fix
Configure Shopify meta tags: title and description per page, robots directives, canonical URLs, OG tags, theme defaults vs custom overrides.
Audit current meta output
View source on 5 representative URLs. Check title length (50-60 ideal), description length (≤160), canonical present, robots sensible, OG complete.
Set per-product SEO title and description
In product editor, scroll to 'Search engine listing preview'. Click Edit. Set custom Page title (60 chars) and Meta description (160 chars). Override the default (which uses product title).
Set per-collection SEO meta
Same as products. Collections page → edit collection → Search engine listing preview. Override defaults with keyword-targeted, click-through-optimised meta.
Configure theme defaults
Theme settings vary, but most provide site-wide SEO defaults (homepage title pattern, brand suffix). Configure in Online Store → Themes → Customize → Theme settings → SEO section.
Verify canonical URLs
Shopify auto-canonicals products to /products/SLUG (not /collections/X/products/SLUG variants). Verify in view-source. Custom canonicals for special cases via theme.liquid edits.
Configure robots via theme/robots
txt.liquid. robots.txt.liquid for site-wide rules. For per-page noindex (e.g., thank-you pages, internal-search results), edit theme.liquid: {% if template == 'search' %}<meta name='robots' content='noindex'>{% endif %}.
Configure OG tags
Themes output OG automatically from product/collection data. Override per-page via theme.liquid conditionals or via apps (Smart SEO has OG override per page).
Tags: meta-analyzer, Shopify meta tags, Shopify SEO title, Shopify meta description, Shopify canonical, Shopify robots
Fix
Configure WordPress meta tags: title tags, descriptions, robots directives, OG and Twitter cards, canonical URLs, plugin options and validation workflow.
Step 1: Audit current meta output
View source on 5 representative URLs. Check: title length (50-60 chars ideal), description length (≤160), canonical URL present, robots directives sensible, OG tags complete.
Step 2: Configure global title patterns in SEO plugin
Yoast/Rank Math/AIOSEO → Titles & Meta → set patterns per post type. Example for posts: '%title% | %sitename%'. For categories: '%category% Archives | %sitename%'. Use plugin variables, not hardcoded text.
Step 3: Override titles per page where needed
On individual posts/pages, override the auto-generated title in the SEO plugin section below the editor. Useful for: homepage (custom branded title), key landing pages (CTR-optimised titles), product pages.
Step 4: Write per-page meta descriptions
Auto-generated meta descriptions (excerpted from content) are weak. Write custom 150-160 char descriptions on every important page. Include focus keyword + value proposition + CTA.
Step 5: Configure canonical URLs
Plugin auto-sets canonical to current URL. For duplicate content scenarios (variations, /amp/, archive pages), manually override. Check Search Console Coverage for 'Duplicate without user-selected canonical' errors.
Step 6: Configure robots directives
Default: index, follow. Exceptions: tag archives, author archives, search results, paginated content past page 5 — often noindex. Plugin Settings → Search Appearance → toggle per archive type.
Step 7: Configure OG and Twitter cards
Plugin generates these automatically based on title + description + featured image. Override per-page if needed. Test via Facebook Sharing Debugger and Twitter Card Validator.
Tags: meta-analyzer, WordPress meta tags, WordPress title tag, WordPress meta description, WordPress canonical, WordPress robots meta
Fix
Build a complete 301 redirect map for site migration: every old URL needs a destination, every chain becomes single-hop.
Run the Migration Audit audit
Open the Migration Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Migration Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: migration-audit, migration, redirects
Fix
Fix missed brand-mention opportunities. Detect unanswered mentions, recover where possible, prevent recurrence with workflow fixes that close gaps.
Did monitoring see it?
Spot-check missed mentions: were they in your monitoring tool's index? If not, why? Keyword coverage gap? Platform not monitored? Tool inadequate for the volume? Fix: keyword expansion, platform addition, tool upgrade.
Was it classified correctly?
Look at the missed mentions that WERE detected. Why classified as no-response? Was the classifier (human or auto) too conservative? Was it pattern-matched to "Background" when it was actually "Standard" or higher? Fix: triage criteria refinement, training, calibration sessions.
Did anyone have time?
Detected and triaged correctly but never got responded to. Capacity gap. Fix: redistribute, add capacity for highest-tier, OR reduce SLA target to realistic level. Better to commit to fewer mentions reliably than promise blanket coverage and miss the important ones.
Could anyone respond?
Detected, triaged, time available, but no one had decision rights. Common pattern: everything needs exec/legal approval, exec/legal not available, mention stales. Fix: pre-approved templates, delegated authority for non-crisis tiers, faster escalation path.
Did responder know what to say?
Detected, triaged, person available, but responder didn't know how to handle. Fix: response templates library, training, escalation paths for unfamiliar situations. Expand monitoring coverage: - Add more keyword variations (brand misspellings, product names, internal codenames, exec names, related topics) - Cover more platforms — review monthly which are surfacing missed mentions, add them - Set up secondary alerts for high-value mentions (e.g. anytime a named journalist or competitor mentions you) - Tune for false-positive vs false-negative balance — too noisy and people ignore alerts; too quiet and misses accumulate Triage decision tree documented: - Severity criteria with examples - Reach criteria with examples - Specific test cases responders have practiced classifying - Calibration: monthly review where team triages the same mentions independently, compare classifications, identify drift Capacity allocated by tier: - Crisis: dedicated on-call, no other duties when on-call - High: dedicated business-hours coverage, clear primary/backup - Standard: distributed across team with daily quota - Background: explicit "may not be responded to" expectation If a tier consistently misses, options: - Add capacity to that tier - Reclassify some mentions downward (less goes into that tier) - Reduce SLA expectation for that tier - Automate some responses (template + light human review) Decision rights matrix: - Standard responses: any trained responder approves - Customer service / refunds within £X: support lead approves - Public correction of factual error: responder approves with peer review - Legal language: legal lead approves regardless of tier - Crisis statements: comms lead approves; exec briefed - Anything responder is unsure about: escalate without shame The principle: push authority DOWN to where speed matters. Reserve exec/legal involvement for genuinely needs-them situations. Monthly metric: Miss rate = mentions where warrant=Yes AND engagement=No divided by total warranted mentions Targets: Crisis tier: 0% miss rate (anything else is failure) High tier: under 5% miss rate Standard tier: under 15% miss rate Background tier: rate irrelevant; this tier explicitly optional Track: - Trend over time (improving / declining) - By tier (where are misses concentrated) - By platform (which surfaces produce most misses) - By responder (any individual struggling) - By time-of-day (overnight gap?) Quarterly improvement focus: pick the highest-miss tier or pattern, make one systemic change, measure for 8 weeks, evaluate.
Tip: 💡 The shift that fixes most miss patterns: stop trying for 100% coverage of all mentions and start aiming for near-100% coverage of mentions that genuinely warrant response. Aggressive triage that classifies clearly-no-response mentions as Background frees capacity for the High/Standard tier where misses actually cost something. Sites at 5% miss rate on warranted mentions outperform sites at 95% response rate on everything — fewer responses, better ones, on the things that mattered.
Tags: brand-mention, missed mention opportunities, late mention response, mention backlog, mention coverage gap, brand engagement misses
Fix
Fix missing or wrong lang attribute from the HTML Checker. Set html lang correctly, use BCP 47 codes, handle multilingual pages, link hreflang.
Choose language-only or language+region
BCP 47 codes can be just a language ( en , fr , de ) or language + region ( en-GB , fr-CA , pt-BR ). Rule: use the most specific code that genuinely matches your content. Generic English content → lang="en" British spellings, UK audience → lang="en-GB" American spellings, US audience → lang="en-US" Brazilian Portuguese specifically → lang="pt-BR" Generic Portuguese → lang="pt"
Edit header.php
<!DOCTYPE html> <html <?php language_attributes(); ?>> language_attributes() outputs lang="en-GB" (or whatever you set in Settings → General → Site Language). If your theme has a hardcoded <html lang="en"> , replace it with language_attributes() so the value follows WP settings.
Edit theme.liquid
<html lang="{{ request.locale.iso_code }}"> request.locale.iso_code outputs the active locale (e.g. en , en-GB ). For multi-locale stores via Shopify Markets, this automatically matches the visitor's locale.
Set lang in app/layout.tsx
export default function RootLayout({ children }) { return ( <html lang="en-GB"> <body>{children}</body> </html> ); } For internationalised routing, use the active locale from next-intl or the App Router's locale param.
Wrap foreign-language content
<html lang="en"> <body> <p>Welcome to our French bakery.</p> <p>Our signature: <span lang="fr">pain au chocolat</span>.</p> </body> Screen readers switch pronunciation mid-sentence based on the lang switch. Without it, "pain au chocolat" gets pronounced in English: "pain-aw-chock-o-lat" instead of the correct French pronunciation.
Add hreflang link tags to head
In the <head> of EVERY language variant: <link rel="alternate" hreflang="en-GB" href="https://yourdomain.com/uk/page" /> <link rel="alternate" hreflang="en-US" href="https://yourdomain.com/us/page" /> <link rel="alternate" hreflang="fr-FR" href="https://yourdomain.com/fr/page" /> <link rel="alternate" hreflang="x-default" href="https://yourdomain.com/page" /> Self-referencing (each variant links to itself plus all siblings) and x-default for the fallback language are both required.
Re-run the HTML Checker
The lang-missing finding should clear. Spot-check via: curl -s https://yourdomain.com/ | grep -i '<html' Expected output: <html lang="en-GB"> (or your chosen code).
Tags: html-checker, fix html lang, missing lang attribute, BCP 47, hreflang, multilingual SEO, screen reader lang, language declaration
Fix
Create llms.txt at your domain root so AI engines can discover and prioritise your most useful content.
Run the llms.txt audit
Open the llms.txt Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your llms.txt audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: llms, missing, llms-txt
Fix
Add og:title, og:description, og:image and og:url so links shared on social media render with rich previews.
Run the Open Graph audit
Open the Open Graph Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Open Graph audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: og-generator, missing, og
Fix
Fix mixed-content from the Redirect Checker. HTTPS page pulling HTTP assets gets blocked. Use protocol-relative URLs and upgrade-insecure-requests CSP.
DevTools Console
Open any HTTPS page → DevTools → Console tab → reload. Mixed content warnings appear as red errors: Mixed Content: The page at 'https://...' was loaded over HTTPS, but requested an insecure script 'http://...'. This request has been blocked. Each warning lists the offending URL.
Lighthouse audit
Lighthouse → "Uses HTTPS" audit lists every HTTP resource requested by the page. Direct, actionable list.
Server-side grep
# Find HTTP URLs in HTML/CSS/JS files grep -rn "http://" ./public --include="*.html" --include="*.css" --include="*.js" | \ grep -v "http-equiv" | \ grep -v "schema.org" | \ head -50 # Schema URLs and http-equiv attributes are not actual asset requests — skip those. add_header Content-Security-Policy "upgrade-insecure-requests" always; Header always set Content-Security-Policy "upgrade-insecure-requests" <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"> # With WP-CLI wp search-replace 'http://example.com' 'https://example.com' \ --skip-columns=guid --dry-run # After confirming output wp search-replace 'http://example.com' 'https://example.com' \ --skip-columns=guid # For external HTTP image references in older content wp search-replace 'src="http://' 'src="https://' --skip-columns=guid # Caveat: some external sites may not support HTTPS — check before bulk replace # Find first grep -rln 'src="http://' ./public --include="*.html" grep -rln 'href="http://' ./public --include="*.css" # Replace (test on one file first) sed -i 's|src="http://example\.com|src="https://example.com|g' file.html # Bulk for own domain find ./public -name "*.html" -exec sed -i \ 's|src="http://example\.com|src="https://example.com|g' {} + # Identify which external domains are referenced over HTTP grep -rEh 'http://[a-z0-9.-]+' ./public --include="*.html" --include="*.js" | \ grep -oE 'http://[a-z0-9.-]+' | \ sort -u | head -20 # Check each: does the domain support HTTPS now? for url in $(cat http-urls.txt); do https_url="${url/http/https}" status=$(curl -s -o /dev/null -w "%{http_code}" "$https_url") echo "$status $https_url" done <!-- Old Google Analytics --> <script src="http://www.google-analytics.com/ga.js"></script> <!-- Current GA4 --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script> <!-- Old --> <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script> <!-- New --> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> // Custom HTML widgets, sidebar widgets often have hardcoded HTTP URLs // Search and update via WP admin or database search-replace // Sanitise on input: strip http: from img/script tags function sanitiseUserHTML($html) { return preg_replace( '|(<(img|script|iframe|link)[^>]*\bsrc=["\'])http://|i', '$1https://', $html ); } <!-- WRONG: HTTPS page → form POST over HTTP --> <form action="http://example.com/submit" method="post"> <input type="email" name="email"> </form> <!-- RIGHT --> <form action="https://example.com/submit" method="post"> <input type="email" name="email"> </form>
DevTools Console clean
Reload page in incognito. Console shows zero "Mixed Content" warnings. Network tab shows all resources as HTTPS.
Browser address bar
Chrome shows green/grey padlock for HTTPS. With mixed content, padlock shows warning triangle or "Not Secure". Clean fix returns the padlock to default.
Lighthouse re-audit
"Uses HTTPS" audit should pass. All resource lists clear. # Lint rule: block http:// in templates and content # .eslintrc — for JS frameworks { "rules": { "no-restricted-syntax": [ "error", { "selector": "Literal[value=/^http:\\/\\//]", "message": "Use https:// instead of http://" } ] } } # Pre-commit hook git diff --cached | grep -nE '^\+.*http://' && { echo "ERROR: http:// found in staged changes" exit 1 }
Tip: 💡 Add upgrade-insecure-requests CSP as the safety net first — it catches stragglers without breaking the page. Then work through grep results to fix at source. Eventually you can remove the CSP directive if confident no http:// URLs remain, but it's cheap to leave in place as defence-in-depth.
Tags: redirect-checker, fix mixed content, HTTPS HTTP mixed, upgrade-insecure-requests, CSP, protocol relative, mixed content browser block, secure asset loading
Fix
Improve Core Web Vitals on mobile specifically: optimise for slow CPU and slow network conditions.
Run the Core Web Vitals audit
Open the Core Web Vitals Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Core Web Vitals audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cwv, mobile
Fix
Manage SEO for a business with multiple physical locations: separate GBPs, location-specific landing pages, consistent NAP.
Run the Local SEO audit
Open the Local SEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Local SEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: local-seo, multi, location
Fix
Resolve conflicts when a page emits multiple JSON-LD blocks that contradict each other: consolidate or remove duplicates.
Run the Schema Builder audit
Open the Schema Builder Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Builder audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-builder, multiple, schemas
Fix
Audit Name/Address/Phone consistency across every directory listing and citation: byte-identical everywhere.
Run the Local SEO audit
Open the Local SEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Local SEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: local-seo, nap, consistency
Fix
Respond to negative brand mentions. Triage by severity and reach, draft responses, distinguish criticism from defamation, restore reputation systematically.
Sentiment ratio
Positive : Neutral : Negative mention counts over rolling 90 days. Healthy SaaS sites typically run 60:30:10 or better. Sites with reputation issues see 30:30:40 or worse. Watch trend month-over-month, not absolute numbers.
SERP composition for brand queries
Search your brand name + variants. Top 10 results: how many are owned (your site, your social), how many are positive/neutral third-party, how many are negative? Aim for 7-8 owned-or-positive in top 10. Negative results in top-3 require urgent counter-signal work.
AI engine description
Ask ChatGPT, Claude, Perplexity, Gemini: "Tell me about [your brand]". Note tone, accuracy, specific complaints surfaced. AI engines synthesise from corpus; a high-reach negative campaign shows up in their descriptions. If the AI description leads with negative, you have urgent work — see wrong attribution for handling.
1. Triage: severity × reach matrix
Not all negative mentions deserve the same response. Build a 2×2 matrix: High reach Low reach High severity (substantive claim) IMMEDIATE: public response within 24h, executive involvement RESPOND: thoughtful reply, document, may escalate Low severity (mild criticism) ACKNOWLEDGE: brief public reply, offer to help offline OPTIONAL: respond if easy, often leave alone Reach indicators High reach : G2/Trustpilot/Capterra reviews, large subreddit posts (10K+ subs), major news/trade press, viral social posts (50+ engagements), top SERP results for brand-related queries Medium reach : smaller subreddits, mid-size blogs, smaller social posts, niche trade publications Low reach : personal blogs with low traffic, social posts with minimal engagement, comment threads on unrelated content Severity indicators High severity : false factual claims, allegations of fraud/discrimination/illegal conduct, safety concerns, organised campaigns Medium severity : substantive product/service criticism with specifics, customer service failure stories, pricing complaints Low severity : opinion-based criticism, taste-based dislike, minor friction reports, comparison to competitors
2. Response patterns by quadrant
High severity × high reach Response time: within 24 hours, ideally 4-12 Channel: public on the platform, possibly amplified via press Authority: executive or comms lead, not junior staff Tone: acknowledge, take responsibility where due, specifics Content: - State what's true (don't dispute things that are accurate) - Correct specific factual errors with evidence - Describe what you're doing about it - Offer direct contact for resolution - Avoid corporate-speak, deflection, blame-shifting Example structure: "Thank you for raising this. You're right that [acknowledge accurate parts]. The specific claim about [X] is incorrect — [evidence]. We've reviewed our process and are [specific action]. If you'd like to discuss directly, [direct contact]. — [Real name, role]" High severity × low reach Response time: 24-72 hours Channel: on-platform; usually don't amplify externally Authority: appropriate level for the platform Tone: same as above — acknowledge, correct, action Reasoning: even low-reach high-severity mentions get screenshot-shared if mishandled. Treat with care; don't dismiss as "small audience". Document everything in case the mention later goes viral. Low severity × high reach Response time: 48-72 hours Channel: on-platform Authority: customer success / support Tone: warm, specific, helpful Content: - Acknowledge the specific issue - Offer to help via specific channel - Don't be defensive or argue - Brief; this isn't a crisis Example: "Sorry to hear that [specific issue mentioned]. We'd love to help — [link to support / direct contact]. Always working to make this better." Low severity × low reach Default: leave alone Exceptions: - Easy specific fix you can offer - Mention has SEO value (good linking domain) - Pattern emerging across multiple low-reach mentions Resist the urge to respond to every Twitter complaint. Volume of responses doesn't equal good reputation work; selectivity does.
3. Distinguishing criticism from defamation
Legal threshold matters because it dictates response channel — direct legal action vs platform reporting vs public response vs ignore. Category Definition Response channel Opinion (protected) "I think their product is bad" — subjective view Public reply or ignore Honest mistake Wrong factual claim, no malice Polite correction with evidence Misleading framing Selective truth that creates wrong impression Public reply providing full context Defamatory false claim False statement of fact, identifiable subject, causing harm Legal counsel BEFORE responding publicly Harassment / threat Personal attacks, doxxing, threats Platform report + possibly law enforcement Trademark abuse Misuse of brand mark in misleading way IP counsel, DMCA-style takedown
4. Platform-specific response patterns
G2, Capterra, Trustpilot, Yelp Patterns: - Most platforms allow vendor responses - Response visible publicly under the review - Cannot delete review (unless TOS violation) - Can dispute review with evidence Best practice: - Respond to ALL reviews — positive and negative - For negative: acknowledge specific issue, offer resolution - Sign with real name and role (not "The Team") - Don't argue point-by-point in public — invite offline conversation - Update review when issue resolves (some platforms allow follow-up) Dispute path: - Fake reviews (no customer relationship) — disputable with evidence - Competitor-written reviews — provable patterns get removed - TOS violations (profanity, personal attacks) — flagged and reviewed Reddit Reddit dynamics: - Subreddit norms vary; lurk before engaging - Posting as official account often welcome IF flair'd correctly - Posting as "regular user" without disclosure is policy violation Best practice: - Use flair: "Verified Acme Corp employee" or similar - Reply once, substantively; don't argue threadwise - Don't downvote critical comments (detectable, backfires) - Don't ask Reddit moderators to remove (rarely successful) - For substantive complaints, link to specific resolution path Twitter / X Patterns: - Reply publicly only when high-value or high-risk - DM for resolution discussions - Don't engage with bait or pile-on dynamics - Pin a positive substantive thread when needed for context Avoid: - Defensive subtweets - Mass-blocking critics (becomes a story) - Engaging with bots / coordinated negative campaigns (waste of effort; document and ignore)
5. Build positive counter-signal
You cannot scrub negative mentions out of existence. The recovery strategy is producing genuine positive volume that dilutes negative concentration: Customer success stories with named, real customers Case studies with verifiable metrics and outcomes Active community presence with helpful content Press coverage of positive milestones (product launches, hires, partnerships) Genuine reviews encouraged from real customers (don't fake; AI engines detect) Thought leadership content from named executives Industry awards and recognition (where earned, not bought) This is also the foundation of brand mention strategy for AI visibility — the same activity that builds AEO authority also dilutes negative concentration.
6. Sentiment recovery measurement
You can't manage what you don't measure. The Brand Mention Monitor tracks sentiment over time. Key metrics: Metric 1 Sentiment ratio Positive : Neutral : Negative mention counts over rolling 90 days. Healthy SaaS sites typically run 60:30:10 or better. Sites with reputation issues see 30:30:40 or worse. Watch trend month-over-month, not absolute numbers. Metric 2 SERP composition for brand queries Search your brand name + variants. Top 10 results: how many are owned (your site, your social), how many are positive/neutral third-party, how many are negative? Aim for 7-8 owned-or-positive in top 10. Negative results in top-3 require urgent counter-signal work. Metric 3 AI engine description Ask ChatGPT, Claude, Perplexity, Gemini: "Tell me about [your brand]". Note tone, accuracy, specific complaints surfaced. AI engines synthesise from corpus; a high-reach negative campaign shows up in their descriptions. If the AI description leads with negative, you have urgent work — see wrong attribution for handling.
7. Crisis-mode response
For major incidents — viral negative story, product failure, executive scandal — escalate beyond normal triage: Establish crisis communications lead (often comms VP or CEO directly) Single source of truth — one statement, distributed everywhere, not contradictory replies Acknowledge within hours; full statement within 24 hours Update cadence: every 12-24 hours during active phase Specific action plan with dates, not vague commitments Independent verification where appropriate (third-party audit, recall) Direct outreach to most-affected customers Long-term policy/process changes documented publicly <div
Tip: 💡 The instinct is to silence negative mentions; the reality is that silencing rarely works and often amplifies. The recovery path is calibrated response + sustained positive activity. Sites that respond well to criticism often emerge stronger than they were before the incident — the demonstrated willingness to engage builds trust competitors don't have. Hide and you signal weakness; engage and you signal confidence.
Tags: brand-mention, negative brand mentions, brand reputation, online review response, crisis response, brand defamation, ORM
Fix
Diagnose and resolve newly appearing schema validation errors flagged by drift detection.
Run the Schema Drift audit
Open the Schema Drift Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Drift audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-drift, new, validation, errors
Fix
Remove nofollow attributes from internal links so equity flows to the pages you want to rank.
Run the Internal Links audit
Open the Internal Links Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Internal Links audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: internal-links, nofollow, internal
Fix
Fix excessive nofollow backlink ratio. Pitch sources that pass authority, diversify follow vs nofollow, balance with attributes link signals.
1. Healthy follow vs nofollow distribution
For natural editorial profiles: Follow: 60-80% Nofollow: 20-40% Excessive nofollow indicators: - > 70% nofollow = source mix is weak - Heavy social profile links (LinkedIn, Twitter — all nofollow) - Wiki contributions (all nofollow) - Forum signatures (all nofollow) - Web 2.0 properties / profile sites (nofollow) - Some news/PR services (nofollow as policy)
2. New link attributes
Google's link attributes: rel="nofollow" Don't pass authority rel="sponsored" Paid or sponsored placement rel="ugc" User-generated content (no attribute) Standard follow link, passes authority Google MAY pass some authority via sponsored/ugc in some cases (unlike strict nofollow), but treat them similar to nofollow for planning.
3. Identify source-type bias
Audit your backlink profile by source type: Social profiles (LinkedIn bio, Twitter bio): 100% nofollow Wikipedia / wikis: 100% nofollow Forum posts / comments: 100% nofollow News PR wires (some): 100% nofollow Editorial blogs: Usually follow Resource pages: Usually follow Trade press articles: Usually follow Industry directories: Varies Guest posts: Usually follow Your profile percentage = (follow links / total) × 100
4. Build follow-link sources
To shift the ratio, build in editorial categories: Industry trade publications — bylined articles, expert commentary Editorial blogs — guest contributions, original research mentions Resource pages — university, government, association pages Best-of roundups — inclusion as recommended tool/source Podcast show notes — most podcasts use follow links Conference and event pages — usually follow
5. Don't abandon nofollow sources
Nofollow links still help: - Brand mentions (see how-to-fix-brand-mentions) - Referral traffic - Audience exposure - Some indirect ranking signal Keep building social, wiki, community presence. Just add follow sources to balance the mix.
6. Track ratio quarterly
Healthy trajectory: Quarter 1: 25% follow Quarter 4: 35% follow Year 2: 45% follow Year 3: 55%+ follow Gradual shift via consistent editorial outreach. No sudden spikes (looks unnatural). <div
Tip: 💡 Don't obsess over chasing the perfect ratio. A natural mix emerges from genuine activity across many channels. Sites that try to "fix" a nofollow-heavy profile via aggressive editorial link campaigns often build worse profiles (forced-looking patterns). Build broadly and the ratio improves organically.
Tags: backlinks, nofollow ratio, follow backlinks, link diversification, authority passing links
Fix
Fix conflicting noindex and Disallow rules from the Robots Tester. Disallow blocks crawl, noindex drops index. Use them in the right sequence, never together.
Remove Disallow rule
# If currently: User-agent: * Disallow: /to-deindex/ # Remove that line so Google can crawl
Add noindex to the pages
<!-- HTML meta --> <meta name="robots" content="noindex, follow"> <!-- "follow" tells Google to follow outbound links from the page --> <!-- Use "noindex, nofollow" only if you also don't want link signals to propagate -->
Force re-crawl in Search Console
Search Console → URL Inspection → enter URL → Request Indexing. Speeds up Google's re-crawl. Repeat for each URL or wait for natural crawl over 2-8 weeks.
Monitor deindexing
Search Console → Pages → "Excluded by noindex tag" count rises. site:example.com inurl:/to-deindex/ in Google search shows decreasing results.
After deindexing, optionally re-add Disallow
# Once URLs are confirmed deindexed (zero results in site: query): User-agent: * Disallow: /to-deindex/ # Now prevents future crawl budget waste Pages stay deindexed (noindex meta still applies on actual crawls), and budget isn't wasted on re-checking them. location ~* \.(pdf|doc|docx)$ { if ($request_uri ~* "/internal/") { add_header X-Robots-Tag "noindex, nofollow" always; } } <FilesMatch "\.(pdf|doc|docx)$"> Header set X-Robots-Tag "noindex, nofollow" </FilesMatch> // Express app.use('/api/', (req, res, next) => { res.setHeader('X-Robots-Tag', 'noindex'); next(); }); // Next.js — middleware.ts export function middleware(request) { if (request.nextUrl.pathname.startsWith('/staging/')) { const response = NextResponse.next(); response.headers.set('X-Robots-Tag', 'noindex, nofollow'); return response; } } // Express app.get('/old-discontinued-page', (req, res) => { res.status(410).send('Gone'); }); // nginx location = /old-discontinued-page { return 410; } # BEFORE (wrong): robots.txt: Disallow: /wp-admin/ HTML: <meta name="robots" content="noindex"> # Result: stays indexed # CORRECTION: # Step 1 — temporarily allow crawl robots.txt: # Disallow: /wp-admin/ (commented out) # Step 2 — noindex meta still in place # Step 3 — wait for deindexing # Step 4 — re-add Disallow once deindexed # Best: HTTP basic auth on staging (no crawl, no index, no deindex problem) # Plus: noindex header for belt-and-braces location / { auth_basic "Staging"; auth_basic_user_file /etc/nginx/.htpasswd; add_header X-Robots-Tag "noindex, nofollow" always; } # If PDFs already indexed: # Step 1 — add X-Robots-Tag: noindex header # Step 2 — Search Console URL Inspection to expedite # Step 3 — wait for deindexing # Step 4 — move files behind auth or delete
Re-run Robots Tester
Conflict findings clear. No URLs flagged with both Disallow and noindex active.
Search Console index coverage
"Indexed, though blocked by robots.txt" count should drop to zero. Deindexed pages move to "Excluded by noindex tag" or "Crawled - currently not indexed".
site: query confirmation
site:example.com inurl:/to-deindex/ returns zero results. Page no longer surfaces in normal search either.
Tip: 💡 The single rule: never combine Disallow and noindex on the same URL when you want it deindexed. Use one or the other, in the correct sequence. noindex first to drop indexed pages, then Disallow after deindexing completes to save crawl budget. Doing both simultaneously locks the URL into "indexed but uncrawlable" purgatory.
Tags: robots-sitemap, fix noindex disallow conflict, robots.txt vs meta noindex, X-Robots-Tag, deindex sequence, crawl vs index, noindex follow, robots conflict
Fix
Add og:type=article plus article:published_time, article:modified_time and article:author for proper article previews.
Run the Open Graph audit
Open the Open Graph Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Open Graph audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: og-generator, og, article
Fix
Force Facebook, LinkedIn and Twitter to refresh cached previews after updating og tags using their debug tools.
Run the Open Graph audit
Open the Open Graph Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Open Graph audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: og-generator, og, cache
Fix
Use the correct og:image dimensions (1200×630), under 1MB and absolute URL for reliable rich previews on every platform.
Run the Open Graph audit
Open the Open Graph Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Open Graph audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: og-generator, og, image, size
Fix
Set og:locale and og:locale:alternate correctly so social platforms render content in the right language and regional format.
Run the Open Graph audit
Open the Open Graph Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Open Graph audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: og-generator, og, locale
Fix
Configure OG tags in Next.js: Metadata API openGraph field, dynamic OG image generation with @vercel/og, Twitter cards, validation.
Set static OG in Metadata API
layout.tsx: openGraph: { title, description, url, siteName, locale, type, images: [{url: '/og-default.png', width: 1200, height: 630}] }. Site-wide defaults that any page can override.
Set per-page OG
page.tsx generateMetadata: openGraph: { title: post.title, description: post.excerpt, images: [{url: post.coverImage, width: 1200, height: 630}] }. Overrides layout defaults for this page.
Generate dynamic OG images with @vercel/og
Install @vercel/og. Create app/og/route.tsx as edge function returning ImageResponse with React JSX. URL like /og?title=Hello generates a 1200x630 image at request time. Use as image in metadata: openGraph: { images: [`/og?title=${encodeURIComponent(post.title)}`] }.
Configure Twitter Cards
metadata.twitter: { card: 'summary_large_image', title, description, images }. Often identical to OG; Next.js doesn't automatically derive Twitter from OG, set explicitly.
Set metadataBase
Required for OG image URLs to resolve absolute. layout.tsx: metadataBase: new URL('https://example.com'). Without it, OG image URLs are relative and platforms can't fetch them.
Validate
Facebook Sharing Debugger, Twitter Card Validator, LinkedIn Post Inspector. Refresh each after deploys. Verify image dimensions, title length, description length.
Optimise OG image dimensions and weight
1200x630 ideal. PNG or JPG (not WebP — preview platform support limited). Under 1MB. Test on slow connections — large OG images don't get fetched in time, fallback shown.
Tags: og-generator, Next.js Open Graph, Next.js OG image, Next.js @vercel/og, Next.js openGraph, Next.js social preview
Fix
Configure Shopify Open Graph tags: og:image, og:title, og:description per product/collection. Theme defaults, per-page overrides, Facebook debugger workflow.
Audit current OG output
View source on 5 URLs. Check og:title, og:description, og:image, og:url, og:type. Most Shopify themes output complete OG; problems are usually content (wrong image, missing description).
Set OG image fallback
Theme settings → Social → upload default OG image (1200x630). Used when a page has no specific image (e.g., contact, about). Branded fallback ensures consistent social previews.
Verify per-product OG image
Each product's OG image is its featured image. Audit products: every active product should have a featured image (Image tab in product editor). Products without images use the fallback — set above.
Override per-page where needed
For special landing pages, use theme.liquid conditionals or apps to set specific OG tags. Smart SEO has per-page OG override.
Set per-collection OG image
Collections also have an image field — used as OG image for collection page. Ensure every active collection has an image.
Test with Facebook Sharing Debugger
developers.facebook.com/tools/debug. Paste URL. Check rendering. 'Scrape Again' button refreshes Facebook's cache after changes.
Test Twitter Card and LinkedIn
cards-dev.twitter.com/validator, linkedin.com/post-inspector. Different platforms cache differently. Refresh each after OG changes.
Tags: og-generator, Shopify Open Graph, Shopify OG image, Shopify social sharing, Shopify Facebook preview, Shopify Twitter card
Fix
Add and validate Open Graph tags so links render with rich previews on Facebook, LinkedIn, Discord, Slack and iMessage.
Run the Meta Analyzer audit
Open the Meta Analyzer Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Meta Analyzer audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: meta-analyzer, og, tags
Fix
Resolve mismatches between og:title and the page that cause social platforms to render unexpected previews.
Run the Open Graph audit
Open the Open Graph Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Open Graph audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: og-generator, og, title, mismatch
Fix
Set og:url to the canonical, absolute URL of the page so social platforms attribute correctly.
Run the Open Graph audit
Open the Open Graph Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Open Graph audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: og-generator, og, url
Fix
Configure Open Graph tags in WordPress: og:title, og:description, og:image, og:type, og:url. Plugin settings, Facebook debugger workflow, per-page overrides.
Step 1: Audit current OG output
View source on 5 URLs. Check: og:title, og:description, og:image, og:url, og:type. If any missing or wrong, your social previews are suboptimal.
Step 2: Configure OG defaults in SEO plugin
Yoast → Social → Facebook. Rank Math → Titles & Meta → Social. AIOSEO → Social Networks. Set: default OG image (1200x630 PNG/JPG, under 1MB), default OG title pattern, default description pattern.
Step 3: Set per-post OG overrides
On individual posts, SEO plugin meta box has 'Social' or 'Open Graph' tab. Override: og:image (use a custom share image, not just the featured image), og:title (often punchier than SEO title), og:description (different from meta description, more shareable).
Step 4: Test with Facebook Sharing Debugger
developers.facebook.com/tools/debug → paste URL. Shows what Facebook sees. 'Scrape again' button refreshes Facebook's cache after you've made changes.
Step 5: Test with Twitter Card Validator
cards-dev.twitter.com/validator. Confirms Twitter card type is summary_large_image, image renders, title and description are correct. Twitter cards use OG tags as fallback but also support twitter:* specific tags.
Step 6: Configure LinkedIn previews
LinkedIn uses OG tags. Post Inspector (linkedin.com/post-inspector) shows what LinkedIn extracts. LinkedIn caches aggressively — 7 days. Refresh with the inspector after OG changes.
Step 7: Monitor and audit on plugin updates
OG output can regress on plugin updates. Periodically re-test 3-5 critical URLs in the Facebook Debugger. Add to monthly QA checklist.
Tags: og-generator, WordPress Open Graph, OG tags WordPress, Facebook sharing WordPress, og:image WordPress, social preview WordPress
Fix
Add complete Organization schema sitewide with name, url, logo, sameAs links and contact points.
Run the Schema Builder audit
Open the Schema Builder Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Builder audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-builder, organization, schema
Fix
Step-by-step fix for orphan pages from the Site Crawler. Identify orphans, decide keep-and-link or delete, build internal links and prevent recurrence.
Run the Site Crawler with sitemap cross-reference
Run a fresh Site Crawler with the option Include sitemap URLs enabled. The crawler crawls from your homepage and follows internal links; then it checks every URL in sitemap.xml against what it found. URLs in the sitemap but not reached are orphans.
Export the orphan list
Export to CSV. Columns: orphan URL, page title, last-modified date, sitemap inclusion. You'll often see 5-15% of indexed pages on mid-sized sites flagged as orphans.
Find related parent content
For each orphan worth keeping, identify 3-5 pages that should contextually link to it. Use the site search ( site:yourdomain.com [topic] in Google) or your CMS search to find pages about related topics.
Add contextual links
Edit each related page to add a contextual link to the orphan, ideally in the body content (not just a "related posts" widget). Use descriptive anchor text — not "click here" or "read more". The first link in body content carries more weight than a sidebar link.
Add to hub pages
If you have category/hub pages, add the previously-orphan URL to the relevant hub. Hub pages aggregate topical authority and pass it to linked spokes. # WordPress: Redirection plugin, add rule # nginx: rewrite ^/old-orphan/$ /new-target/ permanent; # Apache: Redirect 301 /old-orphan/ /new-target/ # Shopify: Online Store > Navigation > URL Redirects # nginx location = /old-orphan/ { return 410; } # Apache .htaccess Redirect 410 /old-orphan/
Editorial policy: no publish without inbound link
Require any new page to have at least one inbound internal link from a relevant parent page before publishing. Either via editorial workflow or CMS validation (custom plugin that checks for inbound links before allowing publish).
Quarterly orphan audit
Orphans accumulate as sites grow. Add an orphan audit to a quarterly SEO routine. Catch new orphans early when content memory is fresh and decisions are easier.
Tip: 💡 Adding ONE good contextual link from a relevant high-authority page does more than five sidebar links from low-authority pages. Quality of source matters more than quantity.
Tags: site-crawler, fix orphan pages, orphaned URLs, internal linking, site architecture, hub and spoke, topic clusters, link inbound, crawl orphans
Fix
Find product pages with zero internal links and either restore links from category/collection pages or remove from the sitemap.
Run the Ecommerce Crawl audit
Open the Ecommerce Crawl Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Crawl audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-crawl, orphan, products
Fix
Handle out-of-stock products correctly: schema availability flag for temporary OOS, 301 to parent for permanent OOS.
Run the Ecommerce Crawl audit
Open the Ecommerce Crawl Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Crawl audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-crawl, out, of, stock
Fix
Reduce excessive internal links from the same page that dilute equity and create poor user experience.
Run the Internal Links audit
Open the Internal Links Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Internal Links audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: internal-links, overlinking
Fix
Fix slow pages losing AI agent fetches. TTFB under 800ms, FCP under 2.5s, CDN edge cache, lighter HTML for agents, eliminate origin latency spikes.
curl from agent-region IPs
# Run from US-East (Virginia) or EU-West (Ireland) # Most AI infrastructure lives there curl -w "@-" -o /dev/null -s -A "GPTBot/1.0" https://example.com/ <<'EOF' dns: %{time_namelookup}s tcp: %{time_connect}s tls: %{time_appconnect}s ttfb: %{time_starttransfer}s total: %{time_total}s bytes: %{size_download} EOF
WebPageTest agent simulation
webpagetest.org → choose location US-East (or matching your audience) → set user agent to GPTBot /1.0 → "Disable JavaScript" ✓ → run. Reports TTFB + bytes + render time exactly as a crawler sees. # Cloudflare cache rule URL: example.com/* NOT URL: example.com/admin/* OR example.com/api/* Settings: Cache Level: Cache Everything Edge TTL: 4 hours (or longer for static content) Browser TTL: 1 hour Bypass on Cookie: session_* # Result for cached pages: TTFB drops from 600ms to 30-80ms globally # Origin only hit on cache miss or authenticated request # nginx Brotli + gzip gzip on; gzip_comp_level 6; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; brotli on; brotli_comp_level 6; brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # Brotli typically 15-25% smaller than gzip # Most agents accept Brotli (Accept-Encoding: br) # Strip whitespace, comments, redundant quotes # Cuts 20-40% of HTML bytes typically # Next.js: enabled by default in production # Astro: enabled by default # WordPress: use Autoptimize plugin or W3 Total Cache # Custom: html-minifier-terser at build time <!-- Bad: blocking JS in head --> <script src="/analytics.js"></script> <script src="/chat-widget.js"></script> <!-- Good: defer or async --> <script src="/analytics.js" defer></script> <script src="/chat-widget.js" async></script> <!-- Better: lazy-load on interaction --> <script> // Only load chat widget when user shows intent document.addEventListener('scroll', loadChatOnce, { once: true, passive: true }); </script> // Express middleware example app.use((req, res, next) => { const ua = req.get('User-Agent') || ''; if (/GPTBot|ClaudeBot|PerplexityBot|CCBot|OAI-SearchBot/i.test(ua)) { req.isAgent = true; } next(); }); // Route app.get('/products/:id', async (req, res) => { const product = await db.getProduct(req.params.id); if (req.isAgent) { // Stripped HTML: just content + schema, no images, no JS, no CSS return res.render('product-agent', { product }); } return res.render('product-full', { product }); }); // Agent version typically 80% smaller, parses in < 50ms UAS=("GPTBot/1.0" "ClaudeBot/1.0" "PerplexityBot/1.0" "anthropic-ai/1.0" "OAI-SearchBot/1.0") for ua in "${UAS[@]}"; do ttfb=$(curl -s -o /dev/null -A "$ua" -w "%{time_starttransfer}\n" https://example.com/) echo "$ttfb $ua" done # Every TTFB should be < 0.8 (800ms) # If 1+ exceeds, WAF challenge or origin congestion under those UAs
Tip: 💡 If you can do just one thing: Cloudflare or similar CDN with "cache everything" rule on public pages. TTFB drops from origin-bound (300-1500ms) to edge-bound (30-80ms) globally. Most agent speed problems disappear. CWV scores also improve as a side effect.
Tags: agent-readiness, page speed AI agents, TTFB agents, FCP agents, agent latency, CDN AI crawlers, origin latency
Fix
Optimise Next.js performance: choose rendering strategy, reduce client JS, use edge runtime, configure caching, optimise images, monitor real users.
Baseline
PageSpeed Insights on key pages. Vercel Analytics for field data. TTFB target < 400ms; LCP < 2.5s; INP < 200ms; CLS < 0.1.
Choose right rendering
Static pages (marketing, blog): SSG via getStaticProps or App Router static rendering. Dynamic content: SSR or ISR. Authenticated dashboards only: client-side. Never client-side for public content pages.
Reduce client JS
App Router: limit 'use client' to interactive components. Most layouts and content should be Server Components, no JS shipped.
Use next/image and next/font
Discussed in detail in 'Fix Images in Next.js' and CWV guides. Both eliminate common speed gotchas.
Configure caching
Static pages: cached by CDN automatically. SSR: configure revalidate (ISR) or cache headers. Vercel: cache headers honoured at edge. Self-hosted: configure Nginx or your CDN.
Use edge runtime where appropriate
Edge functions (middleware.ts, API routes with runtime: 'edge') run at global edge, lower TTFB than regional functions. Limits: no Node.js APIs, limited memory. Suitable for: middleware, auth checks, simple API responses.
Audit and defer third-party
next/script with strategy. Move analytics, chat widgets, marketing scripts to lazyOnload. Each third-party can cost 100-300ms.
Tags: pagespeed, Next.js page speed, Next.js performance, Next.js edge runtime, Next.js caching, Next.js LCP
Fix
Speed up Shopify: theme choice, app pruning, image optimisation, JS deferral, CDN settings, Shopify Plus features.
Baseline
PageSpeed Insights on homepage, collection, product. Note LCP, INP, CLS, TTFB and total page weight. Shopify TTFB should be 200-400ms globally; if higher, edge issue.
Audit theme
Pre-2.0 themes (Debut, Brooklyn) are speed-disadvantaged structurally. Dawn and other 2.0 themes (Sense, Studio, Crave) score significantly better. Theme migration is highest-leverage single change for stores on old themes.
Prune apps
Most Shopify stores have 8-15 apps installed but only need 4-6. Each app adds JS and CSS that loads on every page. Audit: which apps are essential? Uninstall unused. For essential apps with heavy weight, find lighter alternatives.
Optimise images
Verify Shopify CDN delivers WebP/AVIF. Compress large originals via Crush.pics or TinyIMG. Ensure responsive srcset working in theme.
Defer non-critical JS
Apps like Booster: Page Speed or Hyperspeed defer non-critical app JS. Test carefully — deferring some scripts breaks checkout.
Configure CDN aggressively
Standard Shopify uses Shopify's own CDN (Fastly-based). Shopify Plus stores can front with Cloudflare via custom DNS — adds another optimisation layer (image optimisation, edge caching). Worth doing for international stores.
Monitor and iterate
Re-test monthly. Plugin updates and content additions both regress speed. Set Search Console alerts for Core Web Vitals.
Tags: pagespeed, Shopify page speed, Shopify performance, Shopify theme speed, Shopify app speed, Shopify CDN
Fix
Speed up WordPress: caching plugins, image optimisation, database cleaning, CSS/JS minification, CDN, hosting choice, theme and plugin discipline.
Step 1: Baseline measurement
Before any fix, record where you stand. Run three tools on five representative URLs: PageSpeed Insights at pagespeed.web.dev — Google's official measurement, drives Core Web Vitals ranking signals. Note LCP, INP, CLS scores under mobile. GTmetrix — waterfall view of every resource, identifies blocking resources and slow requests. WebPageTest — multi-location testing including emerging-market connections that reveal CDN gaps. Record TTFB (Time To First Byte) — anything over 600ms means the origin server is slow before optimisations help. Total page weight — over 3MB is heavy; over 5MB is severe. Number of requests — over 100 is fragmented and benefits from combination.
Step 2: Install a caching plugin
This single step usually delivers the biggest speed gain on WordPress — caching means PHP doesn't regenerate the page for every visitor; instead the cached HTML is served directly. Three solid choices: WP Rocket ($59/year) — easiest setup, best out-of-box results, includes image lazy loading, database optimisation, font optimisation and CDN integration. The recommended choice if budget allows. WP Fastest Cache — solid free version, paid version ($49.99 one-time) adds minification and image optimisation. Performs nearly as well as WP Rocket with more manual configuration. W3 Total Cache — free, extremely powerful, complex. Best for advanced users running their own server with Redis/Memcached. Wrong choice for beginners. Whichever you pick: enable page caching, browser caching, GZIP compression. Test every page type after enabling — caching sometimes breaks logged-in user experiences (cart pages, account pages, dynamic content). Add exclusions for those URLs.
Step 3: Optimise images
Images are 50-70% of page weight on most WordPress sites. The image-specific workflow (bulk compression, WebP conversion, srcset verification, lazy loading, CDN delivery) is covered in detail in Fix Images in WordPress . Quick summary: install ShortPixel or Smush, bulk-compress, enable WebP, verify srcset works, confirm native lazy loading on below-fold images.
Step 4: Clean the database
Active WordPress sites accumulate database bloat: post revisions, transients (expired and unexpired), spam comments, orphaned post meta, draft auto-saves. On a 3-year-old blog this can mean 60% of database size is junk. Install WP-Optimize (free) or use WP Rocket's built-in database tab. Run: Remove post revisions (keep latest 5 per post) Remove expired transients Remove spam and trashed comments older than 30 days Remove orphaned post metadata Optimise database tables (defragment, reduce overhead) Before running: back up the database. Database cleaning is mostly safe but disasters happen. Limit future revisions via wp-config.php : define('WP_POST_REVISIONS', 5); define('AUTOSAVE_INTERVAL', 300);
Step 5: Minify and combine CSS/JS
Most caching plugins include this. Be cautious — minification can break things. Enable in stages: Minify CSS first. Almost always safe. Re-test every page type after enabling. Minify JS next. Sometimes breaks third-party scripts (analytics, chat widgets, payment processors). Test analytics fires, test form submissions, test e-commerce checkout. Combine JS last (or skip). Combines many small JS files into one — fewer HTTP requests. Frequently breaks page builders and dynamic features. With HTTP/2 and HTTP/3 the benefit of combining is reduced anyway; many performance experts now recommend skipping JS combination entirely. ⚠ After enabling any minification, run a full site-test pass: every template type, mobile and desktop, logged-in and logged-out. Set up Slack alerts for JS errors via a tool like Sentry to catch regressions.
Step 6: Add a CDN
A Content Delivery Network serves static assets (images, CSS, JS) from edge servers close to visitors, reducing latency. International audiences see the biggest benefit. Cloudflare (free) — sign up, change nameservers, enable orange-cloud proxy on www. Activate Brotli compression, HTTP/3, Rocket Loader (test it doesn't break anything), Mirage and Polish for image optimisation. Cloudflare APO plug-in ($5/month) further caches HTML at the edge. BunnyCDN — $1/month base, faster than Cloudflare in some regions, simple URL-rewrite via plugin. Either way, configure your caching plugin to rewrite asset URLs to the CDN domain. Test image and CSS URLs in view-source to confirm they're pointing at the CDN, not your origin.
Step 7: Audit hosting and theme
If TTFB exceeds 600ms even after caching and CDN, hosting is the bottleneck. Hosting tier matters more than most WordPress site owners realise: Cheap shared hosting (Bluehost, Hostgator basic): TTFB 800-1500ms, frequent outages, oversold servers Mid-tier shared hosting (SiteGround, A2): TTFB 500-800ms, better support, still shared resources Managed WordPress (Kinsta, WP Engine, Pressable): TTFB 200-400ms, server-level caching, expert support, $30-100/month VPS / dedicated (Hetzner CX21, DigitalOcean droplet, Linode): TTFB 100-200ms, requires server management skills, $5-50/month Theme choice matters but less than commonly believed. Lightweight themes (GeneratePress, Astra, Blocksy) load faster out of the box but a well-tuned heavy theme can outperform a poorly-configured lightweight one. The 80/20: pick a reputable lightweight theme, then optimise plugins. Plugin discipline is critical. Audit every plugin annually: which are essential, which are nice-to-have, which haven't been updated in 2+ years (security risk + likely slow). Aim for under 25 active plugins; over 40 plugins is a red flag.
Step 8: Re-measure and iterate
After each batch of changes, re-run PageSpeed Insights and GTmetrix. Compare against your baseline. Target: LCP under 2.5 seconds, INP under 200ms, CLS under 0.1. Field data (real-user CrUX metrics) takes 4-6 weeks to update. Re-test monthly because plugin updates regress performance. <div
Tip: 💡 Build a tracking sheet: date, page tested, LCP, INP, CLS, TTFB, total weight. Re-test after every major plugin/theme update. You'll quickly see which updates regress performance and which improve it.
Tags: pagespeed, WordPress page speed, WordPress caching, WP Rocket, WP fastest cache, WordPress LCP, WordPress site speed
Fix
Improve overall page speed: optimise images, defer non-critical resources, reduce TTFB, hit LCP under 2.5s.
Run the Site Audit audit
Open the Site Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Site Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: site-audit, page, speed
Fix
Break long paragraphs into shorter ones (2–3 sentences) for better scanability and reading ease.
Run the Readability audit
Open the Readability Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Readability audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: readability, paragraph, length
Fix
Reduce passive voice usage in content for clearer, more direct writing and improved reading ease scores.
Run the Readability audit
Open the Readability Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Readability audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: readability, passive, voice
Fix
Optimise PDPs for SEO: unique product copy, complete schema, internal links from category pages, fast load.
Run the Ecommerce Audit audit
Open the Ecommerce Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-audit, pdp
Fix
Improve PDP loading speed: hero image preload, reduce third-party widgets, defer below-fold content.
Run the Ecommerce Audit audit
Open the Ecommerce Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-audit, pdp, speed
Fix
Diagnose why Perplexity doesn’t cite your site and improve the signals it relies on: authority, schema, content extractability.
Run the AEO audit
Open the AEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your AEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: aeo, perplexity, invisibility
Fix
Resolve conflicts when multiple plugins emit competing schema markup on the same page.
Run the Schema Drift audit
Open the Schema Drift Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Drift audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-drift, plugin, schema, conflicts
Fix
Identify and address messaging or positioning gaps where competitors differentiate and you don’t.
Run the Competitor Analysis audit
Open the Competitor Analysis Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Competitor Analysis audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: competitor-analysis, positioning, gaps
Fix
Amplify positive brand mentions for SEO, AEO, and conversion. Convert reviews into testimonials, mentions into case studies, advocates into ambassadors.
1. Capture comprehensively
Set up monitoring across all platforms positive mentions can appear: Review platforms — G2, Capterra, TrustRadius, Trustpilot, Yelp (per category) Press / trade publications — Google Alerts, Talkwalker, mention monitoring Social media — Twitter/X, LinkedIn, Reddit, niche communities YouTube — descriptions, video mentions, comments Podcasts — show notes, transcripts (where available) Blogs — backlink monitor catches mentions with links; supplement with brand search for unlinked mentions Internal channels — customer support tickets praising the product, sales calls with quotable moments (with consent)
2. Build a structured library
Spreadsheet / Notion / Airtable with fields: - Mention date - Author name + title - Author organization - Platform / URL - Mention text (verbatim quote) - Mention type (review / press / social / podcast / blog) - Sentiment intensity (1-5) - Use rights (asked / granted / public / unclear) - Asset created (testimonial / case study / quote card / etc) - Status (raw / asked / approved / deployed) This becomes the source-of-truth for marketing/sales/SEO to draw from. Single place, kept current, structured for reuse.
3. Triage the top mentions
Not all positive mentions are equal. Prioritise: Recognised brands — mention from a customer your prospects know by name (Acme Corp, well-known media outlet) carries disproportionate weight Recognised individuals — industry analysts, influencers, technical authorities in your category Specific outcomes — "increased X by 47%" or "saved us 12 hours per week" beats "we love it" Honest framing — balanced reviews that acknowledge limitations but still recommend are more credible than gushing ones Long-form — full blog post or case study mentions carry more substance than one-line social posts Recent — within last 12 months for B2B; within last 6 for B2C From the library, identify top 20-50 for expansion effort. The Pareto reality: 80% of social proof value comes from 20% of mentions.
4. Request permission to reuse
Email template Subject: Permission to quote your mention of [your brand] Hi [name], I came across your [tweet/review/post/article] mentioning [your brand] on [date]: "[verbatim quote]" Thank you — that genuinely meant a lot. With your permission, we'd like to feature this quote on our [landing page / case study / testimonials page], with attribution to you ([name, title, company]). If you're open to it, I'd also love to chat for 15 minutes about your specific use case — we'd write it up as a case study (with your approval before publishing) and you'd get featured prominently. Either way: thank you again for the kind words. [Your name] [Your role] Response rates: 40-70% yes for individual quotes. 20-40% yes for case study expansion. Better than most cold outreach because you're starting from genuine warmth.
5. Repurpose into assets
Asset 1: Landing page testimonial <div class="testimonial"> <img src="/customers/jane-baker.jpg" alt="Jane Baker"> <blockquote>"[exact quote with permission]"</blockquote> <cite> <strong>Jane Baker</strong> <span>Sales Operations Lead, Acme Corp</span> </cite> </div> With schema: { "@type": "Review", "author": { "@type": "Person", "name": "Jane Baker", "jobTitle": "Sales Operations Lead", "worksFor": { "@type": "Organization", "name": "Acme Corp" } }, "reviewBody": "[exact quote]", "itemReviewed": { "@type": "Product", "name": "[your product]" }, "datePublished": "2026-03-15" } Asset 2: Full case study For high-value mentions, expand to full case study with interview. Standard structure: Title: How [Customer] [achieved specific outcome] with [your product] Sections: 1. Customer profile (size, industry, what they do) 2. Challenge they faced before your product 3. Why they chose your product (vs alternatives) 4. Implementation 5. Specific results (metrics where possible) 6. Quote / endorsement 7. What's next for them Length: 800-1500 words, conversational not corporate Production: 30-90 minute interview, light editing, customer approval Asset distribution: landing pages, sales pitches, blog post, PDF download, video summary if budget allows Asset 3: Logo wall For customers who give written permission to use logo: build a "trusted by" section. Show on homepage, sales pages, demo flow. Single logo carries weight; 8-20 logos carries dramatically more. Permission must be explicit and current (refresh annually). Asset 4: Social proof carousel Rotating display of quotes on key pages. Implementation: simple JavaScript carousel, or library like Senja, Testimonial.to. Keep rotation gentle (8+ seconds per slide) so visitors can read. Asset 5: Press quote section For press mentions, build a /press or /in-the-news page aggregating all coverage. Include: publication logo, headline, brief excerpt, link. This page itself becomes a strong AEO citation source — when AI engines are asked "what does X say about [your brand]" they often surface press aggregation pages. Asset 6: Review showcase Embed widget showing live reviews from G2 / Trustpilot: - Most platforms provide embed code - Refreshes automatically as new reviews come in - Adds external-source credibility - Schema markup typically included Plus selective full-quote display for highest-value reviews. Cycle which ones feature; keep page feeling current.
6. Engage with mention authors
The follow-up after permission isn't just transactional — it's relationship building: Send a thank-you (handwritten note works wonders for B2B) Send the published asset for their approval and to share with their network Offer them inclusion in customer council / beta access / referral programme Notice their other content — engage with it authentically Year-on-year: send a check-in, ask about new wins, refresh the testimonial Loyal customers who feel valued become repeat advocates. One mention turns into five mentions over two years.
7. AEO benefit of positive mention amplification
Beyond direct marketing value, captured positive mentions improve AI visibility: AI engines reading your /customers or /press page extract the quotes as evidence Schema Review entities attached to testimonials build product reputation signal Diverse mention sources (different customers, different press, different roles) signal broad adoption Named individuals with verifiable roles strengthen entity signals (per schema references ) Recency of mentions signals current relevance vs historical The same effort that boosts conversion on your landing pages also improves how AI engines describe your product when users ask about your category.
8. Avoid pitfalls
Fabricated testimonials — detected, hurt trust catastrophically when discovered Stock photos for "customers" — reverse-image-searchable, look fake Vague attributions — "John D., Marketing Director" looks generic; full name + company + photo carries weight Stale testimonials — 2018 quotes feel like the product hasn't earned new advocates since Over-curating — too-polished testimonials read as scripted; some raw enthusiasm signals authenticity Ignoring negative-balanced mentions — "I love X about it, though Y is rough" is more credible than pure gushing; consider using with permission One-and-done campaigns — ongoing capture beats sporadic batch projects <div
Tip: 💡 The compound win: every captured positive mention reinforces every other. A landing page with 20 named, photographed, attributed quotes — drawn from G2, Twitter, press, podcasts — outperforms one with three generic testimonials by 2-4x in conversion. And that same density signals AI engines you're a real, validated category player. Capture relentlessly, deploy strategically, refresh quarterly.
Tags: brand-mention, positive brand mentions, testimonial amplification, customer advocacy, brand mentions SEO, review marketing
Fix
Force every social platform (Facebook, LinkedIn, Twitter, Slack, Discord) to refresh cached link previews.
Run the Social Preview audit
Open the Social Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Social Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: social-preview, preview, cache
Fix
Debug Next.js social previews on Facebook, Twitter, LinkedIn: Metadata API OG, @vercel/og dynamic images, Vercel deployment caching, refresh workflows.
Test current preview per platform
Facebook Sharing Debugger, Twitter Card Validator, LinkedIn Post Inspector, WhatsApp (paste into draft). Each shows different behaviour. Test all four — they cache differently.
Verify metadataBase configured
layout.tsx: metadataBase: new URL('https://example.com'). Without it, relative OG image URLs don't resolve and social platforms can't fetch images.
Verify openGraph image URL absolute
Metadata API openGraph.images can be relative if metadataBase set, or absolute. Test: view source on production page, find og:image meta tag, copy URL, paste in browser — should load image.
Set up @vercel/og for dynamic images
If using dynamic OG images, ensure /api/og or /og route is working. Visit URL directly in browser — should return PNG image. Common bugs: edge runtime not configured, JSX in @vercel/og has unsupported features (e.g., complex CSS).
Refresh cached previews per platform
Facebook: Sharing Debugger → Scrape Again. Twitter: re-paste URL in Card Validator. LinkedIn: Post Inspector → Inspect. WhatsApp: hardest (30-day cache), append ?v=2 query parameter to force refresh.
Verify Vercel Image Optimization isn't breaking OG
Vercel auto-optimises images via /_next/image. OG images don't always go through this; verify image URLs in OG tags are not /_next/image URLs (some platforms reject the path).
Monitor across platforms
Monthly: re-test 3-5 critical URLs in each platform debugger. After each Next.js or Vercel deploy: spot-check.
Tags: social-preview, Next.js social preview, Vercel og image, Next.js Facebook preview, Next.js Twitter card, Vercel og
Fix
Debug Shopify social previews: cached old image, missing OG, wrong dimensions, Twitter cards, LinkedIn caching, WhatsApp gotchas.
Test current preview per platform
Facebook Sharing Debugger, Twitter Card Validator, LinkedIn Post Inspector, WhatsApp (paste into a draft). Note differences per platform — usually indicate platform-specific issues.
Fix common
missing OG image. Product or page has no featured image — OG falls back to default or none. Audit: every active product needs a featured image. Set theme default OG image for non-product pages.
Fix common
wrong dimensions. 1200x630 ideal. Smaller (e.g., 600x600 square crop) gets cropped or rejected. Shopify CDN handles resizing if you upload large enough — upload 2400x1260 originals, CDN serves 1200x630.
Force refresh cached previews
Facebook: Sharing Debugger → Scrape Again. Twitter: re-paste in Card Validator. LinkedIn: Post Inspector → Inspect. WhatsApp: hardest — caches 30 days, sometimes requires URL parameter to bypass (?v=2).
Fix Twitter card type
Should be summary_large_image. Default Shopify themes output this. If 'summary' (small thumbnail) by mistake, override in theme.liquid.
Fix WhatsApp preview-not-showing
WhatsApp needs OG in first 4KB of HTML, image under 300KB. Shopify with many apps loads heavy head; move OG tags earlier in theme.liquid head.
Monitor
Monthly re-test 3-5 critical URLs in each platform's debugger. After theme/app updates, re-test immediately.
Tags: social-preview, Shopify social preview, Shopify Facebook preview, Shopify Twitter card, Shopify OG, Shopify sharing
Fix
Debug WordPress social previews: cached old image, missing tags, wrong dimensions, broken Twitter cards, LinkedIn caching, refresh workflows per platform.
Step 1: Test current preview on each platform
Facebook Sharing Debugger, Twitter Card Validator, LinkedIn Post Inspector, WhatsApp link preview (paste into a draft message). Note what each shows. Differences often indicate platform-specific issues.
Step 2: Fix common: missing OG image
Each post should have a featured image set (required for OG). Audit posts without featured images. Set default OG image in SEO plugin for posts where no featured image exists.
Step 3: Fix common: OG image too large or wrong dimensions
1200x630 ideal. Over 8MB rejected by Facebook. Wrong aspect ratios get cropped awkwardly. Audit and replace problem images. Use a plugin that auto-generates OG images at correct dimensions (Branda, OG Image Generator).
Step 4: Force refresh cached previews
Facebook: Sharing Debugger → Scrape Again. LinkedIn: Post Inspector → Inspect. Twitter: Card Validator typically refreshes on each test. WhatsApp: harder — they cache for ~30 days; sometimes require URL parameter to bypass (?v=2).
Step 5: Fix Twitter card type
Default should be summary_large_image (large hero image preview). Some plugins default to 'summary' (small thumbnail). Override in plugin's Social/Twitter settings.
Step 6: Fix WhatsApp preview-not-showing
WhatsApp requires: OG tags in first 4KB of HTML, og:image accessible without auth, image under 300KB, server returns 200 for image URL with proper Content-Type. Often fails on heavy WordPress headers — move OG tags up in .
Step 7: Set up monitoring
Periodically (monthly) re-test critical URLs in each platform's debugger. After major plugin updates, immediately re-test. Track OG image rendering — broken images on social are a slow-bleed brand issue.
Tags: social-preview, WordPress social preview, Facebook preview WordPress, Twitter card WordPress, LinkedIn preview WordPress, social sharing WordPress
Fix
Update your privacy policy to satisfy GDPR Articles 13 and 14: every disclosure, plain language, easy to find.
Run the GDPR audit
Open the GDPR Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your GDPR audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gdpr, privacy, policy
Fix
Sign GDPR-compliant Data Processing Agreements (DPAs) with every processor handling personal data on your behalf.
Run the GDPR audit
Open the GDPR Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your GDPR audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: gdpr, processor, contracts
Fix
Optimise product images: descriptive filenames, alt text, multiple angles, WebP format, lazy-loading.
Run the Ecommerce Audit audit
Open the Ecommerce Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-audit, product, images
Fix
Fix product page internal linking: related products, cross-sells, category-up links, breadcrumbs, content-to-product links.
Add comprehensive breadcrumbs
Home → Category → Subcategory → Product. Schema.org BreadcrumbList. Helps SEO (link equity flow) + UX (back-navigation). Visible on every product page.
Curate related products
'Related products' section — 4-8 truly related items. Automatic recommendations often weak (random products from same category). Manual curation or sophisticated recommendation algorithm more effective. Strong related-product sections boost both SEO and AOV.
Cross-sell with intent
'Frequently bought together' (Amazon's model). Bundle-aware. Encourage multi-item purchases. SEO benefit: distributes link equity across product range; conversion benefit: increases average order value.
Link from blog content to products
Editorial content drives traffic; product links drive conversion. Blog posts about category should link to relevant products with descriptive anchor text. 'Here are our [recommended lightweight backpacks for trail running]'.
Use 'category up' links
Product page links back to parent category. Helps users navigate; helps Google understand hierarchy.
Link comparison and FAQ content
Comparison pages ('Product A vs Product B') and FAQ content link to product pages. Bottom-of-funnel content drives high-converting traffic to products.
Audit orphan products
Products only reachable through navigation drilling (not linked from content, related products, or comparison pages) underperform. Surface via 'related products' on similar items + editorial content.
Tags: internal-links, product page internal linking, related products SEO, cross-sell SEO, ecommerce internal links, product SEO architecture
Fix
Resolve Product schema validation errors: required fields (name, image, offers), brand, sku, aggregateRating compatibility.
Run the Schema Debugger audit
Open the Schema Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-debugger, product, schema
Fix
Handle product variants (size, colour) consistently: canonical to parent or separate URLs, schema variants, no duplicates.
Run the Ecommerce Crawl audit
Open the Ecommerce Crawl Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Crawl audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-crawl, product, variants
Fix
Accelerate ranking recovery after migration: monitor week-by-week, fix the gaps causing drops, request re-crawl of recovered pages.
Run the Migration Audit audit
Open the Migration Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Migration Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: migration-audit, ranking, recovery
Fix
Improve blog post readability: sentence length, paragraph structure, transitions, active voice, vocabulary, scannability for web readers.
Measure current readability
Tools: Hemingway Editor (highlights long sentences, passive voice, complex words), Grammarly, Readable. Target Flesch Reading Ease 60-70 for general blog content; 50-60 for technical. Lower scores OK in specialised contexts.
Shorten long sentences
Sentences over 25 words: hard to follow. Break into shorter ones. Best blog writing: average sentence length 15-20 words, with occasional shorter (5-8) and longer (25+) for rhythm. Variety matters.
Improve paragraph structure
One-idea paragraphs work for web. 2-4 sentences per paragraph average. Long paragraphs (8+ sentences) overwhelm web readers — break up. White space helps scanning.
Add visual breaks
Subheadings every 200-300 words. Bullets for lists. Images every 400-600 words. Pull quotes for emphasis. Block quotes for citations. Visual variety prevents scrolling fatigue.
Use active voice
'The team launched the product' (active) > 'The product was launched by the team' (passive). Active voice is more direct, shorter, more engaging. Passive has legitimate uses (when actor unknown or unimportant); minimise.
Optimise vocabulary
Complex words where simpler work: replace. 'Utilise' → 'use'. 'Approximately' → 'about'. Industry jargon: define on first use for general audience; use freely in specialised content. Match vocabulary to audience expertise.
Read aloud as final check
Read your draft out loud. Awkward phrasing, run-on sentences, unclear references — all surface when read. Fix what trips you up. Best readability check available; takes 10 minutes per post.
Tags: readability, blog readability, article readability, blog writing, readability score, blog SEO writing
Fix
Improve product page readability: spec layout, benefit-led copy, trust signals, scanning patterns, conversion-focused structure.
Establish information hierarchy
Critical info above fold: product name, price, key image, primary CTA. Secondary: short description, benefit bullets, reviews summary. Tertiary: full description, specs table, FAQs, related products. Hierarchy maps to decision flow.
Write benefit-led copy
Headlines and lead copy emphasise benefits (what it does for user), not features (what it is). 'Filters 99.9% of impurities in 30 seconds' (benefit) vs 'Multi-layer carbon filtration system' (feature). Features support benefits in detail sections.
Spec tables for technical buyers
Some buyers want exhaustive specs (electronics, automotive, machinery). Provide in dedicated table. Two-column format: spec name | value. Scannable, copy-pasteable, supports comparison shopping.
Add scannable benefit bullets
3-7 key benefits as bulleted list. Each bullet: lead with the benefit verb, brief explanation. Visual scanning friendly. Don't write paragraphs where bullets work.
Show trust signals near CTA
Reviews stars + count near 'Add to Cart'. Trust badges (verified seller, secure checkout). Return policy summary ('30-day return'). Shipping info ('Free over £50'). All build confidence to convert.
Mobile-first layout
Most ecommerce traffic is mobile. Test on mobile: hierarchy clear, CTA visible without scrolling, spec table responsive. Tap targets adequate (44px minimum).
Optimise for 'comparison shopping' use case
Buyers compare 3-5 products before buying. Each product page should make comparison easy: prominent specs, clear differentiation from similar products, FAQ addressing common questions. Site search within product type. Comparison-friendly product pages convert more.
Tags: readability, product page readability, ecommerce copy, product description SEO, product page structure, product page conversion
Fix
Improve technical documentation readability: information architecture, code examples, error reference, navigation, search optimisation.
Design information architecture
Hierarchy: getting started → core concepts → API reference → guides → troubleshooting. Each level serves different reader intent. Side navigation visible; users navigate from intent to information.
Front-load with examples
Each concept: show code first, then explain. Developers read code more easily than prose. 'Here's how to authenticate:' [code block] then 'In this example, we...' Code first reduces time-to-understanding.
Provide complete runnable examples
Snippets that work in isolation. Include imports, setup, expected output. Copy-paste should run; not 'here's the relevant line in context'. Complete examples reduce friction.
Document errors comprehensively
Every error your system produces: documented with cause, solution, example. Searchable. When developers hit error, they search; comprehensive error docs save support tickets and improve developer experience.
Optimise for in-doc search
Site search (Algolia DocSearch, similar) on every page. Search is primary navigation for docs. Indexes: titles, headings, code, prose. Lightning-fast results.
Use diagrams for architecture
Sequence diagrams (Mermaid, PlantUML) for request flows. Architecture diagrams for system components. Visual representations save thousands of words. Embed inline.
Test with real users
Watch developers use your docs. Where do they get stuck? Where do they search? Where do they leave? Improve weak spots. Best docs teams run usability testing on documentation like they would on product.
Tags: readability, technical documentation, API docs, docs readability, developer documentation, docs SEO
Fix
Restore eligibility for the Recipe rich result carousel: required schema fields, image, totalTime, recipeIngredient.
Run the Rich Snippets audit
Open the Rich Snippets Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Rich Snippets audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: rich-snippets, recipe, carousel
Fix
Resolve Recipe schema validation errors: required fields, image, ratings, prep/cook time formats.
Run the Schema Debugger audit
Open the Schema Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-debugger, recipe, schema
Fix
Step-by-step fix for redirect chains and loops from the Site Crawler. Diagnose multi-hop redirects, collapse to single 301, prevent edge-vs-origin conflicts.
Run the crawler
Run the Site Crawler with the option Report redirect chains enabled (it usually is by default). Export the chains CSV. Columns: starting URL, hop 1, hop 2, ..., final URL, final status.
Verify each chain with curl
The crawler shows what it found during the scan. Server state may differ now. Spot-check the worst offenders with: curl -ILs --max-redirs 10 https://yourdomain.com/old-path/ 2>&1 | grep -iE "^(http|location)" Expected good output: one HTTP/2 301 , one location: https://... , one HTTP/2 200 . Anything more is a chain.
Audit existing redirects
WP Admin → Tools → Redirection → Redirects . Sort by source URL. Find the redirects that chain together. For chain /old-A/ → /old-B/ → /final/ : edit the first rule to point directly at /final/ , then delete the middle rule.
Audit rewrite and return rules
In your vhost config: # BEFORE (chain) rewrite ^/old-a$ /old-b permanent; rewrite ^/old-b$ /final permanent; # AFTER (single hop) rewrite ^/old-a$ /final permanent; rewrite ^/old-b$ /final permanent; Keep the rule for /old-b in case external links exist. Reload nginx: nginx -t && nginx -s reload
Refactor Redirect chains
# BEFORE (chain) Redirect 301 /old-a /old-b Redirect 301 /old-b /final # AFTER (single hop) Redirect 301 /old-a /final Redirect 301 /old-b /final Save the .htaccess file. Changes apply immediately.
Audit existing redirect rules
Cloudflare Dashboard → Rules → Redirect Rules (or Bulk Redirects ). Find chained rules and edit them to point at the final destination.
Identify which layer owns which redirects
Run curl with verbose output to see where the redirect comes from: curl -ILs https://www.yourdomain.com/old/ 2>&1 | grep -iE "^(http|server|location|cf-ray)" The cf-ray header confirms Cloudflare was involved. server: cloudflare on the 301 response means Cloudflare did it. server: nginx means origin did it.
Consolidate at edge layer (recommended)
Move ALL content-related redirects to Cloudflare Redirect Rules. Remove them from origin .htaccess/nginx config. Origin then only handles application requests; redirects fire once at the edge.
Find the loop
curl -ILs --max-redirs 10 https://yourdomain.com/path/ 2>&1 | grep -i "^location" If the same URL appears twice in the location list, you have a loop. Common cause: HTTP-to-HTTPS rule plus a non-HTTPS canonical somewhere.
Trace and remove the offending rule
Look at all redirect rules that touch the looping URL pattern. One of them sends traffic back. Remove or fix.
Tip: 💡 You don't need to update every legacy link. Focus on links from your most-trafficked pages — they're the ones causing measurable redirect overhead on real users.
Tags: site-crawler, fix redirect chains, redirect loops, 301 chains, multi-hop redirects, redirect map, redirect audit, edge redirects, origin redirects
Fix
Fix redirect loops from the Redirect Checker. Diagnose ERR_TOO_MANY_REDIRECTS, find conflicting rules at edge, origin, and CDN. Resolution checklist.
Browser test in incognito
Incognito ensures no cached redirect or auth state. Visit the affected URL. Browser shows ERR_TOO_MANY_REDIRECTS after ~20 hops.
curl trace
curl -IL --max-redirs 10 -v https://example.com/affected-page 2>&1 | \ grep -E "< HTTP|< Location|> Host" Shows each hop's status and target. Look for the URL that appears twice — that's where the loop kicks in.
Identify the loop pattern
Common patterns: A → B → A (2-hop loop, most common) A → B → C → A (3-hop, rarer) HTTP → HTTPS → HTTP (CDN/origin protocol confusion) www → non-www → www (canonicalisation conflict) # Cloudflare "Always Use HTTPS" page rule: ON # Origin nginx config: server { listen 80; return 301 https://$host$request_uri; } # But Cloudflare connects to origin on port 80 (Flexible SSL mode) # Origin sees HTTP request, redirects to HTTPS # Cloudflare receives HTTPS redirect, applies its own HTTPS rule (already HTTPS, no change) # But if Cloudflare connects via HTTP and origin's redirect target is HTTPS via Cloudflare... # Result: depends on Cloudflare SSL mode # Fix: set Cloudflare SSL to "Full" or "Full (Strict)" — origin connection uses HTTPS # Then disable origin's HTTP→HTTPS redirect, let Cloudflare handle it # OR keep origin's redirect and use Cloudflare "Off" SSL (not recommended) # Cloudflare page rule: www.example.com → example.com (301) # .htaccess on origin: RewriteCond %{HTTP_HOST} ^example\.com$ RewriteRule (.*) https://www.example.com/$1 [R=301,L] # Cloudflare strips www → origin sees non-www → origin adds www → Cloudflare strips again. LOOP. # Fix: pick one place to do canonicalisation. Disable at the other. # WordPress Redirection plugin: /old-page → /new-page (301) # .htaccess (forgotten from previous setup): Redirect 301 /new-page /old-page # Plugin and .htaccess each pointing the other's source at its target. LOOP. # Fix: check .htaccess for orphan Redirect/RewriteRule lines. # Delete the ones that conflict with plugin-managed redirects. # Cloudflare "Flexible SSL": connects to origin via HTTP # Origin checks: if $scheme != "https" return 301 https://... # Origin always sees HTTP from Cloudflare, always 301s, infinite loop # Fix: use X-Forwarded-Proto header instead of $scheme if ($http_x_forwarded_proto != "https") { return 301 https://$host$request_uri; } # Origin trusts the header that Cloudflare sets. No loop.
List every redirect source
Where can a redirect originate? CDN (Cloudflare page rules, redirect rules, workers) Load balancer (AWS ALB, GCP LB) nginx/Apache server config .htaccess WordPress core (e.g. canonical redirect) SEO plugin (Yoast, Rank Math, AIOSEO) Dedicated redirect plugin (Redirection, Simple 301) Application-level (Express middleware, Django middleware, Rails)
Pick one canonical authority per concern
For each redirect type, decide which layer owns it: HTTPS upgrade: CDN (cheapest, fastest) www canonicalisation: CDN Trailing slash : server config Marketing campaign redirects: dedicated plugin or app Permanent URL moves: dedicated plugin or app
Disable redirects at all other layers
For HTTPS handled by Cloudflare: comment out the server-level HTTPS redirect. Test. Confirm Cloudflare's redirect still works without origin help.
Re-test the loop URLs
curl -IL each URL. Should resolve in 1-2 hops without ERR_TOO_MANY_REDIRECTS. # redirect-ownership.md # - HTTPS upgrade: Cloudflare → "Always Use HTTPS" page rule # - www canonicalisation: Cloudflare → page rule www.* → root domain # - Trailing slash: Origin nginx → return 301 with slash # - Custom moves: WordPress Redirection plugin # # DO NOT add redirects in .htaccess or origin server config without updating this doc. # Test with curl mimicking different agents curl -IL -A "Mozilla/5.0 (iPhone; ..." https://example.com/page curl -IL -A "Mozilla/5.0 (X11; Linux x86_64) ..." https://example.com/page # If only one User-Agent loops, the mobile detection logic has a bug. # Reproduce without cookies curl -IL --cookie-jar /dev/null https://example.com/page # If clean without cookies but loops with cookies, check cookie-setting on the redirect target.
Test in fresh incognito
Affected URLs load without ERR_TOO_MANY_REDIRECTS. Page renders normally.
Run the Redirect Checker site-wide
No URLs flagged for loops. Chains all 1-2 hops.
Monitor Search Console for crawl errors
"Redirect error" findings in Search Console drop after Google re-crawls (1-4 weeks).
Tip: 💡 The single rule that prevents most loops: one redirect layer per concern. HTTPS upgrade gets handled at exactly one place. www canonicalisation gets handled at exactly one place. Trailing slash at exactly one place. Document it, enforce in code review.
Tags: redirect-checker, fix redirect loop, ERR_TOO_MANY_REDIRECTS, redirect debugging, infinite redirect, CDN origin loop, HTTPS loop, conflicting redirects
Fix
Fix redirects in Apache and nginx: RewriteRule patterns, nginx return 301, regex pitfalls, permanent vs redirect flags, performance considerations.
Choose correct redirect directive
Apache: Redirect (simple), RedirectMatch (regex), RewriteRule (full regex + flags). nginx: return (recommended), rewrite (less efficient). For SEO-critical: prefer Apache Redirect/RedirectMatch and nginx return — simpler, faster, fewer pitfalls than RewriteRule/rewrite.
Write simple redirects first
Apache: 'Redirect 301 /old-page /new-page'. nginx: 'location = /old-page { return 301 /new-page; }'. Exact-match simple — no regex needed for one-to-one redirects.
Write regex carefully
Apache RewriteRule: '^old-pattern/(.*)$ /new-pattern/$1 [R=301,L]'. nginx rewrite: 'rewrite ^/old-pattern/(.*)$ /new-pattern/$1 permanent;'. Common pitfalls: forgetting ^ start anchor (matches anywhere), forgetting $ end anchor (matches partial), greedy quantifiers (.+ vs .+?) causing wrong captures.
Use correct flags
Apache: R=301 (permanent) vs R=302 (temporary). L (last — stop processing). NC (case-insensitive if needed). nginx: 'permanent' (301) vs 'redirect' (302). Forget L and rules cascade; intended single redirect becomes a chain.
Prevent redirect loops
Test: does the rule's destination match its own pattern? If 'rewrite ^/foo/(.*)$ /bar/$1 permanent;' and /bar pattern also redirects, loops. Add condition guards (RewriteCond in Apache) or restructure patterns.
Test before deploying
Apache: 'apachectl configtest'. nginx: 'nginx -t'. Both validate syntax. Beyond syntax: test redirects with curl -I -L from staging environment. Validate all critical SEO redirects before production deploy.
Deploy with rollback ready
Backup current config. Deploy new. Test immediately with curl. Have rollback command ready (cp old.conf current.conf && nginx -s reload). Production redirect bugs cause outages or SEO disasters; rollback speed matters.
Tags: redirect-checker, Apache redirects, nginx redirects, RewriteRule, nginx return 301, Apache 301 redirect
Fix
Fix Cloudflare redirects: Page Rules vs Bulk Redirects vs Redirect Rules, 301 vs 302, Workers for complex logic, edge vs origin redirects.
Understand the four mechanisms
Page Rules — legacy, limited to 3 free / 20 paid, being deprecated. Bulk Redirects — high-volume (up to 100K-1M+ depending on plan), simple URL-to-URL mappings. Redirect Rules — modern replacement for Page Rules, more flexible, included in all plans. Workers — JavaScript at edge for complex logic. Choose by use case.
For simple bulk redirects use Bulk Redirects
Cloudflare → Rules → Bulk Redirects. Upload CSV: source URL, target URL, status code (301 or 302), preserve query string flag. Scales to hundreds of thousands. Best for migrations or large redirect lists.
For pattern-based redirects use Redirect Rules
Cloudflare → Rules → Redirect Rules. Conditions (URL contains/equals/regex) + actions (redirect to URL with status). Modern interface. Best for: www → non-www, HTTP → HTTPS, mobile redirects, geo-redirects.
For complex logic use Workers
Custom JavaScript at edge. Conditional redirects based on user agent, cookie, geolocation, query strings, A/B test cohort. Most powerful, requires development. $5/month for 10M requests.
Configure status code correctly
301 for permanent. 302 for temporary. 307/308 for HTTP method preservation (rare). Default: 302 in Cloudflare UI — change to 301 explicitly for SEO redirects.
Resolve edge vs origin conflicts
Cloudflare runs at edge (before origin). If origin server (Apache, nginx) also has redirects, both can fire — chain. Audit: pick one layer for each redirect. Cloudflare typically faster (edge response, no origin hit) but origin redirects persist if you ever bypass Cloudflare.
Test. curl -I -L through Cloudflare
Test with Cloudflare cache bypass (Page Rules cache bypass on test URL) to ensure rules apply. Cloudflare's redirect Rules section has a 'Test' button for individual rules.
Tags: redirect-checker, Cloudflare redirects, Cloudflare Page Rules, Cloudflare Bulk Redirects, Cloudflare Redirect Rules, Cloudflare Workers redirects
Fix
Fix redirects in Plesk and cPanel: domain alias redirects, .htaccess vs panel rules, redirect chains, 301 vs 302, panel-versus-config precedence.
Identify all redirect sources
Panel UI redirects (Plesk: Domain → Hosting Settings → Permanent SEO-safe 301 redirect from www to non-www; cPanel: Redirects). .htaccess rules. nginx-additional configuration. Multiple sources stacking cause chains.
Choose single source of truth
Best practice: use ONE redirect mechanism per pattern. For www-to-non-www: use Plesk's hosting setting OR .htaccess rule, not both. For URL rewrites: use .htaccess (Apache) or nginx-additional (nginx). Document which mechanism handles which patterns.
Configure Plesk www-to-non-www
Plesk → Domain → Hosting Settings → 'Permanent SEO-safe 301 redirect from www to non-www' (or vice versa). One toggle handles the entire pattern. Avoid duplicating in .htaccess.
Audit chained redirects
Tools: HTTP status code checker (curl -I -L follows the chain), screaming frog redirect chains report. Max 1 hop (HTTPS upgrade or www canonicalisation). 2+ hops cause crawl waste and slight ranking signal loss.
Fix common loop patterns
Loops: A → B → A. Causes: conflicting redirects from .htaccess + panel + WordPress plugin. Test by reading rules in order; identify circular references.
Set 301 not 302 for permanent moves
Plesk panel redirects default to 301 (correct). .htaccess: 'Redirect 301 /old /new' (not Redirect 302 or Redirect with no code). 302 = temporary, doesn't pass ranking signals reliably. Use 301 unless genuinely temporary.
Test. curl -I -L https
//yoursite.com — check status code chain. screaming frog redirect chains report. Search Console → URL Inspection → checks Google sees correct canonical.
Tags: redirect-checker, Plesk redirects, cPanel redirects, Plesk 301 redirect, cPanel redirect rules, domain alias redirect
Fix
Configure WordPress redirects: 301 vs 302 selection, Redirection plugin patterns, .htaccess rules, redirect chains, loops, broken redirects.
Step 1: Audit current redirects
Install Redirection plugin (free) → Tools tab → shows total redirect count. Crawl with Screaming Frog → response codes → see redirect chains and loops. Most WordPress sites have 50-500 redirects accumulated.
Step 2: Choose 301 vs 302 correctly
301 = permanent, passes link equity. Use for: URL structure changes, content migration, page consolidation. 302 = temporary, doesn't pass equity. Use for: A/B testing, geo-redirection, maintenance pages. Mistake: using 302 for permanent moves loses ranking authority.
Step 3: Configure Redirection plugin
Tools → Redirection → Add new. Source URL pattern (regex supported), target URL, HTTP code (301 default). Group redirects logically (post-migration, plugin-changes, content-cleanup) for easier maintenance.
Step 4: Fix redirect chains
Chain = A → B → C. Every hop adds latency and loses some equity. Find via Screaming Frog. Resolve by updating A's redirect to point directly at C (skip B). Redirection plugin → bulk edit option.
Step 5: Fix redirect loops
Loop = A → B → A. Causes infinite redirect. Find via Search Console errors or Screaming Frog. Resolve by removing one direction of the loop.
Step 6: Move common redirects to .htaccess
For high-traffic redirects, .htaccess rules are faster than plugin-handled redirects (no PHP overhead). Format: 'Redirect 301 /old-path /new-path'. Be careful — bad .htaccess crashes the site. Test on staging first.
Step 7: Set up monitoring
Redirection plugin tracks 404 hits. Review weekly — convert recurring 404s to 301 redirects. Search Console Coverage report shows new 4xx URLs.
Tags: redirect-checker, WordPress redirect, Redirection plugin, WordPress 301 redirect, WordPress htaccess redirect, WordPress redirect chain
Fix
Close referring domain gap with competitors. Identify where competitors get links you don't, outreach to same sources, build new domains for category authority.
1. Run a link-gap analysis
Ahrefs / Semrush / Majestic all support link gap reports: - Input your domain + 3-5 competitors - Output: domains linking to competitors but NOT you - Sorted by: domain authority, link count, relevance Filter for high-value gaps: - DR/DA 40+ - Topically relevant to your category - Editorial sites (not auto-generated) - Active sites (post in last 12 months)
2. Categorise the gap sources
Industry trade publications: - Pitch contributor pieces, expert quotes - Available via HARO / Qwoted Aggregator / round-up sites: - "Top X tools for Y" — pitch inclusion - Often gated by paid placement OR genuine reach Resource pages: - Universities, government, industry associations - Pitch as a useful resource Tool / SaaS directories: - G2, Capterra, AlternativeTo, etc. - Claim listing, optimise profile Editorial blogs: - Pitch guest content, original research - Build relationships over months Podcast / interview sites: - Pitch as guest expert - Each appearance = show notes link + brand mention
3. Outreach by source type
Each source type needs different outreach: Trade press: - Pitch original data, expert commentary - Time-sensitive angle (newsjack relevant developments) - HARO / Qwoted for journalist inquiries Resource pages: - "I noticed you link to X and Y; we offer Z which adds [specific value]" - Show how it improves THEIR resource page Aggregator inclusion: - "I noticed your roundup of CRMs — we'd be a fit because [specific differentiator]" - Provide curated assets (one-pager, logo, screenshot) Guest content: - Pitch SPECIFIC topic for SPECIFIC publication - Don't mass-pitch templates - Show familiarity with their content
4. Build category authority broadly
Beyond competitor-replication: - Original research that gets cited → see citation frequency - Tools that get linked (calculators, generators) - Open-source contributions (developer audiences) - Conference speaking (link from event sites + content shares) - Community presence (Reddit, Slack, Discord, forums) - Podcast guesting (each appearance = 1+ link) Pattern: be discoverable in places your category cares about. Links accumulate as a side effect.
5. Track gap closure quarterly
Monthly: - New referring domains gained - Lost referring domains - Net change Quarterly: - Gap vs top 3 competitors - Authority score trend - Categories of sources improved Healthy growth: 5-15 new referring domains per month for an active site. At 10/month: 120/year — closing meaningful gap over 2-3 years.
6. Quality over quantity
10 DR-50 domain links beat 100 DR-10 directory links. Don't optimise the count alone: Editorial mentions > directory listings Topically relevant > unrelated DA Body-content links > footer/sidebar Branded or natural anchor > exact-match keyword (per anchor distribution ) Live, indexed, dofollow > nofollow or 404 <div
Tip: 💡 Referring domain gap closure is the slowest moving SEO lever. 12-24 months of sustained activity produces meaningful results. Sites that try to accelerate via bought links risk penalties; sites that grind real outreach for 18 months see compound authority gains. There's no shortcut.
Tags: backlinks, referring domain gap, link gap analysis, competitor backlink strategy, link acquisition
Fix
Diagnose a sudden audit score drop: correlate with deploys, plugin updates and Google algorithm releases.
Run the Score History audit
Open the Score History Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Score History audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: score-history, regression
Fix
Fix render-blocking CSS from the CSS Checker. Inline critical CSS, defer non-critical, preload important fonts, lift FCP and LCP measurably.
Lighthouse audit
Chrome DevTools → Lighthouse → Generate report. Look for the "Eliminate render-blocking resources" opportunity. It lists every CSS file blocking first paint with the estimated savings.
DevTools Network timing
Network tab → reload → look at the "Waterfall" column. CSS files with bars stretching to or past First Contentful Paint are blocking it. Sort by start time to see the request order. npm install --save-dev critical // Build script const critical = require('critical'); await critical.generate({ src: 'index.html', target: { html: 'index-critical.html' }, width: 1300, height: 900, inline: true, }); <head> <meta charset="UTF-8"> <title>Page title</title> <style> /* Critical CSS — 10-20 KB max */ *{box-sizing:border-box;margin:0;padding:0} body{font-family:system-ui,sans-serif;line-height:1.5} .header{background:#1e3a8a;color:#fff;padding:16px 24px} .hero{padding:64px 24px;text-align:center} .hero h1{font-size:48px;margin-bottom:16px} /* ... continued for above-the-fold only ... */ </style> <!-- Non-critical CSS loaded async --> <link rel="preload" href="/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="/main.css"></noscript> </head> <link rel="stylesheet" href="/main.css" media="print" onload="this.media='all'"> <script> const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = '/main.css'; document.head.appendChild(link); </script> <link rel="preload" href="/fonts/inter-bold.woff2" as="font" type="font/woff2" crossorigin> @font-face { font-family: 'Inter'; src: url('/fonts/inter.woff2') format('woff2'); font-display: swap; /* show fallback while font loads */ } /* Self-hosted instead of Google Fonts */ @font-face { font-family: 'Inter'; src: url('/fonts/inter.var.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
Lighthouse before/after
Run Lighthouse on the same URL before and after. Expected changes: First Contentful Paint: typically -500ms to -1500ms on mobile Largest Contentful Paint: typically -300ms to -1000ms Lighthouse Performance score: +5 to +15 points "Eliminate render-blocking resources" opportunity: greatly reduced or gone
Real user monitoring
Search Console Core Web Vitals report shows real-user 75th-percentile FCP and LCP. Changes typically reflect over 28 days as real traffic accumulates new measurements.
Tip: 💡 Most performance audits identify CSS as render-blocking long after the actual bottleneck has moved elsewhere. Once critical CSS is inlined and the rest is deferred, focus shifts to JS, images, and server response time. Address render-blocking CSS first because it's the cheapest win.
Tags: css-checker, fix render blocking CSS, critical CSS, defer CSS, preload CSS, FCP, LCP, above the fold, render blocking resources
Fix
Fix render-blocking JavaScript from the JS Checker. Use defer and async attributes, split modules, move third-party scripts to async, lift FCP.
Lighthouse audit
DevTools → Lighthouse → Generate. Look for "Eliminate render-blocking resources" opportunity. Lists each blocking script with estimated savings.
DevTools Network waterfall
Network tab → reload. Scripts in the head with no async/defer attributes block the parser. Look at the waterfall: scripts that finish loading before FCP are blocking it. <!-- Default: synchronous, blocks parser --> <script src="/app.js"></script> <!-- async: parallel download, executes ASAP, order unpredictable --> <script src="/app.js" async></script> <!-- defer: parallel download, executes after parsing, in document order --> <script src="/app.js" defer></script> <!-- module: defer by default, plus module semantics --> <script src="/app.js" type="module"></script> <!-- Before --> <head> <script src="/app.js"></script> <script src="/analytics.js"></script> <script src="/chat-widget.js"></script> </head> <!-- After --> <head> <script src="/app.js" defer></script> <script src="/analytics.js" async></script> <script src="/chat-widget.js" defer></script> </head> <!-- Google's recommended snippet uses async, keep it --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script> // Load chat widget only when user opens menu or after 5 seconds idle function loadChat() { const script = document.createElement('script'); script.src = 'https://widget.intercom.io/...'; script.async = true; document.body.appendChild(script); } // Trigger on user interaction setTimeout(loadChat, 5000); // or document.querySelector('.menu-btn').addEventListener('click', loadChat); <head> <link rel="modulepreload" href="/app.js"> <link rel="modulepreload" href="/components.js"> <link rel="modulepreload" href="/utils.js"> <script type="module" src="/app.js"></script> </head> // Static import: everything bundles into initial JS import Chart from 'chart.js/auto'; // Dynamic import: separate chunk, loads when called async function showChart() { const { default: Chart } = await import('chart.js/auto'); new Chart(...); } button.addEventListener('click', showChart); // React Router lazy routes import { lazy, Suspense } from 'react'; const Product = lazy(() => import('./pages/Product')); <Suspense fallback={<Loading />}> <Product /> </Suspense> <head> <script> // Theme preference applied synchronously to prevent flash const theme = localStorage.getItem('theme') || 'auto'; document.documentElement.dataset.theme = theme; </script> </head>
Lab metrics
Re-run Lighthouse. Expected improvements: FCP: -200ms to -1500ms LCP: -200ms to -1000ms TBT (Total Blocking Time): can drop dramatically with heavy third-party scripts moved to async Performance score: +5 to +20 points
Tip: 💡 Defer all your app code; async all third-party. That single rule fixes most render-blocking JS issues. The rest is splitting and modulepreload tuning.
Tags: js-checker, fix render blocking JS, defer async script, module preload, JS code splitting, third party scripts, FCP TBT, JavaScript performance
Fix
Eliminate render-blocking CSS and JS: inline critical, defer non-critical, async third-party scripts.
Run the Page Speed audit
Open the Page Speed Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Page Speed audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: pagespeed, render, blocking
Fix
Fix slow brand-mention response. Set SLAs by tier, build triage workflow, deploy monitoring tools, train responders. The systematic fix for reputation lag.
Choose tool by volume
Under 50 mentions/month: - Google Alerts (free, basic) - Talkwalker Alerts (free, better than Google's) - Native platform notifications 50-500 mentions/month: - Brand24 (£59-£199/mo) - Mention (£41-£449/mo) - Sprout Social (£199+/mo) - Brand Mention Monitor (this platform) 500+ mentions/month: - Brandwatch - Meltwater - Sprinklr - Talkwalker (enterprise) Switching cost is non-trivial. Pick once, commit 12-24 months, review based on actual usage vs anticipated.
Pipe alerts to the right channels
Each tier gets a different alert path: Crisis: SMS + phone + Slack to on-call High: Slack channel + email to responding team Standard: Slack channel only, batched 2x daily Background: weekly digest only Wrong-channel routing causes both: missed crises and alert fatigue from over-notification.
Configure keywords / queries
Beyond brand name: product names, executive names, common misspellings, internal codenames, related topics. Negative queries for false positives. Per-platform tuning where the tool allows. Alert fires → First responder reviews (often comms / community manager): - Read the mention - Determine tier (severity × reach matrix) - Determine route (support / comms / exec / legal) - Decide response approach Tier-appropriate action: Crisis → Slack alert exec, draft public response, get approval, post High → Draft response per template, peer review, post within SLA Standard → Respond per template, no review needed (responder authority) Background → Optional response or no action Update tracking system: - Mark as triaged, in progress, resolved - Capture response text and timing - Tag for later analytics Resolution check: - Did the response close the issue? - Is escalation needed? - Is follow-up scheduled? Hi [name], Sorry to hear you've been dealing with [specific issue mentioned]. That's not the experience we want for our customers. I'm escalating this to our [support / engineering / billing] team right now. Please DM me at [contact] and we'll get this sorted within [time commitment]. Thanks for letting us know, [Real name] [Real role] Hi [name], Thanks for mentioning us — wanted to clarify one point: [the incorrect claim] isn't quite right. The actual situation is [brief, factual correction with link to evidence]. Happy to chat more if it'd help. [Real name] [Real role] Hi [name], Genuinely appreciate the detailed feedback. You're right that [acknowledge what's accurate]. We've been working on [specific change] which addresses [related concern]. Your feedback helps us prioritise — [specific next step we're taking]. If you'd like to test the [improvement / new approach], I'd love to send it your way. [Real name] [Real role] We're aware of [the situation] and taking it seriously. Our team is investigating and we'll provide a full update by [specific time]. In the meantime, customers affected can [specific action — refund path, support contact, etc]. — [Company name] Monthly review metrics: - TTA per tier (p50, p90) - TTR per tier (p50, p90) - Coverage rate (% mentions responded) - Escalation rate (% bumped up tier) - Customer satisfaction follow-up (where applicable) - Response → resolution rate (did our response actually resolve the issue?) - Public perception of responses (likes, follow-up engagement) Quarterly review: - SLA hit rate per tier - Patterns in escalations (training need?) - Tool effectiveness vs cost - Workload distribution across team - Continuous improvement on response templates
Tip: 💡 The transformative pattern: SLA-tiered response means crisis-tier issues get crisis-tier attention (24/7, exec involvement, immediate) while standard mentions get standard handling (business hours, template-based, responder authority). Both calibrate against the alternative — either spending exec time on every Twitter complaint or letting genuine crises slide while everyone's polite. Match resource to severity; the structure becomes a competitive advantage in markets where most competitors are still flat-rate-too-slow.
Tags: brand-mention, response time mentions, brand response SLA, mention monitoring tools, reputation response, social SLA
Fix
Add valid Review and aggregateRating schema to product pages so review stars appear in rich snippets.
Run the Ecommerce Audit audit
Open the Ecommerce Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-audit, review, schema
Fix
Restore or enable Review rich snippets: aggregateRating with real reviews, schema-content match, request indexing.
Run the Rich Snippets audit
Open the Rich Snippets Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Rich Snippets audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: rich-snippets, review, snippets
Fix
Recover from Product Review Update. Original testing, named experts, original photos/video, balanced pros and cons. The recovery pattern for review content.
1. Audit existing review content honestly
For each review page, check: - Did someone actually use the product? - Are there original photos of the actual unit (not stock)? - Is the reviewer named with relevant expertise? - Are pros AND cons covered (not just pros)? - Are alternatives mentioned with honest comparison? - Is methodology disclosed? - Are claims backed by testing data or just opinion? If most are "no", the page is review-update vulnerable.
2. Add original testing
The difference between recoverable and not: did you actually test? Photos : unboxing, in-use, comparison shots — taken by you, not stock Video : brief demo, testing footage, comparison clips Quantitative tests : battery life, speed, durability, accuracy — actual numbers Use period : "Used daily for 6 weeks" — not "Initial impressions after 10 minutes" Failures : what didn't work, what surprised you, what you'd skip
3. Named reviewer with credentials
Reviewer bio (visible on each review): - Real name (not "Editorial Team") - Credentials relevant to the category (chef reviewing kitchen tools, photographer reviewing cameras) - Use history with the category (years of experience) - Link to fuller bio page - sameAs to LinkedIn / professional profile Use Person schema as covered in how-to-fix-author-trust-signals to make these signals machine-readable.
4. Balanced pros AND cons
Reviews with only pros read as affiliate hype. Reviews with honest cons read as actual reviews: For each product reviewed: - 3-5 specific pros with examples ("X works because Y") - 2-4 specific cons (real friction points, not "nothing") - Honest verdict on who SHOULDN'T buy this - Better alternatives for the people who shouldn't buy this Even paid-for-review content can be honest about cons. Google rewards balance even with affiliate links present. Disclose affiliate relationships — concealment hurts more.
5. Compare against named alternatives
Single-product reviews lose to comparison content. For each product, build a comparison section: - vs direct competitor 1 - vs direct competitor 2 - vs cheaper alternative - vs more expensive alternative This signals you tested in context, not in isolation. Comparison tables (per how-to-fix-content-extractability) extract well into AI engine answers AND rich-result snippets.
6. Disclose methodology
Either per-review OR site-wide "How we test": - What products are evaluated - Selection criteria - Testing duration and conditions - Specific tests run (battery, durability, accuracy) - Scoring rubric (if used) - Conflicts of interest policy - Update cadence Methodology pages signal serious review operation. Google's Search Quality Rater Guidelines specifically cite methodology as a quality indicator for reviews.
7. Recovery timeline
Review updates run several times per year, smaller than core. Recovery typically faster than core/HCU: Month 0: Update hits review content Month 0-2: Rebuild affected reviews with testing + bios Month 2-4: Next review update — partial recovery Month 4-8: Following updates — fuller recovery The condition: actually do the testing. Sites that fake it (using AI to generate "test results") stay flagged or drop further on next update. <div
Tip: 💡 Product Review Updates have the clearest cause-effect of any algorithm: do real testing, document it visibly, name your reviewers, balance pros/cons. Sites that do this recover reliably. Sites that try to game it by adding photos from stock libraries and "Editor scored: 9/10" without methodology fail predictably.
Tags: algo-impact, product review update, affiliate content recovery, original testing reviews, named reviewer credentials
Fix
Build a sustainable cadence of new reviews on Google, Yelp and Trustpilot: ask consistently, respond fast.
Run the Local SEO audit
Open the Local SEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Local SEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: local-seo, review, velocity
Fix
Resolve every error reported in the Search Console Enhancements report to restore rich result eligibility.
Run the Rich Snippets audit
Open the Rich Snippets Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Rich Snippets audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: rich-snippets, rich, result, errors
Fix
Get rich snippets for content sites: Article schema, FAQPage, HowTo, Recipe schema, validation, CTR impact, AI Overview eligibility.
Map content to rich snippet types
Articles → Article schema. Q&A content → FAQPage. Step-by-step instructions → HowTo. Recipes → Recipe. Reviews → Review/AggregateRating. Each content type has matching schema unlocking specific rich snippet displays.
Implement Article schema universally
Every editorial post: Article schema with headline, image, author Person, datePublished, dateModified, publisher Organization. Standard implementation; most CMS plugins handle. Foundation for content-site SEO.
Add FAQPage to applicable content
Content with Q&A sections: mark up with FAQPage schema. Each question gets Question + Answer pair. Eligible for SERP FAQ accordion (CTR boost). Don't add to non-Q&A content (Google may treat as spam if unrelated).
Implement HowTo for instructional content
Step-by-step tutorials: HowTo schema. Each step: HowToStep with name + text + optional image. totalTime field. SERP can show step-by-step preview. Don't add to non-instructional content.
Recipe schema for food sites
Recipes: comprehensive Recipe schema with ingredients, instructions, prep time, cook time, yield, nutrition, ratings. Eligible for Recipe rich snippet (image + rating + time visible in SERP). Food sites: essential.
Validate everything
Google Rich Results Test before publishing. Schema validator. Errors block rich snippet eligibility. Common: missing required fields, invalid date format, schema-content mismatch.
Measure CTR impact
Search Console — compare CTR for pages with rich snippets vs without. Rich snippets often deliver 20-100% CTR uplift. Worth implementation effort for traffic-generating content.
Tags: rich-snippets, content rich snippets, Article rich snippet, FAQPage SEO, HowTo schema, content site schema
Fix
Earn rich snippets on Shopify: review stars, product cards, FAQ accordions. Review app integration, Product schema, eligibility issues, validator.
Audit current rich snippet status
Search Console → Performance → rich results filter → see which queries trigger which rich results. Also Search Console → Enhancements → schema types Google has identified.
Validate Product schema
Rich Results Test on 5 representative products. Should detect Product, BreadcrumbList. Note errors blocking rich snippet eligibility.
Install review app
Judge.me ($15/mo), Loox ($9.99/mo), Stamped ($23/mo), Yotpo (varies). All output aggregateRating and Review schema. Reviews dramatically improve product rich snippets.
Configure review schema
Review app settings → ensure schema output enabled. Verify in Rich Results Test on a product with reviews. Should show aggregateRating with ratingValue and reviewCount.
Add FAQ schema
Apps: Easy FAQ, FAQ Page Pro, HelpCenter. Or theme code edit. Add real FAQ blocks to product pages (or collection pages, or About). Google's 2023 update requires genuine FAQ content, not forced FAQs.
Fix eligibility issues
Common: self-reviews (Google rejects), missing required fields, generic content. Audit via Search Console → Enhancements → Product/Review/FAQPage for specific errors.
Monitor
Search Console weekly. Plugin updates regress schema; catch quickly.
Tags: rich-snippets, Shopify rich snippets, Shopify review stars, Shopify Product schema, Shopify FAQ snippets, Shopify SERP
Fix
Earn rich snippets on WordPress: review stars, FAQ accordions, HowTo carousel, Product cards. Schema requirements, validator workflow.
Step 1: Audit current rich snippet status
Search Console → Performance → look for queries with 'rich result' filter. Note which pages earn which rich results. Also: Search Console → Enhancements → list of schema types Google has identified.
Step 2: Validate schema with Rich Results Test
search.google.com/test/rich-results. Test 10 representative URLs. Note: detected schemas, warnings, errors. Errors block rich results; warnings don't but should be fixed.
Step 3: Fix Review schema
Common issues: missing aggregateRating (need ratingCount and ratingValue), self-reviews (Google rejects reviews on your own products by your own organisation), generic reviews (Google wants reviews tied to specific products/services). Use legitimate review plugins (WP Customer Reviews, Site Reviews) — not custom fake reviews.
Step 4: Fix FAQ schema
Common issues: FAQ schema on non-FAQ content (Google's 2023 update requires genuine Q&A intent), missing answers, duplicate Q&As. Only add FAQPage schema to content with clearly visible Q&A blocks in the page. Yoast/Rank Math FAQ blocks satisfy this.
Step 5: Fix HowTo schema
Issues: missing totalTime (must be ISO 8601 like PT30M), missing step images (Google prefers steps with illustrations), generic step text. Use schema-aware HowTo blocks from Yoast/Rank Math or schema plugins.
Step 6: Fix Product schema
Issues: missing offers.price or offers.priceCurrency, missing aggregateRating, missing brand. WooCommerce + SEO plugin (Yoast WC SEO, Rank Math WooCommerce) handles most. Verify with Rich Results Test on individual products.
Step 7: Monitor Search Console weekly
Enhancements → click each schema type → view errors. New errors usually indicate plugin updates that broke schema. Fix promptly — Google removes rich result eligibility quickly when errors appear.
Tags: rich-snippets, WordPress rich snippets, review schema WordPress, FAQ rich snippet, HowTo rich snippet, WordPress structured data
Fix
Fix every Rich Snippets audit finding: missing schema, ineligible pages, content-schema mismatch, coverage drops and how to win stars, FAQ accordions and rich result types in Google.
Match content to rich result type
Use this mapping table to pick the right schema: Your content Schema type Rich result Blog post / news article Article Top stories carousel, author byline Product page Product Price, stars, availability Recipe page Recipe Image, time, rating in carousel FAQ with visible Q&A FAQPage Expandable accordion Step-by-step guide HowTo Step carousel (mobile only) Local business LocalBusiness Map pack, knowledge panel Event listing Event Event card with date/location Job posting JobPosting Google for Jobs listing
Generate JSON-LD
Open the Schema Builder . Pick your schema type. Fill in every field — required and recommended. Copy the generated JSON-LD output.
Paste into the page head
Paste between <script type="application/ld+json"> and </script> tags in the page <head> . WordPress: use Yoast, Rank Math, or a custom-field plugin. Static sites: paste directly into the template.
Validate before publishing
Run Google's Rich Results Test on the page. Confirm three things: (1) schema parses without errors, (2) page is eligible for rich results, (3) no warnings beyond cosmetic ones. If errors remain, fix them before publishing.
FAQ schema with invisible questions
Adding FAQPage schema with 10 questions when the page only shows 3 visibly to users. Google detects this and disqualifies the rich result. Either make all 10 questions visibly accessible (in an expandable accordion is fine) or reduce the schema to match what's visible.
Product schema with stale prices
Product schema cached with old price while visible page shows new price. Mismatch demotes the page in shopping results. Force schema cache invalidation on every product update; verify with the Rich Results Test post-update.
Aggregate rating without real reviews
Product schema with aggregateRating 4.9 stars and 200 reviews when the page shows no visible reviews. This is a manual-action-risk violation. Either add visible reviews or remove the aggregateRating from schema.
Open Search Console Enhancement reports
Search Console → Enhancements (sidebar). You'll see one report per schema type detected on your site. Click into each to see affected URLs and specific errors.
Fix errors before warnings
Sort by "Error" first. These block rich result eligibility. Common errors: missing required fields ( name , image , author ), invalid date format, unsupported type. Fix and re-validate.
Mark fixed and request validation
In the Enhancement report, click "Validate Fix" after fixing each error type. Google re-crawls a sample of affected URLs. Validation typically completes in 1-7 days.
URL Inspection in Search Console
Paste the URL in the top search bar of Search Console. Click "Request Indexing". One-by-one — there's no bulk option. Rich results typically appear within 1-7 days of successful re-index.
Re-submit your sitemap
Search Console → Sitemaps. Click your sitemap, then "Submit" again. Google re-discovers all URLs in the sitemap, prioritising recently changed ones.
Search Console Enhancement trend
Each enhancement report shows a trend chart. A sudden drop in "Valid" pages or spike in "Error" pages means something broke. Investigate immediately — most drops are caused by a recent CMS or plugin update.
Schema Drift Detector
For ongoing coverage protection, use the Schema Drift Detector . It probes your top pages hourly and alerts on baseline diffs — catching coverage breaks within 1 hour rather than 1-4 weeks.
Tip: 💡 Use the JSON Validator first to catch syntax errors (trailing commas, unescaped quotes) before running the Rich Results Test. JSON syntax errors are silent killers — Google ignores invalid JSON-LD with no warning.
Tags: rich-snippets, fix rich snippets, schema markup, rich results, structured data, JSON-LD, rich snippet eligibility, schema validation, rich result test
Fix
Decide which AI crawlers (GPTBot, ClaudeBot, PerplexityBot) to allow or block and configure robots.txt accordingly.
Run the llms.txt audit
Open the llms.txt Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your llms.txt audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: llms, robots, ai, blocks
Fix
Fix accidental robots.txt blocks. Diagnose Disallow rules, staging-config leaks, parameter blocks and how to scope crawl restrictions correctly.
Fetch the file
curl https://yourdomain.com/robots.txt Read every Disallow line. For each, ask: what URLs does this block, and was that intentional?
Check for the catastrophic patterns
Look for these red flags: # Blocks everything from all crawlers — disaster on production User-agent: * Disallow: / # Blocks Google specifically — usually a development leak User-agent: Googlebot Disallow: / # Blocks everything except homepage — common staging artefact User-agent: * Disallow: / Allow: /$ If any of these are in production, immediately fix.
Run the crawler with robots-respect enabled
Run the Site Crawler . It reports any URLs it discovered but couldn't fetch because robots.txt blocked them. Compare these to your intent: which were meant to be blocked (admin, internal endpoints), and which were unintentionally blocked?
Verify production robots.txt is the right one
Compare your production robots.txt against your repo. If they don't match, find the deployment step that's copying the wrong file. Common culprits: CI pipeline copying robots.staging.txt regardless of environment, Docker container using a staging entrypoint script in production.
Environment-aware robots.txt
Best practice: serve robots.txt dynamically based on environment. Example for Next.js: // app/robots.ts export default function robots() { const isProd = process.env.VERCEL_ENV === 'production'; return isProd ? { rules: [{ userAgent: '*', allow: '/' }], sitemap: 'https://yourdomain.com/sitemap.xml' } : { rules: [{ userAgent: '*', disallow: '/' }] }; } Production allows; staging/preview blocks. No file-copy gymnastics required.
A clean production robots.txt template
User-agent: * Allow: / # Block administrative paths Disallow: /admin/ Disallow: /wp-admin/ Allow: /wp-admin/admin-ajax.php # Block search results and parameter combinations Disallow: /search? Disallow: /*?session= # Block crawl traps Disallow: /calendar/*/* Sitemap: https://yourdomain.com/sitemap.xml
Use the robots.txt Tester
Search Console → Settings → robots.txt (the legacy tester is being phased out). Or use the URL Inspection tool: paste an affected URL, look at the "Crawl allowed" line. Should say "Yes". If "No: blocked by robots.txt", review the rule that's blocking it.
Request re-crawl
Once your robots.txt is fixed, use Search Console's URL Inspection tool on key pages. Click Request Indexing . Google re-crawls within 24-72 hours typically.
Tags: site-crawler, fix robots.txt blocks, robots disallow, staging robots, googlebot blocked, accidental noindex, robots.txt audit, crawl blocks
Fix
Resolve robots meta tag errors: accidental noindex, conflicting directives, max-snippet limits.
Run the Meta Analyzer audit
Open the Meta Analyzer Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Meta Analyzer audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: meta-analyzer, robots, meta
Fix
Configure robots.txt and sitemap in Next.js: static files vs robots.ts and sitemap.ts, next-sitemap library, locale-aware sitemaps, validation.
Create app/robots
ts (App Router). Export default function returning MetadataRoute.Robots: { rules: [{ userAgent: '*', allow: '/', disallow: ['/admin', '/api'] }], sitemap: 'https://example.com/sitemap.xml' }. Next.js generates /robots.txt at build.
Add AI bot policy
Multiple rule blocks per user-agent. For example: rules: [{ userAgent: 'GPTBot', allow: '/' }, { userAgent: 'PerplexityBot', allow: '/' }, { userAgent: 'CCBot', disallow: '/' }, { userAgent: '*', allow: '/', disallow: ['/admin'] }]. Document AI policy.
Create app/sitemap
ts. Export default async function returning MetadataRoute.Sitemap: [{ url: 'https://example.com', lastModified: new Date(), changeFrequency: 'weekly', priority: 1 }, ...]. Fetch your data, map to sitemap entries, return.
Generate sitemap dynamically from data
For blogs: fetch all posts → map to { url, lastModified, ... } entries. For products: same with product data. Append static URLs (homepage, about, etc.). Return combined array.
For Pages Router
use next-sitemap. npm install --save-dev next-sitemap. Configure next-sitemap.config.js. Runs as postbuild script generating public/sitemap.xml. Pages Router doesn't have built-in sitemap.ts.
Handle large sitemaps
Single sitemap.xml has 50,000 URL limit. For larger sites, use sitemap index: app/sitemap.ts returns multiple sitemap files (sitemap-products.xml, sitemap-blog.xml, etc.). next-sitemap handles this with siteUrl + sitemapSize config.
Submit to Search Console
Search Console → Sitemaps → add sitemap.xml URL. Verify it processes successfully. Repeat in Bing Webmaster Tools.
Tags: robots-sitemap, Next.js robots.txt, Next.js sitemap, Next.js robots.ts, Next.js sitemap.ts, next-sitemap
Fix
Configure Shopify robots.txt and sitemap: robots.txt.liquid customisation, exclude crawl waste, AI bot policy, sitemap structure, Search Console submission.
Audit current robots
txt. Visit yourstore.com/robots.txt. Default Shopify robots.txt blocks: /admin, /cart, /checkout, /policies, /search, /apple-app-site-association, /account, and other operational paths. Most defaults are good.
Customise via robots
txt.liquid. Online Store → Themes → Edit code → create templates/robots.txt.liquid. Copy default rules from Shopify docs, add custom rules. Format: '{{- 'now' | date: '%Y-%m-%d' -}}' for liquid; standard robots.txt syntax for rules.
Configure AI bot policy
Add User-agent rules for AI agents. Allow GPTBot, ClaudeBot, PerplexityBot if you want AI visibility. Block training-only crawlers if you have copyright concerns.
Block additional low-value paths
Common additions: '/search', URL parameters '/collections/*?filter_*', any vanity URLs not for indexing. Edit robots.txt.liquid carefully — typos break crawling entirely.
Audit sitemap
Visit yourstore.com/sitemap.xml. Should include sub-sitemaps: sitemap_products_*.xml, sitemap_collections_*.xml, sitemap_pages_*.xml, sitemap_blogs_*.xml. Verify each has expected URL counts.
Sitemap exclusion
Shopify auto-excludes draft products, draft collections, archived blog posts. Verify by checking expected URL count matches active content count. Excludes can't be customised without theme-level filtering.
Submit to Search Console
Search Console → Sitemaps → Add 'sitemap.xml'. Repeat in Bing Webmaster Tools. Verify successful processing.
Tags: robots-sitemap, Shopify robots.txt, Shopify sitemap, robots.txt.liquid, Shopify crawl config, Shopify sitemap.xml
Fix
Configure WordPress robots.txt and sitemap.xml: SEO plugin settings, crawl exclusions, AI bot policies, sitemap priorities, validation.
Step 1: Audit current robots.txt
Visit yoursite.com/robots.txt. Check: User-agent rules, Disallow paths, Sitemap URL line. WordPress virtual robots.txt is permissive by default; SEO plugins extend it.
Step 2: Edit robots.txt via SEO plugin
Yoast → Tools → File editor → robots.txt. Rank Math → General Settings → Edit robots.txt. AIOSEO → Tools → Robots.txt editor. Add Disallow rules for low-value paths.
Step 3: Configure useful disallows
Block: /wp-admin/ (already default), /wp-login.php, /?s= (internal search), /tag/ (if you noindex tags anyway), /cart/, /checkout/, /my-account/ (for WooCommerce). Allow: /wp-admin/admin-ajax.php (needed for legitimate AJAX).
Step 4: Configure AI bot policy
Decide: allow GPTBot, ClaudeBot, PerplexityBot, Google-Extended (typical permissive policy), or block training-only crawlers. Add User-agent + Allow/Disallow rules accordingly. Document choices with comments.
Step 5: Audit current sitemap
Visit /sitemap_index.xml (Yoast/Rank Math/AIOSEO standard location). Confirm sub-sitemaps exist: post-sitemap, page-sitemap, category-sitemap, author-sitemap (if used). Check URLs, lastmod dates, priorities.
Step 6: Configure sitemap exclusions
Plugin Settings → Sitemap. Exclude: media items (usually irrelevant), tag archives (if low-value), author archives (single-author sites), date archives. Include all genuine content URLs.
Step 7: Submit to Search Console
Search Console → Sitemaps → Add new sitemap → enter /sitemap_index.xml. Verify it processes successfully. Repeat for Bing Webmaster Tools.
Tags: robots-sitemap, WordPress robots.txt, WordPress sitemap, Yoast sitemap, Rank Math sitemap, crawl WordPress, robots WordPress
Fix
Fix robots.txt syntax errors from the Robots Tester. Wildcards, user-agent format, comments, group ordering, end-of-line, and validators that catch issues.
Search Console tester
Open robots.txt Tester. Should show no syntax warnings. Test 5-10 representative URLs — each shows correctly Allowed or Blocked.
Re-run the Robots Tester audit
Zero syntax findings. Intended blocks confirmed in test URLs.
1. The directive grammar
Every directive is one line of Field: value : User-agent: * Disallow: /admin/ Allow: /admin/help/ Sitemap: https://example.com/sitemap.xml # Comment line User-agent: Googlebot Disallow: /private/ Field rules Field name is case-insensitive ( User-agent , user-agent , USER-AGENT all work) Value is case-sensitive (paths are case-sensitive) Trailing colon is required ( User-agent: , not User-agent ) One directive per line Lines are trimmed — leading/trailing spaces ignored
2. Groups and User-agent
A User-agent: line opens a group. All following Allow / Disallow apply to that user-agent until the next User-agent: line. # Group 1: all crawlers User-agent: * Disallow: /admin/ Disallow: /private/ # Group 2: Googlebot specifically User-agent: Googlebot Disallow: /tmp/ # Group 3: Bingbot specifically User-agent: Bingbot Disallow: /experimental/ # Sitemap is global, applies to all groups Sitemap: https://example.com/sitemap.xml Important: Googlebot ignores * group when it has its own group # Bad assumption User-agent: * Disallow: /admin/ Disallow: /private/ User-agent: Googlebot Disallow: /tmp/ # Googlebot only obeys its own group: Disallow: /tmp/ # /admin/ and /private/ are NOT blocked for Googlebot # To block these for Googlebot too, repeat them in its group: User-agent: * Disallow: /admin/ Disallow: /private/ User-agent: Googlebot Disallow: /admin/ Disallow: /private/ Disallow: /tmp/
3. Wildcards: * and $
Two wildcard characters with different meanings: Wildcard Meaning Example * Matches any sequence of characters /*.pdf matches /file.pdf, /docs/file.pdf $ Anchors to end of URL /*.pdf$ matches /file.pdf but NOT /file.pdf?id=1 Common wildcard patterns # Block all PDFs Disallow: /*.pdf$ # Block all URLs with query strings Disallow: /*? # Block specific query parameter Disallow: /*?sort= # Block paths containing /admin/ anywhere Disallow: /*admin/ # Block CSV downloads in a specific directory Disallow: /reports/*.csv$ Implicit wildcard at end # These two are equivalent Disallow: /admin/ Disallow: /admin/* # Without trailing slash, prefix match Disallow: /admin # Matches /admin, /admin/, /admin/page, /administrator
4. Common syntax errors
Typo: Disalow vs Disallow # BAD: silently ignored Disalow: /admin/ # RIGHT Disallow: /admin/ Missing colon # BAD: silently ignored User-agent * Disallow /admin/ # RIGHT User-agent: * Disallow: /admin/ Comments without # # BAD: tries to disallow "/admin/ temporary block" Disallow: /admin/ temporary block # RIGHT Disallow: /admin/ # temporary block # or # temporary block Disallow: /admin/ Mixed BOM and encoding # File saved with UTF-8 BOM (byte-order mark) confuses some parsers # Save as UTF-8 without BOM, Unix line endings (LF, not CRLF) # Check with hexdump: hexdump -C robots.txt | head -1 # Should NOT start with ef bb bf Wildcards in User-agent # BAD: wildcards do NOT work in User-agent values User-agent: Googlebot* # RIGHT: list each user-agent explicitly User-agent: Googlebot User-agent: Googlebot-Image User-agent: Googlebot-News Disallow: /
5. Non-standard directives
Google supports: User-agent , Allow , Disallow , Sitemap . Common non-standard directives: # Crawl-delay: ignored by Google, used by Bing/Yandex User-agent: Bingbot Crawl-delay: 10 # Host: Yandex-specific Host: example.com # Clean-param: Yandex-specific Clean-param: ref /forum/showthread.php # Comments — always supported # This is a comment For crawl rate control with Google, use Search Console → Settings → Crawl rate (legacy feature, varies by account).
6. Validators
Google's official tester Search Console → Settings → robots.txt Tester # Paste your robots.txt # Test specific URLs to confirm allow/disallow status # Identifies syntax warnings with line numbers Python: google-robotxt pip install google-robotxt # Parser identical to Googlebot's python3 -c " from google_robotxt import RobotsTxt r = RobotsTxt.from_file('robots.txt') print(r.is_allowed('Googlebot', '/some/path/')) " Node: robots-parser npm install robots-parser const robotsParser = require('robots-parser'); const fs = require('fs'); const content = fs.readFileSync('robots.txt', 'utf-8'); const robots = robotsParser('https://example.com/robots.txt', content); console.log(robots.isAllowed('https://example.com/admin/', 'Googlebot')); // false (blocked) curl with manual inspection # Fetch and look for byte-level issues curl -v https://example.com/robots.txt 2>&1 | grep "Content-Type" # Should be: text/plain; charset=utf-8 # Check first bytes — no BOM curl -s https://example.com/robots.txt | head -1 | hexdump -C | head -1
7. CI/CD validation
# GitHub Actions example - name: Validate robots.txt run: | pip install google-robotxt python3 -c " from google_robotxt import RobotsTxt r = RobotsTxt.from_file('public/robots.txt') # Critical paths must be allowed critical = ['/', '/products/', '/about/'] for path in critical: assert r.is_allowed('Googlebot', path), f'CRITICAL: {path} blocked' # Sensitive paths must be blocked blocked = ['/admin/', '/api/internal/'] for path in blocked: assert not r.is_allowed('Googlebot', path), f'SENSITIVE: {path} allowed' print('robots.txt validation passed') "
8. Verify after fixes
Step 1 Search Console tester Open robots.txt Tester. Should show no syntax warnings. Test 5-10 representative URLs — each shows correctly Allowed or Blocked. Step 2 Re-run the Robots Tester audit Zero syntax findings. Intended blocks confirmed in test URLs. <div
Tip: 💡 The single most dangerous robots.txt typo is "Disalow:" missing an L — it silently does nothing. Always paste your file into Google's official tester after any edit. Don't trust visual inspection.
Tags: robots-sitemap, fix robots.txt syntax, user-agent format, wildcards, robots comments, robots validator, group ordering, robots syntax errors, googlebot directives
Fix
Fix schema conflicts from the JSON Checker. Consolidate duplicate blocks, resolve contradictory values, remove plugin schema bloat, use one source of truth.
Count JSON-LD blocks per page
curl -s https://yoursite.com/page | grep -c 'application/ld+json' Healthy: 1. Common: 3-5. Problem: 6+. Each block needs justification.
Test with Rich Results Test
search.google.com/test/rich-results shows each block separately under "Detected items". Compare blocks — values for the same field should match across blocks.
Identify the sources
In your page source, JSON-LD blocks often have HTML comments above them indicating the source: <!-- Yoast SEO --> <script type="application/ld+json">{...}</script> <!-- Theme --> <script type="application/ld+json">{...}</script> <!-- WP Schema Pro --> <script type="application/ld+json">{...}</script> Each is a candidate for consolidation or removal. <!-- From theme --> <script type="application/ld+json"> { "@type": "Organization", "name": "Acme Co", "url": "https://acme.com" } </script> <!-- From Yoast --> <script type="application/ld+json"> { "@type": "Organization", "name": "Acme Co Ltd.", "url": "https://www.acme.com", "logo": "https://acme.com/logo.svg" } </script> <!-- Block 1 says publish date 2024-01-15 --> <script type="application/ld+json"> { "@type": "Article", "datePublished": "2024-01-15" } </script> <!-- Block 2 says publish date 2024-02-03 --> <script type="application/ld+json"> { "@type": "Article", "datePublished": "2024-02-03" } </script> <script type="application/ld+json"> { "@context": "https://schema.org", "@graph": [ { "@type": "Organization", "@id": "https://acme.com/#organization", "name": "Acme Co", "url": "https://acme.com", "logo": "https://acme.com/logo.svg" }, { "@type": "WebSite", "@id": "https://acme.com/#website", "url": "https://acme.com", "publisher": { "@id": "https://acme.com/#organization" } }, { "@type": "WebPage", "@id": "https://acme.com/page#webpage", "url": "https://acme.com/page", "isPartOf": { "@id": "https://acme.com/#website" }, "inLanguage": "en-GB" }, { "@type": "Article", "@id": "https://acme.com/page#article", "headline": "Article title", "datePublished": "2024-01-15", "isPartOf": { "@id": "https://acme.com/page#webpage" }, "publisher": { "@id": "https://acme.com/#organization" } }, { "@type": "BreadcrumbList", "@id": "https://acme.com/page#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://acme.com/" }, { "@type": "ListItem", "position": 2, "name": "Article" } ] } ] } </script> // functions.php - remove theme schema output function disable_theme_schema() { remove_action('wp_head', 'theme_function_that_outputs_schema'); } add_action('init', 'disable_theme_schema'); {%- comment -%} Disable built-in schema, app handles it {%- endcomment -%} {%- comment -%} {% render 'structured-data' %} {%- endcomment -%}
Re-test with Rich Results
One block, multiple entities, all valid. No duplicates of the same @type. No contradictions across fields.
Check Search Console enhancements
Search Console → Enhancements section shows which schema types Google detects. After consolidation, the count of detected entities should match what you ship — no phantom duplicates.
Tip: 💡 The @graph pattern is what every modern schema reference recommends. Yoast and Rank Math both ship @graph output by default in recent versions. If your tooling outputs separate blocks per entity, it's probably old — check for updates.
Tags: json-checker, fix schema conflicts, duplicate JSON-LD, contradictory schema, schema plugin bloat, schema consolidation, single source of truth schema
Fix
Fix schema date errors from the JSON Checker. Use ISO 8601 format, distinguish datePublished and dateModified, handle time zones, fix CMS date bugs.
Run the JSON Checker
Findings include the page URL, the offending field, and the bad value. Group by source — typically all from one or two templates.
Common error patterns
Localised format : "January 15, 2024" — caused by templates outputting strftime("%B %d, %Y") into JSON DD/MM vs MM/DD ambiguity : "01/02/2024" — never use this in schema Mixed format : some pages ISO, some not — different code paths Future-dated : dateModified in 2026 when current date is 2025 — bug in template Empty string : "datePublished": "" — CMS field not set // functions.php or schema plugin $schema = [ '@type' => 'Article', 'datePublished' => get_the_date('c', $post), // 'c' is ISO 8601 'dateModified' => get_the_modified_date('c', $post), ]; {% assign date_iso = article.published_at | date: '%Y-%m-%dT%H:%M:%S%z' %} "datePublished": "{{ date_iso }}" // Use Date.toISOString() for UTC { "datePublished": article.publishedAt.toISOString() // 2024-01-15T14:30:00.000Z } // Or date-fns formatISO() import { formatISO } from 'date-fns'; { "datePublished": formatISO(article.publishedAt) } from datetime import datetime, timezone article.published_at.isoformat() # 2024-01-15T14:30:00+00:00 # Django template filter {{ article.published_at|date:"c" }} // Database: stored as UTC publishedAt: 2024-01-15T14:30:00Z // Schema output: UTC "datePublished": "2024-01-15T14:30:00Z" // HTML display: user's local time via JS <time datetime="2024-01-15T14:30:00Z">15 January 2024</time> // Event with start and end { "@type": "Event", "startDate": "2025-06-15T19:00:00+01:00", "endDate": "2025-06-15T22:00:00+01:00", "eventStatus": "https://schema.org/EventScheduled" } // Offer with expiry { "@type": "Offer", "price": "29.99", "priceCurrency": "USD", "priceValidUntil": "2025-12-31", "availability": "https://schema.org/InStock" }
Schema.org validator
validator.schema.org reports invalid date formats specifically. After fixes, every date should parse cleanly.
Google Rich Results Test
Catches date errors specific to rich-result eligibility — Article needs both dates, Event needs start, Offer needs validity. Findings show "Missing field" or "Invalid format".
Tip: 💡 Set up a unit test: parse every JSON-LD date in every page through a strict ISO 8601 parser. Failing tests block deploy. Catches regressions before they ship; the test is trivial to write and runs in seconds.
Tags: json-checker, fix schema dates, ISO 8601, datePublished, dateModified, schema date format, time zone JSON-LD, valid through, article dates
Fix
Resolve every schema error in Search Console Enhancement reports to restore rich result eligibility.
Run the Schema Debugger audit
Open the Schema Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-debugger, schema, errors
Fix
Add the schema types competitors have on their pages that you don’t, prioritised by rich result impact.
Run the Competitor Analysis audit
Open the Competitor Analysis Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Competitor Analysis audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: competitor-analysis, schema, gaps
Fix
Use stable @id references to disambiguate entities across pages so AI engines and Google link them correctly.
Run the Schema Builder audit
Open the Schema Builder Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Builder audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-builder, schema, id, refs
Fix
Fix schema @id errors from the JSON Checker. Set stable URIs, cross-reference entities, build a connected entity graph, fix missing or duplicate IDs.
Find entities missing @id
Run the JSON Checker . Findings include entities declared without @id, entities with non-URI @id (just a string), entities with duplicate @id across blocks pointing at different content.
Look for inline embedded entities
Often, Article schema embeds the full Organization object instead of referencing it by @id. Each page that re-embeds is another "fresh" Organization declaration with no link to others on your site. // Site-level entities — use root URL with fragment "@id": "https://yoursite.com/#organization" "@id": "https://yoursite.com/#website" // Page-level entities — use page URL with fragment "@id": "https://yoursite.com/about#webpage" "@id": "https://yoursite.com/about#breadcrumb" // Per-resource entities — use resource URL with fragment "@id": "https://yoursite.com/blog/post-slug#article" "@id": "https://yoursite.com/products/widget#product" // People — use a profile page if you have one "@id": "https://yoursite.com/team/jane#person" // Article on /blog/post-1 { "@type": "Article", "publisher": { "@type": "Organization", "name": "Acme Co", "url": "https://acme.com", "logo": "https://acme.com/logo.svg" } } // Same Article on /blog/post-2 — Organization re-embedded { "@type": "Article", "publisher": { "@type": "Organization", "name": "Acme Co", "url": "https://acme.com", "logo": "https://acme.com/logo.svg" } } // Define Organization once, identified by @id { "@type": "Organization", "@id": "https://acme.com/#organization", "name": "Acme Co", "url": "https://acme.com", "logo": "https://acme.com/logo.svg" } // Article references via @id { "@type": "Article", "publisher": { "@id": "https://acme.com/#organization" } } <script type="application/ld+json"> { "@context": "https://schema.org", "@graph": [ { "@type": "Organization", "@id": "https://acme.com/#organization", "name": "Acme Co", "url": "https://acme.com", "logo": { "@type": "ImageObject", "@id": "https://acme.com/#logo", "url": "https://acme.com/logo.svg", "width": 600, "height": 60 } }, { "@type": "WebSite", "@id": "https://acme.com/#website", "url": "https://acme.com", "name": "Acme Co", "publisher": { "@id": "https://acme.com/#organization" }, "potentialAction": { "@type": "SearchAction", "target": "https://acme.com/search?q={search_term_string}", "query-input": "required name=search_term_string" } }, { "@type": "WebPage", "@id": "https://acme.com/blog/post-1#webpage", "url": "https://acme.com/blog/post-1", "isPartOf": { "@id": "https://acme.com/#website" }, "primaryImageOfPage": { "@id": "https://acme.com/blog/post-1#primaryimage" } }, { "@type": "ImageObject", "@id": "https://acme.com/blog/post-1#primaryimage", "url": "https://acme.com/images/post-1-hero.jpg", "width": 1200, "height": 630 }, { "@type": "Article", "@id": "https://acme.com/blog/post-1#article", "isPartOf": { "@id": "https://acme.com/blog/post-1#webpage" }, "headline": "Article title", "datePublished": "2024-01-15T14:00:00Z", "dateModified": "2024-02-03T10:30:00Z", "author": { "@id": "https://acme.com/team/jane#person" }, "publisher": { "@id": "https://acme.com/#organization" }, "image": { "@id": "https://acme.com/blog/post-1#primaryimage" } }, { "@type": "Person", "@id": "https://acme.com/team/jane#person", "name": "Jane Doe", "url": "https://acme.com/team/jane" }, { "@type": "BreadcrumbList", "@id": "https://acme.com/blog/post-1#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://acme.com/" }, { "@type": "ListItem", "position": 2, "name": "Blog", "item": "https://acme.com/blog/" }, { "@type": "ListItem", "position": 3, "name": "Article title" } ] } ] } </script> "@id": "https://acme.com/sku/PRD-001#product" // URL can change, @id stays constant "url": "https://acme.com/products/widget-v2" // Wrong — not a URI "@id": "main-organization" "@id": "12345" // Right — proper URI "@id": "https://acme.com/#organization" // Page A "@id": "https://acme.com/#org" // Page B "@id": "https://acme.com/organization/1" // Different IDs = different entities to search engines // Wrong — every Article has same @id "@id": "https://acme.com/article" // Right — unique per article "@id": "https://acme.com/blog/specific-post#article"
Schema.org validator structural check
Pass the page through schema.org validator. It reports unresolved @id references — places where you reference an @id that isn't defined elsewhere in the graph.
Knowledge graph check
Search Console → Performance shows queries triggering knowledge panels and entity-based features. Sites with strong @id consistency get more entity-driven impressions over time.
Tip: 💡 Treat @ids like primary keys in a database — globally unique, stable, never reused. The pattern of URL+fragment per entity type covers 95% of needs. Pick the pattern, document it, and apply it consistently.
Tags: json-checker, fix schema @id, stable URI, entity graph, schema references, cross reference JSON-LD, schema.org id, knowledge graph
Fix
Fix schema image errors from the JSON Checker. Use ImageObject, set width and height, meet Google rich-result image requirements, fix broken URLs.
Run the JSON Checker
Findings categorise as: image is a bare URL string (limits rich results), missing width/height (fails Article rich result), URL is relative (Google can't resolve), URL returns 404 or non-image content (broken).
Test with Rich Results Test
Google's Rich Results Test reports image-specific issues: "Image dimensions too small", "Cannot fetch image", "Missing field image". The error messages here are specific and actionable. { "@type": "Article", "image": "https://yoursite.com/article-hero.jpg" } { "@type": "Article", "image": { "@type": "ImageObject", "url": "https://yoursite.com/article-hero.jpg", "width": 1200, "height": 675, "caption": "Optional caption text" } } { "@type": "Article", "image": [ { "@type": "ImageObject", "url": "https://yoursite.com/article-hero-16x9.jpg", "width": 1200, "height": 675 }, { "@type": "ImageObject", "url": "https://yoursite.com/article-hero-4x3.jpg", "width": 1200, "height": 900 }, { "@type": "ImageObject", "url": "https://yoursite.com/article-hero-1x1.jpg", "width": 1200, "height": 1200 } ] } // WRONG "url": "/images/article-hero.jpg" "url": "//cdn.example.com/image.jpg" // protocol-relative also fails // RIGHT "url": "https://yoursite.com/images/article-hero.jpg" "url": "https://cdn.example.com/image.jpg" // WordPress "url": "<?php echo esc_url(get_the_post_thumbnail_url($post, 'large')); ?>" // Next.js "url": `https://yoursite.com${image.url}` # Verify Googlebot can fetch curl -A "Mozilla/5.0 (compatible; Googlebot/2.1)" \ -I https://yoursite.com/images/article-hero.jpg # Expect: 200 OK, Content-Type: image/... { "@type": "Organization", "@id": "https://yoursite.com/#organization", "name": "Acme Co", "url": "https://yoursite.com", "logo": { "@type": "ImageObject", "url": "https://yoursite.com/logo.png", "width": 600, "height": 60 } } $thumb_id = get_post_thumbnail_id($post); $meta = wp_get_attachment_metadata($thumb_id); $url = wp_get_attachment_url($thumb_id); $image_object = [ '@type' => 'ImageObject', 'url' => $url, 'width' => $meta['width'], 'height' => $meta['height'] ]; import sharp from 'sharp'; async function getImageDimensions(filePath) { const meta = await sharp(filePath).metadata(); return { width: meta.width, height: meta.height }; } // At build or request time const { width, height } = await getImageDimensions(article.imagePath); const imageObject = { '@type': 'ImageObject', url: `https://yoursite.com${article.image}`, width, height };
Rich Results Test per page
Paste URL into Rich Results Test. Image section should show "Valid" with no errors. Preview thumbnail shows what Google would display in a rich result.
Search Console enhancements
Wait 2-7 days after deployment. Search Console → Enhancements → Articles (or relevant type) should show "Valid items" counts rising and "Invalid items" dropping as Google re-processes.
Tip: 💡 The "Articles" Search Console enhancement report is your scoreboard. Errors there mean you're losing rich-result placements. A clean Articles report with growing Valid count over time is the goal.
Tags: json-checker, fix schema images, ImageObject, image dimensions schema, rich results image, JSON-LD image, structured data image, Google image requirements
Fix
Restore schema markup after migration: rebuild the schema generator in the new platform and validate every page type.
Run the Migration Audit audit
Open the Migration Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Migration Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: migration-audit, schema, migration
Fix
Resolve schema nesting issues so Google parses the right entity hierarchy: don’t over-nest, use @id references.
Run the Schema Builder audit
Open the Schema Builder Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Builder audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-builder, schema, nesting
Fix
Resolve warnings about recommended (not required) schema fields for stronger rich result eligibility.
Run the Schema Debugger audit
Open the Schema Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-debugger, schema, recommended
Fix
Fix schema reference errors from the JSON Checker. Repair broken @id refs, use sameAs for external entities, build a connected cross-page graph.
Run the JSON Checker
Look for: @id references with no matching definition, sameAs URLs returning 404, sameAs values pointing at deleted social accounts.
Manual cross-check
# Find @id values referenced on the page grep -oP '"@id":\s*"[^"]+"' page.html | sort -u # Compare what's defined vs what's referenced # Anything referenced but not defined = broken reference { "@type": "Article", "publisher": { "@id": "https://acme.com/#organization" }, "author": { "@id": "https://acme.com/team/jane#person" } } // Neither Organization nor Person defined on this page → broken { "@context": "https://schema.org", "@graph": [ { "@type": "Organization", "@id": "https://acme.com/#organization", "name": "Acme Co", "url": "https://acme.com" }, { "@type": "Person", "@id": "https://acme.com/team/jane#person", "name": "Jane Doe", "url": "https://acme.com/team/jane" }, { "@type": "Article", "publisher": { "@id": "https://acme.com/#organization" }, "author": { "@id": "https://acme.com/team/jane#person" } } ] } // shared/organization.json { "@type": "Organization", "@id": "https://acme.com/#organization", "name": "Acme Co", "url": "https://acme.com", "logo": { "@type": "ImageObject", "@id": "https://acme.com/#logo", "url": "https://acme.com/logo.svg", "width": 600, "height": 60 }, "sameAs": [ "https://en.wikipedia.org/wiki/Acme_Co", "https://twitter.com/acmeco", "https://www.linkedin.com/company/acmeco" ] } // Every page includes this in its @graph { "@type": "Organization", "@id": "https://acme.com/#organization", "name": "Acme Co", "sameAs": [ "https://en.wikipedia.org/wiki/Acme_Co", "https://www.wikidata.org/wiki/Q12345", "https://www.crunchbase.com/organization/acme-co", "https://twitter.com/acmeco", "https://www.linkedin.com/company/acmeco", "https://www.facebook.com/acmeco", "https://www.youtube.com/@acmeco", "https://github.com/acmeco" ] } { "@type": "Person", "@id": "https://acme.com/team/jane#person", "name": "Jane Doe", "jobTitle": "Chief Engineering Officer", "worksFor": { "@id": "https://acme.com/#organization" }, "sameAs": [ "https://en.wikipedia.org/wiki/Jane_Doe", "https://www.linkedin.com/in/janedoe", "https://twitter.com/janedoe", "https://github.com/janedoe", "https://orcid.org/0000-0000-0000-0000" ] } { "@type": "Place", "name": "Acme HQ", "address": { ... }, "sameAs": [ "https://www.google.com/maps/place/...", "https://www.openstreetmap.org/way/123456" ] } # Quick check for url in $(grep -oP 'https://[^"]+' sameAs-list.txt); do status=$(curl -s -o /dev/null -w "%{http_code}" "$url") echo "$status $url" done # Anything not 200 needs review
Find your Wikidata Q-number
Search wikidata.org for your Organization. The URL has form /wiki/Q12345 . Q12345 is the entity's Wikidata identifier.
Include in sameAs
"sameAs": [ "https://www.wikidata.org/wiki/Q12345" ]
If no Wikidata entry exists
You can create one for notable entities. Wikidata's notability rules are looser than Wikipedia's — most established companies, public figures, and organisations qualify. { "@type": "Person", "@id": "https://yoursite.com/authors/jane-doe#person", "name": "Jane Doe", "url": "https://yoursite.com/authors/jane-doe", "image": "https://yoursite.com/authors/jane-doe.jpg", "jobTitle": "Senior Reporter", "worksFor": { "@id": "https://yoursite.com/#organization" }, "sameAs": [ "https://www.linkedin.com/in/janedoe", "https://twitter.com/janedoe", "https://muckrack.com/jane-doe" ] }
Schema.org validator
Reports unresolved references explicitly. After fixes, no unresolved refs should remain.
Search "your brand" in Google
Strong sameAs and entity definitions help knowledge panels appear. After consistent schema and crawl cycles, your brand should trigger a sidebar knowledge panel for branded searches.
Tip: 💡 Build the Organization entity once with proper @id and rich sameAs. Use it on every page. That single decision is the highest-leverage schema improvement most sites can make — anchors your brand identity across the entire knowledge graph.
Tags: json-checker, fix schema references, broken @id, sameAs schema, external references, entity disambiguation, knowledge graph, JSON-LD cross reference
Fix
Add every required field for the schema types you use: required errors block rich result eligibility entirely.
Run the Schema Builder audit
Open the Schema Builder Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Builder audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-builder, schema, required, fields
Fix
Fix missing required schema fields from the JSON Checker. Meet Google rich result requirements for Article, Product, Recipe, FAQ, Event and more.
Run Google's Rich Results Test
search.google.com/test/rich-results — paste URL → see per-type detection with explicit "Missing field X" errors and warnings.
Search Console Enhancements report
Each rich-result type has a Search Console Enhancements report (Articles, Products, FAQ, etc.). Shows "Invalid items" with specific error messages and affected URLs. Useful for site-wide patterns. { "@type": "Article", "headline": "Article title (required)", "image": [ { "@type": "ImageObject", "url": "https://yoursite.com/article.jpg", "width": 1200, "height": 675 } ], "datePublished": "2024-01-15T14:00:00Z", "dateModified": "2024-02-03T10:30:00Z", "author": { "@type": "Person", "name": "Jane Doe", "url": "https://yoursite.com/authors/jane-doe" }, "publisher": { "@type": "Organization", "name": "Acme Co", "logo": { "@type": "ImageObject", "url": "https://yoursite.com/logo.png", "width": 600, "height": 60 } } } { "@type": "Product", "name": "Product name", "image": ["https://yoursite.com/product.jpg"], "description": "Product description...", "sku": "SKU-12345", "brand": { "@type": "Brand", "name": "Brand name" }, "offers": { "@type": "Offer", "url": "https://yoursite.com/products/widget", "priceCurrency": "USD", "price": "29.99", "availability": "https://schema.org/InStock", "itemCondition": "https://schema.org/NewCondition" }, "aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.5", "reviewCount": "234" } } { "@type": "Recipe", "name": "Recipe name", "image": ["https://yoursite.com/recipe.jpg"], "author": { "@type": "Person", "name": "Chef Name" }, "datePublished": "2024-01-15", "description": "Description...", "recipeYield": "4 servings", "recipeIngredient": [ "2 cups flour", "1 tsp salt", "1 cup water" ], "recipeInstructions": [ { "@type": "HowToStep", "text": "Mix ingredients" }, { "@type": "HowToStep", "text": "Bake for 30 minutes" } ], "prepTime": "PT15M", "cookTime": "PT30M", "totalTime": "PT45M" } { "@type": "FAQPage", "mainEntity": [ { "@type": "Question", "name": "Question text?", "acceptedAnswer": { "@type": "Answer", "text": "Answer text..." } }, { "@type": "Question", "name": "Second question?", "acceptedAnswer": { "@type": "Answer", "text": "Second answer..." } } ] } { "@type": "Event", "name": "Event name", "startDate": "2025-06-15T19:00:00-05:00", "endDate": "2025-06-15T22:00:00-05:00", "eventStatus": "https://schema.org/EventScheduled", "eventAttendanceMode": "https://schema.org/MixedEventAttendanceMode", "location": { "@type": "Place", "name": "Venue name", "address": { "@type": "PostalAddress", "streetAddress": "123 Main St", "addressLocality": "City", "postalCode": "12345", "addressCountry": "US" } }, "image": ["https://yoursite.com/event.jpg"], "description": "Event description", "offers": { "@type": "Offer", "url": "https://yoursite.com/events/buy-ticket", "price": "25.00", "priceCurrency": "USD", "availability": "https://schema.org/InStock", "validFrom": "2024-12-01T00:00:00-05:00" }, "organizer": { "@type": "Organization", "name": "Organiser name", "url": "https://organiser.example" } }
Rich Results Test confirms each type
After fixes, Rich Results Test reports each detected type as "Valid". Eligibility for rich result is granted; appearance depends on Google's ranking decisions but you're now in the pool.
Search Console Enhancements valid items grow
Within 1-4 weeks of fix deployment, Search Console enhancement reports show Valid items count rising as Google re-processes pages.
Tip: 💡 Bookmark Google's structured data documentation per type. Check the "Required properties" section before implementing any new schema type — saves debugging cycles when you discover missing fields after deploy.
Tags: json-checker, fix schema required fields, rich results requirements, Article schema fields, Product schema, FAQ schema, missing required field
Fix
Choose the right schema @type for each page: Article vs BlogPosting vs NewsArticle, Product vs SoftwareApplication, etc.
Run the Schema Builder audit
Open the Schema Builder Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Builder audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-builder, schema, type, choice
Fix
Fix schema @type errors from the JSON Checker. Use the most specific type, fix wrong types, understand schema.org type hierarchy, pick correct subtypes.
List every @type used
# Across all pages, get unique types grep -roh '"@type":\s*"[^"]*"' . | sort -u Should match content categories. A blog with NewsArticle, Article, BlogPosting, CreativeWork all mixed is inconsistent — pick one per content type.
Compare to Google rich result eligibility
Each Google rich result requires specific @type. Generic types don't qualify: News carousel: NewsArticle (not Article) Recipe card: Recipe (not HowTo) Product carousel: Product (not Offer) Event search: Event (not just WebPage) Thing ├── CreativeWork │ ├── Article │ │ ├── BlogPosting │ │ ├── NewsArticle │ │ ├── Report │ │ ├── ScholarlyArticle │ │ └── TechArticle │ ├── Book │ ├── Recipe │ ├── Movie │ ├── VideoObject │ └── WebPage │ ├── AboutPage │ ├── CheckoutPage │ ├── ContactPage │ ├── FAQPage │ ├── ItemPage ← product detail │ ├── ProfilePage │ ├── QAPage │ └── SearchResultsPage ├── Event │ ├── BusinessEvent │ ├── EducationEvent │ ├── ExhibitionEvent │ ├── Festival │ └── ... ├── Organization │ ├── Corporation │ ├── LocalBusiness ← geo-located businesses │ │ ├── Restaurant │ │ ├── Store │ │ └── ... │ ├── NGO │ └── ... ├── Person ├── Place │ ├── LocalBusiness (also subtype here) │ ├── TouristAttraction │ └── ... ├── Product │ ├── IndividualProduct │ ├── ProductModel │ └── SomeProducts └── ... // A recipe video that's also a how-to { "@type": ["Recipe", "HowTo"], "name": "How to make sourdough", // Recipe fields "recipeIngredient": [...], "recipeInstructions": [...], // HowTo fields "totalTime": "PT24H", "tool": [ { "@type": "HowToTool", "name": "Dutch oven" } ] } // WRONG — blog post tagged generically { "@type": "WebPage", "name": "How we tripled conversions..." } // RIGHT — specific Article subtype { "@type": "BlogPosting", "headline": "How we tripled conversions through better onboarding", "datePublished": "2024-01-15", "author": { ... }, "publisher": { ... } } // SUBOPTIMAL — generic for news { "@type": "Article", "headline": "Breaking: Major event..." } // BETTER — eligible for News carousel { "@type": "NewsArticle", "headline": "Breaking: Major event...", "datePublished": "2024-01-15T08:30:00-05:00", "dateline": "London, UK", // ... rest } // WRONG — restaurant tagged generically { "@type": "Thing", "name": "Joe's Pizza" } // RIGHT — Local Pack eligible { "@type": "Restaurant", "name": "Joe's Pizza", "address": { ... }, "telephone": "+44 20 1234 5678", "servesCuisine": "Italian", "priceRange": "$$", "openingHoursSpecification": [ ... ] } "@type": "AboutPage" — your /about page "@type": "ContactPage" — your /contact page "@type": "FAQPage" — pages with Q&A content "@type": "QAPage" — single question + answers (forum-style) "@type": "ProfilePage" — author/team member pages "@type": "CollectionPage" — category/listing pages "@type": "ItemPage" — single product/item detail "@type": "CheckoutPage" — checkout flow steps "@type": "SearchResultsPage" — internal search results
Rich Results Test per page type
For each content type, run Rich Results Test on a representative page. Confirm detected @type matches what you specified and the rich-result eligibility is granted.
Search Console reports per type
Search Console Enhancements section gains a new report for each new schema type Google detects. NewsArticle gets a "Top Stories" or news-specific enhancement; Recipe gets a Recipe enhancement, etc.
Tip: 💡 Audit @type quarterly. Schema.org and Google's rich results catalogue evolve — new specific types get supported, old ones get deprecated. Staying current means using the latest applicable types for maximum rich-result eligibility.
Tags: json-checker, fix schema types, wrong @type, schema.org hierarchy, specific schema type, NewsArticle vs Article, subtype selection, type inheritance
Fix
Resolve schema warnings (not just errors) to maximise rich result eligibility and quality signal.
Run the Schema Debugger audit
Open the Schema Debugger Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Debugger audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-debugger, schema, warnings
Fix
Add and validate schema markup for every relevant page type: Article, Product, FAQPage, Organization, BreadcrumbList.
Run the Site Audit audit
Open the Site Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Site Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: site-audit, schema
Fix
Break through an audit score plateau by addressing the issues you’ve been deferring: schema gaps, mobile UX, content depth.
Run the Score History audit
Open the Score History Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Score History audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: score-history, score, plateau
Fix
When audit scores improve but traffic doesn’t, diagnose the disconnect: content quality, intent match, algorithm effects.
Run the Score History audit
Open the Score History Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Score History audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: score-history, score, traffic, divergence
Fix
Match content to the search intent (informational, commercial, transactional, navigational) Google ranks for each query.
Run the Keywords audit
Open the Keywords Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Keywords audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: keywords, search, intent
Fix
Handle seasonal products correctly: preserve URL year over year, use canonical to evergreen page, build authority cumulatively.
Run the Ecommerce Crawl audit
Open the Ecommerce Crawl Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Ecommerce Crawl audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: ecommerce-crawl, seasonal, products
Fix
Distinguish seasonal traffic and score variance from real regression: baseline against last year, not last month.
Run the Score History audit
Open the Score History Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Score History audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: score-history, seasonal
Fix
Replace div soup with semantic HTML5. Article, main, section, nav, aside, header, footer help AI engines identify and prioritise content regions.
W3C HTML validator
validator.w3.org/nu — paste URL. Catches nesting errors, missing required elements, deprecated attributes.
Outline checker
Browser DevTools → Accessibility tab → "Landmarks" view. Shows the semantic structure as AI engines and screen readers see it. Missing main, multiple unlabelled navs, no banner — all visible immediately.
1. The semantic elements
Element Purpose Implicit ARIA role <header> Page or section intro, branding banner (if page-level) <nav> Major navigation block navigation <main> Primary content of the document main <article> Self-contained content (post, product, comment) article <section> Thematic grouping with heading region <aside> Tangentially related (sidebar, callout) complementary <footer> Footer of document or section contentinfo (if page-level) <address> Contact info for nearest article/body — <time> Date/time with machine-readable datetime — <figure> + <figcaption> Image/diagram with caption figure
2. Page-level structure
Bad: div soup <div class="page"> <div class="header"> <div class="logo">...</div> <div class="nav-bar"> <div class="nav-item">...</div> </div> </div> <div class="content"> <div class="article"> <div class="title">...</div> <div class="body">...</div> </div> <div class="sidebar">...</div> </div> <div class="footer">...</div> </div> Good: semantic HTML5 <body> <header> <a href="/" class="logo">Acme</a> <nav aria-label="Main"> <ul> <li><a href="/audit-tools.html">Products</a></li> <li><a href="/seo-audit-platform.html">About</a></li> </ul> </nav> </header> <main> <article> <header> <h1>Article title</h1> <p>By <a href="/learning-hub.html">Jane</a>, <time datetime="2026-05-18">18 May 2026</time></p> </header> <section> <h2>Introduction</h2> <p>...</p> </section> <section> <h2>Methodology</h2> <p>...</p> </section> <footer> <p>Tags: <a href="/learning-hub.html">CRM</a></p> </footer> </article> <aside aria-label="Related"> <h2>Related articles</h2> <ul>...</ul> </aside> </main> <footer> <p>© 2026 Acme. <a href="/seo-auth/privacy.html">Privacy</a></p> </footer> </body>
3. Article vs section decision
Article : self-contained, could syndicate. Blog post, news item, product card, comment, forum reply. Section : thematic chunk within larger content. Introduction, Methodology, FAQ within an article. <!-- Article contains multiple sections --> <article> <h1>Complete CRM buyer's guide</h1> <section> <h2>What is a CRM?</h2> <p>...</p> </section> <section> <h2>How to evaluate options</h2> <p>...</p> </section> </article> <!-- Multiple articles on a listing page --> <main> <h1>Latest posts</h1> <article> <h2><a href="/learning-hub.html">Post 1</a></h2> <p>Excerpt...</p> </article> <article> <h2><a href="/learning-hub.html">Post 2</a></h2> <p>Excerpt...</p> </article> </main>
4. Headings rules
One h1 per page. h2 starts main sections. Don't skip levels (h2 → h4 is wrong). <main> <article> <h1>Complete CRM buyer's guide</h1> <section> <h2>Pricing</h2> <section> <h3>Subscription vs perpetual</h3> <p>...</p> </section> <section> <h3>Per-user vs flat-rate</h3> <p>...</p> </section> </section> <section> <h2>Implementation</h2> <p>...</p> </section> </article> </main>
5. Figure and figcaption
<figure> <img src="/charts/cwv-improvement.png" alt="CWV improvement chart showing 40% LCP reduction over 3 months" width="800" height="400" /> <figcaption> Core Web Vitals improvement after CDN migration: LCP dropped 40%, FCP dropped 35%, TTFB dropped 75% (Jan-Mar 2026) </figcaption> </figure> <!-- figcaption text is extractable; alt text is fallback --> <!-- Both reach AI engines -->
6. Time element
<p>Published <time datetime="2026-05-18T09:00:00Z">18 May 2026</time> </p> <p>Updated <time datetime="2026-08-22T14:30:00Z">22 August 2026</time> </p> <!-- Machine-readable datetime attribute lets AI engines parse exact instant --> <!-- Visible text can be human-friendly -->
7. Multiple navs — disambiguate
<nav aria-label="Main"> <ul> <li><a href="/audit-tools.html">Products</a></li> <!-- ... --> </ul> </nav> <nav aria-label="Breadcrumb"> <ol> <li><a href="/">Home</a></li> <li><a href="/audit-tools.html">Products</a></li> <li aria-current="page">Widget</li> </ol> </nav> <nav aria-label="On this page"> <ul> <li><a href="#intro">Introduction</a></li> <li><a href="#pricing">Pricing</a></li> </ul> </nav> <!-- aria-label differentiates multiple navs to AI engines and screen readers -->
8. Validate
Step 1 W3C HTML validator validator.w3.org/nu — paste URL. Catches nesting errors, missing required elements, deprecated attributes. Step 2 Outline checker Browser DevTools → Accessibility tab → "Landmarks" view. Shows the semantic structure as AI engines and screen readers see it. Missing main, multiple unlabelled navs, no banner — all visible immediately. <div
Tip: 💡 The 80/20 fix: wrap your existing template in <header> + <main> + <footer> at minimum. That single change tells every AI engine where your boilerplate ends and content begins. Then iteratively swap inner divs for article, section, nav, aside as you touch templates.
Tags: agent-readiness, semantic HTML, HTML5 elements, article tag, main element, nav element, div soup, AI parsing HTML
Fix
Detect and respond to sudden negative sentiment shifts. Identify root cause — incident, competitor, viral, organic decay — and act before damage compounds.
1. Establish baseline before shifts happen
You can't detect deviation without knowing normal. Set up baseline monitoring before any incident: Baseline metrics (rolling 90-day windows): - Total mention volume (per platform) - Sentiment distribution (positive / neutral / negative %) - Volatility (standard deviation week-over-week) - Topic distribution (what mentions discuss) - Source distribution (which platforms produce mentions) After 90 days of baseline: Alert thresholds (deviation from baseline): - 20%+ drop in positive ratio over 2 weeks - 50%+ increase in negative volume over 2 weeks - Sudden topic concentration (60%+ mentions on one issue) - Platform-specific surges (2x normal Reddit volume) - Cross-platform synchronisation (3+ platforms shifting same direction)
2. Diagnose root cause
Five common shift causes; diagnosis drives response: Cause 1: Specific incident Signs: - Cluster of mentions referencing same event - Specific timeline correlation (event date → mention surge) - Specific complaint pattern (outage, billing issue, data breach) Examples: - Service outage that lasted 4 hours - Pricing change announcement - Executive leaving / scandal - Product feature removal - Customer data incident Response: incident-specific (public statement, root-cause analysis, remediation timeline). See how-to-fix-negative-mentions for crisis response patterns. Cause 2: Organic decay Signs: - Gradual decline (4-12 weeks) not sudden - No single incident; diffuse complaint topics - Complaint patterns shifting (newer complaints differ from old) - Often correlated with product changes or competitive pressure Examples: - Quality declining as product ages without investment - Competitor product superior on key dimensions - Customer success org understaffed; tickets pile up - Documentation falling behind product changes Response: product/process root cause, not communications. Sentiment reflects experience; fix experience to fix sentiment. Cause 3: Viral negative post Signs: - Sudden spike from single source - High engagement (retweets, upvotes, comments) - Often picked up by news / aggregators - Conversation centred on specific claim Examples: - Customer's viral Twitter thread about bad experience - Reddit post hitting front page - Influencer with audience criticising publicly - Investigative journalism piece Response: address specific claim publicly (per how-to-fix-negative-mentions high-severity × high-reach playbook). Don't try to suppress viral content; engage substantively or it metastasises. Cause 4: Coordinated campaign Signs: - Cluster of mentions with similar phrasing - Accounts with no other category activity - Timing aligned with competitor launch / industry event - Geographic clustering in unusual regions - Multiple accounts created recently Examples: - Competitor's astroturf campaign - Disgruntled ex-employee organising criticism - Politically-motivated brand attack - Bot network surge Response: document pattern, report to platforms (anti-manipulation policies on most major ones), don't engage individual instances, amplify authentic positive counter-signal. Cause 5: Algorithm / monitoring change Signs: - Numerical shift without corresponding content shift - Often affects only one platform / source - Coincides with monitoring tool updates Examples: - Sentiment classifier upgraded; recategorises old neutral as negative - New source added to monitoring (now seeing complaints you missed before) - Platform algorithm change (Reddit removing certain accounts changes baseline) Response: investigate before acting. If the shift is artefact, document and adjust baseline. If real new visibility, address as you would have had it always been visible.
3. Match response tier to cause
Tier When Response Crisis Major incident, viral negative, defamation, data breach Executive lead, public statement within hours, full response within 24h Active Sustained negative trend, multiple incidents, coordinated campaign Comms + product lead, weekly updates, content production, customer outreach Investigative Organic decay, ambiguous signals, unclear cause Root cause analysis, customer interviews, no public response until clearer Monitoring Single-source shifts, possible artefacts, low engagement Watch closely, document pattern, prepare response plan, no public action yet
4. Execute response plan
Crisis tier execution Day 0 (incident emerges): - Comms lead notified, exec involvement - First public acknowledgement (Twitter, status page, email to affected) within 2-6 hours - Holding statement: "We are aware, investigating, will update by [time]" Day 1: - Full public statement (blog, press, social) - Direct outreach to most-affected customers - Press calls / media response - Update cadence established (every 6-12 hours during active phase) Day 2-7: - Update cadence continued - Specific remediation plan published - Customer-facing actions (refunds, credits, escalation paths) - Post-mortem starts (will be published when complete) Day 14-30: - Post-mortem published - Long-term policy/process changes announced - Move out of crisis cadence - Continue monitoring sentiment recovery Active tier execution Week 1-2: diagnosis and stabilisation - Root-cause analysis (product? process? perception?) - Identify top 3 specific issues driving sentiment - Stop further damage (pause any actions making it worse) Week 3-4: communication and action - Public update if pattern is publicly visible - Direct customer outreach to vocally affected - Product/process changes scoped and committed publicly Month 2-3: execution and measurement - Deliver committed changes - Track sentiment trend weekly - Publish progress updates - Capture positive mentions as recovery proof
5. Track recovery curve
Sentiment recovery follows predictable curves depending on cause: Single incident, well-handled: - Sentiment recovers to baseline in 2-6 weeks - Volume returns to baseline in 4-8 weeks - Acceleration after specific remediation announcements Single incident, poorly handled: - Sentiment may never fully recover - Recovery takes 12-24 months - Brand sometimes restructured / renamed Organic decay, addressed: - Recovery proportional to fix speed - 3-12 months typical - Watch leading indicators (new customer reviews) before trailing (NPS) Coordinated campaign, weathered: - Sentiment recovers as campaign fades in 4-12 weeks - Authentic positive volume during the period helps - Watch for second wave (campaigns sometimes repeat) Major crisis, transformed response: - 6-18 months to baseline - Sometimes ends stronger than before - Demonstrated good handling becomes positive signal itself
6. Build resilience for next shift
Recovery from one shift is also preparation for the next: Crisis response playbook documented (who decides what, when) Comms templates pre-written for likely scenarios Customer outreach lists segmented by tier and recent activity Status page infrastructure ready to use Press contacts maintained even outside crisis Positive mention library continually capturing (per positive mentions guide) Monitoring thresholds and alerts refined based on each incident
7. AEO impact of sentiment shifts
AI engines (ChatGPT, Claude, Perplexity, Gemini) integrate sentiment into how they describe brands. A sustained negative shift propagates to AI responses with 2-12 week lag depending on engine: Test AI engine descriptions weekly during recovery — "Tell me about [your brand]" Note when negative themes start appearing in AI responses Note when they fade as positive recovery accumulates Track in same prompt-set as AI tracking baseline <div
Tip: 💡 The discipline that matters most: don't manage sentiment without managing underlying reality. Sentiment is the symptom; experience is the cause. Sites that respond to every shift with messaging-only PR fail predictably. Sites that diagnose the cause, fix the cause, and communicate the fix recover reliably and emerge with stronger trust than they started. Treat sentiment as feedback signal, not enemy to be silenced.
Tags: brand-mention, sentiment shift, sentiment monitoring, brand sentiment crisis, sentiment decline diagnosis, reputation tracking
Fix
Add BreadcrumbList schema and ensure visible breadcrumbs match it so Google shows breadcrumbs in search results.
Run the SERP Preview audit
Open the SERP Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your SERP Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: serp-preview, serp, breadcrumbs
Fix
Make star ratings appear in SERP listings: aggregateRating with real reviews, schema-content match, valid markup.
Run the SERP Preview audit
Open the SERP Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your SERP Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: serp-preview, serp, stars
Fix
Help Google pick the right thumbnail image for SERP listings: image schema, og:image, sized for SERP display.
Run the SERP Preview audit
Open the SERP Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your SERP Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: serp-preview, serp, thumbnails
Fix
Improve how URLs appear in Google search results: clean URL structure, BreadcrumbList schema, no parameter junk.
Run the SERP Preview audit
Open the SERP Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your SERP Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: serp-preview, serp, url
Fix
Set service-area boundaries correctly on GBP for service-area businesses (SAB) without storefront locations.
Run the Local SEO audit
Open the Local SEO Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Local SEO audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: local-seo, service, areas
Fix
Recover from AI Overviews stealing clicks. Become a cited source IN the overview, expand mid-funnel content, build brand-recall queries that bypass SERP.
1. Identify which queries lost clicks
Search Console → Performance: - Compare clicks vs impressions trend by query - Impressions stable / rising + clicks falling = AI Overview interception - Test the queries manually — is an AI Overview present? - Note: AI Overview presence varies by user, location, device Categorise affected queries: Definition queries ("what is X") - AI Overview wins most Factual lookup ("when did X happen") - AI Overview wins most Comparison ("X vs Y") - AI Overview common Procedural ("how do I X") - AI Overview growing Recommendation ("best X for Y") - AI Overview emerging Decision support ("should I X") - Still mostly clicks Local intent - Mostly clicks Transactional - Mostly clicks
2. Become a cited source in AI Overviews
The same patterns that earn citations from ChatGPT/Claude earn AI Overview citations: Comprehensive schema (FAQPage, HowTo, Article) — see schema for AI Extractable atoms : query-as-H2, definitive sentences, structured lists — see AI extraction Author authority : named credentialed authors via author trust signals Original data : AI Overviews preferentially cite primary sources Recency : dateModified up to date, visible "Last updated" header Citation in AI Overview shows your brand to the user even if they don't click — eventually they search your brand name directly, bypassing the overview entirely.
3. Build brand-recall queries
AI Overviews intercept generic informational queries. Brand queries ("yourbrand X feature") bypass them entirely. Strategy: - Get users to know your brand exists for the topic - When they search later, they search "yourbrand topic" - That query type goes straight to your domain, no AI Overview How to build brand recall: - Be cited in AI Overviews (even without click) - Brand mentions in industry press, podcasts, Reddit - Memorable original research that gets shared - Distinct positioning that's easy to recall
4. Shift to AI-Overview-resistant queries
Queries AI Overviews struggle to fully answer: Decision-support queries: "Should I choose X or Y for [specific situation]" AI gives generic answer; users want personalised Recent/changing topics: "Latest changes to X" AI Overview lags behind real-time changes Complex multi-step: "How to do X if you also need to do Y and Z" AI Overview gives generic; users want detail Experience-required: "What's it actually like to X" AI Overview can't replicate first-hand Tool-use queries: "X calculator", "X simulator" AI Overview can't replace interactive tool Shift content production toward these query types.
5. Optimise mid-funnel content
AI Overviews intercept top-funnel informational queries hardest. Mid-funnel and bottom-funnel content holds: Top funnel (AI Overview risk: HIGH): "what is CRM" "how does CRM work" Mid funnel (AI Overview risk: MEDIUM): "best CRM for B2B SaaS under 20 employees" "HubSpot vs Salesforce for consultancies" "CRM migration timeline" Bottom funnel (AI Overview risk: LOW): "HubSpot Marketing Hub pricing" "is Salesforce Essentials right for me" "CRM with QuickBooks integration" Shift investment toward mid/bottom funnel content where clicks survive AI Overviews.
6. Improve click-through when AI Overview present
Some users still click past AI Overviews to original sources. Maximise CTR on those: Compelling title (60c, clear value-add beyond what AI Overview gives) Distinct meta description that positions your page as deeper/different Hint at original data, tools, calculator availability — "Includes [X tool]" Author/brand authority signal visible in SERP — schema-driven rich results
7. Track the right metrics
Traditional metric: organic clicks Now misleading — clicks can fall while AI Overview citations rise New mix: - AI Overview citation share (manual checks weekly) - Brand query volume trend - Mid/bottom-funnel click share - Direct traffic growth (brand recall converting) - AI Visibility tracker (Perplexity, ChatGPT, Claude correlate with AI Overview) Measure success differently in the post-AI-Overview SERP. <div
Tip: 💡 AI Overviews aren't reversible — they're Google's direction. The win condition isn't "regain the click". It's "be the cited brand whose name people search next". Sites that adapt to citation-as-currency and brand-recall as conversion path outperform those still optimising for the 2023 click pattern.
Tags: algo-impact, SGE impact, AI Overviews clicks, Google AI Overviews recovery, zero-click search, AI Overview citations
Fix
Solve Shopify crawl problems: collection tag URL explosion, variant URLs, /collections/all/, hidden products, crawl budget on large catalogues.
Audit current crawl
Search Console → Coverage. Note 'Excluded' URLs especially 'Crawled — currently not indexed' and 'Discovered — not indexed'. Both indicate crawl budget waste.
Identify URL pattern problems
Collection-tag URLs (/collections/COLLECTION/TAG) multiply quickly: 50 collections × 20 tags = 1000 URLs. Most are low-value. Variant URLs (/products/SLUG?variant=12345) duplicate parent content.
Handle collection tags
Two approaches: A) noindex via robots.txt.liquid 'User-agent: * / Disallow: /collections/*/' (be careful — also blocks legitimate collection pages); B) more selective: edit robots.txt.liquid to disallow specific tag patterns; C) canonical tag-filtered URLs back to parent collection.
Handle /collections/all/
This shows every product in random order. Usually low-value for crawl. Add noindex meta tag via theme template if you don't use it: {% if template == 'collection' and collection.handle == 'all' %}<meta name='robots' content='noindex'>{% endif %}.
Handle variant URLs
Variants are usually canonicalled to parent product URL by Shopify automatically. Verify in view-source. If you have many high-value variant URLs (size/colour with distinct demand), consider separate products instead.
Configure robots
txt.liquid. Online Store → Themes → Edit code → create templates/robots.txt.liquid. Block: /search/, /cart/, /checkout/, internal-search-result patterns. Don't block /collections/ wholesale — that kills your category pages.
Monitor crawl stats
Search Console Settings → Crawl Stats. Watch trend. After interventions, total crawl requests should stabilise; indexed product count should increase.
Tags: ecommerce-crawl, Shopify crawl, Shopify tag pages, Shopify variants, Shopify /collections/all, Shopify crawl budget
Fix
Shopify migration SEO: from WooCommerce/Magento/BigCommerce, redirect mapping, URL structure differences, sitemap regeneration, recovery monitoring.
Pre-migration baseline
Crawl old site with Screaming Frog. Export all URLs, titles, meta, internal links. Export Search Console: queries, clicks, impressions, top pages. This is your baseline.
Map old URLs to new
Old WooCommerce /product/SLUG/ → Shopify /products/SLUG (note prefix change). Old Magento /category/SLUG.html → Shopify /collections/SLUG. Build URL mapping spreadsheet — old, new, status.
Plan content carry-over
Product descriptions, blog posts, page content. Shopify's bulk CSV import handles products. Blog posts often need manual reformatting due to platform differences. Custom pages need manual recreation.
Set up 301 redirects in Shopify
Online Store → Navigation → URL Redirects. Bulk import via CSV (Shopify supports this — old path → new path). Every key URL from old site needs a redirect.
Configure Shopify sitemap
Auto-generated at /sitemap.xml. After product import, regenerate by visiting it (forces update). Submit to Search Console new property + Bing.
Verify schema and meta
Re-validate Product schema on imported products. Set SEO titles and descriptions (Shopify doesn't import these reliably from other platforms). Spot-check 20 top-traffic products.
Post-migration monitoring
Within 7 days: re-crawl, verify redirects working. Within 30 days: organic traffic should approach baseline. Search Console new property: 'Change of Address' from old domain to new (if domain changed).
Tags: migration-audit, Shopify migration, Shopify SEO migration, Shopify from WooCommerce, Shopify redirects, Shopify URL migration
Fix
Work through Next.js site audit findings: prioritise critical/major/moderate, fix common Next.js-specific patterns (hydration, schema, CWV), validate.
Triage by impact
Critical: 5xx errors, content not rendering server-side (SSR failure), missing schema. Major: 4xx errors, hydration mismatches, missing meta. Moderate: thin content, missing alt text. Minor: long URLs.
Fix critical
SSR not rendering. If audit flags 'content not in initial HTML' — your rendering strategy is wrong. Verify SSR/SSG is enabled, not CSR. Check for accidental 'use client' on data-fetching pages.
Fix critical
5xx errors. Check error monitoring (Sentry, Vercel logs) for stack traces. Common: env vars missing in production, server-only imports in client code, database timeouts.
Fix major
missing schema. Build Article, Product, FAQPage schemas as needed. Detailed in 'Build Schema in Next.js'.
Fix major
missing meta tags. Use Metadata API in App Router or next-seo in Pages Router. Set metadata on every page; don't rely on layout inheritance for unique values.
Fix moderate
thin content. Pages with under 300 words and no unique value. Either: expand content, consolidate similar pages, or noindex (low-value system pages).
Re-audit
After fixing batches, re-run audit. Findings should drop substantially. Set quarterly re-audits.
Tags: site-audit, Next.js site audit, Next.js technical SEO, Next.js audit findings, Next.js SEO checklist
Fix
Work through Shopify site audit findings: prioritise critical/major/moderate, fix common issues (schema, thin content, duplicates), validate.
Triage by impact
Critical: 5xx errors, indexation blockers, broken schema. Major: 4xx errors, duplicate content from variants, missing meta. Moderate: thin collection content, missing alt text. Minor: long URLs. Fix in order.
Fix critical
5xx errors. Server errors on Shopify are rare. If present, contact Shopify support — usually app-related or theme code error. Address by uninstalling problem app or rolling back theme change.
Fix major
4xx errors. Crawl with Screaming Frog. Identify 404s. Convert legitimate 404s (had traffic or backlinks) to 301 redirects via Online Store → Navigation → URL Redirects.
Fix major
duplicate content. Variant URLs duplicate parent product — Shopify auto-canonicals; verify in source. Collection-tag URLs duplicate collection — noindex via robots.txt.liquid. /collections/all/ — noindex if low value.
Fix major
missing meta. Audit findings list pages without titles/descriptions. Backfill in priority order: high-traffic products first. Use Smart SEO bulk view or manual workflow.
Fix moderate
thin content. Empty or near-empty collections, products with 50-word descriptions. Either: expand content, consolidate similar (merge collections), or noindex if archive/system pages.
Re-audit
After fixing batches, re-run audit. Confirm findings decreasing. Set quarterly re-audits as standard.
Tags: site-audit, Shopify site audit, Shopify SEO audit, Shopify Screaming Frog, Shopify technical SEO, Shopify audit
Fix
Work through site audit findings on WordPress: prioritise issues, fix common errors (4xx, 5xx, duplicate content, missing schema, thin pages), validate fixes.
Step 1: Triage findings by impact
Categorise audit findings into: critical (blocking indexation, 5xx errors, broken schema), major (duplicate content, 4xx errors, missing meta), moderate (thin content, missing alt text, long titles), minor (long URLs, missing OG image). Fix in order.
Step 2: Fix critical: 5xx errors and broken indexation
Server errors block crawling. Address per the HTTP errors guide. Verify no key pages are 'Discovered — currently not indexed' or 'Excluded' for the wrong reasons. Each blocker fixed unlocks crawler access.
Step 3: Fix major: 4xx errors
Broken internal links produce 4xx. Install Broken Link Checker plugin (free), find broken links, fix or redirect. Convert valuable broken external inbound links into 301s to current URLs.
Step 4: Fix major: duplicate content
WordPress generates duplicates from: pagination, tag archives, author archives, /amp/ pages, www vs non-www, http vs https. Configure SEO plugin to canonical or noindex appropriately. Set preferred domain in Settings → General.
Step 5: Fix major: missing meta titles/descriptions
Audit findings list pages without titles/descriptions. Backfill in priority order (high-traffic first). Use SEO plugin patterns for defaults plus custom titles for top pages.
Step 6: Fix moderate: thin content
Pages under 300 words rarely rank well. Either: expand thin pages, consolidate similar pages, or noindex if archive/pagination pages. For tag archives with thin auto-generated content, noindex is usually correct.
Step 7: Fix minor: structural improvements
Long URLs (shorten via slug editing), missing alt text (bulk plugin), missing OG images (set defaults). These have lower individual impact but cumulative effect on AI/social/accessibility signals.
Tags: site-audit, WordPress site audit, WordPress SEO audit, Screaming Frog WordPress, technical SEO WordPress, WordPress audit findings
Fix
Earn and shape Google sitelinks: clear site structure, hierarchical internal linking, distinct page titles.
Run the SERP Preview audit
Open the SERP Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your SERP Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: serp-preview, sitelinks
Fix
Fix 404 and redirected URLs in your sitemap from the Robots Tester. Validate every URL returns 200, automate with CI, prevent crawl-budget waste.
Run the Robots Tester
Findings list each sitemap URL with status code, grouped by issue: 404s, 301s, noindex pages, blocked by robots.
Bulk-check yourself
# Extract URLs from sitemap curl -s https://example.com/sitemap.xml | \ grep -oE '<loc>[^<]+</loc>' | \ sed 's/<[^>]*>//g' > sitemap-urls.txt # Check status of each while read url; do status=$(curl -s -o /dev/null -w "%{http_code}" "$url") echo "$status $url" done < sitemap-urls.txt | grep -v "^200" > sitemap-issues.txt cat sitemap-issues.txt | head -20 // Filter sitemap to exclude noindex, password-protected, drafts add_filter('wpseo_sitemap_entry', function($entry, $type, $object) { // Exclude noindex pages if ($object && method_exists('YoastSEO', 'meta')) { $robots = YoastSEO()->meta->for_post($object->ID)->robots; if (isset($robots['index']) && $robots['index'] === 'noindex') { return false; } } return $entry; }, 10, 3); import requests from xml.etree import ElementTree as ET def is_indexable(url): try: r = requests.head(url, allow_redirects=False, timeout=5) # Must return 200 directly (no redirect) if r.status_code != 200: return False # Check meta robots via GET r = requests.get(url, timeout=5) if 'noindex' in r.text.lower(): return False return True except: return False def generate_sitemap(candidate_urls): urlset = ET.Element('urlset', xmlns='http://www.sitemaps.org/schemas/sitemap/0.9') for url in candidate_urls: if is_indexable(url): u = ET.SubElement(urlset, 'url') ET.SubElement(u, 'loc').text = url return ET.tostring(urlset, encoding='unicode') // app/sitemap.ts export default async function sitemap() { const posts = await getPosts({ status: 'published', indexable: true }); // Filter at query time — only publish-status, indexable=true return posts.map(post => ({ url: `https://example.com/blog/${post.slug}`, lastModified: post.updatedAt, })); } name: Validate Sitemap on: [push, deployment_status] jobs: validate: runs-on: ubuntu-latest steps: - name: Extract URLs from sitemap run: | curl -s https://example.com/sitemap.xml | \ grep -oE '<loc>[^<]+</loc>' | \ sed 's/<[^>]*>//g' > urls.txt echo "URLs to validate: $(wc -l < urls.txt)" - name: Check each URL returns 200 run: | failed=0 while read url; do status=$(curl -s -o /dev/null -w "%{http_code}" "$url") if [ "$status" != "200" ]; then echo "FAIL: $status $url" failed=$((failed+1)) fi done < urls.txt if [ $failed -gt 0 ]; then echo "$failed URLs failed validation" exit 1 fi #!/bin/bash # .git/hooks/pre-commit if git diff --cached --name-only | grep -q "sitemap"; then echo "Sitemap changed — running validation" ./scripts/validate-sitemap.sh || exit 1 fi Search Console → Sitemaps → Click your sitemap entry → "Submit" again to force re-fetch → Status updates within hours
Re-run Robots Tester
All URLs report 200. Zero 404s, zero redirects, zero noindex pages.
Search Console processing
Sitemap status: "Success". URL count matches expected. "Discovered URLs" vs "Submitted URLs" delta should narrow over time.
Crawl budget metrics
Search Console → Settings → Crawl stats. Pages-per-day metric stable, crawl errors dropping after sitemap cleanup.
Tip: 💡 The single rule: sitemap query should be the same query that determines whether a page should be indexed. If your CMS publishes a page → its URL appears in sitemap. If your CMS unpublishes/deletes → URL drops from sitemap immediately. Source-of-truth filtering at query time is more robust than post-hoc cleanup.
Tags: robots-sitemap, fix sitemap 404, stale sitemap urls, sitemap validation, crawl budget, sitemap CI check, indexable URLs only, dead links sitemap
Fix
Fix sitemap declaration from the Robots Tester. Absolute URL in robots.txt, Search Console submission, and avoiding the relative-URL silent failure.
Fetch the sitemap
curl -I https://example.com/sitemap.xml # Expected: # HTTP/2 200 # Content-Type: application/xml or text/xml
Validate XML structure
curl -s https://example.com/sitemap.xml | xmllint --noout - # No output = valid XML # Errors listed if malformed # Correct: absolute URL with protocol User-agent: * Disallow: /admin/ Sitemap: https://example.com/sitemap.xml # One Sitemap line per file Sitemap: https://example.com/sitemap-pages.xml Sitemap: https://example.com/sitemap-posts.xml Sitemap: https://example.com/sitemap-products.xml # Better: single sitemap index Sitemap: https://example.com/sitemap-index.xml # The index file then references all child sitemaps # BAD: silently invalid Sitemap: /sitemap.xml # RIGHT: absolute URL Sitemap: https://example.com/sitemap.xml # BAD Sitemap: example.com/sitemap.xml # RIGHT Sitemap: https://example.com/sitemap.xml # Must match the actual canonical host # Site uses https://www.example.com canonical: # BAD Sitemap: http://example.com/sitemap.xml # RIGHT Sitemap: https://www.example.com/sitemap.xml # BAD: HTML sitemap page, not XML Sitemap: https://example.com/sitemap.html # RIGHT: machine-readable XML Sitemap: https://example.com/sitemap.xml # BAD: sitemap.xml is itself blocked User-agent: * Disallow: /sitemap Sitemap: https://example.com/sitemap.xml # Some crawlers may refuse to fetch # RIGHT: ensure sitemap path is allowed (default is allowed) <!-- /sitemap.xml (the index) --> <?xml version="1.0" encoding="UTF-8"?> <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <sitemap> <loc>https://example.com/sitemap-pages.xml</loc> <lastmod>2024-01-15</lastmod> </sitemap> <sitemap> <loc>https://example.com/sitemap-posts-2024.xml</loc> <lastmod>2024-01-20</lastmod> </sitemap> <sitemap> <loc>https://example.com/sitemap-products.xml</loc> <lastmod>2024-01-20</lastmod> </sitemap> </sitemapindex> # robots.txt only references the index Sitemap: https://example.com/sitemap.xml
Google Search Console
Open Search Console → property → Sitemaps "Add a new sitemap" → enter sitemap URL path (e.g. sitemap.xml ) Click Submit Status updates within hours: "Success" with URL count, or error message
Bing Webmaster Tools
Bing Webmaster Tools → site → Sitemaps Submit sitemap URL Same idea — status updates over time
Yandex (optional)
Yandex Webmaster has similar sitemap submission. Worth using if you target Russian-speaking markets. # POST to IndexNow endpoint POST https://api.indexnow.org/indexnow Content-Type: application/json { "host": "example.com", "key": "your-key-here", "urlList": [ "https://example.com/new-page", "https://example.com/updated-page" ] } # Place key verification file at: # https://example.com/your-key-here.txt # containing the key value as plain text
curl robots.txt
curl -s https://example.com/robots.txt | grep -i Sitemap # Expected output: # Sitemap: https://example.com/sitemap.xml
Search Console status
Search Console → Sitemaps → "Submitted sitemaps". Each listed with URL count, status (Success/Couldn't fetch/Has issues), last read date.
Re-run Robots & Sitemap Tester
Declaration findings cleared. Sitemap parses successfully. URL count matches expectations. # Yoast generates: /sitemap_index.xml # Rank Math generates: /sitemap_index.xml # Add to robots.txt manually if plugin doesn't: Sitemap: https://example.com/sitemap_index.xml // app/robots.ts (App Router) export default function robots() { return { rules: { userAgent: '*', allow: '/' }, sitemap: 'https://example.com/sitemap.xml', }; } // astro.config.mjs import sitemap from '@astrojs/sitemap'; export default defineConfig({ site: 'https://example.com', integrations: [sitemap()], }); // Generates /sitemap-index.xml and /sitemap-0.xml
Tip: 💡 The single rule: absolute URL with protocol, on its own line in robots.txt, AND submit via Search Console. Doing both ensures every crawler — including ones you didn't think of — finds the sitemap. Two minutes of setup, prevents weeks of indexing delays.
Tags: robots-sitemap, fix sitemap declaration, robots.txt sitemap directive, absolute URL sitemap, search console sitemap, ping sitemap, sitemap submission
Fix
Fix lastmod accuracy from the Robots Tester. ISO 8601 format, reflect real content changes, why Google ignores priority and changefreq, modern best practice.
Check the values
curl -s https://example.com/sitemap.xml | \ grep -E "<loc>|<lastmod>" | \ head -40 # Spot-check 5-10 URLs. Open each URL in a browser. # Does lastmod match when you last actually edited that content?
Look for the all-same-date red flag
# Count unique lastmod values curl -s https://example.com/sitemap.xml | \ grep -oE '<lastmod>[^<]+</lastmod>' | \ sort -u | head -10 # If you see one or two unique dates and they're recent — the file regenerates with current time on every URL <!-- Date only (recommended for most cases) --> <lastmod>2024-01-15</lastmod> <!-- Date with time and timezone (for high-frequency updates) --> <lastmod>2024-01-15T14:30:00+00:00</lastmod> <lastmod>2024-01-15T14:30:00Z</lastmod> <!-- BAD: US format --> <lastmod>01/15/2024</lastmod> <!-- BAD: Unix timestamp --> <lastmod>1705325400</lastmod> <!-- BAD: no timezone with time component --> <lastmod>2024-01-15T14:30:00</lastmod> <!-- BAD: human format --> <lastmod>January 15, 2024</lastmod> // In a custom sitemap generator $args = [ 'post_type' => ['page', 'post'], 'post_status' => 'publish', ]; $query = new WP_Query($args); while ($query->have_posts()) { $query->the_post(); echo "<url>"; echo "<loc>" . get_permalink() . "</loc>"; echo "<lastmod>" . get_the_modified_date('Y-m-d') . "</lastmod>"; echo "</url>"; } // Yoast / Rank Math handle this correctly by default -- Source from updated_at column SELECT slug, updated_at -- NOT created_at FROM pages WHERE status = 'published' ORDER BY updated_at DESC; # Hugo {{ range .Site.RegularPages }} <url> <loc>{{ .Permalink }}</loc> <lastmod>{{ .Lastmod.Format "2006-01-02" }}</lastmod> </url> {{ end }} # Hugo .Lastmod sources from front-matter or git history # Next.js export default async function sitemap() { const posts = await getPosts(); return posts.map(post => ({ url: `https://example.com/${post.slug}`, lastModified: post.updatedAt, // Date object or ISO string })); } // @astrojs/sitemap reads from page front-matter // Set updatedDate in content collections schema import { defineCollection, z } from 'astro:content'; const blog = defineCollection({ schema: z.object({ title: z.string(), publishDate: z.date(), updatedDate: z.date().optional(), }) }); // Sitemap auto-uses updatedDate || publishDate // BAD: every URL gets the current timestamp const sitemap = urls.map(url => ({ loc: url, lastmod: new Date().toISOString() // ← every URL gets "now" })); // RIGHT: lastmod from each URL's actual modification record const sitemap = posts.map(post => ({ loc: `https://example.com/${post.slug}`, lastmod: post.updatedAt.toISOString() // ← from database })); <!-- BEFORE --> <url> <loc>https://example.com/about</loc> <lastmod>2024-01-15</lastmod> <changefreq>monthly</changefreq> <priority>0.8</priority> </url> <!-- AFTER --> <url> <loc>https://example.com/about</loc> <lastmod>2024-01-15</lastmod> </url> <url> <loc>https://example.com/products/widget</loc> <lastmod>2024-01-15</lastmod> <image:image xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"> <image:loc>https://example.com/images/widget.jpg</image:loc> <image:caption>Blue ceramic widget</image:caption> </image:image> </url> <url xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"> <loc>https://example.com/news/article-1</loc> <news:news> <news:publication> <news:name>Example News</news:name> <news:language>en</news:language> </news:publication> <news:publication_date>2024-01-15T14:30:00+00:00</news:publication_date> <news:title>Article title</news:title> </news:news> </url> <!-- News sitemap entries auto-removed after 2 days --> // functions.php add_action('save_post', function($post_id) { // Trigger Yoast cache flush on every save if (function_exists('wpseo_xml_sitemaps_init')) { do_action('wpseo_ping_search_engines'); } }); # Hugo example - WRONG {{ now.Format "2006-01-02" }} <!-- always current build time --> # RIGHT - source from front-matter {{ .Lastmod.Format "2006-01-02" }} <!-- from content file -->
Compare to known edits
Pick 5 pages. Note when you last edited each. Check sitemap shows the matching lastmod. If consistently off, the source is wrong.
Search Console correlation
Update a single page. Wait 1-2 days. Check Search Console URL Inspection on that URL. "Last crawled" should be near the lastmod. If Google ignores recent lastmod, you've over-claimed in the past and lost trust.
Re-run Robots Tester
lastmod findings clear. Format ISO 8601, dates reflect actual content age, no priority/changefreq noise.
Tip: 💡 The single rule: lastmod equals when the content was last edited, not when the file was last regenerated. Source from your CMS's modified_at column or equivalent. Don't strip priority/changefreq aggressively — just stop generating them in new code. Existing values do no harm.
Tags: robots-sitemap, fix sitemap lastmod, ISO 8601 sitemap, sitemap priority, changefreq, sitemap accuracy, content modified, sitemap date
Fix
Rebuild and resubmit sitemaps after migration: include every URL on the new domain, submit via Search Console.
Run the Migration Audit audit
Open the Migration Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Migration Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: migration-audit, sitemap, migration
Fix
Step-by-step fix for sitemap mismatches from the Site Crawler. Find 404s, noindex pages, missing URLs, stale lastmod and regenerate clean sitemap.
Run the Site Crawler
Run the Site Crawler with Sitemap audit enabled. The crawler fetches every URL in your sitemap and checks status code, indexability, canonical alignment and lastmod accuracy.
Review the categorised report
Mismatches are grouped: URLs returning 404, 410, 5xx URLs that 301 to a different URL URLs with noindex meta or X-Robots-Tag URLs canonical-tagged to a different URL URLs with implausibly recent lastmod URLs missing from sitemap but reachable on the site
Decide per URL: restore or remove
For each 404 in the sitemap: is the URL meant to exist? Yes (accidental deletion, broken route) → restore the page No (intentional removal) → remove from the sitemap; 410 or 301 as appropriate
Remove from sitemap
How depends on your sitemap source: WordPress (Yoast/Rank Math): sitemap auto-regenerates as you delete pages. If the dead URL still appears after deletion, clear sitemap cache via plugin settings. Shopify: sitemap auto-managed; deleted products fall out within 24-48h. Static/headless: rebuild your sitemap generator. Make sure the URL discovery step queries actual live pages, not a cached source.
Update sitemap to use canonical URLs
If /old-url/ 301s to /new-url/ , your sitemap should contain /new-url/ , not /old-url/ . Many sitemap generators export the old URL because their source-of-truth was last updated before the redirect.
Decide per URL: drop the noindex or drop from sitemap
A page that says noindex shouldn't be in your sitemap. Either: Remove the noindex if the page is actually worth indexing Remove the URL from the sitemap if the noindex is correct
Review crawler-found, sitemap-missing list
URLs the Site Crawler reached but that aren't in the sitemap are signalling-gaps. Most should be added.
Find the sitemap generator's gap
Common causes: custom post types in WordPress that the SEO plugin doesn't include by default, dynamic routes in a headless app that the sitemap script doesn't traverse, products marked "hidden" in Shopify that still have public URLs.
Verify lastmod source
Open your sitemap.xml and look at the <lastmod> values. If they all match the same date (likely your last deploy), your sitemap generator is using build time, not content modification time. Bad signal to Google.
Fix the generator
Sitemap generators should read each page's actual last-modified date from the CMS. Examples: WordPress: post_modified is correct; do NOT use post_date alone Headless CMS: use the updatedAt field, not query time Static sites: use git log dates per content file If you can't get accurate lastmod, omit it entirely. Empty is better than wrong.
Regenerate
Force a regeneration of the sitemap from your generator. Verify the new sitemap reflects your fixes by spot-checking URLs: curl -s https://yourdomain.com/sitemap.xml | head -50
Submit in Search Console
Search Console → Sitemaps . Confirm your sitemap is listed with no errors. If you have a sitemap index, submit the index URL only — Google reads it and discovers the child sitemaps.
Tags: site-crawler, fix sitemap mismatches, sitemap 404, sitemap noindex, sitemap lastmod, sitemap audit, sitemap regeneration, XML sitemap errors
Fix
Fix oversized sitemaps from the Robots Tester. 50,000 URLs or 50MB max per file, split with sitemap index, compress with gzip, organise by content type.
Count URLs in your sitemap
curl -s https://example.com/sitemap.xml | grep -c "<loc>" # If > 50,000 you need to split # If close to 50k, split sooner rather than later
Check file size
curl -sI https://example.com/sitemap.xml | grep -i content-length # Compare to 52428800 bytes (50MB) # If close to 50MB, split or gzip immediately <!-- sitemap.xml (the index) --> <?xml version="1.0" encoding="UTF-8"?> <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <sitemap> <loc>https://example.com/sitemap-pages.xml</loc> <lastmod>2024-01-15</lastmod> </sitemap> <sitemap> <loc>https://example.com/sitemap-posts.xml</loc> <lastmod>2024-01-20</lastmod> </sitemap> <sitemap> <loc>https://example.com/sitemap-products-1.xml</loc> <lastmod>2024-01-20</lastmod> </sitemap> <sitemap> <loc>https://example.com/sitemap-products-2.xml</loc> <lastmod>2024-01-20</lastmod> </sitemap> </sitemapindex> <!-- sitemap-pages.xml --> <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://example.com/about/</loc> <lastmod>2024-01-15</lastmod> </url> <!-- ... up to 50,000 URLs ... --> </urlset> sitemap-posts-2024.xml sitemap-posts-2023.xml sitemap-posts-2022.xml sitemap-archive.xml sitemap-products-electronics.xml sitemap-products-clothing.xml sitemap-products-home.xml sitemap-stores.xml sitemap-brands.xml sitemap-products-1.xml (URLs 1-50,000) sitemap-products-2.xml (URLs 50,001-100,000) sitemap-products-3.xml (URLs 100,001-150,000) // Customise URLs per sitemap add_filter('wpseo_sitemap_entries_per_page', function() { return 2000; // Default 1000, can go up to 50000 }); // Exclude content types add_filter('wpseo_sitemap_exclude_post_type', function($excluded, $type) { if ($type === 'attachment') return true; return $excluded; }, 10, 2); // app/sitemap.ts — sitemap index import type { MetadataRoute } from 'next'; export default async function sitemap(): Promise<MetadataRoute.Sitemap> { return [ { url: 'https://example.com/sitemap-pages.xml', lastModified: new Date(), }, { url: 'https://example.com/sitemap-posts.xml', lastModified: new Date(), }, ]; } // app/sitemap-pages.xml/route.ts — generate child sitemap import { generateSitemap } from '@/lib/sitemap'; export async function GET() { const pages = await getPagesForSitemap(); return new Response(generateSitemap(pages), { headers: { 'Content-Type': 'application/xml' } }); } // @astrojs/sitemap auto-splits at 45,000 URLs per file import sitemap from '@astrojs/sitemap'; export default defineConfig({ site: 'https://example.com', integrations: [sitemap({ entryLimit: 45000, // optional, default 45000 filter: (page) => !page.includes('/admin/'), })] }); from xml.etree.ElementTree import Element, SubElement, tostring from datetime import datetime def generate_sitemap_chunk(urls, output_path): urlset = Element('urlset', xmlns='http://www.sitemaps.org/schemas/sitemap/0.9') for url in urls: u = SubElement(urlset, 'url') SubElement(u, 'loc').text = url['loc'] if 'lastmod' in url: SubElement(u, 'lastmod').text = url['lastmod'] with open(output_path, 'wb') as f: f.write(b'<?xml version="1.0" encoding="UTF-8"?>\n') f.write(tostring(urlset)) def generate_sitemaps(all_urls, base_url, chunk_size=45000): chunks = [all_urls[i:i+chunk_size] for i in range(0, len(all_urls), chunk_size)] sitemap_urls = [] for i, chunk in enumerate(chunks): filename = f'sitemap-{i+1}.xml' generate_sitemap_chunk(chunk, f'public/{filename}') sitemap_urls.append(f'{base_url}/{filename}') # Generate index sitemapindex = Element('sitemapindex', xmlns='http://www.sitemaps.org/schemas/sitemap/0.9') for url in sitemap_urls: s = SubElement(sitemapindex, 'sitemap') SubElement(s, 'loc').text = url SubElement(s, 'lastmod').text = datetime.now().isoformat() with open('public/sitemap.xml', 'wb') as f: f.write(b'<?xml version="1.0" encoding="UTF-8"?>\n') f.write(tostring(sitemapindex)) # Generate compressed version gzip -k sitemap.xml # Creates sitemap.xml.gz, keeps original Sitemap: https://example.com/sitemap.xml.gz server { gzip on; gzip_types application/xml text/xml; gzip_min_length 1000; gzip_comp_level 6; }
Submit index to Search Console
Submit only the index URL ( https://example.com/sitemap.xml or /sitemap_index.xml ). Google discovers child sitemaps automatically.
Wait for processing
Search Console → Sitemaps. Each child sitemap appears listed with URL count, status, last read date.
Check for errors
Status "Couldn't fetch" = file too large, malformed, or 404. "Has issues" = some URLs problematic. Click for details.
Each file under limits
for f in /var/www/html/sitemap-*.xml; do size=$(stat -c%s "$f") count=$(grep -c "<loc>" "$f") echo "$count URLs, $((size/1024)) KB - $f" done # Each line should show count <= 50000, size <= 51200 KB
Re-run Robots Tester
Size warnings clear. Each sitemap validates. URL counts match expectations.
Tip: 💡 Start splitting by content type even before hitting limits. Smaller specialized sitemaps regenerate faster and let crawlers focus their attention. A 10k-URL pages sitemap that's always fresh is more useful than a 60k-URL mega-sitemap that updates slowly.
Tags: robots-sitemap, fix sitemap size, sitemap index, 50k url limit, sitemap split, gzip sitemap, sitemap.xml.gz, large site sitemap, sitemap segmentation
Fix
Make links render correctly in Slack with Open Graph tags, image dimensions and proper content-type headers.
Run the Social Preview audit
Open the Social Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Social Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: social-preview, slack, preview
Fix
Fix slow page templates from the Site Crawler. Profile TTFB, find DB and asset bottlenecks, apply template-specific fixes and verify speed gain.
Group findings by template
Export the Site Crawler response-time report. Sort by template type and TTFB. If all pages of one template show TTFB over 1s while other templates are under 500ms, the template is the problem.
Install Query Monitor
WP Admin → Plugins → Add New → Query Monitor. Activate. Load the slow page logged in as admin. The QM toolbar shows total time, query count, slowest queries, plugin overhead.
Look for these patterns
Queries: 500+ per page indicates N+1 pattern. Fix by adding eager-loading via WP_Query meta_query batching. Query time: any single query over 200ms is a fix-now item. HTTP API calls: external API calls during page render are killers. Cache via transients. Plugin overhead: any plugin taking over 100ms per request needs review — disable and benchmark.
Install Shopify Theme Inspector
Install the official Shopify Theme Inspector Chrome extension. Open a slow page, click the extension. Reports Liquid render time per section and per snippet.
Find slow Liquid
Common Shopify killers: Nested for loops over collections.all.products — looping through every product on the storefront. Replace with paginated queries. Apps injecting JavaScript that fires before render. Audit installed apps; remove unused ones. Custom metafield queries inside loops. Pre-resolve metafields outside loops or use Storefront API.
Use Vercel Analytics or Next.js built-in instrumentation
Vercel Analytics shows per-route TTFB and Web Vitals at the 75th percentile. For self-hosted, add OpenTelemetry instrumentation in instrumentation.ts : // instrumentation.ts export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { await import('./instrumentation.node'); } } Then ship traces to Sentry / Honeycomb / Datadog.
Decide rendering mode per route
Slow SSR routes are often candidates for SSG or ISR. Route only needs to dynamic-render if the data changes per request. Otherwise pre-render at build time or revalidate periodically: // app/product/[slug]/page.tsx export const revalidate = 3600; // re-render every hour <script src="https://example.com/widget.js" defer></script> window.addEventListener('load', () => { const s = document.createElement('script'); s.src = 'https://example.com/widget.js'; document.body.appendChild(s); }); <img src="..." loading="lazy" decoding="async"> <iframe src="..." loading="lazy"></iframe>
Tip: 💡 Most slow-template fixes lift every page using that template. A 500ms improvement on a product template that serves 10,000 products is 10,000 page-loads-per-day faster.
Tags: site-crawler, fix slow templates, slow TTFB, template performance, slow product pages, slow category pages, WordPress slow templates, Shopify slow templates
Fix
Recover from spam update. Audit historical link-building, AI-generated spam, doorway pages, expired-domain abuse. Identify and remove manipulative patterns.
1. Identify which spam pattern triggered the drop
Audit each category against your site: Link schemes: - Bought / exchanged links at scale - Excessive guest posting with exact-match anchor - Foreign-language link directories - Comment / forum link drops → See how-to-fix-toxic-backlinks for diagnosis Doorway pages: - Multiple near-identical pages targeting variant keywords - Auto-generated location/category permutations - Pages that redirect users vs what they promised AI-generated spam at scale: - Mass production of unedited AI content - Programmatically generated with thin variations - No human review or first-hand experience added Expired-domain abuse: - Buying expired domains for their backlinks - Republishing on those domains for ranking - Different content than original purpose Hidden text / links: - White-on-white text - Font-size:0 keyword stuffing - Hidden links in templates Cloaking: - Different content served to Googlebot vs users - Hidden in JavaScript or via user-agent detection
2. Stop the manipulation first
Recovery is impossible while the spam tactic continues. Hard stop: Cancel link-buying / exchange contracts Pause guest-post campaigns; restart only with editorial integrity Stop bulk AI content production until you have human-edited workflow Decommission doorway page generators Remove all hidden text/links from templates
3. Remove or disavow obvious spam
For each spam category, the response differs: Spam category Response ───────────────────────────────────────────────────────────── Link scheme - Try to remove via outreach to linking sites - Disavow remaining: see how-to-fix-disavow-file - Document the effort for any reconsideration Doorway pages - 301 to closest legitimate page OR - 410 Gone for clean removal - Don't try to "fix" — Google penalises the pattern AI spam - Audit each, prune ruthlessly - Surviving content needs first-hand experience added - Stop production until workflow includes editing Expired domain - Migrate content to legitimate domain - Don't try to "explain" — Google has policies Hidden text/links - Remove immediately - Audit templates, plugins, third-party widgets Cloaking - Serve same content to all UAs - Audit JS-based rendering for inconsistency
4. Document the cleanup
If you receive a manual action, you'll need to file a reconsideration request. Documentation accelerates that: For each spam pattern fixed: - What it was (specific URLs / link sources) - When it started (date evidence available) - When it stopped (commit history, contract end) - What you removed/disavowed (URLs, disavow file) - Process changes preventing recurrence Reconsideration requests succeed when they show: 1. Acknowledgement of what went wrong 2. Evidence of cleanup 3. Process to prevent recurrence 4. Don't bargain or blame Google's classifier
5. Build legitimate replacement signals
After cleanup, the gap fills slowly. Replace manipulated signals with earned ones: Editorial links via brand mentions and PR Original content with named authors (no more anonymous bulk) Customer-driven testimonials and case studies Industry recognition (awards, speaking, contributions) Time — the trust deficit reduces over 6-18 months of clean behaviour
6. Recovery timeline
If algorithmic only (no manual action): - Next spam update reassesses - 3-9 months for partial recovery - 12-24 months for full If manual action received: - File reconsideration request with evidence - 2-6 weeks for review - Then algorithmic reassessment still required - Plan 6-12 months total <div
Tip: 💡 Spam-update recovery is the hardest of the three algorithm-impact types. Sites that recover are the ones that fundamentally change behaviour, not just hide it better. Sites that try to "fix" while continuing variants of the manipulation drop again on the next update. The honest path is the only path.
Tags: algo-impact, spam update recovery, link scheme audit, expired domain abuse, doorway pages, AI spam, Google spam policies
Fix
Diagnose and fix SSL/TLS errors: expired certificates, wrong hostnames, weak ciphers, mixed content warnings.
Run the HTTP Error Audit audit
Open the HTTP Error Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your HTTP Error Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: http-error-audit, ssl, errors
Fix
Wire dateModified to the real content edit timestamp so AI engines and Google trust your freshness signals.
Run the Schema Drift audit
Open the Schema Drift Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Schema Drift audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: schema-drift, stale, datemodified
Fix
Refresh keyword targeting as search intent evolves: monitor SERP changes, retire dead keywords, capture new ones.
Run the Keywords audit
Open the Keywords Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Keywords audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: keywords, stale
Fix
Refresh llms.txt regularly so AI engines see current content: update dates, link counts, summaries.
Run the llms.txt audit
Open the llms.txt Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your llms.txt audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: llms, stale, llms-txt
Fix
Diagnose a sudden audit score drop within 24 hours: identify the breaking deploy and roll back or patch.
Run the Score History audit
Open the Score History Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Score History audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: score-history, sudden, drop
Fix
Reduce TBT: break up long JavaScript tasks, defer non-critical work, profile and optimise event handlers.
Run the Core Web Vitals audit
Open the Core Web Vitals Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Core Web Vitals audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cwv, tbt
Fix
Close technical SEO gaps where competitors are stronger: Core Web Vitals, schema, mobile UX, structured data depth.
Run the Competitor Analysis audit
Open the Competitor Analysis Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Competitor Analysis audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: competitor-analysis, technical, gaps
Fix
Fix thin content from the Site Crawler. Identify low-value pages, decide improve consolidate or prune, restore quality and protect against helpful-content updates.
Run the Site Crawler with word-count reporting
Run a fresh Site Crawler . The crawler reports word count per page and flags pages under common thresholds (200, 300, 500 words). Export to CSV.
Cross-reference with Search Console
Export your Search Console performance report for the last 90 days (URLs + clicks + impressions). Join with the thin-content CSV by URL. You now have, per thin page: word count, clicks, impressions, average position.
SERP-analyse top-ranking competitors
For each improve-target page, search the target query in Google. Look at the top 3 ranking pages. Their depth is your minimum bar. Note: word count, sections covered, examples used, expert quotes, original data, FAQ inclusion, schema markup.
Add genuine depth, not filler
Filler hurts. Real depth helps. The five types of content that lift thin pages: Original research or data — your own survey results, your own test results, your own analytics insights Named expert commentary — quotes from real people with credentials (E-E-A-T signal) Concrete examples — case studies, real numbers, before/after FAQ section — answers to specific questions, not "what is X" filler Visuals with original information — custom diagrams, screenshots, comparison tables (not stock photos)
Update lastmod and re-submit
After improving a page, update its CMS modification date and re-submit via Search Console URL Inspection → Request Indexing. Google often re-crawls within 24-48 hours when explicitly asked.
Pick the winner
The winner is usually: The URL with the most external backlinks (Search Console Links report) If tied: the cleanest URL slug If still tied: highest current impressions
Merge the content
Take unique paragraphs from each loser page and integrate into the winner. Don't just concatenate — restructure into coherent sections. Then 301 each loser to the winner.
Update internal links
Internal links to losers now hit a 301. Use the bulk find-and-replace technique from the broken links guide to point them directly at the winner.
Stage 1: noindex
For pages decisively flagged as prune: add <meta name="robots" content="noindex, follow"> . Remove from sitemap.xml. Monitor for 60-90 days. If no traffic gained back (most won't), proceed to stage 2.
Stage 2: 410 Gone
After 60-90 days noindex, return 410 Gone for these URLs. 410 is a stronger deletion signal than 404 — Google removes the URL faster. // nginx location = /old-thin-page/ { return 410; } // Apache .htaccess Redirect 410 /old-thin-page/
Tip: 💡 Two-stage pruning protects you from removing pages that turn out to have residual value. If a page gains traffic during noindex, you can quietly re-enable it. Once you 410, the URL is gone.
Tags: site-crawler, fix thin content, low word count, content quality, content pruning, content consolidation, helpful content update, E-E-A-T, content audit
Fix
Fix third-party tag bloat from the JS Checker. Audit GTM, defer non-essential tags, lazy-load chat widgets, set performance budgets for third parties.
List every third-party domain
DevTools → Network tab → filter to JS → reload. Sort by Domain. Every domain that isn't yours is a third-party. Common list: googletagmanager.com, google-analytics.com, intercom.io, hotjar.com, segment.com, mixpanel.com, cdn.amplitude.com, etc.
Get size and timing per tag
Network panel shows transfer size, decoded size, and start/end timing per request. The "third-party usage" Lighthouse audit summarises this in one view with grand totals.
Cross-reference with business value
For each tag, ask the owner: "What business decision did this drive in the last 90 days?" Tags that can't answer are candidates for removal. Typical audit cuts 30-40% of tags. <!-- Before — synchronous, blocks render --> <script src="https://cdn.heatmap.com/tracker.js"></script> <script src="https://cdn.chat.com/widget.js"></script> <!-- After — non-blocking --> <script async src="https://cdn.heatmap.com/tracker.js"></script> <script defer src="https://cdn.chat.com/widget.js"></script> // Idle imports — pre-fetches without blocking function lazyLoadChat() { const script = document.createElement('script'); script.src = 'https://widget.intercom.io/widget/APP_ID'; script.async = true; document.body.appendChild(script); // Remove the trigger so we don't load twice removeTriggers(); } function removeTriggers() { ['mousemove', 'touchstart', 'click', 'scroll'].forEach(evt => { document.removeEventListener(evt, lazyLoadChat); }); } ['mousemove', 'touchstart', 'click', 'scroll'].forEach(evt => { document.addEventListener(evt, lazyLoadChat, { once: true, passive: true }); }); // Idle 5s after page load window.addEventListener('load', () => { setTimeout(lazyLoadChat, 5000); }); // Visible support button with placeholder; loads widget on click document.querySelector('.support-button').addEventListener('click', async () => { await loadChat(); // Open the widget once loaded if (window.Intercom) window.Intercom('show'); });
List all tags in the container
GTM → Container → Tags. Each tag has a trigger. Audit each: What does it fire on? "All Pages" is the most expensive trigger. What does it do? Analytics ping, ad pixel, custom JavaScript. Who owns it? Is anyone using its data?
Tighten triggers
Tags should fire on the specific event that matters, not All Pages: Conversion pixel: fire on /thank-you page only Cart add tracking: fire on add-to-cart event only Form submission: fire on form submit event only Page view ping: All Pages is correct here
Set Consent Mode V2
For marketing and advertising tags, configure them to wait for user consent before firing. GTM Settings → Consent → assign tags to advertisement/analytics consent. Tags don't fire until consent state is granted. # Build script: fetch latest GA4 script and serve from your domain curl -o public/js/gtag.js https://www.googletagmanager.com/gtag/js?id=G-XXXX # Then in HTML <script async src="/js/gtag.js"></script> # Lighthouse CI { "ci": { "assert": { "assertions": { "third-party-summary": ["error", { "maxNumericValue": 300 }], "resource-summary:third-party:size": ["error", { "maxNumericValue": 250000 }] } } } }
Tip: 💡 The third-party-tag review is uncomfortable but high-leverage. One quarterly meeting auditing every tag's business value typically removes 30-40% and adds a few months of headroom before the next bloat cycle. Document each tag's purpose and decision rights — future-you will thank past-you.
Tags: js-checker, fix third party tags, GTM audit, tag manager performance, chat widget lazy load, analytics performance, third party scripts, marketing tags
Fix
Audit and optimise third-party scripts (analytics, ads, chat widgets) that slow page load and inflate INP.
Run the Page Speed audit
Open the Page Speed Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Page Speed audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: pagespeed, third, party
Fix
Diagnose and fix HTTP timeout errors: slow backend, exhausted connection pool, badly tuned timeouts at origin or CDN.
Run the HTTP Error Audit audit
Open the HTTP Error Audit Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your HTTP Error Audit audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: http-error-audit, timeout, errors
Fix
Write title tags that match search intent, hit the optimal length, include the primary keyword and end with the brand.
Run the Meta Analyzer audit
Open the Meta Analyzer Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Meta Analyzer audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: meta-analyzer, title, tags
Fix
Stop Google truncating your title tags: keep titles under 580px wide, put the most important words first.
Run the SERP Preview audit
Open the SERP Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your SERP Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: serp-preview, title, truncation
Fix
Identify and remove toxic backlinks before they trigger penalties. PBN detection, paid link patterns, comment spam, outreach removal then disavow.
1. Toxic backlink patterns
Pattern Detection signal ───────────────────────────────────────────────────────────── PBN (Private Blog Network) Same IP, similar templates, low-quality content Paid link networks Footer/sidebar links to multiple unrelated sites Comment spam User-generated links in blog comments Forum signature spam Excessive forum signatures with same anchor Low-quality directory Pay-for-listing directories, no editorial Foreign-language link farms Languages your audience doesn't speak Exact-match anchor explosion Same keyword used 50+ times across sites Sitewide links from random Footer links across thousands of pages
2. Audit workflow
Sources: - Google Search Console → Links → Top linking sites - Ahrefs / Semrush / Majestic backlink reports - Your existing aiwebpageseo backlinks audit For each linking domain: - Domain authority (genuine sites usually 20+) - Topic relevance (related to your category?) - Content quality (real site or thin spam?) - Anchor text pattern (natural variety or exact-match?) - Outbound link profile (links to many unrelated sites = spam)
3. Remove first, disavow second
For each toxic source: - Contact site owner via WHOIS / contact form - Polite request: "Please remove link to example.com from URL X" - Wait 14 days - Document the request (email, screenshot) - If removed: confirm and stop - If ignored / refused: add to disavow file Outreach success rate: 20-40% typical. Don't be aggressive; some sites won't respond. That's normal.
4. Build the disavow file
See disavow file fix for full syntax. Quick form: # Domain-level disavow (more aggressive but cleaner) domain:spammydomain.com domain:linkfarm.net # URL-level disavow (when only specific pages are toxic) http://example.com/spam-page-1 http://example.com/spam-page-2 Upload via Search Console → Disavow Tool. Google processes within 2-6 weeks.
5. Monitor after action
Post-disavow tracking: - Weekly backlink count (some toxic links 404 over time) - Ranking changes (recovery slow, 4-12 weeks) - GSC impressions for affected query clusters - Watch for new toxic links appearing (negative SEO attempts) If new toxic links appear regularly: - Consider competitor doing negative SEO - Set up monitoring alerts (Ahrefs, Semrush) - Update disavow monthly <div
Tip: 💡 Don't disavow defensively. Disavow is for actually-toxic links. Disavowing healthy links by accident hurts you. Audit honestly, remove what you can, disavow what remains. Most sites need this maybe twice a year, not constantly.
Tags: backlinks, toxic backlinks, toxic link removal, PBN detection, spam links disavow, link audit
Fix
Fix trailing slash inconsistency from the Redirect Checker. Pick one convention, enforce server-side with 301, fix internal links, handle file extensions.
Both versions side by side
curl -I https://example.com/aboutpage curl -I https://example.com/aboutpage/ # Healthy state — one redirects to the other: # /aboutpage → 301 → /about/ (consistent: with slash) # /aboutpage/ → 200 (canonical) # OR # /aboutpage → 200 (canonical) # /aboutpage/ → 301 → /about (consistent: no slash) # Problem state — both serve content: # /aboutpage → 200 # /aboutpage/ → 200 ← duplicate content
Check sitemap and canonical tags
# Sitemap URLs curl -s https://example.com/sitemap.xml | grep -oE ' [^ ' | head -10 # Mix of with-slash and without is the problem. # Pick one, regenerate sitemap. server { # Add trailing slash to non-file URLs rewrite ^([^.]*[^/])$ $1/ permanent; } # Note the regex: [^.]*[^/]$ means "no dot in path AND doesn't end in slash" # Protects file URLs like /style.css from getting a trailing slash server { # Strip trailing slash from non-root URLs rewrite ^(.+)/$ $1 permanent; # But keep / for root (otherwise it loops) # The (.+) requires at least one character before / } RewriteEngine On # Add trailing slash to non-file URLs RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_URI} !(.*)/$ RewriteRule ^(.*)$ /$1/ [R=301,L] RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.+)/$ /$1 [R=301,L] // Force trailing slash example addEventListener('fetch', event => { const url = new URL(event.request.url); const path = url.pathname; // Skip files (with extension) const isFile = path.match(/\.[a-z0-9]{2,5}$/i); // Skip root if (path === '/' || isFile) { return event.respondWith(fetch(event.request)); } // Add slash if missing if (!path.endsWith('/')) { url.pathname = path + '/'; return event.respondWith(Response.redirect(url, 301)); } return event.respondWith(fetch(event.request)); }); # Correct rule excludes file extensions RewriteCond %{REQUEST_URI} !\.[a-z0-9]+$ RewriteRule ^(.*[^/])$ /$1/ [R=301,L] # Without the exception, /style.css → /style.css/ → 404 # Settings → Permalinks # Make sure permalink structure ends with / for with-slash convention # Or no / for no-slash # Then run a database search-replace to update any hardcoded links wp search-replace 'href="/seo-audit-platform.html"' 'href="/seo-audit-platform.html"' --skip-columns=guid # Find inconsistent links in HTML grep -rE 'href="[^"]+(?<!/)"' ./public/ # Or use the framework's built-in trailing slash setting # Next.js: module.exports = { trailingSlash: true } # Astro: export default defineConfig({ trailingSlash: 'always' }) # Hugo: [permalinks] posts = "/:slug/" curl -s https://example.com/sitemap.xml | \ grep -oE '<loc>[^<]+</loc>' | head -20 # All should consistently use (or not use) trailing slash <!-- canonical tag matches the actual canonical URL form --> <link rel="canonical" href="https://example.com/about-page/" /> <!-- or --> <link rel="canonical" href="https://example.com/about-page" /> # Bad rule rewrite ^(.+)$ $1/ permanent; # Matches / → / → / → infinite # Good rule rewrite ^(.+[^/])$ $1/ permanent; # Only matches paths that DON'T end in slash, root is excluded # Bad — drops query string rewrite ^(.+)/$ $1 permanent; # Good — preserves query string (nginx auto-appends unless you use ?) rewrite ^(.+)/$ $1 permanent; # Default behaviour preserves query # Or explicit rewrite ^(.+)/$ $1?$args permanent;
curl both versions
curl -IL https://example.com/about-page curl -IL https://example.com/about-page/ # One should redirect to the other with 301 # The other should return 200
Run Redirect Checker
Crawl the site. Slash mismatch findings should clear. Internal links all use canonical convention.
Search Console URL inspection
Inspect a few representative URLs. Canonical reported by Google should match your chosen convention.
Tip: 💡 Pick the convention your CMS or framework defaults to — you'll fight the platform less. WordPress likes with-slash, modern frameworks vary. Whatever you pick, document it in your team wiki so future devs maintain consistency.
Tags: redirect-checker, fix trailing slash, URL consistency, 301 trailing slash, nginx slash, apache slash, canonical URL slash, file extension slash
Fix
Add transition words (“however”, “then”, “as a result”) that improve reading flow and Flesch score.
Run the Readability audit
Open the Readability Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Readability audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: readability, transitions
Fix
Add the trust signals (named experts, citations, contact info, transparent ownership) that strengthen E-E-A-T.
Run the E-E-A-T audit
Open the E-E-A-T Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your E-E-A-T audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: eeat, trust, signals
Fix
Reduce TTFB: server optimisation, CDN, caching, smaller HTML, faster database queries.
Run the Core Web Vitals audit
Open the Core Web Vitals Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Core Web Vitals audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: cwv, ttfb
Fix
Fix unclosed and mismatched HTML tags from the HTML Checker. Find unclosed div, mismatched nesting, self-closing pitfalls, validate and prevent recurrence.
Run the HTML Checker
Run the HTML Checker on your site. Export findings with URL, line number, column, and the type of error: unclosed tag, mismatched close, stray closing tag, unexpected element.
Group findings by template
If every product page reports an unclosed div at line 234, the bug is in your product template, not in each individual product. Fixing the template fixes thousands of pages at once.
View page source and find the line
View Source on a flagged URL. Jump to the line number reported. Look at the surrounding structure — is this template code, content from the CMS, or output from a widget/plugin?
Identify the template
Different platforms expose templates differently: WordPress: wp-content/themes/your-theme/*.php . Use Theme File Editor or SSH. Shopify: Online Store → Themes → Edit Code → sections/ or snippets/ . Next.js / React: app/ or components/ JSX files. CMS content fields: dive into the page editor; broken HTML often lives in WYSIWYG-saved content. <!-- BROKEN --> <div class="card"> <h3>Title</h3> <p>Content</p> <!-- missing </div> --> <!-- FIXED --> <div class="card"> <h3>Title</h3> <p>Content</p> </div> <!-- BROKEN --> <section> <article> Content </section> </article> <!-- FIXED --> <section> <article> Content </article> </section> <!-- BROKEN (HTML5) --> <div class="row" /> <!-- FIXED --> <div class="row"></div> <!-- BROKEN (paragraph closes early) --> <p>Some text <div>more content</div> rest of paragraph.</p> <!-- FIXED --> <p>Some text</p> <div>more content</div> <p>rest of paragraph.</p>
Add server-side HTML sanitisation
PHP: HTMLPurifier . Node.js: DOMPurify . WordPress already runs wp_kses_post() for capabilities-limited users — extend its allowed-tags list rather than disabling it.
Configure TinyMCE/CKEditor for strict cleanup
In TinyMCE config: valid_elements defines allowed tags, paste_word_valid_elements filters Word paste. CKEditor: config.allowedContent restricts to a known-clean schema. Reject anything outside the schema.
Validate every build
Add an HTML validation step to your CI pipeline: # GitHub Actions - name: Validate HTML run: npx html-validator-cli --file=dist/index.html --format=text Fail builds on validation errors. Broken HTML never reaches production again.
Pre-commit hooks
Even earlier: add a pre-commit hook that runs HTML validation on changed templates. Catches issues before they enter the repo.
Tags: html-checker, fix unclosed tags, unclosed div, mismatched HTML, self-closing tags, HTML validation, malformed HTML, broken nesting, HTML parser errors
Fix
Fix unused CSS from the CSS Checker. Identify dead rules, run PurgeCSS, split CSS per route, tree-shake utility frameworks, prevent regression.
Chrome DevTools Coverage
Open DevTools → Cmd/Ctrl+Shift+P → "Show Coverage" → Reload. Coverage tab shows every CSS and JS file with the percentage unused on the current page. Look for CSS files in the 60-90% unused range — that's where the gains are.
Test multiple pages
Coverage shows usage for ONE page. Visit several representative pages (homepage, product, category, blog post). Combine the used classes — that's your real "used" set across the site. npm install --save-dev @fullhuman/postcss-purgecss // postcss.config.js module.exports = { plugins: [ require('@fullhuman/postcss-purgecss')({ content: ['./src/**/*.html', './src/**/*.js', './src/**/*.jsx'], defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || [] }) ] } // tailwind.config.js module.exports = { content: [ './src/**/*.{html,js,jsx,ts,tsx}', './public/**/*.html', ], // ... } // postcss.config.js with safelist require('@fullhuman/postcss-purgecss')({ content: [...], safelist: [ 'is-open', 'has-error', 'dark-mode', /^bg-(red|green|blue)-(100|200|300|400|500)$/, // colour utilities used dynamically ] }) // React Router lazy routes const Product = lazy(() => import('./pages/Product')); const About = lazy(() => import('./pages/About')); <head> <style> /* Critical CSS for above-the-fold: 10-20kb max */ body { font: 16px/1.5 sans-serif; margin: 0; } .header { ... } .hero { ... } </style> <link rel="preload" href="/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="/main.css"></noscript> </head>
Find unreferenced CSS files
# Find CSS files not imported anywhere for f in $(find src -name "*.css"); do basename=$(basename "$f" .css) if ! grep -r "$basename" src/ --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" --include="*.html" --include="*.scss" -q; then echo "Unreferenced: $f" fi done
Use CSS-in-JS dead-code analysis
Static CSS-in-JS frameworks (vanilla-extract, Linaria) integrate with bundler tree-shaking. Unused style exports get removed automatically.
Tip: 💡 Don't aim for 100% used CSS on every page — that requires per-page generation that adds build complexity. 70-80% used is a great balance between bundle size and build sanity.
Tags: css-checker, fix unused CSS, PurgeCSS, CSS tree-shaking, CSS code splitting, dead CSS, Tailwind purge, CSS bundle size, critical CSS
Fix
Surface real article update dates (visible + schema) so users and Google see freshness on revised content.
Run the E-E-A-T audit
Open the E-E-A-T Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your E-E-A-T audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: eeat, update, dates
Fix
Fix UX signals algorithms weight: pogo-sticking, dwell time, return visits. Fast TTFB, intent-matching content, no interstitials, clear navigation.
1. Pogo-sticking is the killer
Pogo-sticking: user clicks your result, returns to SERP within seconds, clicks a competitor. Strongest negative signal in modern SEO. Diagnose: GA4 → engagement_rate by landing page Pages with engagement_rate < 30% are likely pogo-sticking Cross-reference with GSC query data — what users searched Mismatch between query intent and page content = pogo-stick Patterns that cause it: - Title promises X, content delivers Y - Page loads slow, user gives up - Required action (login, paywall) blocks content - Page layout disorients (popups, autoplay, sticky elements) - Content irrelevant to query intent
2. Match content to query intent precisely
For each high-traffic page: - GSC: what queries does this page rank for? - Read the queries — what do users actually want? - Does your page deliver that in the first 100 words? - If "how do I X", does the page actually teach X? - If "what is X", does the page define X first? - If "best X for Y", does it actually recommend? Intent mismatch is the #1 cause of pogo-sticking. Fix by restructuring lead paragraph to deliver immediately.
3. First contentful paint speed
Fast pages reduce pogo-sticking. The threshold is harsh: Targets: TTFB: < 600ms FCP: < 1.8s LCP: < 2.5s Total page: visually complete < 3.5s Above these, pogo-sticking rises measurably. See how-to-fix-page-speed-agents for specific fixes. Mobile speed matters most — mobile-first index. Quick wins: - Compress images - Defer non-critical JS - Server-render above-fold content - Self-host critical fonts - HTTP/2 or HTTP/3 - Geographically near origin or CDN
4. Remove interstitial friction
Cookie banners — required by GDPR but minimise visual footprint, don't block content Email signup popups — delay until engagement signal (scroll, time) App install banners — Google penalises full-screen on mobile Login walls — fine for /account; fatal for content pages Autoplay video / audio — kills mobile experience Sticky bars stacking — header + footer + chat widget eats viewport Sponsored content interrupters — break reading flow
5. Clear navigation and information architecture
Users who find what they came for + see related content stay. Users who land and can't orient pogo-stick. Patterns that help: - Sticky table of contents on long pages - Breadcrumbs (and BreadcrumbList schema) - Visible "Next" / "Related" links at end - Clear primary nav with category labels - Search functionality on long-tail content sites Patterns that hurt: - Hidden hamburger nav with too many items - Unclear category labels - "Mystery meat" icons without text - Deep folder structure exposed in URLs - No way back to parent page
6. Dwell time and engagement
Long dwell signals satisfaction. Short dwell + return = dissatisfaction. Optimise for genuine engagement (not tricks): - Comprehensive content (per how-to-fix-content-extractability) - Internal links to related content (continue journey) - Comments / discussion (users return to check) - Tools / calculators / interactive elements - Video for content that benefits from it - Updated dates visible — users return to fresh content Avoid dark patterns: - Splitting into 10 pages to inflate pageviews — pogo-stick magnet - Hiding content behind progressive reveals — frustration - Autoplay that prevents back-button — punished
7. Return visit signals
Users returning to your site signal authority. Build return reasons: - Email newsletter with original insight - Updates to evolving guides - Tools that get re-used (calculators, generators) - Community / forum - Content series that continues
8. Measurement
Watch these GA4 metrics by landing page: - engagement_rate (% of engaged sessions) - average engagement time - scroll percentage (90% reaches signal) - return visit rate GSC: - clicks per impression (CTR) - average position trend after UX fixes Cross-correlate: pages with rising engagement should show ranking improvement within 4-12 weeks if other quality signals are present. <div
Tip: 💡 UX signals are slow-moving but compound. Sites that obsess over click-through and pogo-sticking month after month build a measurable advantage over competitors that focus only on rank. The signal isn't any single metric — it's the pattern: users find what they want fast, stay long enough to engage, return for more.
Tags: algo-impact, UX signals SEO, pogo-sticking, dwell time, bounce rate algorithm, user satisfaction signals
Fix
Add the standard responsive viewport meta tag so mobile devices render the page correctly.
Run the Meta Analyzer audit
Open the Meta Analyzer Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Meta Analyzer audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: meta-analyzer, viewport
Fix
Fix rankings that bounce 5+ positions weekly. Signals weak relevance or low-confidence rankings. Build depth, authority, query coverage to stabilise.
1. Diagnose volatility cause
Why rankings bounce: Weak relevance signal: - Content is borderline match for query - Other pages slightly better — they swap with you Low authority: - Few backlinks, light brand mentions - Google reweights and you drop, then back up Thin / narrow content: - Page answers only part of query intent - Better-covering pages outrank intermittently Competitive churn: - Many sites at similar level - Small algorithm tweaks shuffle order Fresh-content bias bouncing: - Briefly ranked on freshness, falls when assessed - Cycle repeats with each update
2. Build content depth
The single highest-leverage fix for volatility. Thin content bounces; deep content holds: For each volatile page: - Add 3-5 sections you don't currently cover - Address related questions users have - Add original data or research - Add expert commentary (HARO / Qwoted) - Include specific examples and case studies - Add comparison content (X vs Y) if relevant - Build FAQ section addressing the long tail Depth signals: time-on-page, scroll depth, return visits. All of which Google measures and use to calibrate confidence.
3. Broaden query coverage
Volatile pages often rank for a narrow query slice. Broaden by addressing the full intent: Target query: "CRM for small business" Currently covered: - Top 5 CRMs for small business Should also cover: - What is a CRM (definition) - When does a small business need one - Cost comparison - Implementation timeline - Migration considerations - Common mistakes This is breadth WITHIN the topic, not jumping to new topics. The query becomes a full topic; you become a full source for it.
4. Build site-wide authority
Page-level depth combines with site-level signals: More brand mentions across the web More backlinks from authoritative sources in your category Named authors with credentials (per author trust signals ) Stronger internal linking — your authoritative pages link to your volatile ones Wider topical authority — more pages on the surrounding topic, not just the one
5. Strengthen internal linking
For each volatile page, audit incoming internal links: - How many other pages on YOUR site link to it? - From which positions (body link vs footer)? - With what anchor text? - From pages of what authority? If a volatile page has < 5 internal links: - Find your authoritative pages on related topics - Add contextual body links pointing at the volatile page - Use varied, descriptive anchor text - Treat each authoritative page as boosting the linked page Internal linking is the highest-ROI stability work. Free, fast, no external dependencies.
6. Reduce competing pages on your own site
Volatility sometimes comes from internal keyword cannibalisation: Search Console → Performance → filter by the bouncing query → multiple URLs from your site ranking for it? → Google can't decide which to surface Fix: - Pick the canonical page for that query - 301 the other pages OR rewrite them to target different queries - Update internal links to point at the canonical - Add a self-referencing canonical tag Often the "bouncing" is between two of YOUR pages, not against competitors. Consolidate and the volatility stops.
7. Timeline
Internal linking + cannibalisation fix: - 2-6 weeks for stability improvement Content depth + breadth: - 4-12 weeks before Google's confidence rises Site-wide authority: - 3-12 months — slowest moving signal Track weekly ranking variance, not just position. "Position 5 ±2" is more important than "average 5". <div
Tip: 💡 Volatility is usually a confidence problem, not an algorithm problem. Build the signals that raise Google's confidence in your page (depth, authority, internal links, brand) and the bouncing stops. Sites that try to "manipulate stability" with rank-tracking tricks waste effort; sites that improve actual signals see ranges narrow within 2-3 months.
Tags: algo-impact, SERP volatility, ranking instability, low confidence rankings, ranking fluctuation fix, stability SEO
Fix
Make links render correctly in WhatsApp: og:image, og:title, og:description and proper content-type headers.
Run the Social Preview audit
Open the Social Preview Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your Social Preview audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: social-preview, whatsapp, preview
Fix
Solve WooCommerce crawl problems: faceted navigation traps, infinite filter URLs, crawl budget waste, sitemap pruning, robots.txt, and parameter handling.
Step 1: Audit current crawl status
Search Console → Coverage report. Note 'Excluded' URLs, particularly 'Crawled — currently not indexed' and 'Discovered — not indexed' which indicate budget waste.
Step 2: Identify faceted navigation URLs
Look at Search Console for URLs with ?filter_, ?attribute_, ?orderby parameters. Each represents a potentially-crawlable but low-value page. Count them — if over 10x your real product count, you have a faceted-nav problem.
Step 3: Implement parameter handling
Choose one approach: A) AJAX-only filters that don't change URLs (best for crawl); B) Canonical tags pointing back to the base category from all faceted variants; C) noindex on faceted URLs via Yoast/Rank Math conditional rules; D) Block faceted parameters in robots.txt.
Step 4: Configure robots.txt to block low-value paths
Block: /cart/, /checkout/, /my-account/, /?add-to-cart=, /?orderby=, /*?filter_*. Allow: /product/, /shop/, /product-category/. Add via Yoast/Rank Math robots.txt editor.
Step 5: Prune sitemap aggressively
Include only canonical product URLs and category landing pages. Exclude variation child products, out-of-stock if discontinued, low-stock-low-traffic items. Yoast WooCommerce SEO includes per-product sitemap inclusion toggle.
Step 6: Configure crawl rate in Search Console
For large WooCommerce stores (5000+ products), set crawl rate to 'Higher' in Search Console legacy settings if available, or via Bing Webmaster Tools.
Step 7: Monitor crawl stats over time
Search Console → Settings → Crawl stats. Watch the trend. After interventions, you should see total crawl requests stable or down, but indexed product count up.
Tags: ecommerce-crawl, WooCommerce crawl, faceted navigation WooCommerce, crawl budget WooCommerce, WooCommerce robots.txt, WooCommerce sitemap
Fix
WordPress migration SEO: 301 redirects from old URLs, database search-replace, sitemap regeneration, schema cleanup, post-migration audit checklist.
Step 1: Pre-migration audit
Crawl with Screaming Frog. Record all URLs, titles, meta descriptions, internal links. Export Search Console data: queries, clicks, impressions. This is your baseline.
Step 2: Plan redirect mapping
Old URL → New URL. For domain changes, every URL needs a redirect. For structure changes (permalinks edit), every old URL must redirect to new equivalent. Spreadsheet: old_url, new_url, status (301), test result.
Step 3: Run database search-replace
After migration, hardcoded URLs in post content reference the OLD domain. Use WP-CLI: 'wp search-replace olddomain.com newdomain.com'. Or Better Search Replace plugin. CRITICAL: must handle serialised data correctly — WP-CLI does this, naive SQL doesn't.
Step 4: Configure 301 redirects
Install Redirection plugin. Import your redirect mapping. Test each redirect resolves to 200 (not 301→404 chains). For .htaccess-level redirects (faster), add to .htaccess directly: 'Redirect 301 /old-url /new-url'.
Step 5: Regenerate sitemap and resubmit
Yoast/Rank Math/AIOSEO auto-regenerate sitemap. Visit /sitemap_index.xml to confirm. Resubmit in Search Console (Sitemaps → Add new). Use both old and new sitemap URLs if you've redirected the sitemap location.
Step 6: Update schema canonical and Organization
Schema's canonical and Organization.url should reflect new domain. If managed by SEO plugin, plugin updates automatically once you change Settings → General URLs. Custom schemas in theme code need manual update.
Step 7: Post-migration validation and monitoring
Within 24h: crawl new site, verify no 404s in critical pages. Within 7 days: Search Console new property submitted, change-of-address tool used (if domain change), redirects resolved correctly. Within 30 days: organic traffic should approach baseline. If down by more than 20% after 30 days, deeper audit needed.
Tags: migration-audit, WordPress migration, WordPress redirect after migration, WordPress URL change, search and replace WordPress, WordPress site move
Fix
Fix when AI engines cite your domain but attribute claims you didn't make. Corrections, citation-circle distribution, provider feedback channels.
Capture the exact misattribution
Required evidence: - Screenshot of the AI response with timestamp - The exact prompt that triggered it - Engine identifier (ChatGPT-4, Claude 3.5, Perplexity Pro, etc.) - Date and time - Cited URL on your domain - Specific claim attributed to you - Your actual position on that claim Save in a tracking spreadsheet: date | engine | prompt | claimed_url | wrong_claim | actual_position
Determine severity
High : claim is opposite of your actual position, harms reputation, or is provably false Medium : claim is misleading paraphrase of nuanced view Low : claim is roughly accurate but technically wrong source Allocate response effort proportionally. High needs all four steps; low may only need 1-2. <!-- /clarifications/ai-attribution-x --> <article> <h1>Clarification: our position on [X]</h1> <p>Some AI engines have attributed the claim "[exact wrong claim]" to aiwebpageseo. This is incorrect.</p> <h2>Our actual position</h2> <p>[Clear, definitive statement of your actual position. Short, quotable, unambiguous. AI engines extract this verbatim.]</p> <h2>What we have written about [X]</h2> <p><a href="...">Our [Year] guide on [topic]</a> covers our considered view, which is [summary]. We have never claimed [wrong claim]; suggestions that we have are based on misreading.</p> <h2>Sources we recommend instead</h2> <p>If you're researching [X], we'd point you to [legitimate sources we endorse].</p> </article> At top of the page (after H1): <aside class="clarification"> <strong>Note:</strong> This article does not endorse [wrong claim]. Our actual position is [correct position]. See our <a href="/aipageseo-demo-pages/how-to-fix-wrong-attribution.html">clarification page</a>. </aside> In schema (Article): { "@type": "Article", ... "isBasedOn": { "@type": "WebPage", "url": "https://example.com/clarifications/ai-attribution-x", "name": "Clarification: our position on [X]" } } Add a "What this article does NOT say" section - bullet-list the wrong claims - short, declarative, easy to extract
Re-run the triggering prompt weekly
Same exact prompt, same engines. Track when the misattribution disappears, when it changes, when it's replaced by a correct citation. Document the timeline — useful for future similar incidents.
Search for derivative misattributions
Variants of the prompt may produce similar wrong attributions. Probe: "What does example.com say about [X]?", "Does example.com recommend [Y]?", "What's example.com's position on [Z]?" — catch the misattribution family, not just one instance.
Tip: 💡 The strongest counter to wrong attribution is explicit denial in extractable form. AI engines respect first-person definitive statements: "We do not recommend [X]" or "Our position is [Y], not [Z]". One short sentence in a published correction page often does more than 10 paragraphs of nuanced clarification.
Tags: ai-visibility, AI wrong attribution, AI misquote, AI hallucination citation, correct AI attribution, ChatGPT feedback
Fix
Fix www vs non-www splits from the Redirect Checker. Pick a canonical, 301 the other, update Search Console, fix internal links and sitemaps.
Both versions independently
curl -I https://www.example.com/ curl -I https://example.com/ # Expected (correctly canonicalised): # One returns 200 OK # The other returns 301 with Location: https://[canonical]/ # Problem state: # Both return 200 OK (content served independently) # Or chains exist
Search results check
Search site:example.com and site:www.example.com separately in Google. If both return separate page sets, you have two indexed sites. If only one returns results, you're already canonicalised.
Run Redirect Checker
Hostname mismatch findings list URLs where non-canonical version doesn't redirect. Audit the full set. server { listen 80; listen 443 ssl; server_name example.com; return 301 https://www.example.com$request_uri; } server { listen 80; listen 443 ssl; server_name www.example.com; # serves content root /var/www/html; } server { listen 80; listen 443 ssl; server_name www.example.com; return 301 https://example.com$request_uri; } server { listen 80; listen 443 ssl; server_name example.com; root /var/www/html; } RewriteEngine On RewriteCond %{HTTP_HOST} ^example\.com$ [NC] RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L] RewriteEngine On RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC] RewriteRule ^(.*)$ https://example.com/$1 [R=301,L] Rules → Redirect Rules → Create rule When: Hostname equals "example.com" (or "www.example.com") Then: Static redirect URL: https://www.example.com${http.request.uri.path}${...} Status: 301 Preserve query string: enabled # WP-CLI search-replace (test with --dry-run first) wp search-replace 'https://example.com' 'https://www.example.com' \ --dry-run --skip-columns=guid # After confirming, run live wp search-replace 'https://example.com' 'https://www.example.com' \ --skip-columns=guid # Find affected files grep -rl "https://example\.com" ./public # Replace (test on one file first) sed -i 's|https://example\.com|https://www.example.com|g' file.html # Bulk find ./public -name "*.html" -exec sed -i \ 's|https://example\.com|https://www.example.com|g' {} + # MySQL UPDATE wp_posts SET post_content = REPLACE(post_content, 'https://example.com', 'https://www.example.com') WHERE post_content LIKE '%https://example.com%'; # Backup database BEFORE running. <url> <loc>https://www.example.com/page</loc> <lastmod>2024-01-15</lastmod> </url> <link rel="canonical" href="https://www.example.com/page" />
Verify both properties
Add both https://www.example.com and https://example.com as URL-prefix properties in Search Console. OR add a single Domain property example.com that covers all variants.
Submit canonical sitemap
In the canonical property, submit sitemap.xml. The non-canonical property doesn't need its own sitemap (URLs redirect anyway).
Use URL inspection on test pages
Inspect a non-canonical URL → "Test live URL" → confirm Google sees the 301 and the canonical URL. Inspect the canonical URL → confirm "Indexing allowed" and indexed status. // Google Analytics 4 // Set "Hostname" as a custom dimension to spot stragglers // Filter reports to canonical hostname only // gtag.js setup gtag('config', 'G-XXXX', { cookie_domain: 'www.example.com' // or '.example.com' for shared });
curl confirmation
curl -IL https://example.com/page # Expected: 301 → https://www.example.com/page → 200 curl -IL https://www.example.com/page # Expected: 200 directly, no redirect
Browser bar test
Type non-canonical URL in browser. Browser should resolve to canonical version. Address bar shows canonical URL after the redirect.
Crawl with audit tool
Re-run the Redirect Checker site-wide. Zero hostname mismatch findings. All internal links use the canonical host.
Tip: 💡 The most-forgotten part: internal links. Many sites correctly redirect external traffic but keep hardcoded internal URLs pointing at the non-canonical host. Every internal click then takes 2 hops. Use search-replace on the database AND on static template files. Audit afterwards by crawling your own site.
Tags: redirect-checker, fix www canonicalisation, www vs non-www, canonical host, 301 redirect www, GSC property, internal links canonical, sitemap host
Fix
Add missing hreflang x-default from the Hreflang Checker. Fallback for unmatched locales. Point at picker page or English-international. Implementation guide.
View source on each variant
Each page in the cluster should show the same x-default URL. Use DevTools or curl + grep: curl -s https://example.com/en/about | grep hreflang curl -s https://example.com/fr/about | grep hreflang # Both should include the same hreflang="x-default" line
Re-run Hreflang Checker
x-default warnings clear. Cluster validates as complete.
1. What x-default does
x-default is a special hreflang value meaning "serve this URL to any user who doesn't match my other locales". Acts as the catch-all fallback. Without it, Google's behaviour for unmatched users is inconsistent. Typical use cases User in a country you don't explicitly target User with browser language Google doesn't have a locale for Bot that doesn't send language preferences Anyone falling through the matching rules
2. Pick the x-default target
Pattern 1: Language picker landing page https://example.com/ The root URL shows a country/language picker. User explicitly chooses. Best for sites with 5+ locales, complex regional differences, e-commerce with currency/shipping variations. <link rel="alternate" hreflang="en-GB" href="https://example.com/uk/about" /> <link rel="alternate" hreflang="en-US" href="https://example.com/us/about" /> <link rel="alternate" hreflang="fr-FR" href="https://example.com/fr/about" /> <link rel="alternate" hreflang="de-DE" href="https://example.com/de/about" /> <link rel="alternate" hreflang="x-default" href="https://example.com/" /> Pattern 2: English-international https://example.com/en/about Point x-default at your English-language version without region. Best for sites where English serves international users adequately and most users would prefer "just give me English" over picking explicitly. <link rel="alternate" hreflang="en" href="https://example.com/en/about" /> <link rel="alternate" hreflang="fr-FR" href="https://example.com/fr/about" /> <link rel="alternate" hreflang="de-DE" href="https://example.com/de/about" /> <link rel="alternate" hreflang="x-default" href="https://example.com/en/about" /> What NOT to do <!-- BAD: x-default to a country-specific version --> <link rel="alternate" hreflang="x-default" href="https://example.com/us/about" /> <!-- US English shown to French Canadian users? They'd prefer fr-CA fallback or picker -->
3. Add to every page in the cluster
Same x-default URL on every variant page. Treat it like another locale entry in your cluster config. // locales-config.js export const aboutCluster = { 'en': 'https://example.com/en/about', 'fr-FR': 'https://example.com/fr/about', 'de-DE': 'https://example.com/de/about', 'x-default': 'https://example.com/en/about' // ← fallback }; // Generator function function renderHreflang(cluster) { return Object.entries(cluster).map(([lang, href]) => `<link rel="alternate" hreflang="${lang}" href="${href}" />` ).join('\n'); } // Output on EVERY page in the cluster, including the x-default target itself
4. WordPress (WPML / Polylang)
Most WordPress translation plugins generate x-default automatically. If missing, check plugin settings: WPML → Languages → SEO options - "Add alternate language links to the head" → enabled - "Send default language as x-default" → enabled Polylang → Languages → Settings - "Generate hreflang" → enabled - "Default language is x-default" → enabled
5. Next.js i18n
// app/[locale]/about/page.tsx export async function generateMetadata({ params }) { return { alternates: { canonical: `https://example.com/${params.locale}/about`, languages: { 'en': 'https://example.com/en/about', 'fr-FR': 'https://example.com/fr/about', 'de-DE': 'https://example.com/de/about', 'x-default': 'https://example.com/en/about' } } }; }
6. Static sites (Hugo / Astro / Eleventy)
Hugo {{ range .Translations }} <link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}" /> {{ end }} <link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}" /> <link rel="alternate" hreflang="x-default" href="{{ .Site.BaseURL }}{{ .RelPermalink | replaceRE "^/[a-z]{2}/" "/en/" }}" /> Astro --- const locales = { 'en': '/en/about', 'fr-FR': '/fr/about', 'de-DE': '/de/about' }; --- {Object.entries(locales).map(([lang, path]) => ( <link rel="alternate" hreflang={lang} href={`https://example.com${path}`} /> ))} <link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />
7. Verify the fix
Step 1 View source on each variant Each page in the cluster should show the same x-default URL. Use DevTools or curl + grep: curl -s https://example.com/en/about | grep hreflang curl -s https://example.com/fr/about | grep hreflang # Both should include the same hreflang="x-default" line Step 2 Re-run Hreflang Checker x-default warnings clear. Cluster validates as complete. <div
Tip: 💡 The single rule: x-default points at the page you'd want a confused international user to see — language picker for "let them choose" sites, English-international for "they probably want English" sites. Don't point it at a regional version like en-US — that confuses fallback with explicit targeting.
Tags: hreflang, hreflang x-default, language picker, fallback locale, international SEO default, x-default pattern, default page hreflang
Fix
Strengthen E-E-A-T specifically on Your Money Your Life (YMYL) content: named experts, citations, transparent methodology.
Run the E-E-A-T audit
Open the E-E-A-T Fixes hub to find the audit tool. Run a fresh scan to ensure you have current data — issues fixed weeks ago may no longer apply, and new ones may have appeared.
Export the affected pages to CSV
Filter the audit output by this specific finding type. Export the full URL list to CSV so you can work through it systematically without missing any pages.
Sort by priority
Prioritise high-traffic or high-revenue pages first. Fixes on your top 20% of pages typically deliver 80% of the impact, so focus there before going long-tail.
Trace the source of the issue
Find where the issue originates: in your CMS template, in a plugin, in editorial content, in a third-party script, or in server configuration. Each source requires a different fix approach.
Test on staging
Deploy the fix to staging. Verify the change resolves the issue on a sample page. Check that nothing else broke as a result.
Apply at scale
For small page counts, edit each page directly. For bulk changes, use a CMS search-and-replace plugin, a deploy script, or a database update via your CMS’s CLI tools. Always back up before bulk-editing production.
Deploy to production
Deploy in a low-traffic window. Monitor server logs for the first hour for any unexpected errors. Be prepared to roll back if the fix causes new issues.
Re-run the audit
Run a fresh scan after the fix is deployed. The pages flagged before should now pass. If any still fail, investigate — either the fix didn’t deploy correctly, or some pages need a different approach.
Request re-indexing
For high-priority pages, use Search Console URL Inspection to request a fresh crawl. Google typically re-indexes within 24–48 hours after request, vs 1–7 days for organic re-discovery.
Set up alerts
Configure your E-E-A-T audit to run on a schedule (daily or weekly is typical) with alerts when the issue count rises above zero. Catch regressions before they affect rankings.
Add a build-time check
If the issue is template-driven, add a build-time or CI check that catches it before deploy. Pre-empting regressions in CI is much cheaper than reacting to them post-deploy.
Tip: 💡 If the issue appeared suddenly across many pages on the same date, it correlates with a recent deploy, plugin update or theme change. Check your deploy log first — rolling back the trigger is often faster than patching.
Tags: eeat, ymyl, content
Fix
Recover YMYL (Your Money Your Life) ranking drops. Heightened E-E-A-T requirements: credentialed authors, expert review, citations, organisational backing.
1. Determine if your content is YMYL
Clear YMYL categories: - Medical / health information - Financial advice (investing, lending, insurance, tax) - Legal advice - Safety information - Major life decisions (jobs, education, housing) - News with civic/societal impact - Drugs, supplements - Heavy machinery, vehicles Borderline (treated as YMYL): - Nutrition and fitness with health claims - Mental health content - Parenting advice with safety implications - Career advice that affects income NOT YMYL: - Entertainment, hobbies, lifestyle (most cases) - Product reviews (unless health/finance) - General how-to that doesn't affect health/finance/safety
2. Credentialed authors are non-negotiable for YMYL
Required: - Author has demonstrable credentials in the topic (MD/DO for medical, CFP/CFA for finance, JD for legal) - Credentials visible on page near content - Author bio page with detailed credentials - sameAs to professional registries (medical: state medical board, GMC, etc.) - Person schema with hasCredential property Unacceptable for YMYL: - "Written by Editorial Team" anonymous content - Author whose credentials don't match topic - Credentials claimed but not verifiable
3. Add expert review separate from author
Pattern Google's Search Quality Rater Guidelines highlights: - Author (writer) + Reviewer (separate domain expert) - Reviewer credentials shown - Review date visible - Reviewer different from author Why: separates "I researched and wrote this" from "someone with deeper expertise verified it". Major medical sites use this pattern (WebMD, Healthline, Mayo Clinic). Smaller YMYL sites should adopt it. Schema: Article { author: { Person, name: "Writer", ... }, reviewedBy: { Person, name: "Reviewer MD", hasCredential: ... }, dateModified: "2026-04-22" }
4. Citations and primary sources
Cite peer-reviewed studies, official guidance (NHS, NICE, FDA, government) Link directly to primary sources, not aggregators Date citations — "Per 2025 NICE guidelines" beats "studies show" Disclose when content is opinion vs evidence-backed Limitations : note when evidence is mixed or specific to populations
5. Organisational backing
Sites carrying YMYL content benefit from: - "About us" with organisational history, mission - Editorial board with named medical/financial/legal experts - Editorial standards page (how content is created, reviewed, updated) - Corrections policy (how errors are handled) - Funding/business model disclosure - Conflict of interest policy Individual experts can rank YMYL content too, but require even stronger credentialing and visible accountability. Anonymous YMYL content barely ranks post-2024 algorithms.
6. Update cadence
YMYL content goes stale fast. Old guidance can become wrong or dangerous: Quarterly review for medical/legal content: - Has guidance changed? - Are statistics still current? - Are linked sources still correct? - Has the FDA / NICE / equivalent updated position? Visible "Last reviewed by [reviewer] on [date]". schema dateModified reflects actual review, not deploy. Pages without recent review dates downrank on YMYL queries.
7. Specific YMYL pitfalls
Affiliate links on medical content — high suspicion; if used, disclose prominently and use only credentialed-author content Personal anecdotes as medical advice — fine as story, not as guidance AI-generated medical content — high risk; effectively requires expert review even more strictly Out-of-jurisdiction legal/financial — clarify the jurisdiction your content applies to Outdated drug interactions, dosages — dangerous and Google-penalised
8. Recovery timeline
YMYL recovery follows core update cycle plus organisation-building lag: - Credentialing existing content: 4-8 weeks - Adding reviewer pipeline: 8-12 weeks (find, hire, integrate) - Updating site-wide editorial standards: 4-6 weeks - First core update reassessment: 3-6 months - Steady recovery: 9-18 months YMYL is the hardest category to bluff. Sites without legitimate expertise rarely recover; sites that invest in real experts generally do. <div
Tip: 💡 YMYL is Google's clearest "trust signal" category. If you publish YMYL content without real credentials, the algorithm correctly penalises you. The fix isn't technical — it's hiring real experts, paying for review, and showing the work. Sites unwilling to do this should reconsider whether YMYL is the right category.
Tags: algo-impact, YMYL rankings, YMYL E-E-A-T, medical content SEO, financial content authority, legal content reviewers
Tutorial
GDPR is a law telling you what you can and cannot do with personal information about your users. This guide explains it without legal jargon, so you understand what actually applies to you.
What the four letters stand for
GDPR stands for General Data Protection Regulation. It came into force across the European Union on 25 May 2018. After Brexit, the United Kingdom kept its own almost-identical version called the UK GDPR. If your website has any visitors from Europe or the UK, this law applies to you — regardless of where your business is based. The law does not care where your server lives. It cares where your users live.
What "personal data" actually means
Personal data is any information that identifies a living person, directly or indirectly. The obvious examples: name, email, phone number, home address, date of birth. The less obvious examples that also count: IP address, device ID, browser cookies, GPS location, a photo showing someone's face, a username, a customer reference number — anything that could be linked back to an individual. If you store it, transmit it, or process it, and it can be tied to a specific person, it is personal data and GDPR applies.
Why the law exists
Before GDPR, organisations could harvest mountains of data without telling people, sell it to advertisers, lose it in breaches, and face almost no consequences. GDPR gives ordinary people rights: the right to know what organisations hold about them, the right to have it deleted, and the right to complain when someone misuses it. For website owners, it means you cannot just grab data and do whatever you want with it. You need a valid reason, you need to tell people what you are doing, and you need to look after the data properly.
The six things you must do
GDPR boils down to six core principles. One: only collect data for clear, specific reasons and tell people what those reasons are. Two: only collect what you genuinely need — not everything that might be useful someday. Three: keep the data accurate and up to date. Four: do not keep it forever — delete it when you no longer need it. Five: keep it secure — no public servers, no unlocked inboxes. Six: be able to prove you are doing all five of the above. The sixth one is the one most organisations forget, and it is the one that gets them fined.
What a "lawful basis" is
Every time you collect or use personal data, you need a legal reason — what GDPR calls a "lawful basis." There are six to pick from. Most websites use one of three: consent (the user ticks a box saying yes), contract (you need their details to deliver something they bought), or legitimate interests (you have a sensible business reason that does not trample their privacy). You must decide which basis applies BEFORE you collect the data — you cannot pick one retrospectively.
The rights your users have
Your users can demand things from you, and you must respond within one calendar month. They can ask: what data do you have about me (right of access), please correct this detail (right of rectification), please delete everything (right of erasure), please send my data to another organisation (right of portability), please stop sending me marketing (right to object), and please explain how this automated decision was made (rights regarding automated decisions). You need a process for each. If you ignore a request, the user can complain to the regulator, and the regulator can fine you.
What happens if you get it wrong
The maximum fine under UK GDPR is £17.5 million or 4% of your worldwide annual turnover, whichever is higher. For EU GDPR, it is €20 million or 4%. In practice, tiny businesses are not hit with £17.5 million fines for minor slips. The ICO (the UK regulator) generally targets repeat offenders, deliberate misconduct, or cases that cause real harm. But fines do happen — Clearview AI, H&M, British Airways and Amazon have all been hit. The bigger consequence for most small businesses is reputational: no one wants to hand their data to an organisation known for losing it.
Who the law applies to
GDPR applies to you if any of these are true. One: your organisation is based in the EU or UK. Two: you offer goods or services to people in the EU or UK. Three: you monitor the behaviour of people in the EU or UK (for example, running analytics cookies that track their browsing). Points two and three catch almost every commercial website on the internet. If you run a hobby blog that happens to get EU visitors, the "household exemption" probably covers you. If you sell anything, collect emails, or run any analytics, you are in scope.
Tip: Think of GDPR like health and safety regulations at work. It feels like bureaucracy until something goes wrong. Getting the basics right — a proper privacy policy, a working cookie banner, and a documented process for handling data requests — covers 90% of what you need. The rest is paperwork proving you are doing what you said you are doing.
Tags: gdpr, basics, privacy, data-protection, compliance, law
Tutorial
If you run any website that might be visited by someone in the UK or EU, this is almost certainly a yes. Here is exactly who is in scope, who is not, and the grey areas in between.
The three tests
GDPR applies if ANY of the following is true: one, your organisation is established in the UK or EU; two, you offer goods or services to people in the UK or EU, even for free; three, you monitor the behaviour of people in the UK or EU. If even one of these tests hits, the law applies. The most important point: you do NOT have to be a UK or EU business for this law to apply to you. An organisation based in the USA, Australia or anywhere else is still bound by UK GDPR if it sells to UK customers or tracks UK visitors.
What "offering goods or services" means
Selling products is obviously in scope. So is offering a free service with a login. So is offering free downloads in exchange for an email address. The key question the regulator asks: would a reasonable person conclude that you intended to reach UK or EU users? Signals that you did: your site shows prices in GBP or EUR, accepts European payment methods, is written in a European language, explicitly mentions shipping to UK/EU addresses, or uses UK/EU-based testimonials in marketing. If none of those apply and UK/EU users just happen to find you by accident, you might be outside scope — but err on the side of compliance.
What "monitoring behaviour" means
This test is much broader than people expect. If you run Google Analytics, you monitor behaviour. If you use Facebook Pixel, you monitor behaviour. If you run Hotjar session recordings, heat maps, or A/B testing, you monitor behaviour. Even basic server logs that track which pages users visit could count. The intent of the law is: if you are profiling or analysing how your users behave, GDPR applies. There are essentially no commercial websites that do not monitor behaviour in some form.
The rare cases where GDPR does NOT apply
GDPR explicitly exempts: purely personal or household activity (your family blog, a private photo sharing site for friends), law enforcement processing covered by other laws, national security processing, activity outside the scope of EU law (deceased persons' data in most cases). If you are running a side project that is genuinely non-commercial and does not monitor visitors, the household exemption likely covers you. The moment you have a contact form, an email list, or analytics, you leave that exemption.
B2B vs B2C — does it matter?
GDPR applies to personal data regardless of whether your customers are businesses or consumers. A B2B website still handles personal data: the names of your contacts at client organisations, their work email addresses, their phone numbers. Work emails ARE personal data — firstname.lastname@organisation.com identifies an individual. The misconception that "B2B is not GDPR" is one of the most common and expensive mistakes. B2B marketing lists have been hit with significant fines under GDPR.
The UK GDPR vs EU GDPR difference
For most practical purposes, they are identical. The text was copied almost word-for-word when the UK left the EU. The main differences: UK GDPR is enforced by the ICO; EU GDPR is enforced by each member state's data protection authority (CNIL in France, BfDI in Germany, and so on). Fines are quoted in pounds for UK GDPR (£17.5m max) and euros for EU GDPR (€20m max). The obligations and rights are the same. If your site serves both, you must comply with both — which in practice means doing the stricter interpretation.
What you actually need to do if in scope
At minimum: publish a privacy policy that accurately describes your data practices, obtain valid consent for non-essential cookies and marketing, have a process for responding to user data requests within one month, keep basic records of what data you process and why, notify the ICO (and affected users) if you suffer a personal data breach, and ensure any third-party services you use (hosting, email, analytics, payments) are also GDPR-compliant. The ICO provides a free self-assessment tool at ico.org.uk that walks you through each obligation.
Tip: If you are unsure whether you are in scope, assume you are and comply. The cost of compliance is modest. The cost of ignoring a law you are bound by is enormous — and "I thought it did not apply to me" is not a legal defence anywhere in the UK or EU.
Tags: gdpr, applicability, scope, uk-gdpr, eu-gdpr, compliance
Tutorial
What Counts as "Personal Data" Under GDPR? (It is More Than You Think)
Most people know name and email are personal data. Fewer realise that IP addresses, cookie IDs, device fingerprints and even pseudonymised identifiers also count. This guide covers every category in plain English.
The legal definition
GDPR Article 4 defines personal data as "any information relating to an identified or identifiable natural person." The key phrase is "identifiable." Data does not have to name the person directly — if it could be used, in combination with other information, to work out who the person is, it counts. A customer ID number is personal data if your database links that ID to a name. A browser cookie is personal data if that cookie is tied to a user account.
Obvious examples
These are personal data without question: first name and surname, home address, email address, phone number, date of birth, National Insurance number, passport number, driving licence number, photograph showing a face, video footage showing an individual, voice recording. If it directly names or pictures a specific living person, it is personal data and GDPR applies to everything you do with it.
The less obvious examples
These all count as personal data too. IP address (your ISP can link it to a subscriber; websites can link it to an account). Cookie ID or device ID (unique to a device, often used for tracking). Geolocation data from a phone or browser. Browser fingerprint (combination of screen size, fonts installed, plugins — unique enough to identify). MAC address of a network device. Customer reference numbers (if linkable to identity). Usernames and online handles. Employee ID numbers. A photograph of a person's car (with number plate visible). CCTV footage. Voice samples.
Pseudonymised data is still personal data
A common misconception: "I replaced the name with a code — that means GDPR no longer applies." Wrong. If you still hold the key that maps the code back to the person, the data is pseudonymised but still personal data. Pseudonymisation is a good security practice (it reduces risk if leaked) but does not take the data out of GDPR scope. The only way to truly exit GDPR scope is anonymisation — the data must be impossible to re-link to an individual, even by you, even with all your records.
Special category data — extra rules apply
Some categories are considered sensitive and have much stricter rules under Article 9. These are: racial or ethnic origin, political opinions, religious beliefs, trade union membership, genetic data, biometric data (fingerprints, face recognition), health data, sex life and sexual orientation. Processing any of these requires an explicit lawful basis AND an additional Article 9 condition, usually explicit consent. A GP practice, gym collecting health data, or HR system tracking union membership — all need extra justification and safeguards.
What is NOT personal data
Genuinely anonymous data is outside GDPR scope — for example, aggregated statistics like "45% of our visitors came from Germany" where no individual can be picked out. Data about deceased people is excluded under UK GDPR (though separate rules apply in some EU countries). Data about legal entities (a company name, a company registration number) is not personal data on its own — but if a company is named after a person (John Smith Ltd), care is needed because it could identify John Smith.
The "relating to" test
Article 4 says data must "relate to" a person. The Court of Justice has interpreted this broadly. If the data's content, purpose or effect concerns a person, it relates to them. Example: a delivery address relates to the person who lives there. A product purchase history relates to the person who bought. Even data apparently about something else can relate to a person if used to make decisions about them — for example, analysing traffic patterns to target ads at individual commuters.
Practical implications for your website
Because personal data is so broadly defined, almost everything your website captures falls under GDPR. Contact forms obviously. But also: analytics data, server logs, A/B test assignments, cart abandonment tracking, search queries entered on your site, scroll depth tracking, heatmap recordings. Treat everything as personal data by default. The exceptions are rare: truly aggregated analytics with no individual-level data, and server metrics that do not identify users. Everything else needs a lawful basis and appropriate protection.
Tip: When deciding if something is personal data, ask: "Could this data, alone or combined with other data I hold, be used to work out who a specific individual is?" If yes, treat it as personal data. If unsure, treat it as personal data. The cost of treating something as personal data when it is not is negligible. The cost of NOT treating it as personal data when it is can be severe.
Tags: gdpr, personal-data, definition, pii, privacy, data-protection
Tutorial
The 7 GDPR Principles Explained in Plain English
GDPR is built on seven principles that govern everything you do with personal data. This guide explains each one with practical examples of what compliance looks like and what violations look like.
Principle 1 — Lawfulness, fairness and transparency
Every bit of personal data you process must have a lawful basis (one of the six: consent, contract, legal obligation, vital interests, public task, legitimate interests). "Fairness" means you cannot use data in ways that would be unreasonable or surprising to the user. "Transparency" means you tell people clearly what you are doing with their data — in plain language, not buried in a 40-page privacy policy written in legalese. Violation: sending marketing emails to customers who only gave their address to receive a delivery. Compliance: telling them at signup that you will send marketing unless they opt out, with a clear tick box.
Principle 2 — Purpose limitation
You must collect data for specified, explicit, legitimate purposes — and not use it for anything incompatible with those purposes later. If you collect an email for order confirmations, you cannot later start using it for marketing without asking. Violation: a retailer selling their customer email list to a marketing agency when the customers only signed up to receive purchase receipts. Compliance: collecting emails at checkout, using them only for order-related communication, and asking separately with a tick box if the user wants marketing emails.
Principle 3 — Data minimisation
Only collect what you actually need for the specific purpose. If you are running a newsletter, you probably need email and maybe first name. You do NOT need date of birth, phone number, home address, gender, or occupation. Every extra field on your signup form increases your GDPR risk without adding business value. Violation: a signup form asking for 15 fields of personal information when the service only uses 4. Compliance: ruthlessly trimming forms to the minimum needed for the stated purpose, and only asking for more when a genuine need arises.
Principle 4 — Accuracy
Data must be accurate and, where necessary, kept up to date. You must take reasonable steps to correct or erase inaccurate data without delay. Violation: a bank refusing to update a customer's address because "it is too much hassle" and continuing to send statements to the old address for years. Compliance: providing users with an account settings page where they can update their own details, and honouring rectification requests from people who cannot self-serve. You do not need to proactively verify accuracy of all your data constantly — just respond promptly when inaccuracies are flagged.
Principle 5 — Storage limitation
You must not keep personal data for longer than necessary for the purpose you collected it. Once the purpose is fulfilled and there is no legal reason to retain it, you must delete it. Violation: keeping every customer record ever created, forever, "just in case." Compliance: having a documented retention policy that states exactly how long each category of data is kept (for example: customer records for 7 years after last transaction for HMRC purposes, then deleted; inactive account data deleted after 3 years of no login; marketing contact data deleted within 90 days of unsubscribe). Automating the deletion is ideal because humans forget.
Principle 6 — Integrity and confidentiality (security)
You must protect personal data using "appropriate technical and organisational measures." This is deliberately vague because what is appropriate for a small blog is different to what is appropriate for a hospital. At minimum: encrypt data in transit (HTTPS everywhere), encrypt sensitive data at rest, control who has access to the data (not everyone in your organisation needs to see customer records), keep software patched, use strong passwords and two-factor authentication, and do not leave backups on unsecured storage. Violation: storing passwords in plain text, or leaving a database open to the internet without authentication. Compliance: following industry best practices proportionate to your size and the sensitivity of the data.
Principle 7 — Accountability
You must be able to demonstrate that you comply with all the other principles. This is the principle that trips up most organisations. It is not enough to comply — you must be able to prove it. Proof looks like: a written privacy policy, records of processing activities (what data, why, who shares it, retention periods), consent records (when and how each user consented), staff training records, data breach response plans, and records of responses to user rights requests. If the ICO knocks on your door, you need documentation ready. "We were definitely doing the right things, we just did not write it down" does not cut it.
How to check compliance with all seven
Run through this checklist quarterly. For every data category you hold, ask: do I have a lawful basis (principle 1)? Am I using it for the stated purpose only (principle 2)? Is the data set minimal (principle 3)? Do I have a way to update it (principle 4)? Do I have a retention and deletion plan (principle 5)? Is it secured appropriately (principle 6)? Can I prove all the above with documents (principle 7)? Any "no" answer is a gap to fix. Most small organisations discover their weakest link is principle 5 (retention) or principle 7 (documentation).
Tip: The seven principles sound abstract until you apply them to a real data category. Pick one — say, your newsletter subscriber list — and walk through all seven for it. You will quickly see which principles you comply with and which need work. Repeat for every data category you hold. This one exercise will reveal more about your GDPR compliance than any consultant interview.
Tags: gdpr, principles, article-5, compliance, data-protection, basics
Tutorial
The 6 Lawful Bases for Processing Personal Data
You cannot just collect and use personal data — you need a legal reason, called a "lawful basis." There are exactly six to pick from. This guide explains each one with examples of when it applies.
Why you need a lawful basis
Article 6 of GDPR says that any processing of personal data is unlawful unless one of six specific conditions applies. You must identify which condition applies before you collect the data, document that choice, and tell the user about it in your privacy policy. If you cannot fit your processing into one of the six, you cannot do it legally. "It is useful for my organisation" is not a lawful basis.
Basis 1 — Consent
The user has given clear, freely-given, specific, informed, and unambiguous permission for a specific purpose. Example: a user ticks a box saying "Yes, email me your weekly newsletter." For consent to be valid, it must be as easy to withdraw as to give, cannot be bundled with other terms, and cannot be obtained through pre-ticked boxes. Consent is the right basis for marketing emails, optional cookies, and anything you want to do with data that is not strictly necessary for delivering a contract.
Basis 2 — Contract
The processing is necessary to deliver a contract you have with the user, or to take steps at their request before entering a contract. Example: you need the customer's address to ship their order, so you can process the address under "contract." This basis is usually the right choice for core service functionality: account management, order fulfilment, payment processing, delivery. It does not cover marketing — that needs consent or legitimate interests.
Basis 3 — Legal obligation
You are legally required to process the data. Example: UK employers must retain payroll records for tax purposes — that is a legal obligation under HMRC rules. You do not need consent to comply with legal obligations. This basis covers: tax and accounting records, employment records, anti-money-laundering checks for regulated industries, court-ordered data production. The legal obligation must be a UK or EU law obligation — not "I felt obliged because of industry practice."
Basis 4 — Vital interests
The processing is necessary to protect someone's life. This basis is rarely relevant for websites. It applies in emergencies: a hospital accessing an unconscious patient's records, emergency services tracing a 999 caller's location. For a commercial website, this basis almost never applies. If you are relying on it, you probably need a different basis.
Basis 5 — Public task
The processing is necessary to perform a task in the public interest or exercise official authority. This basis is for public bodies and those exercising official functions: local councils, NHS trusts, universities, statutory regulators. A private business cannot use this basis for normal commercial activity. Unless you are a public body or carrying out a specific public-interest task formally delegated to you, this is not your basis.
Basis 6 — Legitimate interests
You (or a third party) have a legitimate interest in processing the data, the processing is necessary to achieve that interest, and the interest is not overridden by the user's rights and freedoms. Example: an organisation processing employee contact details for internal directory use. This is the most flexible basis but requires a "three-part test" every time: identify the interest (purpose test), show the processing is necessary (necessity test), and balance against user rights (balancing test). You must document this test — the ICO will ask for it. Direct marketing to existing customers often uses legitimate interests (with a clear opt-out option), but you must genuinely balance the interest against user expectations.
Which basis applies to common website activities?
Sending order confirmations: contract. Processing payments: contract + legal obligation (for keeping records). Sending marketing emails: consent (or legitimate interests with opt-out, for existing customers via "soft opt-in"). Running analytics that track individual users: consent (because analytics is not strictly necessary for the service). Operating a contact form: consent or legitimate interests, depending on context. Storing shipping addresses: contract. Keeping financial records for HMRC: legal obligation. Employee payroll: contract + legal obligation.
What you must document for each basis
For every data processing activity, record: what data you process, why, which lawful basis applies, how long you retain it, who you share it with. Keep this in a Record of Processing Activities (ROPA) — a spreadsheet is fine. Additionally: for consent-based processing, keep a record of when and how each user consented. For legitimate-interests processing, keep a record of the three-part balancing test. For contract-based processing, keep a copy of the contract terms. This documentation is your proof of compliance under the accountability principle.
Tip: Choose the most appropriate basis for each processing activity and document it BEFORE you start processing. You cannot casually change the basis later. If you initially relied on consent and the user withdraws consent, you cannot suddenly switch to "legitimate interests" to keep processing — you must stop. Get the basis right first time.
Tags: gdpr, lawful-basis, consent, legitimate-interests, article-6, compliance
Tutorial
Consent Under GDPR: What Makes It Valid (and What Invalidates It)
Most "consent" collected by websites is not actually valid under GDPR. Pre-ticked boxes, bundled consent, buried opt-ins — all of it fails. Here is what real consent looks like, and how to collect it properly.
The four tests for valid consent
GDPR Article 4(11) says consent must be: FREELY GIVEN — the user had a genuine choice to say no without negative consequences; SPECIFIC — given for a single defined purpose, not a bundle; INFORMED — the user knew exactly what they were agreeing to; and UNAMBIGUOUS — given by a clear affirmative action (ticking a box, clicking a button). Miss any of the four and the consent is invalid, which means you have no lawful basis, which means your processing is unlawful. The ICO treats invalid consent as worse than no consent at all.
Why pre-ticked boxes are banned
A pre-ticked box is the classic GDPR violation. It fails the "unambiguous" test because no affirmative action was taken by the user. Silence, inactivity, or pre-ticked boxes do not count as consent. This was settled by the EU Court of Justice in the Planet49 case in 2019. If you have ANY pre-ticked boxes on your signup forms, cookie banners, or order pages, fix them today. Every one is a compliance violation and evidence of wider sloppiness that invites ICO investigation.
Why bundled consent is invalid
Bundling is asking for consent to multiple things with one tick box: "I agree to the terms, privacy policy, marketing emails, and to share my data with partners." This fails the "specific" test. Each purpose needs its own separate consent tick box. The user must be able to agree to sign up but decline marketing. They must be able to accept essential cookies but reject analytics cookies. If they can only say yes to everything or no to everything, consent is invalid.
Why consent must be genuinely freely given
If saying no means the user cannot use your service (when the service does not actually need that data), consent is not freely given. Example: making marketing consent a condition of creating a free account, when the account does not need marketing. If the user has no real choice, consent is invalid. This is why the "accept cookies or leave" banners are illegal in many EU jurisdictions — no real choice was offered. The user must be able to refuse without losing access to the service.
Withdrawal must be as easy as giving
Article 7(3) requires that withdrawing consent must be as easy as giving it. If you collect consent with a single tick box, you cannot require users to email support, post a letter, or jump through hoops to withdraw. An unsubscribe link in every marketing email is mandatory. A settings page where users can toggle off any consent they previously gave is best practice. You must also tell users, BEFORE they consent, that they can withdraw at any time.
Keeping consent records
For every consent you rely on, you must be able to prove: who consented, when, how (which form/page/tick box), and what they were told at the time. This is the accountability principle applied to consent. A database table with user_id, consent_type (marketing/cookies/etc), consented_at timestamp, privacy_policy_version, and consent_method (signup/banner/settings) is the minimum. Without records, consent is worth nothing — it is your word against theirs, and the user wins by default.
When consent expires
GDPR does not set a hard expiry, but the ICO recommends refreshing consent every 2 years or when your data practices change materially. If you change what you do with the data, the original consent is for something different and no longer valid — you need fresh consent. If a user has not engaged with your marketing for 18 months, consider whether they still actually want it. Stale consent is worse than no consent because you are relying on a yes that the user has effectively forgotten they gave.
What to do if existing consent is invalid
If you realise your existing consent records do not meet GDPR standards (pre-ticked boxes used, bundled consent, no records kept), you have two options. Option 1: re-request consent from everyone — send a one-time email asking them to confirm they still want to hear from you. Expect 70-90% not to respond and lose them. Option 2: switch to a different lawful basis if one fits (for marketing to existing customers, "legitimate interests" with an opt-out might work — see the separate legitimate interests tutorial). Do NOT simply carry on with invalid consent and hope nobody notices.
Tip: Test your consent collection by asking a non-technical friend: "What exactly are you agreeing to by clicking this?" If they cannot give a clear, specific answer without reading fine print, your consent is not informed enough to be valid. Real consent is consent the user can describe in one sentence without help.
Tags: gdpr, consent, lawful-basis, cookies, marketing, opt-in
Tutorial
Legitimate Interests: The Most Flexible (and Most Misused) Lawful Basis
Legitimate interests is the lawful basis most organisations reach for when consent feels too difficult — but it requires a formal three-part balancing test. This guide walks through the test with a real example.
What legitimate interests actually is
Article 6(1)(f) says you can process personal data if: (a) you or a third party have a legitimate interest, (b) the processing is necessary to achieve that interest, and (c) the interest is not overridden by the rights and freedoms of the data subject. All three conditions must be met. This is the most flexible lawful basis because "legitimate interest" is broadly defined — it can be any genuine business, commercial, or societal interest. But flexibility comes with the burden of proving you did the analysis.
The three-part balancing test (LIA)
For every legitimate-interests processing activity, you must complete a Legitimate Interests Assessment — often called an LIA. It has three parts. Part 1 — Purpose test: what is the specific interest, why does it matter, and is it lawful? Part 2 — Necessity test: could you achieve the same outcome with less data, or with consent instead? If yes, the processing is not necessary and this basis does not apply. Part 3 — Balancing test: what impact does the processing have on the individual, do they expect it, could it cause them harm, and do their interests override yours?
A worked example: sending a retention email
Scenario: you want to email customers who have not logged in for 6 months to encourage them back. Purpose test: retention is a legitimate commercial interest. Necessity test: is email the least intrusive way? Arguably yes — cheaper than post, more targeted than ads. Could you use consent? They already have an account and agreed to your service, so separate consent is overkill. Balancing: the user gave you their email for service purposes. A single retention email is mild processing. They can unsubscribe easily. The balance favours you. LIA passes — legitimate interests is a valid basis. Document the LIA.
When legitimate interests fails the test
Scenario: you want to sell your customer email list to a third-party marketing agency. Purpose test: commercial gain is a legitimate interest. Necessity test: could you achieve the same income another way? Probably — and users did not hand you emails for onward sale. Balancing: users would not expect their email to be sold. They cannot easily opt out since the buyer will contact them. The balance favours the user. LIA fails — legitimate interests cannot be used. You need consent specifically for onward sharing, which users are extremely unlikely to give.
Legitimate interests for marketing to existing customers
Under UK PECR (Privacy and Electronic Communications Regulations), you can use legitimate interests for direct marketing to existing customers in a limited way called "soft opt-in." Conditions: you obtained their details during a sale or negotiation for a sale, the marketing is for similar products or services, you gave them a clear opportunity to opt out at the time of collection, and every email gives them an easy unsubscribe link. Soft opt-in does NOT apply to prospects, anyone who only downloaded free content, or marketing unrelated to their original purchase.
Your right to object is stronger here
Under Article 21, the data subject has an absolute right to object to processing based on legitimate interests. Once they object, you must stop — unless you can demonstrate compelling legitimate grounds that override their interests, which is a high bar. For direct marketing, the right to object is absolute: if a user objects, you stop. No arguing, no "compelling grounds" defence. This is different from consent-based marketing where withdrawal of consent has the same practical effect.
Documenting your LIA properly
A written LIA for each legitimate-interests processing activity should include: the processing activity being assessed, the specific interest pursued, whether the processing is necessary to achieve that interest, alternative approaches considered and why rejected, the potential impact on individuals, mitigating measures in place (opt-out, limited data retention, etc.), and the conclusion. Keep the LIA on file. When the ICO asks why you are processing without consent, you hand them the LIA. No LIA means you cannot justify the basis, which means your processing is unlawful.
When to switch from legitimate interests to consent
If your LIA is borderline, if the processing is intrusive (profiling, tracking, advertising), if users would be surprised, or if special category data is involved, consent is the safer choice. Legitimate interests is for mild, expected processing. It is not a workaround for activities that actually need consent. Marketing cookies, third-party ad tracking, behavioural profiling — consent, not legitimate interests. Simple retention emails, fraud prevention, internal analytics — legitimate interests is appropriate.
Tip: Legitimate interests is tempting because you do not need to ask the user — but it places a bigger burden on YOU to justify the processing if challenged. For any borderline case, consent is safer. Reserve legitimate interests for activities where users would expect what you are doing and would not reasonably object.
Tags: gdpr, legitimate-interests, lawful-basis, lia, balancing-test, compliance
Tutorial
Using "Contract" as Your Lawful Basis: When It Works and When It Does Not
The "contract" lawful basis seems simple — but it only covers processing that is genuinely necessary to deliver the contract. Using it for anything outside that is a common mistake.
What "contract" means as a lawful basis
Article 6(1)(b) lets you process personal data if the processing is necessary for the performance of a contract with the data subject, or to take steps at their request before entering a contract. The contract does not have to be a signed formal document — your Terms of Service count, as does a confirmed order, a paid subscription, or a booking. The test is whether there is a genuine contractual relationship requiring this specific data to fulfil.
The word "necessary" is strict
"Necessary" means you cannot reasonably deliver the contract without this data. If you could deliver the service without collecting the data, the contract basis does not apply and you need a different basis. Example: you sell digital downloads. You need the customer's email to send the download link — necessary under contract. You do NOT need their phone number for digital delivery — cannot justify that under contract, need a separate basis (usually consent).
What contract basis covers in a typical online business
For most ecommerce and SaaS sites, contract basis legitimately covers: account creation (name, email, password), order processing (shipping address, items ordered), payment (billing address, last 4 digits of card via Stripe), order confirmation emails, shipping notification emails, customer support tickets relating to purchases, returning/refunding orders, delivering the actual service or product, and account management (password resets, security alerts).
What contract basis does NOT cover
Marketing emails to customers beyond transactional updates — needs consent or legitimate interests soft opt-in. Analytics tracking of customer behaviour — needs consent for non-essential cookies. Third-party data sharing with advertising networks — needs consent. Retention of data after the contract ends — eventually needs a different basis (legal obligation for tax records, or legitimate interests for fraud prevention). Processing data about people who have not yet purchased — "pre-contract steps" only works if they genuinely initiated the process (asked for a quote, started a cart).
Pre-contract steps at the user's request
The contract basis also covers processing before a contract is signed, IF the data subject requested the pre-contract steps. Someone filling in a "request a quote" form — you can process their details under contract basis to produce the quote. Someone adding items to a cart without checking out — arguably covered. Someone landing on your homepage — NOT covered, they did not request anything. Cold outreach to potential customers — NOT covered, they did not initiate. Pre-contract is for users who took an affirmative step toward a contract.
What happens when the contract ends
When the contract ends (user cancels account, completes purchase, asks for deletion), the contract basis for further processing ends too. You can no longer rely on it. For continued processing you need a new basis. Usually: legal obligation (keeping financial records for 6-7 years for HMRC), legitimate interests (for fraud prevention, typically 12-24 months), or consent (for marketing to past customers). Map out what you keep, for how long, and under what basis AFTER the contract ends — and document it in your retention policy.
B2B relationships under contract basis
When you sell to a business, the "customer" is the business, not the individual. But you still process personal data about the business's contacts (names, work emails). The contract basis covers processing of the individual's data only insofar as necessary to deliver the B2B contract. If you want to market to those contacts beyond the immediate contract, you need consent or legitimate interests — same as B2C. "B2B is GDPR-exempt" is a myth.
Documentation for contract-based processing
For each data type processed under contract basis, document: the specific contract or service it relates to, why this data is necessary (not just useful) to deliver the service, what happens to it when the contract ends, and whether any further retention has a different lawful basis. Keep copies of your Terms of Service at each version — if a user ever disputes what they agreed to, you need to produce the exact terms in force when they signed up.
Tip: Before using contract as your basis, write down what would happen if you did NOT collect this data. If the contract could still be delivered, you cannot use contract basis. If the contract genuinely cannot be delivered without the data, you can. This simple necessity test keeps you out of trouble.
Tags: gdpr, contract-basis, lawful-basis, article-6, compliance, ecommerce
Tutorial
The "Legal Obligation" Lawful Basis: When Law Requires You to Process
Sometimes the law forces you to collect and keep data — payroll records, tax records, anti-money-laundering checks. This is the "legal obligation" lawful basis, and it overrides user preferences.
What legal obligation means
Article 6(1)(c) lets you process personal data when it is necessary to comply with a legal obligation you are subject to. The obligation must be UK law or EU law — "we felt obliged to comply with industry code X" is not enough unless that code is legally binding. The obligation must also genuinely require processing of personal data, not just be a good idea. Once legal obligation applies, the user cannot object, cannot withdraw consent (there was none), and cannot demand deletion — the law overrides their preferences.
Common legal obligations for UK businesses
If you employ anyone: you must keep payroll records, P11Ds, pension contribution records, National Insurance records, tax records for at least 3 years (often 6). If you are VAT registered: you must keep invoices and VAT records for 6 years. If you are a limited company: you must keep accounting records for 6 years. If you are in a regulated industry (finance, insurance, solicitors, estate agents, high-value goods dealers): you must do anti-money-laundering checks and keep the records for 5-10 years. If you are a landlord: you must do right-to-rent checks.
What legal obligation does NOT cover
It does not cover processing that is optional but commonly done. "All our competitors do this" is not a legal obligation. Voluntary industry codes of practice are not legal obligations unless signed into law. Internal company policies are not legal obligations. "We felt we had a duty to..." is not a legal obligation. If a regulator has not published a rule requiring the processing, it is probably not legal obligation. If you are uncertain, treat it as not covered and use a different basis.
How legal obligation interacts with user rights
If you are processing under legal obligation, the user has reduced rights. They CANNOT demand deletion of the data while the obligation applies (e.g. you cannot delete tax records because a user asks, because HMRC requires you to keep them). They CANNOT object to the processing. They CAN still ask for a copy (right of access). They CAN still ask you to correct inaccuracies (right of rectification). Once the legal obligation expires (e.g. 7 years after a tax year ends), the legal basis ends, and normal deletion rules resume.
Documenting legal obligation
For each data type processed under this basis, record: the specific law or regulation that creates the obligation (e.g. "HMRC record-keeping rules under section 388 of the Companies Act 2006"), the specific data required to comply, the retention period required by law, what happens to the data after the retention period ends. Generic references like "tax law" are insufficient — name the specific statute or regulator guidance. This documentation protects you if a user disputes why you cannot delete their data.
Common mistakes with legal obligation
Mistake 1: using legal obligation as a catch-all for anything "for record-keeping." Most record-keeping is legitimate interests, not legal obligation, unless a specific law requires it. Mistake 2: over-retaining data because "the law says 7 years" when actually only specific records need that retention. Keep only what the law requires, for only as long as required. Mistake 3: assuming the legal obligation covers ALL data about the user — it usually covers only specific categories (financial records, not the user's browsing history).
When to combine legal obligation with other bases
Most business activity is not a single lawful basis. Example: processing a customer order. The name/address/order details might be covered by "contract" (necessary to fulfil). The invoice data is also covered by "legal obligation" (HMRC retention). Marketing emails based on the purchase are "consent" or "legitimate interests." Each piece of processing gets its own basis. Document each separately. When the contract ends, contract basis ends — but legal obligation basis continues for the invoice data until the HMRC period expires.
What to tell users in your privacy policy
Your privacy policy must say which data you keep under legal obligation, which law requires it, and for how long. Example: "We retain invoices and VAT records for 6 years as required by HMRC. You cannot request deletion of these records during that period. After 6 years we securely delete them." Transparency protects you — users cannot claim they were misled if the policy states the position clearly.
Tip: When in doubt whether a legal obligation applies, check the regulator's actual published guidance (gov.uk, hmrc.gov.uk, ico.org.uk). Do not rely on industry chat or "everyone does it." A specific citation of a specific rule is your defence; vague appeals to "the law" are not.
Tags: gdpr, legal-obligation, lawful-basis, article-6, retention, hmrc
Tutorial
Special Category Data: When GDPR Gets Much Stricter
Some data types are so sensitive that GDPR imposes extra rules. Health, biometric, political, religious — processing any of these requires both a lawful basis AND an Article 9 condition. Here is what that means in practice.
What counts as special category data
Article 9 lists nine categories of sensitive personal data: racial or ethnic origin, political opinions, religious or philosophical beliefs, trade union membership, genetic data, biometric data (used for identification), data concerning health, data concerning sex life, and data concerning sexual orientation. If you process ANY of these, extra rules apply on top of all the normal GDPR requirements. Note: criminal offence data has similar special treatment under Article 10.
The two-step rule for special category data
Normally, processing personal data requires one of the six lawful bases from Article 6. For special category data, you need TWO justifications: (1) an Article 6 lawful basis as usual, AND (2) one of ten separate conditions under Article 9. Both must apply. Miss either one and the processing is unlawful. This double-gate means you cannot just rely on "I have consent" in the normal sense — you need "explicit consent" which is a higher bar, or one of the other Article 9 conditions.
The ten Article 9 conditions
Short version: (1) Explicit consent from the user. (2) Employment, social security, or social protection law obligations. (3) Vital interests of someone unable to consent. (4) Processing by non-profit bodies about their members. (5) Data made public by the individual themselves. (6) Legal claims or court proceedings. (7) Substantial public interest with a basis in law. (8) Healthcare, medical treatment, or occupational health. (9) Public health. (10) Archiving, research, or statistics with safeguards. Most commercial websites rely on explicit consent (condition 1) or employment/social security law (condition 2).
Explicit consent vs regular consent
Regular consent can sometimes be implied by clear affirmative action. Explicit consent cannot — it must be an unambiguous, purposeful, written or spoken statement of agreement to the specific processing. A tick box saying "I agree to provide my health data for diagnosis" is explicit. A tick box saying "I agree to the Terms of Service" is NOT explicit consent for any special category processing buried in those terms. The user must specifically know what special category data is processed and actively agree to that particular processing.
Common website scenarios involving special category data
Health-related: medical booking systems, dietary requirement fields on event signups, sick-leave records for employees, insurance applications. Biometric: face recognition logins, fingerprint authentication, voice prints. Religion: "Halal/Kosher dietary requirement" fields. Trade union: HR systems tracking union membership for check-off. Political: newsletter signups for political parties or campaigns. Every one of these needs explicit consent or another Article 9 condition, not just general consent.
What to avoid — accidental special category processing
Some data can accidentally become special category if it reveals sensitive attributes. A photograph showing a religious head covering may reveal religious beliefs. A Google Analytics custom dimension recording page URLs on a medical site may reveal health conditions. A newsletter preference for "LGBTQ+ content" reveals sexual orientation. If the data can be used to infer a special category attribute, it may itself be special category data. The ICO and European Court have both taken this broad approach in recent cases.
Stricter security requirements
Special category data requires proportionately stricter security measures. What is "appropriate" under Article 32 is higher for sensitive data. Best practice: encrypt special category data at rest (not just in transit), restrict access on a strict need-to-know basis, log every access for audit purposes, require two-factor authentication for any staff who access it, and separate it from general customer data where practical. A breach involving special category data is treated more seriously by the ICO, so the prevention standard is higher.
Children and special category data
Processing special category data about children is effectively off-limits for most commercial activities. If your service is used by children under 13, you cannot rely on the child's consent for special category data — you need parental consent, which is hard to verify online, which makes the whole exercise impractical. The safest approach: do not collect special category data from anyone under 18 unless you have a very specific, legally supported reason and a robust verification process.
If you accidentally collected special category data without the right basis
Stop processing immediately, even if the service depends on it. Notify the individuals concerned (unless it would require disproportionate effort). Consider whether to notify the ICO (mandatory if the breach creates a high risk). Delete the data unless you can identify a valid Article 9 condition you can retrospectively apply (rare). Document the incident, your response, and the lessons learned. Adjust your systems to prevent recurrence — usually by removing the field from forms and educating staff about what counts as special category data.
Tip: If you are processing anything that touches on health, religion, ethnicity, political views, sex life, genetics, or biometric identification, stop and consult the ICO's specific guidance on that category. Special category data is where the biggest fines land — Clearview AI's £7.5m fine was for biometric data processing. The rules are stricter, the enforcement is tougher, and the reputational damage is worse.
Tags: gdpr, special-category, article-9, health-data, biometric, sensitive
Tutorial
The Right to Be Informed: What Your Privacy Policy Must Tell Users
Articles 13 and 14 of GDPR are the longest in the regulation because they list exactly what you must tell users about your data practices. This is the right to be informed, and it shapes what goes in your privacy policy.
Why this right exists
The right to be informed is the foundation of GDPR transparency. Users cannot meaningfully exercise any of their other rights if they do not know what you are doing with their data. So before you collect or process personal data, you must tell them — in a concise, transparent, intelligible and easily accessible form, using clear and plain language. The place you do this is usually your privacy policy.
When Article 13 applies vs Article 14
Article 13 applies when you collect data DIRECTLY from the user — signup forms, contact forms, account creation, checkout. You must inform them at the moment of collection. Article 14 applies when you receive data from someone OTHER than the user — a data broker, a referral, a public register. You must inform them within a reasonable time and at the latest within one month, or at first contact. Most websites deal with Article 13 situations.
The minimum information you must provide
Your privacy policy must include: the identity and contact details of the data controller (you), the contact details of a Data Protection Officer if you have one, the purposes of the processing, the lawful basis for each purpose, the recipients of the personal data (third parties you share with), any transfers of data outside the UK/EU and the safeguards, the retention period for each data category, the user's rights (all of them), the right to lodge a complaint with the ICO, whether providing the data is a statutory or contractual requirement, and details of any automated decision-making or profiling.
What "clear and plain language" means
Privacy policies written in legalese fail Article 12. You must write in language a reasonable member of the public can understand. Avoid: "data controller," "data subject," "legal basis," "processor." Prefer: "us," "you," "why we can use this," "other companies we work with." The ICO has explicitly criticised policies that use "impenetrable legalese" or "vague and confusing" wording. If a non-lawyer cannot understand your policy without Google, rewrite it.
Where to put the information
The privacy policy should be linked from every page footer. Beyond that, there are a few key contact points where you must re-surface key information: at the point of signup (at minimum, link to the policy; ideally summarise the key points), when collecting special category data (explicit consent needs explicit information), when someone sends a DSAR or exercises a right (confirm you received it and explain what happens next), when your data practices change materially (notify users via email).
Just-in-time notices
Alongside a comprehensive privacy policy, best practice is to show short "just-in-time" notices at the point of data collection. Next to a newsletter signup: "We'll use your email to send our weekly newsletter. Unsubscribe anytime. [Full privacy policy]." Next to a contact form: "Your message and email are used to respond to you. We keep support tickets for 3 years." These supplement (not replace) the full policy.
What to do when data comes from third parties
Article 14 obligations are often forgotten. If you buy an email list (bad idea but legal under narrow conditions), scrape public data, get referrals from partners, or receive data from data brokers — you must tell each individual within one month. Include: the source you got the data from, the categories of data you received, and all the Article 13 information above. Most organisations struggle with this, which is why data acquired indirectly is a bigger compliance headache than data collected directly.
Keeping the policy up to date
Your privacy policy must reflect reality. When you add a new third-party service (analytics provider, CRM, payment processor), update the policy. When you change retention periods, update the policy. When you start processing a new data category, update the policy. A policy from 2018 that never mentions your current tech stack is a red flag to any auditor. Version the policy, note the last updated date, and keep old versions archived in case you need to prove what terms applied at a specific past date.
Common Article 13 failures
Policies that say "we use analytics" without naming which provider. Policies that say "we may share data with partners" without listing which partners. Policies that omit retention periods ("kept for as long as necessary" is not enough — name an actual period). Policies that do not mention the right to complain to the ICO. Policies hidden behind a login or requiring agreement before display. Policies in 9-point grey font on a grey background. All of these fail the transparency test and are easy wins for regulators.
Tip: Test your privacy policy by asking a non-technical friend to read it and summarise what you do with their data. If they cannot do it, or they summarise it wrongly, the policy fails Article 13. Real transparency means the reader can explain your practices after a single read without assistance.
Tags: gdpr, right-to-be-informed, privacy-policy, article-13, article-14, transparency
Tutorial
Data Subject Access Requests (DSARs): What Users Can Ask For and How to Respond
A DSAR is when a user asks you to send them a copy of all the data you hold about them. GDPR gives you one month to comply. Here is exactly what they can demand, how to handle the request, and when you can refuse.
What a DSAR is
Article 15 gives every individual the right to ask: what personal data do you hold about me? You must respond within one calendar month, providing a copy of the data plus a description of the processing. Any user can submit a DSAR for any reason (or no reason). They do not have to use special language — an email saying "please send me all my data" is a valid DSAR and the clock starts from that moment.
What information you must provide
A valid DSAR response includes: a copy of the personal data being processed (the actual data, not just a summary), the purposes of processing, the categories of data, the recipients the data has been shared with, the retention period or criteria for determining it, information about the user's other rights, the source of the data if not collected from the user, and the existence of any automated decision-making. Essentially — everything in your privacy policy, applied to this specific user.
The one-month deadline
You have one calendar month from the date you receive the request. One month means the same date next month (or last day of next month if it does not exist). You can extend by up to two further months if the request is "complex or numerous" — but you must tell the user within the first month that you are extending, and explain why. Starting to gather data is not complex. Needing to consult lawyers about redactions might be.
How to verify the requester
You must satisfy yourself the person asking is who they say they are — but you cannot demand excessive ID. Reasonable verification: if the request comes from their registered email address while logged into the account, that is sufficient. If they ask from a different email, you might ask them to log in and re-send, or provide their account reference plus recent order number. Do NOT demand passport scans or birth certificates — that is itself a GDPR violation (collecting more data than necessary).
Responding in a structured format
You must provide the data in a "commonly used electronic form" unless the user asks otherwise. PDF is fine. CSV or JSON is better for large datasets. Sending someone a 400-page PDF dump when they could have a 10MB JSON file is technically compliant but annoying — and users who are annoyed complain to the ICO. Best practice: offer both a human-readable summary PDF AND a structured data export.
What you can refuse to disclose
You can withhold certain data if disclosing it would reveal information about another person. Example: if the user asks for their customer support tickets and a ticket was filed by a different user about them, you can redact the other user's identity. You can also refuse "manifestly unfounded or excessive" requests — but this is a very high bar. "I do not feel like it" does not qualify. Repeat identical requests by the same person can sometimes be refused or charged for.
When you can charge a fee
Generally, the first copy is free. You can charge a reasonable fee for: additional copies beyond the first, manifestly unfounded or excessive requests, or requests where the user asks for data in an unusual format that requires significant effort. The ICO generally disapproves of charging — if the media or regulators hear about it, you look bad regardless of legal rights. Do not charge unless the request is genuinely abusive and you have documented why.
Practical DSAR response process
Step 1: acknowledge receipt within a few days, confirm what you understand the request to be, give a target response date. Step 2: verify identity. Step 3: identify every system that might hold data about this person (CRM, email platform, analytics, support system, backups). Step 4: extract the data from each system. Step 5: review for data about other people that needs redaction. Step 6: compile the response — both human-readable summary and raw data export. Step 7: deliver securely (password-protected zip via email, or secure file transfer portal). Step 8: document the request, response, and any decisions taken.
What happens if you miss the deadline
The user can complain to the ICO. The ICO typically responds first with a warning or advisory, especially for first-time offenders. Repeat or flagrant failures can escalate to enforcement notices, fines, or orders to comply. Beyond the regulator, the user can also sue you for damages in court. Most ICO enforcement around DSARs has been against organisations that ignore requests entirely — even a late response is better than no response.
Tip: Build a DSAR runbook BEFORE you get your first request. Document: which systems hold customer data, how to export from each, who is responsible for each step, and what the redaction guidelines are. Your first DSAR should take 2-3 days with the runbook. Without one, your first DSAR will burn a full month and still ship late.
Tags: gdpr, dsar, right-of-access, article-15, data-subject-rights, compliance
Tutorial
The Right to Have Inaccurate Data Corrected
Article 16 gives users the right to demand you correct inaccurate personal data about them. This sounds simple but has surprising edge cases — especially when accuracy is disputed or the data came from third parties.
What the right says
Article 16 gives the user the right to have inaccurate personal data about them rectified without undue delay. They also have the right to have incomplete data completed, including by providing a supplementary statement. You must act on the request within one month — the same deadline as DSARs. If the data is shared with third parties, you must notify them of the correction too, unless doing so would be disproportionate or impossible.
What counts as "inaccurate"
Inaccurate means factually wrong. The user spelled their surname "Smithe" when it should be "Smith" — inaccurate. Their date of birth is recorded as 1984 when they were born in 1985 — inaccurate. Their address has a typo — inaccurate. Opinion-based data is trickier. "Customer was rude to staff on 12 March" is an opinion you recorded — is it inaccurate? Generally no, if it is an accurate record of someone's impression at the time. But if the opinion is recorded as objective fact, the user can demand rectification or a supplementary statement added.
The accuracy principle links to this
Principle 4 of GDPR requires data to be accurate and kept up to date. Every rectification request is also evidence you failed the accuracy principle. If you accept the correction promptly, no harm done. If you refuse, you must justify why you think the data IS accurate. Keeping data you know is inaccurate is a compliance breach even without a rectification request being refused — so best practice is to resolve questions about accuracy quickly.
When you can refuse
You can refuse a rectification request only in narrow circumstances. The main one: the data is disputed but evidence supports its current version. Example: a customer claims their invoice total is wrong, but the contract and delivery receipt prove the recorded amount is correct. You can refuse to "correct" — but you should still add a note recording the user's objection (supplementary statement). You cannot refuse simply because correcting is inconvenient or the user has been difficult about something else.
Notifying third parties
If you shared the (now-incorrect) data with third parties — CRMs, email platforms, marketing services, analytics tools — you must notify them of the correction so they can update their copies too. This is the Article 19 "onward notification" obligation. You only have to make reasonable efforts. If you cannot find out where a broker sent the data years ago, you are not expected to do forensic work. But you must notify those you can identify, and you must tell the user which parties you notified if they ask.
Practical rectification process
Step 1: receive the request — it does not need to be formal. "My address is wrong" in a chat message is a valid rectification request. Step 2: verify identity (same approach as DSAR). Step 3: check the claim — what does your record say, what does the user say, is there evidence for the correct version? Step 4: update the record. Step 5: notify any third parties who received the data. Step 6: confirm the update to the user. Step 7: document what changed, when, and based on what evidence.
Automated accuracy workflows
Best practice is to give users self-service ability to correct their own data. An "Edit profile" page where they can update name, address, email, phone, preferences — covers 90% of rectification requests automatically, instantly, and without any administrative overhead for you. For things that cannot be self-edited (transaction records, admin notes, audit logs), have a documented process for staff requests. Keep a log of what was changed and who approved it.
Rectification vs erasure — not the same thing
Rectification changes inaccurate data to accurate data. Erasure deletes data entirely. If a user says "my address is wrong, please delete everything" — they are asking for both. Handle the rectification question first (correct the address) and the erasure question second (is there a lawful basis to keep the data at all). If the user literally wants everything deleted, treat it as an erasure request, not a rectification.
When the data is inferential
Some data you hold about users is inferred rather than given — a credit score calculated from inputs, a "likely to churn" flag from an ML model, an estimated income. If the user disputes the inferred value, Article 22 (automated decisions) may also apply. They can demand human review of the inference, and they can demand rectification of the underlying inputs. Inferences themselves are harder to "correct" — usually you recalculate after fixing inputs, rather than manually overriding the result.
Tip: Give users a self-service "Edit my details" page. It prevents most rectification requests from ever becoming formal requests that start the 30-day clock. Users who can fix their own data rarely escalate. Users who have to email support and wait 4 weeks are the ones who complain to the ICO.
Tags: gdpr, right-of-rectification, article-16, accuracy, data-quality
Tutorial
The Right to Be Forgotten: When You Must Delete a User's Data
Article 17 lets users ask you to delete their personal data. You usually have to comply, but not always. This guide covers when erasure is mandatory, when you can refuse, and how to do it properly.
What the right says
Article 17 gives users the right to ask you to delete personal data about them. You must respond within one month. You do not need to delete in every case — there are specific conditions where erasure is required, and specific exceptions where you can refuse. The common name "right to be forgotten" came from a 2014 court case about search engines and is a bit misleading — you do not have to erase every trace of the person, you have to erase the specific personal data they asked about, where the law requires you to.
The six situations where you MUST delete
You have to delete if any of these apply: (1) you no longer need the data for the purpose you originally collected it (for example, an inactive account after years); (2) the user withdraws consent and consent was your only lawful basis; (3) the user objects to processing under Article 21 and you have no overriding legitimate grounds; (4) you processed the data unlawfully (for example, without a valid lawful basis from the start); (5) you have a legal obligation to delete; (6) the data was collected from a child for online services.
The five situations where you can REFUSE
You can refuse erasure if processing is necessary for: (1) freedom of expression and information (news reporting, public debate); (2) compliance with a legal obligation (HMRC retention for financial records); (3) public interest in the area of public health, research, or archiving; (4) establishment, exercise or defence of legal claims. "Deletion is inconvenient" is not a valid refusal reason. If you refuse, you must tell the user within one month, explain why, and inform them of their right to complain to the ICO.
What "delete" actually means in practice
You must remove the data from your live production systems. You do not have to immediately scrub every backup — that is technically impractical — but you must ensure that if a backup is ever restored, the deletion is re-applied. The cleanest approach is to maintain a "do not restore" list. Deletion from third parties you shared the data with is also required — you must notify them to delete their copies, unless doing so is disproportionate or impossible.
Anonymise vs delete
Sometimes you need to keep a record but not the personal data — for example, financial records for HMRC cannot be deleted, but you do not need the customer's name, email, and phone attached to the invoice. The answer is anonymisation: replace identifying fields with generic values (customer name becomes "Deleted User," email becomes null, etc.) so the transaction record remains but no longer personally identifies anyone. This satisfies both the erasure right AND the retention obligation.
The "legitimate interests" counter-argument
If you rely on legitimate interests as your basis and the user objects + requests erasure, you do NOT have to delete if you can show "compelling legitimate grounds" that override the user's rights. This is a high bar. Fraud prevention often qualifies — you might keep a blacklist of users who committed payment fraud even if they request erasure, because the compelling interest is protecting future victims. Ordinary commercial interests (retention marketing, analytics) almost never qualify.
Verifying the requester
Same as DSAR: satisfy yourself the person asking is who they say they are, without demanding excessive ID. Log in from their registered email is usually sufficient. The stakes here are higher than DSAR because deletion is permanent — you do not want to nuke a real customer's account because a stranger emailed claiming to be them. Err toward slightly more verification for erasure than for access. Time-limit any "confirmation email" link (one-hour token, not permanent).
Practical deletion process
Step 1: receive request, acknowledge, verify identity. Step 2: identify all systems holding data about this user — CRM, email platform, support tool, analytics, payment processor, backups, third-party integrations. Step 3: decide which data must be deleted, which is exempt, which needs anonymising instead of deleting. Step 4: execute the deletions and anonymisations. Step 5: notify third-party recipients to do the same. Step 6: document what happened, which exceptions applied, and why. Step 7: confirm to the user what was deleted and what was retained (with legal justification for what was retained).
Building a self-service deletion feature
Best practice is to give users a button in their account settings that handles the whole erasure process automatically: password re-authentication, "type DELETE to confirm" check, then the deletion logic runs. This is faster, more compliant, and generates an audit trail. See our implementation guide for the complete pattern including database cascades, financial record anonymisation, and forum content preservation.
Tip: Respond to every erasure request within 3-5 days rather than waiting the full month. A fast response, even if you are refusing, prevents users escalating to the ICO. Silence for 29 days then a refusal is much worse than a polite refusal on day 2.
Tags: gdpr, right-to-erasure, right-to-be-forgotten, article-17, deletion, dsar
Tutorial
Data Portability: Letting Users Take Their Data to a Competitor
Article 20 lets users demand their data in a machine-readable format so they can transfer it to another service. This right is narrower than most people realise — and it has specific technical requirements.
What the right says
Article 20 gives users the right to receive personal data they provided to you, in a structured, commonly used, machine-readable format. They can transmit that data to another controller without you interfering. If technically feasible, they can ask you to transmit the data directly to the other controller on their behalf. Response time: one month, same as other rights.
When this right applies (and when it does not)
Data portability is narrower than the right of access. It ONLY applies to data the user PROVIDED to you (directly, or through their use of the service), AND the processing is based on consent or contract, AND the processing is automated. So: a social network must let you export your posts. An email provider must let you export your emails. But a government agency processing data under legal obligation does NOT have to provide portability. Inferred or derived data (credit scores, ML predictions) also does not have to be portable.
What "machine-readable" means
The data must be in a format a computer can easily parse and reuse. The gold standard formats: JSON, XML, CSV. Acceptable: well-structured plain text. NOT acceptable: a PDF (no, even with "text selectable" enabled — PDFs are designed for display, not data extraction), a scanned image of data, a proprietary binary format only your app can read, an HTML page designed for humans. If the user cannot easily import the file into another service or a spreadsheet, you have failed the portability requirement.
The difference from a DSAR
A DSAR (right of access) gives the user a COPY of all data you hold about them, in any reasonable format, for informational purposes. Data portability gives them a SUBSET of data (only what they provided) in a SPECIFIC format (machine-readable) for the purpose of moving to another provider. DSARs include inferences, internal notes, third-party-sourced data. Portability excludes all of those. Some requests might invoke both — you can provide two files: the full DSAR dump, and the narrower portability export.
Direct transmission to another controller
If the user asks, and it is technically feasible, you must transmit the data directly to another controller rather than via the user. This is designed to enable services like "port your Google Calendar to another calendar app." In practice, direct transmission is rare unless both services have implemented APIs for it. If you do not have an API that another service can use to receive the data, you can deliver it to the user and let them handle the import.
What format to use in practice
For most online services, JSON is the best default. It is universally understood, structurally clear, and handles nested data (orders with line items, users with multiple addresses) naturally. Provide one JSON file per logical data category: users.json, orders.json, messages.json. Include a README explaining the structure. CSV is acceptable for tabular data but breaks down for nested structures. XML is dated but still valid. Never ship a ZIP of PDFs and call it "portable."
What you do NOT have to include
Data portability excludes: data you derived from processing (analytics results, ML inferences, credit scores), data you obtained from third parties rather than the user, inferences about the user that they did not provide, and personal data about OTHER people that happens to sit in the user's account (comments from other users on their post, etc.). Be careful to strip third-party personal data from the export.
Practical portability implementation
Best practice: a self-service "Export my data" button in account settings that generates a ZIP containing JSON exports of all user-provided data categories. Run it asynchronously for large accounts and email the user a download link when ready. Include: the user's profile, content they created (posts, messages), their action history (orders, subscriptions), their preferences. Exclude: logs of analytics, internal notes, content involving other users. Document the export in your privacy policy so users know what it contains.
When portability gets tricky
Portability plus third-party content: a user's exported messages include messages FROM other users. Those other users have privacy rights too. Typical solution: include the message contents but redact or pseudonymise the other users' identifying information. Portability for connected accounts: if a user signed up via Google SSO, the Google-provided profile data is technically "provided by the user" (they authorised the handover). Include it in portability exports unless Google's terms restrict you. Portability after deletion: you cannot port data that no longer exists — if the account has been deleted, portability is no longer possible.
Tip: Many organisations never get a portability request, so they have no export feature. If one comes in, you have a month to build it from scratch. Avoid the panic: build a basic "download my data" button now, even if nobody uses it for a year. It also helps with DSARs.
Tags: gdpr, data-portability, article-20, export, machine-readable
Tutorial
The Right to Object (Article 21)
Users can object to processing based on legitimate interests or public task, and they can always object to direct marketing with no exceptions.
The right to object only applies to certain lawful bases. If you are processing under legitimate interests (6(1)(f)) or public task (6(1)(e)), users can object at any time on grounds relating to their particular situation. If you are processing under consent or contract, the right to object does not apply (though users have other routes like withdrawing consent). This distinction trips up a lot of organisations — a user cannot "object" to processing their order because contract necessity is not subject to objection.
Direct marketing is the big exception. If you market to users (email, SMS, post, phone), they have an absolute right to object at any time, and you must stop. No balancing test, no "compelling legitimate grounds" argument — marketing objections are final. This applies whether your lawful basis for marketing is consent or legitimate interests. The ICO has been clear: once someone objects to marketing, that is it. Add them to a suppression list and never contact them again.
When a user objects on "grounds relating to their particular situation" (non-marketing objection), you can still continue processing if you can demonstrate "compelling legitimate grounds" that override the user's interests, rights and freedoms — or if the processing is needed for legal claims. This is a high bar. You must actually run the balancing test and document your conclusion. If the user's objection is reasonable and you have no compelling reason, you must stop. "We would lose revenue" is not a compelling legitimate ground.
You must tell users about the right to object at the point of first communication and explicitly in your privacy notice (Article 21(4)). For marketing specifically, this needs to be presented clearly and separately from other information. A buried sentence in paragraph 17 of your privacy policy does not count. For online services, the ICO expects you to offer automated ways to object — a one-click unsubscribe in every email, a preference centre in user accounts.
Objections must be actioned without undue delay and in any case within one calendar month (same timeline as DSARs, Article 12(3)). You can extend by two further months for complex requests, but you must notify the user within the initial month. Marketing objections in particular should be actioned immediately — waiting 28 days to stop sending emails will draw an ICO complaint.
Build suppression lists, not deletion. If someone objects to marketing, do not delete their record entirely — you need to remember not to contact them if they reappear in a new list upload or CRM sync. A suppression list stores minimal identifiers (email hash, phone hash) with a "do not contact" flag. The ICO explicitly requires this: Regulation 22 of PECR and Article 21 of UK GDPR together mean suppression lists are lawful and necessary despite the right to erasure.
Handle profiling objections carefully. If you profile users for direct marketing purposes, the objection right extends to that profiling — so stop the profiling activity, not just the email sends. If profiling is used for something else (fraud prevention, credit scoring), the user can still object, but you have the compelling-legitimate-grounds defence available.
Document every objection. Log the date received, what the user objected to, what you did, and when. If the ICO investigates a complaint six months later, you need an audit trail. The Information Commissioner fined JD Williams £50,000 in 2019 partly because they could not evidence how they had handled marketing opt-outs. Keep records for at least two years after the objection.
Tip: An absolute right to object to marketing means absolute. Do not try to offer alternatives like "reduce frequency" or "switch to SMS only" when someone clicks unsubscribe — give them out, fully, in one click.
Tags: gdpr, right-to-object, article-21, marketing, legitimate-interests, opt-out
Tutorial
The Right to Restrict Processing (Article 18)
Users can request you pause processing of their data in four specific scenarios — the data stays but cannot be used.
Restriction is distinct from deletion. You keep the data but you cannot do anything with it except store it. Any other processing (analysis, disclosure, transfer) requires the user's consent, a legal claim, another person's rights, or important public interest. Think of it as a "freeze" — the data sits in your systems marked as restricted, and your staff and automated systems must not touch it.
The right applies in four specific scenarios (Article 18(1)). First: when the user contests accuracy, restriction applies while you verify. Second: when processing is unlawful but the user wants restriction instead of erasure. Third: when you no longer need the data but the user needs it for legal claims. Fourth: when the user has objected under Article 21 and you are considering whether your legitimate grounds override theirs — restriction applies during the consideration period.
You must have a technical mechanism to mark records as restricted. For database records, add a "restricted" flag that all application logic checks before reading or exporting. For spreadsheets and flat files, move records to a clearly labelled restricted area. The ICO specifically calls out moving data to a separate system as one valid method. What matters is that no one accidentally uses the data during restriction.
Notify the user before lifting restriction. Article 18(3) requires you to tell the user before you start processing again. Do not silently un-pause once you have verified accuracy or concluded your balancing test — send a clear message explaining what changed and what you will now do. This gives the user a chance to raise fresh objections or request erasure instead.
Downstream recipients must also restrict. Article 19 requires you to communicate any restriction (and rectification, and erasure) to everyone you have shared the data with — unless this is impossible or disproportionate. In practice this means maintaining a log of data-sharing partners per user, and firing off notifications when a restriction request comes in. For shared databases, an API call to trigger restriction on the partner side; for manual shares, an email.
Restriction does not pause legal retention. If you have a statutory duty to retain data (tax records for seven years, employment records for six years), restriction does not override that. You still hold the data, you just cannot use it for operational processing. The ICO has confirmed that legal hold obligations sit alongside restriction rights.
Be honest about system limitations. If your CRM genuinely cannot mark a record as restricted without breaking other functions, say so and explain what alternative you are using (isolated spreadsheet, access control changes, manual process). The ICO expects honest effort, not perfection. Cambridge Analytica was criticised not for imperfect restriction but for having no plan at all.
Time limits match other rights: one calendar month for initial response, extendable by two months for complex cases. Complexity must be documented — you cannot just claim "complex" to buy time. A straightforward "I dispute this email address is mine" is not complex. Investigating a disputed record across seventeen integrated systems probably is.
Tip: Build a "restricted" flag into your database schema from day one if you can. Retrofitting restriction after a DSAR request arrives is painful and prone to human error.
Tags: gdpr, right-to-restrict, article-18, user-rights, data-processing
Tutorial
Rights Against Automated Decision-Making (Article 22)
Users can refuse to be subject to purely automated decisions that have legal or significant effects — with narrow exceptions and mandatory safeguards.
Article 22 applies to decisions that are "solely" automated and produce legal or similarly significant effects. "Solely" means no meaningful human involvement — a human who rubber-stamps an algorithm's output does not break the chain. The ICO has made clear that token human review (a five-second glance at a 300-row output) does not count. The human must actually consider the case and have authority to override.
Legal effects are things like denying credit, refusing a job application, terminating benefits, cancelling a contract. "Similarly significant" is broader: rejecting an insurance application, denying rental accommodation, dynamic pricing that charges some users substantially more, content moderation that removes livelihoods. Targeted advertising generally does not qualify unless it excludes users from important opportunities (housing, employment).
There are three exceptions that allow purely automated decisions: (a) necessary for entering or performing a contract with the user, (b) authorised by law with safeguards, (c) based on explicit consent. Most commercial automated decisions rely on (a) contract necessity — a credit check algorithm for a loan application is necessary to decide whether to offer the contract. "Necessary" is strict; "convenient" or "efficient" does not qualify.
Even under an exception, Article 22(3) requires specific safeguards: the right to human intervention, the right to express a point of view, and the right to contest the decision. Build these in before you deploy automated decision-making. A "request human review" button on the rejection email. A free-text field where the user can explain their situation. An appeals process with a named contact and response timeline.
Special category data (health, biometrics, ethnicity, religion) cannot be used in automated decisions at all unless you have explicit consent or substantial public interest grounds under Article 9(2)(g), plus suitable safeguards. If your algorithm uses any of these inputs (even indirectly through proxies), you need to document why an exception applies. The Amazon hiring algorithm that discriminated against women was ultimately scrapped partly because of this.
Privacy notice obligations go further for automated decisions. Article 13(2)(f) requires "meaningful information about the logic involved" plus the significance and envisaged consequences. You do not need to give up trade secrets, but you must explain the main factors, their relative weight, and the consequences of the decision. "Our algorithm considers factors including your credit history, employment status and existing debt" is the minimum. Hiding behind "proprietary model" is not acceptable.
Document your DPIA. Automated decisions with significant effects are listed on the ICO's mandatory-DPIA list. Your DPIA must assess bias, fairness, accuracy, and the option of less intrusive alternatives. The ICO fined Clearview AI £7.5m in 2022 partly for failing to conduct a meaningful DPIA before deploying facial recognition at scale.
Monitor for bias after deployment. A compliant launch is not enough. Algorithms drift as training data ages and user populations change. Check decision outcomes quarterly by protected characteristic — if your loan algorithm rejects women at 2x the rate of men with equivalent profiles, you have a problem that is now both a GDPR breach and an Equality Act 2010 breach.
Tip: If a human reviewer approves more than 95% of algorithmic decisions without changes, the decision is effectively solely automated regardless of the paper process. Measure override rates to prove meaningful review.
Tags: gdpr, article-22, automated-decisions, profiling, ai, algorithms
Tutorial
All Eight User Rights at a Glance
A compact reference covering every GDPR user right, when it applies, and the common pitfalls for each.
Right to be informed (Articles 13-14) — users must be told who you are, why you process their data, what lawful basis applies, how long you keep it, who you share with, and what their rights are. Applies at collection for data collected directly (Article 13) or within one month for indirect collection (Article 14). Pitfall: privacy notices written in legalese no real user will read.
Right of access (Article 15) — users can request a copy of their personal data plus supplementary information about how it is processed. One month to respond, extendable to three for complex cases. Free in almost all cases. Pitfall: underestimating the scope — emails mentioning the user, meeting notes, CCTV footage and chat logs are all in scope.
Right of rectification (Article 16) — users can correct inaccurate data and complete incomplete data. One month to respond. You must notify downstream recipients unless impossible or disproportionate. Pitfall: refusing corrections because the user "cannot prove it" — the burden is on you to maintain accuracy.
Right to erasure / "right to be forgotten" (Article 17) — users can request deletion in six specific scenarios (no longer necessary, consent withdrawn, objection upheld, unlawful processing, legal obligation, child consent). Not absolute — exemptions exist for free expression, legal obligations, public health, archiving, legal claims. Pitfall: treating every deletion request as requiring deletion.
Right to restrict processing (Article 18) — users can request you "freeze" data in four scenarios (contested accuracy, unlawful processing, no longer needed but user needs for claims, objection pending). Data stays but cannot be used. Pitfall: no technical mechanism to enforce restriction.
Right to data portability (Article 20) — users can request their data in a structured, commonly used, machine-readable format and have it transmitted to another controller where technically feasible. Only applies where the basis is consent or contract AND processing is automated. Pitfall: confusing this with the right of access — portability is narrower.
Right to object (Article 21) — users can object to processing based on legitimate interests or public task. Absolute right to object to direct marketing. You must stop unless (for non-marketing) you have compelling legitimate grounds. Pitfall: treating unsubscribe as optional or multi-step.
Rights in automated decision-making (Article 22) — users have the right not to be subject to purely automated decisions with legal or significant effects, with exceptions for contract necessity, law, or explicit consent — each with mandatory safeguards. Pitfall: "human review" that is actually rubber-stamping.
Tip: Print this list and pin it near your DSAR intake process. Most compliance failures come from misidentifying which right a request invokes, not from failing to handle the right correctly.
Tags: gdpr, user-rights, overview, reference, compliance
Tutorial
How to Handle User Rights Requests
A step-by-step process for receiving, verifying, actioning and closing any rights request within the one-month deadline.
Set up multiple intake channels. Users can make rights requests verbally (phone, in person), in writing (letter, email), via a web form, or even via social media DM. All are valid. Train every customer-facing staff member to recognise a rights request even when the user does not use the word "GDPR" — "please delete my account" is an erasure request; "what info do you have on me" is a DSAR. Forward all to a central inbox within 24 hours.
Start the clock on receipt. The one-month deadline runs from the day you receive the request, not the day you recognise it or assign it to a team member. If a request sits unread in a shared inbox for ten days, you have 21 days left, not 31. Use a ticketing system that timestamps on arrival and shows deadline countdown — Zendesk, Freshdesk, or a custom spreadsheet all work.
Verify identity proportionately. Article 12(6) allows you to request additional information where you have "reasonable doubts" about identity. Do not routinely demand passports for a DSAR from someone logged into their account — their authenticated session is identity enough. For anonymous email requests, a reply to the registered email address asking for confirmation is often sufficient. Over-verification is itself an ICO complaint — do not use it as a stalling tactic.
Clarify the scope if needed. For broad requests like "send me everything", Article 12 allows you to ask for clarification — but only to help the user, not to narrow your obligation. The clock pauses during clarification only if you actually need it to respond. Most users asking for "everything" genuinely mean everything; honour that where possible.
Search comprehensively. Personal data lives in more places than your main database: email archives, backup systems, log files, CCTV, access control systems, HR files, customer service notes, third-party processors. Maintain a data inventory (your ROPA helps here) so your search checklist is comprehensive. Document which systems you searched — if the ICO later investigates, "we forgot to check email" is not a defence.
Apply exemptions carefully. You can redact third-party personal data, withhold legally privileged material, exclude information relating to crime prevention, and apply specific exemptions for references, management forecasts, negotiations. Each exemption must be justified against the statutory test. When in doubt, disclose — over-redaction is a more common ICO finding than over-disclosure.
Respond within one month, in writing, free of charge. Extend by two months only for complex cases, and notify the user within the first month with reasons. Your response should explain what you did, provide the data (for access/portability) in a usable format, confirm actions taken (for rectification/erasure/restriction), and remind the user of their right to complain to the ICO.
Log everything. For every rights request, record: date received, date actioned, what was requested, what was provided or done, any exemptions applied and why, who handled it. Keep these logs for at least two years. The ICO routinely asks for request logs during investigations and the absence of good records turns a minor compliance issue into a major fine.
Tip: Build a simple spreadsheet or Airtable with columns for request date, deadline, user email, request type, status, and actions taken. This is often the only evidence between you and a £20m fine when the ICO comes knocking.
Tags: gdpr, dsar, user-rights, response-process, compliance, procedures
Tutorial
PECR requires consent for almost every cookie that is not strictly necessary — and UK GDPR sets the standard for what valid consent looks like.
The law that actually governs cookies in the UK is PECR (Privacy and Electronic Communications Regulations 2003), not GDPR directly. Regulation 6 of PECR requires consent for storing or accessing information on a user's device, with one exception: cookies "strictly necessary" for a service the user has explicitly requested. Everything else — analytics, advertising, personalisation, social media embeds, A/B testing — needs consent. And because PECR incorporates the UK GDPR definition of consent, the bar is high.
Strictly necessary is narrower than most people think. A session cookie that remembers the contents of a shopping basket is strictly necessary. A cookie that stores language preference set by the user is strictly necessary. A load-balancer cookie is strictly necessary. Analytics cookies — including Google Analytics — are not strictly necessary. The ICO has been unambiguous on this: no, you cannot classify GA as essential just because you want the data.
Consent must be a clear affirmative action. Pre-ticked boxes do not count (Planet49 case, CJEU 2019). Continued scrolling does not count. "By using this site you agree to cookies" does not count. The user must take a deliberate positive action — clicking "Accept", toggling a switch, selecting preferences. The ICO fined DoorStep Dispensaree £275,000 in 2019 partly for taking consent-by-default approaches.
Reject must be as easy as accept. If your banner has a big green "Accept All" button and a tiny "Cookie settings" link that takes three clicks to reach rejection, you are breaking the rules. The ICO updated its guidance in August 2023 to make this explicit — accept and reject must be on the same screen, same prominence, same number of clicks. The French CNIL has fined Google €150m and Facebook €60m for this exact pattern.
Granular consent by purpose. You cannot bundle "analytics + advertising + personalisation" into a single toggle. Users must be able to consent to some purposes and not others. Common categories: strictly necessary (no toggle needed), functional, analytics, advertising, social media. Each gets its own opt-in. If a user accepts analytics but rejects advertising, you must honour that exactly.
List your cookies. Your cookie notice (often linked from the banner as "Learn more" or "Cookie policy") must list every cookie you set, what it does, how long it lasts, and who sets it (first-party vs named third parties). This is not optional — Article 13 of UK GDPR requires specific information about processing, and PECR Regulation 6 requires "clear and comprehensive information". Auto-scanning tools like CookieBot or OneTrust generate this list.
Consent expires. Best practice is re-prompting every 6-12 months even if the user previously consented. Consent to changed purposes always requires fresh consent — if you add a new tracker or change a vendor, old consent does not cover the new activity. Rebuild your banner state when you update your cookie list so users see a fresh prompt for new cookies.
Document consent. Store a record of what each user consented to, when, and what version of the policy they saw. A simple approach: hash the consent configuration (e.g. "functional=1,analytics=0,advertising=0,version=2.1,timestamp=2026-04-21") and store against user ID or anonymous session. The ICO will ask for these records during investigations, and you will need them for any consent-based lawful basis you rely on.
Tip: Run your site in an incognito browser and try to reject all cookies. Time it. If rejection takes more than one click from the banner, your design is non-compliant.
Tags: gdpr, pecr, cookies, consent, cookie-banner, tracking
Tutorial
Google Consent Mode v2: What It Actually Does
Consent Mode v2 is Google's response to UK GDPR and the EU Digital Markets Act — it changes tag behaviour based on user consent signals.
Consent Mode is a way to signal user consent choices to Google tags (Analytics, Ads, Tag Manager) so they behave differently depending on what the user has allowed. It was optional until March 2024, when Google made v2 effectively mandatory for advertisers running campaigns in the UK and EEA — without it, remarketing lists stopped growing and conversion data degraded. If you use Google Ads and serve UK/EU traffic, you need Consent Mode v2.
Two new consent signals were added in v2: ad_user_data (whether user data can be sent to Google for advertising) and ad_personalization (whether data can be used for personalised ads). These sit alongside the existing ad_storage (advertising cookies) and analytics_storage (analytics cookies). All four signals can be set to "granted" or "denied" per user.
Two implementation modes exist: Basic and Advanced. Basic mode blocks all Google tags until consent is given — no pings at all, no data. Simple to explain but loses signal. Advanced mode lets tags fire even without consent, but sends cookieless pings with no personal identifiers — Google uses these for "conversion modelling" to statistically estimate conversions from non-consenting users. Most advertisers choose Advanced.
The ICO has not explicitly blessed Advanced mode. Consent Mode does send data (albeit cookieless and anonymised) to Google before consent — whether that counts as "processing" requiring consent under PECR is legally debatable. The safest reading is that Advanced mode with properly anonymised pings is lawful as legitimate interest, but consent-first lawyers disagree. Document your position in a DPIA.
Your CMP (Consent Management Platform) must be Google-certified to work with Consent Mode v2 for Google Ads purposes. As of April 2026 the certified list includes CookieBot, OneTrust, Usercentrics, Cookie Information, Didomi, iubenda, and around 25 others. Uncertified CMPs may still technically work but you lose access to Consumer Matching features and some conversion signals.
Default state matters. On first page view, before the user has clicked anything, Consent Mode defaults apply. For UK/EU users, all four signals should default to "denied" — this is the compliant starting position. Once the user accepts cookies via your banner, fire a gtag() update call to set them to "granted". If you default to "granted" you are effectively setting cookies before consent, which breaches PECR.
Cookie-less pings send more than people realise. Even in "denied" state, Google tags send: timestamp, referrer, user-agent, a randomised identifier that resets per page, the consent signal values themselves, and a signal about whether an ad click brought the user to the site. No personal identifiers, no cross-session tracking — but technically data leaves the user's browser to Google. Your privacy notice should disclose this clearly.
Test your implementation. Use the Tag Assistant browser extension to inspect what tags fire before and after consent. Check that ad_storage and analytics_storage remain "denied" until the user actually clicks accept. Check that rejection properly sets them to "denied" permanently. Common bug: rejection is logged in the CMP but the gtag() update never fires — tags keep running as if consent was given.
Tip: If Google Ads is showing "conversion modelled" data with no real conversions, Consent Mode v2 is working. If it shows only real conversions, you are probably not sending any signals and need to check your implementation.
Tags: gdpr, consent-mode, google-analytics, google-ads, tracking, consent
Tutorial
Trackers, Pixels and Fingerprinting — the Full Picture
Cookies are only one form of tracking. Pixels, local storage, fingerprinting and server-side events all have consent implications.
PECR Regulation 6 applies to "any information that is stored, or access to information stored" on a user's device. That is deliberately technology-neutral. Cookies are covered, but so are local storage, session storage, IndexedDB, cache storage, Service Worker registrations, ETags used for tracking, and anything that reads information from the device (screen dimensions, installed fonts, battery status). If it stores or reads, it needs consent unless strictly necessary.
Tracking pixels (1x1 transparent images, "web beacons") work by loading a URL from a tracking server. The server logs the request and sets cookies in response. Facebook Pixel, LinkedIn Insight Tag, Twitter Universal Website Tag all use this pattern. They count as both "storing" (the response sets cookies) and "accessing" (reading existing cookies from the tracking domain). Full consent required.
Local storage and IndexedDB are often overlooked. Developers sometimes use these as "cookie alternatives" thinking they escape PECR — they do not. The ICO explicitly includes them in its guidance. If you store a persistent user identifier in localStorage for analytics or advertising, it needs consent exactly the same as a cookie would.
Browser fingerprinting is the creation of a probabilistically-unique identifier from device characteristics: screen size, timezone, installed fonts, canvas rendering quirks, WebGL output, audio context response. Fingerprinting "reads information" from the device, so PECR applies. The ICO guidance from 2019 and 2023 is unambiguous: fingerprinting requires consent just like cookies. Tools like FingerprintJS must be properly gated.
Server-side tracking is not a consent workaround. Google Tag Manager Server-Side, Facebook Conversions API, and similar tools forward user data to platforms from your server instead of the browser. Some vendors market this as "privacy-friendly" because cookies are set first-party. But the underlying data collection — identifying users and tracking behaviour — is unchanged. If the browser-side version needed consent, the server-side version needs consent too.
Device-bound identifiers (mobile app IDFA on iOS, GAID on Android) need consent. Apple's App Tracking Transparency (ATT) prompts are one mechanism for this on iOS; on Android, Google's Privacy Sandbox is progressively introducing similar controls. Do not assume "we're native, not web" exempts you from PECR — it does not for UK users.
CDN tracking cookies are a specific risk. Cloudflare, Akamai and other CDNs may set cookies for legitimate functions (bot detection, load balancing, DDoS mitigation) — these often qualify as strictly necessary. But some CDN features are analytics or advertising (Cloudflare Web Analytics, Akamai mPulse) — these need consent. Audit your CDN configuration and understand what each cookie does.
Consent applies to the act of tracking, not the storage medium. The ICO has stated this clearly: if you are tracking users for analytics, advertising, personalisation, or any non-essential purpose, you need consent regardless of whether the technical mechanism is a cookie, localStorage, fingerprint, pixel, or server-side API call. Focus your compliance analysis on what the tracker does, not how it works.
Tip: Run a site audit with Blacklight (themarkup.org/blacklight) — it reveals every tracker, fingerprint attempt, and session recorder your site runs. The results often surprise even the site owner.
Tags: gdpr, pecr, trackers, pixels, fingerprinting, privacy
Tutorial
Session Recording and Heatmaps: Hotjar, FullStory, Mouseflow
Session recording tools capture detailed user behaviour including mouse movements, clicks, scrolls and form inputs — and need careful consent and masking.
Session recording tools (Hotjar, FullStory, Mouseflow, SmartLook, LogRocket) capture a video-like replay of each user's session: every mouse movement, click, scroll, text input, page change. This is extraordinarily detailed personal data. Even with IP anonymisation and form field masking enabled, the replay itself is personal data because it shows the unique behavioural pattern of one person browsing your site.
Consent is required before recording starts. PECR applies because these tools set cookies and localStorage. GDPR applies because the session replay is personal data. You cannot start recording and then ask for consent later — by the time the user sees your banner, they have already been captured interacting with the page for the 200ms before the banner loaded. Configure the tool to wait for consent before initialising.
Mask sensitive inputs by default. Hotjar, FullStory et al all offer field masking — apply it aggressively. Password fields must always be masked (most tools do this automatically). Payment fields, addresses, phone numbers, dates of birth, medical information, anything special category — all must be masked. Do not rely on auto-detection; explicitly configure masking rules for every form on your site.
Recordings are processor-controlled and must comply with your DPA. The recording tool vendor is your processor — you need a signed DPA under Article 28, and that DPA must document what happens to the data. Check: where is it stored (ideally EEA/UK), how long is it retained (should be the minimum you need, typically 6-12 months), who has access, how is it deleted when your contract ends.
Heatmaps are slightly less intrusive but still require consent. An aggregated heatmap of where users click is less personal than a session replay, but the data collection that generates the heatmap still involves tracking individual user behaviour before aggregation. PECR applies to the collection step. The ICO has confirmed that heatmap tools are not strictly necessary.
Third-country transfers are the silent risk. Many session recording tools are US-based (FullStory, Pendo, LogRocket) and transfer personal data internationally. Even Hotjar, which is EU-headquartered, may use US infrastructure under the hood. You need to have assessed the transfer under the UK International Data Transfer Agreement (IDTA) or EU SCCs, conducted a transfer impact assessment, and documented the decision.
Users have DSAR and erasure rights over recordings. A user can request a copy of their session recordings under Article 15 — so you need to be able to retrieve them by user. A user can request erasure under Article 17 — so you need tooling to delete specific recordings. Before you deploy a session recording tool, confirm the vendor's self-service or API options for both. Most good vendors provide this; some cheap ones do not.
DPIAs are often mandatory. The ICO's list of high-risk processing includes "systematic monitoring" of behaviour, which session recording clearly is. Run a DPIA before deployment, document the necessity analysis, consider less intrusive alternatives (server-side funnel analysis, aggregated analytics), and document mitigating measures like aggressive masking and short retention.
Tip: Watch one of your own sessions in Hotjar or FullStory with the sound of a user narrating it. If you feel uncomfortable about how much you can see, your users would too — tighten your masking rules.
Tags: gdpr, session-recording, hotjar, fullstory, heatmaps, consent
Tutorial
Third-Party Cookies and the Post-Cookie Transition
Chrome is phasing out third-party cookies, but that does not make tracking compliant — alternative mechanisms have the same consent requirements.
A third-party cookie is set by a domain other than the one in the user's address bar. When you embed a Facebook Pixel on shop.example.com, Facebook sets cookies on the facebook.com domain — those are third-party cookies, used to link behaviour across every site with a Facebook Pixel. Safari blocked these by default in 2020, Firefox in 2019, and Chrome has been phasing them out since 2024 with full deprecation planned for 2026.
Third-party cookies are not the same as cross-site tracking. Even with third-party cookies blocked, trackers can still identify users through fingerprinting, first-party cookies set via JavaScript, server-side tracking, and deterministic matching via logged-in identities (Gmail, Meta, TikTok accounts). Deprecation is a browser change, not a GDPR compliance event.
Google's Privacy Sandbox is a set of replacement APIs: Topics (cohort-based interest categories), Protected Audience (remarketing without cross-site identifiers), Attribution Reporting (conversion measurement), Fenced Frames (sandboxed ads). These are designed to enable advertising without individual tracking. The ICO has given cautious approval but flagged the need for user choice and transparency — consent is still required for Topics and similar APIs.
CHIPS (Cookies Having Independent Partitioned State) allows third-party cookies to continue in a partitioned form. A cookie set by embedded-widget.com on shop.example.com is stored separately from the same cookie set on other-shop.com — so tracking is confined to a single first-party context. This restores some legitimate use cases (embedded maps, chat widgets) without enabling cross-site tracking.
Server-side tracking is the main commercial alternative. Instead of Facebook Pixel setting third-party cookies from facebook.com, you set a first-party cookie on your own domain and forward conversion events to Facebook's Conversions API from your server. This works across browser restrictions but raises the same consent questions as client-side tracking — the user is still being tracked for advertising.
First-party cookies set by trackers via JavaScript (e.g. _fbp, _ga) are not affected by third-party deprecation because they are set on your domain. They were always subject to PECR consent requirements, and they still are. Do not mistake "first-party" for "compliant" — the purpose of the cookie determines whether it needs consent, not the domain that sets it.
Contextual advertising is the least-intrusive alternative. Instead of targeting based on user profiles, ads are matched to the content of the current page. Modern contextual ad systems (GumGum, Seedtag, Vibe) use AI to understand page content and match ads to topic, mood, and audience inferred from content consumption patterns — no user tracking needed. This is genuinely GDPR-light and increasingly effective.
The transition is ongoing and the rules keep changing. The UK ICO works with the CMA on Privacy Sandbox concerns. Chrome keeps pushing deprecation dates. Apple updates ITP every iOS release. Subscribe to the ICO's news feed and the UK Tech Policy updates from sites like TechUK to stay current. What was compliant in 2023 may not be in 2027.
Tip: Third-party cookie deprecation solves browser-level tracking but does nothing for compliance. If your advertising stack needed consent before deprecation, it still needs consent after — the lawful basis analysis is identical.
Tags: gdpr, third-party-cookies, privacy-sandbox, advertising, chrome, tracking
Tutorial
Your privacy policy must cover every Article 13 and 14 disclosure in plain language — generic templates routinely miss key sections.
Start with the mandatory Article 13 disclosures for data collected directly from users. You must include: your identity and contact details, the DPO contact if you have one, the purposes and lawful basis for each processing activity, recipients or categories of recipients, any international transfers and the safeguards used, retention periods (or criteria to determine them), all eight user rights, the right to complain to the ICO, whether providing data is statutory or contractual and the consequences of not providing, and any automated decision-making.
If you receive data indirectly (from data brokers, public sources, other companies), Article 14 adds two more required disclosures: the categories of data you received, and the source of that data including whether it came from publicly accessible sources. You must tell the user within one month of receipt, or at first communication, or before further disclosure — whichever is earliest.
Write in plain language. Article 12(1) requires information "in a concise, transparent, intelligible and easily accessible form, using clear and plain language". The ICO tests privacy notices for readability — a reading age of 12-14 is the standard. Run your notice through Hemingway Editor or the Flesch-Kincaid calculator. If it scores above reading age 16, rewrite it. Break long sections into headings, use bullet points, avoid nested conditionals.
Structure by user journey, not by legal article. Users do not think in terms of Article 13(1)(c). They think: "what does this company know about me, why, who sees it, and how do I make them stop?" A good structure: what we collect, why we collect it and on what basis, who we share it with, how long we keep it, your rights, how to contact us. Map Article 13/14 requirements onto these headings rather than the other way round.
Be specific about purposes. "To improve our services" is not a valid purpose description — it is too vague to support a lawful basis analysis. "To analyse website usage patterns using Google Analytics so we can identify which pages users struggle with" is specific and testable. The ICO explicitly criticises vague purpose statements in its audit reports. Each purpose should have its own lawful basis, retention period, and recipient list.
Name your processors where reasonable. You do not have to list every single processor by name, but you should list categories clearly, and name the significant ones. "Hosting: AWS (UK region). Analytics: Google Analytics 4. Customer support: Zendesk (US, under SCCs). Email: SendGrid (EU region)." This level of transparency is now expected. The ICO fined Ticketmaster £1.25m in 2020 partly because their processor chain was opaque to users.
Cover international transfers properly. For every transfer outside the UK, state the country, the recipient (or category), and the Article 46 safeguard you use (UK adequacy decision, International Data Transfer Agreement, Binding Corporate Rules). If you use a US processor, mention the UK Extension to the Data Privacy Framework. Vague statements like "we may transfer data internationally" are no longer sufficient.
Version and date everything. Include a "last updated" date at the top of the policy. When you make material changes, notify existing users (typically by email) and log the old version — you need to be able to prove what each user saw when. A privacy policy is a compliance document; treat it with version control and change logs as you would any regulated document.
Tip: Get someone who does not work on your product to read your privacy policy and then ask them: what does this company collect, and why? If they cannot answer in plain English, your policy fails Article 12.
Tags: gdpr, privacy-policy, privacy-notice, article-13, article-14, compliance-docs
Tutorial
A cookie policy lists every cookie, its purpose, duration and provider — kept accurate and updated whenever your trackers change.
A cookie policy is a separate document (or a dedicated section in your privacy policy) that lists every cookie your site sets and every tracker it embeds. PECR Regulation 6(2)(a) requires you to provide "clear and comprehensive information" about cookies — the ICO interprets this as a full itemised list, not a generic statement. Most sites link to it from the cookie banner as "Cookie Policy" or "Learn More".
For each cookie, include: cookie name (the exact technical name like _ga or PHPSESSID), who sets it (first-party vs named third-party domain), purpose category (strictly necessary, functional, analytics, advertising), specific purpose description (what it actually does), duration (session, or specific expiry like "2 years"), and provider's privacy policy link for third-party cookies. A table format is the norm.
Auto-scan your site with a cookie discovery tool. CookieBot, OneTrust Cookie Compliance, CookieYes, and Usercentrics all offer scanners that crawl your site and enumerate every cookie, pixel, localStorage item and tracker. Run this monthly at minimum — new cookies appear whenever you add a vendor, install a plugin, or embed a widget. Manual maintenance is error-prone.
Categorise honestly. "Strictly necessary" has a specific legal meaning — essential for a service the user explicitly requested. Shopping basket cookies qualify. Session cookies for authentication qualify. Load balancer cookies qualify. Analytics cookies do not qualify, even if they are "essential to your business" — the test is user-centric, not vendor-centric. Miscategorisation is a top-five ICO finding in cookie audits.
Explain the purpose in user terms. "Google Analytics _ga cookie — tracks users across sessions for website statistics" is better than "Client identifier for Google Analytics 4 property". Users need to understand what each cookie does to them, not what it does technically. Keep one sentence per cookie, no jargon.
Link each third-party cookie to its provider's privacy policy. For the Facebook Pixel, link to facebook.com/privacy/policy. For Google Analytics, link to policies.google.com/privacy. This is explicitly required by Article 13(1)(e) for data sharing disclosure. Dead links count as non-compliance — check them quarterly.
Keep a change log. When you add or remove a vendor, document the date and what changed. "April 2026: added LinkedIn Insight Tag (advertising category). Removed Hotjar (analytics category)." This change log helps with fresh-consent decisions — if you added new trackers, existing consent does not cover them and you need to re-prompt.
Ensure alignment between policy and banner. Your banner categories must map exactly to your cookie policy categories. If your banner offers four toggles (necessary, functional, analytics, advertising) but your policy only discusses three, users see a compliance gap and the ICO will too. Review both together every time you change either.
Tip: Install your own site in a fresh browser profile, accept only "strictly necessary", and then check DevTools > Application > Cookies. Any cookie set beyond the strictly-necessary ones is a compliance breach, regardless of what your policy says.
Tags: gdpr, pecr, cookie-policy, cookies, compliance-docs
Tutorial
Records of Processing Activities (ROPA) — Article 30
Your ROPA is the internal document that proves you understand what data you process and why — required for most organisations and essential evidence in any ICO investigation.
Article 30 requires a written record of all processing activities. Controllers must document: name and contact of controller and DPO, purposes of processing, categories of data subjects and personal data, categories of recipients, international transfers and safeguards, retention schedule, general description of technical and organisational security measures. Processors have a slightly shorter list but must still maintain records.
The small-organisation exemption is narrow. You are exempt from Article 30 only if you have under 250 employees AND the processing is occasional AND does not include special category or criminal offence data AND is unlikely to result in risk to rights. In practice almost every commercial organisation fails one of these conditions — if you have customer data, you probably need a ROPA regardless of headcount.
Structure your ROPA as a spreadsheet or database, one row per processing activity. A "processing activity" is a distinct purpose — "sending marketing emails", "processing orders", "managing employee payroll" are each separate activities. Do not try to cram everything into a single catch-all entry; granularity helps you answer specific ICO questions and helps you spot gaps.
Required columns for controllers: activity name, purpose, lawful basis, data subject categories (customers, employees, suppliers), data categories (name, email, payment, etc.), special category data (yes/no + basis), source of data, recipients (internal + external), retention period, security measures, international transfers + safeguards. Add a "last reviewed" date column — you must keep this current.
Link your ROPA to other compliance documents. Each activity should reference: its DPIA (if one was required), the relevant privacy notice section, any processor contracts, and any data sharing agreements. When the ICO investigates, they typically start with the ROPA and request linked documents — a well-organised ROPA cuts investigation time dramatically.
Update on change, not on schedule. Your ROPA must reflect reality. Add a new activity the day you start processing for a new purpose. Remove activities when you stop them (but archive the record with end date). Update recipients when you onboard or offboard processors. A stale ROPA is worse than no ROPA — it signals you are not actually running the process.
The ICO will ask for your ROPA within days of opening an investigation. If you cannot produce one within 72 hours, that itself is evidence of non-compliance with Article 30 — separate to whatever triggered the investigation. Keep your ROPA in a shared location with clear ownership (typically DPO or compliance lead). A password-protected spreadsheet on one person's laptop is a single point of failure.
Map your ROPA to your data inventory. A ROPA is about activities; a data inventory is about what data you hold and where. Together they answer: what data do we have, where is it stored, who has access, why do we process it, and under what basis. Some organisations keep both as tabs in one spreadsheet — activity tab and systems/data tab linked by ID.
Tip: Start your ROPA today, even as a rough draft. A partial, honest ROPA with known gaps is a stronger compliance position than no ROPA or a fabricated one. Iterate weekly for the first quarter.
Tags: gdpr, ropa, article-30, documentation, accountability, processing-records
Tutorial
Privacy Notices at the Point of Collection
You must inform users about data processing at the point they give you their data — a link to the privacy policy is often not enough.
Article 13 requires information to be provided "at the time when personal data are obtained". Not before, not after — at the moment of collection. For a signup form, that means the user sees the key information as they fill in the form, not after they have already clicked submit. A dedicated privacy policy page at the footer is necessary but rarely sufficient alone.
Implement just-in-time notices — short contextual messages that appear next to specific data inputs. When the user enters their phone number, a "why do we ask for this?" tooltip shows "We use your phone number only for two-factor authentication — it is never shared with third parties or used for marketing." These micro-notices respect Article 12's "concise" requirement and dramatically improve user understanding.
Layer your information. The top layer is the just-in-time notice at collection (one sentence). The middle layer is a short summary (what we collect, why, your rights — three paragraphs) linked from signup and checkout pages. The bottom layer is the full privacy policy with every Article 13 and 14 disclosure. Users who want detail can drill down; users who just want to sign up see what they need without overwhelm.
Cover the key Article 13 points even in short notices. At minimum, every collection point should surface: who you are (brand name is enough if privacy policy has full details), what the data is for (specific purpose), and a link to the full privacy policy. Missing any of these three means the user is not informed, period.
Consent-based collection has extra requirements. If the collection is under consent as a lawful basis, the notice must make clear that consent is being sought, what specifically is being consented to, and that consent can be withdrawn at any time. A pre-ticked box with "I agree to the privacy policy" fails all three. An unticked box with "I consent to receiving marketing emails (you can unsubscribe anytime)" passes.
Forms for children need age-appropriate language. If your service is directed at or knowingly used by children, your notices must be in language a child of the target age can understand. The ICO's Age Appropriate Design Code is explicit: notices at collection must be accessible to the youngest user likely to engage with them. For a service used by 12-year-olds, notices should be readable by 10-year-olds.
Indirect collection needs notice too. If you receive data from third parties (buying leads, consuming public records, receiving referrals), you must inform the data subject within one month — or at first direct contact if you reach out to them first. The notice must include where the data came from (Article 14(2)(f)) — "we obtained your details from the public electoral register" or "your details were provided by XYZ Ltd under a data sharing agreement".
Audit your collection points. Make a list of every place you collect personal data: signup forms, contact forms, newsletter subscriptions, event registrations, account settings, surveys, cookies, offline collection (receipts, forms in branches). For each, document what notice the user sees at that moment. Gaps are common — a tucked-away preferences page might not show any notice at all. Fix the gaps.
Tip: The best just-in-time notice you can write is the one the user reads. Test variants: a 20-word notice with icons and plain language will outperform a 100-word legal disclaimer every time.
Tags: gdpr, privacy-notice, just-in-time, transparency, article-13
Tutorial
Detecting a Personal Data Breach
A breach is any security event affecting personal data — and the 72-hour clock starts when you become aware, not when you confirm the damage.
Article 4(12) defines a personal data breach as "a breach of security leading to the accidental or unlawful destruction, loss, alteration, unauthorised disclosure of, or access to, personal data". That is deliberately broad. A laptop stolen from an employee is a breach. A misdirected email containing customer addresses is a breach. A ransomware attack that encrypts your database is a breach (loss of availability). An ex-employee who still has access to the CRM is a breach. A bug that lets User A see User B's order history is a breach.
There are three breach categories: confidentiality (unauthorised disclosure or access), integrity (unauthorised alteration), and availability (loss of access, including temporary loss). Ransomware hits all three. A server outage that makes customer records temporarily inaccessible is an availability breach and still reportable if the outage is significant. The ICO has specifically flagged availability breaches as underreported.
Set up detection capability before you need it. Your systems should generate alerts for: unusual data exports (a user pulling 10x their normal volume), failed authentication spikes, new admin accounts, privilege escalations, data access from unusual IP ranges or countries, and unexpected database schema changes. Without detection, you discover breaches through external notification (customer complaint, journalist, or ICO) — by which point the damage is done and your reputation is worse.
Train staff to report suspected breaches immediately. The single biggest source of breach discovery is staff noticing something wrong — a colleague with suspicious access, an email sent to the wrong list, a USB stick found in the car park. Make reporting frictionless: a single form, a dedicated email address (breach@yourdomain), or a phone line. Reward reporting, never punish it. A staff member who fears blame will hide breaches until they blow up publicly.
The 72-hour clock starts from "awareness". Awareness means a reasonable degree of certainty that a security incident has occurred which has led to personal data being compromised. You do not need to have investigated fully — preliminary confidence is enough. If your monitoring flags 10,000 records accessed by a compromised account, you are aware immediately and the clock is running, even before you know who did it or what they took.
Run tabletop exercises quarterly. Get your incident response team (IT, DPO, comms, legal, exec sponsor) in a room for 90 minutes with a simulated scenario: "At 4pm on Friday, an employee emails to say their laptop was stolen from a pub. It contains unencrypted customer data. What do you do?" Walk through detection, containment, assessment, notification. You will find gaps that no process document can reveal.
Common indicators of a breach: unusual outbound network traffic, database queries from unexpected applications, customer support tickets reporting "I can see someone else's account", sudden spike in password reset requests, unusual file permission changes, processes running as SYSTEM or root that were not installed, cryptocurrency miners, and security tool alerts that get dismissed as false positives. Take every alert seriously on first occurrence.
Know your reporting thresholds. Not every breach requires ICO notification — only those likely to result in risk to rights and freedoms (Article 33). A single misdirected email containing only a first name is low risk and need not be reported (but must be logged, see breach-record tutorial). A database dump containing 50,000 names, emails and passwords is high risk and must be reported. The assessment matters.
Tip: Put "breach@" in your company email contacts and tell staff it is the priority channel. Any breach reported there should ping the DPO and the CISO within minutes, not hours.
Tags: gdpr, breach, incident-response, security, detection, article-33
Tutorial
The 72-Hour Breach Notification to the ICO
Article 33 requires notification to the ICO within 72 hours of becoming aware — even partial information is better than delay.
Article 33(1) requires notification to the ICO "without undue delay and, where feasible, not later than 72 hours after having become aware" of a breach likely to result in risk to rights and freedoms. The 72 hours is the ceiling, not a target. The ICO expects notification as soon as you have preliminary information — typically 12-24 hours is realistic for serious breaches. Waiting until hour 71 to notify signals you are hoping to avoid reporting.
Use the ICO's breach notification form. Go to ico.org.uk/make-a-complaint/report-a-breach or call the helpline on 0303 123 1113 (9am-5pm weekdays). The online form is available 24/7. You need to provide: your organisation details, a description of the breach (nature, categories and approximate numbers of data subjects and records), likely consequences, mitigation measures taken or proposed, and DPO/contact details.
Partial reporting is explicitly allowed. Article 33(4) says information can be provided "in phases without undue further delay". Your initial report can say "approximately 50,000 user records affected, investigation ongoing, will provide further detail within 7 days". Do not wait for a complete picture — the ICO prefers early partial reports over late complete ones. Most breach notifications require at least one follow-up.
If you notify after 72 hours, you must explain the delay (Article 33(1)). A good reason: "Breach occurred on Saturday evening, our forensic team required 36 hours to confirm compromise, initial report sent 30 hours after confirmation." A bad reason: "We wanted to understand the full scope before reporting." The ICO has made clear that "wanting full information" does not excuse delay.
Determine if data subjects need notification too. Article 34 requires you to notify affected users directly if the breach is "likely to result in a high risk" to their rights. This is a higher threshold than the ICO notification. Stolen unencrypted passwords: notify users. Accidental disclosure of a public-already email address: probably not. Notify users "without undue delay" — typically within a few days of confirming severity.
User notification must explain in clear and plain language: the nature of the breach, the DPO contact, likely consequences, and measures taken or proposed to mitigate. Do not bury the notice in legalese. Do not send it from no-reply@ with subject "Important update". The ICO has criticised organisations that make breach notifications look like marketing emails. Use a clear subject line like "Security incident affecting your account — action required".
You can omit user notification if: the data was encrypted (and the key remains secure), you have taken measures that make high risk unlikely, or direct notification would involve disproportionate effort (in which case public notification via press release or website notice is required). Document the reason for any decision not to notify users — the ICO can override your judgment and require you to notify.
After the immediate response, conduct a post-incident review within 30 days. What did we miss? How did the attacker get in, or what human error caused the disclosure? What controls do we need to add? Update your ROPA, your DPIA (if relevant), your security measures, and your breach response plan. Many organisations are fined not for the breach itself but for failing to learn from it — repeat breaches carry much higher penalties.
Tip: Pre-fill as much of the ICO notification form as you can in advance. Save a template with your organisation details, DPO info, and standard text about your response process. In a real breach you will save critical hours.
Tags: gdpr, breach-notification, ico, article-33, 72-hours, incident-response
Tutorial
The Breach Register: Documenting Every Incident
You must record every breach, including those that do not meet the ICO notification threshold — the log itself is a compliance requirement.
Article 33(5) requires controllers to "document any personal data breaches, comprising the facts relating to the personal data breach, its effects and the remedial action taken". Every breach must be logged, even those below the ICO notification threshold. This is not optional — the ICO will ask to see your breach register during any audit or investigation.
Log the following for every incident: unique reference number, date and time of breach occurrence (if known), date and time of discovery, breach category (confidentiality / integrity / availability), cause (hack, insider, accident, system failure), data categories affected, approximate number of data subjects, approximate number of records, containment actions, mitigation actions, risk assessment and justification, whether ICO was notified and when, whether data subjects were notified and when, root cause analysis, lessons learned.
Include near misses and low-risk breaches. A CV attached to the wrong email internally, a customer support agent who saw another customer's address by mistake, a server misconfiguration caught before exploitation — all get logged. These entries demonstrate mature security culture and accurate detection capability. An empty breach register is a red flag to the ICO, not a clean bill of health.
Use a structured format. A spreadsheet works for smaller organisations. Larger organisations typically use a dedicated incident management platform (ServiceNow, Jira Service Management, specialised tools like OneTrust Incident Response). The key is that every entry has the same fields, is timestamped, and has immutable history. Freeform Word documents degrade quickly under real incident pressure.
Retention is typically 6 years from the incident close. There is no specific retention period in GDPR for breach records, but the UK Limitation Act 1980 means legal claims can arise up to 6 years later. ICO guidance also suggests retention for at least the period during which a breach might be relevant to regulatory or civil proceedings. Some organisations retain breach records indefinitely as pattern analysis evidence.
The breach register supports your Article 5(2) accountability obligation. If the ICO questions why you did not report a specific incident, your register must show you considered it and applied a defensible risk assessment. "We decided not to report breach #2026-47 because data was encrypted at rest, the device was recovered within 4 hours, and forensic analysis confirmed no unauthorised access" is a complete record. "No reportable breaches this year" is not.
Run periodic trend analysis. Every quarter, review your breach register for patterns: are most incidents caused by the same team, tool, or process? Are similar root causes recurring? Are response times improving or worsening? This analysis itself is evidence of your accountability obligations being taken seriously — and it should drive security investment priorities.
Test your register with a mock ICO request. Pick a random breach from 8 months ago and ask your DPO: can you produce the full record within 2 hours? Is every field filled? Does the narrative tell a coherent story? If the answer is no, fix the process before a real ICO inquiry. The British Airways fine of £20m in 2020 was reduced from £183m partly because their incident documentation was comprehensive and honest.
Tip: Assign a unique reference number to every breach the moment it is reported — even a hunch or a "probably nothing". You can close it as "not a breach" if investigation confirms, but you cannot retrospectively create a reference trail that the ICO will trust.
Tags: gdpr, breach-register, documentation, article-33, incident-log
Tutorial
What Is a Data Subject Access Request (DSAR)?
A DSAR is any request from a user for a copy of their personal data — and it does not have to mention GDPR, rights, or access to count.
A DSAR is the formal name for a request made under Article 15 of UK GDPR — the right of access. Users can ask you to confirm whether you hold their personal data and, if so, to provide a copy plus supplementary information about how you use it. The request does not have to use specific words. "Send me everything you have on me", "what info is in my account", "I want all my data", even "can you delete my file?" (which often implies asking what is in the file) all count as DSARs.
The response must include more than just the data. Article 15(1) requires you to provide, alongside the personal data itself: the purposes of processing, categories of data held, recipients or categories of recipients (especially any in third countries), retention period or how it is determined, existence of the user's rights (rectification, erasure, restriction, objection), the right to complain to the ICO, the source of data if not collected from the user, and information about any automated decision-making.
The scope of "personal data" is wide. It includes anything that identifies the user directly (name, email, account ID) or indirectly (IP address, cookie ID, device fingerprint). It also includes opinions about the user in emails, meeting notes, performance reviews, and customer service notes. It includes CCTV footage where the user is recognisable. It includes inferences drawn from behaviour (e.g. "this user is likely interested in category X"). Most organisations significantly under-scope their DSAR responses.
DSARs can be made by anyone to anyone. An ex-employee can DSAR their former employer. A customer can DSAR a company they bought from five years ago. A job applicant who was rejected can DSAR the recruiter. There is no time limit on when a DSAR can be made — as long as you still hold the data, you must respond. This is one reason retention periods matter: data kept beyond necessity is still DSAR-able.
Third-party DSARs are permitted but need caution. A solicitor can submit a DSAR on behalf of a client if they provide signed authority. A parent can submit for a young child. A power of attorney holder can submit for an incapacitated adult. But you must verify the relationship — do not just take claims on trust. If there is doubt about authority, ask for evidence before responding. The ICO has warned about organisations disclosing data to fraudsters claiming to represent users.
Employee DSARs are often the most complex. They can cover emails mentioning the employee, performance reviews, disciplinary records, absence records, 360-degree feedback, CCTV from the workplace, access card logs, and instant messages. Consider specific exemptions (management forecasts, negotiations, references) but apply them narrowly. The ICO has fined employers and named them publicly for over-redacting employee DSARs.
Third-party personal data in the response must be handled carefully. If the user's file contains emails where other people are discussed, you cannot release that third-party data without their consent or another justification. Redact third-party names, addresses, opinions. But be careful — over-redaction that makes the response meaningless is itself a breach. The balance is specific to each document and must be documented.
Repeat or manifestly unfounded DSARs can be refused or charged (Article 12(5)) but the bar is high. "Manifestly unfounded" means the request is clearly made in bad faith — using DSAR as harassment, or with no genuine purpose. "Excessive" means repetitive or overlapping with previous requests. The ICO will scrutinise any refusal. Document your reasoning thoroughly before refusing any DSAR.
Tip: Train your customer service team to recognise DSARs in any form. Phrases like "can I have my data?", "what do you know about me?", "send me my account info" should all trigger escalation to the DPO — not be handled as general enquiries.
Tags: gdpr, dsar, subject-access, article-15, user-rights
Tutorial
How to Respond to a DSAR End-to-End
A structured DSAR response process covers verification, search, redaction, exemption analysis, packaging, and delivery within one month.
Log the request the moment it arrives. Capture: date received, user identifier, request wording (copy-paste their exact words), channel, your reference number. Start the clock — one calendar month from the date of receipt. Do not assume the user wants what you think they want; respond to what they actually asked. Forward to the DPO within 24 hours maximum.
Verify identity proportionately. For logged-in users, their authenticated session is usually sufficient. For email requests, send a confirmation email to the address on file and proceed when they reply. Only ask for passport or photo ID when there is genuine doubt about identity — demanding ID routinely is itself an ICO complaint. The ICO has explicit guidance that verification must not be used as a stalling tactic or deterrent.
Scope the request. If the user asked for "everything", that means everything — customer records, emails mentioning them, support tickets, CCTV if held, call recordings, log files, backup snapshots you can reasonably access. If they asked for something specific ("my messages from Q3 2025"), stick to that scope. Article 12(3) allows you to ask for clarification only where genuinely needed — not to narrow your obligations.
Run a comprehensive search. Personal data lives in more systems than most teams realise. Use your ROPA as a checklist. Typical search locations: primary database, email archives (Outlook, Gmail), messaging platforms (Slack, Teams), CRM (Salesforce, HubSpot), support platform (Zendesk, Intercom), HR system, file shares, backup systems, marketing platforms, analytics tools. Document which systems you searched — gaps will come up in ICO investigations.
Review for exemptions and third-party data. For each document or record found, consider: does it contain third-party personal data that should be redacted? Does any specific exemption apply (legal professional privilege, negotiations, references, crime prevention)? The default should be disclosure — apply exemptions narrowly and only where the statutory test is met. Document every redaction decision with reasoning.
Package the response in a usable format. Export data in a commonly used format — PDF or CSV for tabular data, plain text or PDF for documents. Do not deliberately obscure with bad formatting. If there is a lot of data, organise it by system or by category with a table of contents. For very large responses, a secure download link that expires after 14 days is acceptable — do not email 500MB of attachments.
Include all Article 15 supplementary information. In addition to the data, your covering letter or response document must include: purposes of processing, categories of data, recipients, retention periods, the user's rights (including the right to complain to the ICO), source of data if not from the user, and existence of automated decision-making. A template covering letter saves time and ensures consistency.
Deliver within one month, track delivery, close the record. Send the response via a channel that provides evidence of delivery (tracked post, email with read receipt, secure download with access logs). Update your DSAR register with date of response, what was provided, any exemptions used, and any follow-up questions from the user. Keep the full record for at least 2 years — longer if under legal hold or in dispute.
Tip: Build a DSAR playbook with step-by-step instructions for each system your team needs to search. The first DSAR is a scramble; the hundredth should be routine. Organisations with mature DSAR processes can respond in 2-3 days, not 30.
Tags: gdpr, dsar, response-process, article-15, compliance-procedures
Tutorial
When You Can Refuse or Charge for a DSAR
DSARs are free by default, but you can charge or refuse for manifestly unfounded or excessive requests — with strict limits and documented reasoning.
The default rule: DSARs are free. Article 12(5) makes this clear — you cannot charge a fee for responding to a DSAR, nor can you make users jump through hoops. Any attempt to deter legitimate requests through friction (lengthy forms, in-person verification, complicated procedures) is itself a compliance breach. The ICO has criticised organisations that implement "DSAR gates" that appear designed to discourage use.
You can charge a "reasonable fee" in two narrow circumstances: the request is manifestly unfounded or excessive (Article 12(5)(a)), or the user asks for additional copies of data already provided (Article 15(3)). The fee must reflect actual administrative cost — not a deterrent amount. "Reasonable" has not been formally defined but the ICO suggests considering the cost of staff time, copying, postage, and any specialist expertise required.
"Manifestly unfounded" is a high bar. The request must be clearly lacking any legitimate purpose — typically made in bad faith, as harassment, or with no intention of exercising the right. An angry ex-customer who submits 12 DSARs in a month demanding to see "every single email mentioning my name forever" might qualify. A new customer asking once for their data clearly does not. The ICO has emphasised that disliking the user or their tone is not grounds for "manifestly unfounded".
"Excessive" usually means repetitive. If a user made the same request three months ago and their data has not meaningfully changed, a follow-up request is likely excessive. If a user makes a broad DSAR and then, a week later, asks for the same data in a different format without good reason, that second request may be excessive. The word "excessive" does not mean "large" — a first-time DSAR from a 10-year customer with lots of data is not excessive, it is normal.
Refusal must be justified in writing. Article 12(5) requires you to inform the user of: the reason for not taking action, the possibility of lodging a complaint with the ICO, and the possibility of seeking judicial remedy. You must do this within one month, same timeline as a normal response. "We are refusing your request" is not enough — explain the specific grounds with reference to the statutory test.
Exemptions in the Data Protection Act 2018 can allow narrow refusal or redaction. Schedule 2 contains exemptions for: prevention of crime, judicial appointments, immigration, legal professional privilege, management forecasts, negotiations, references for employment or education, confidential references given in confidence, and others. Each exemption has specific conditions — do not apply them generically. The ICO has stated that exemptions should be applied on a "document by document" basis where practical.
If you apply an exemption, be transparent. Tell the user which exemption you are relying on and why. "We have withheld internal emails discussing your redundancy settlement negotiation under paragraph 22 of Schedule 2 of the Data Protection Act 2018" is specific and defensible. "Some information has been withheld" is not. Users have the right to challenge your exemption decisions with the ICO, and vague refusals are almost always overturned.
Document your decision-making. For every refusal, charge, or significant redaction, record: date of decision, who made it, grounds relied on, evidence considered, and alternative actions considered (e.g. could you have partially responded?). If the ICO reviews your DSAR handling, the quality of your decision documentation is often the difference between "no further action" and a reprimand or fine.
Tip: If your instinct is "this DSAR is annoying, I want to refuse", you almost certainly cannot. The legal test is about the request itself, not how inconvenient it is for you to respond. Respond fully unless the request genuinely meets the Article 12(5) test.
Tags: gdpr, dsar, exemptions, article-12, fees, refusal
Tutorial
When You Need a Data Protection Impact Assessment (DPIA)
A DPIA is mandatory for high-risk processing — and the ICO has a published list of activities that always require one.
Article 35(1) requires a DPIA before any processing that is "likely to result in a high risk to the rights and freedoms of natural persons". The assessment must happen before processing starts — retrofitting a DPIA after you have deployed a system is not compliance, it is documentation. The point is to identify and mitigate risks before they materialise.
Three scenarios always require a DPIA under Article 35(3): systematic and extensive evaluation based on automated processing including profiling that produces legal or similarly significant effects; processing special category or criminal offence data on a large scale; and systematic monitoring of publicly accessible areas on a large scale. If any of these apply, DPIA is mandatory — no further analysis needed.
The ICO publishes its own list of processing that always requires a DPIA. The current list includes: innovative use of technology (AI, biometrics, connected devices); denial of service (credit scoring, fraud detection); large-scale profiling; biometric data processing; genetic data; tracking location or behaviour; targeting of children or vulnerable individuals; risk of physical harm; processing personal data without providing a privacy notice. You can find the full list at ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/accountability-and-governance/data-protection-impact-assessments-dpias.
Beyond the mandatory list, use the European Data Protection Board's nine criteria to decide. A DPIA is generally required if your processing meets two or more of: evaluation/scoring, automated decision-making with legal effects, systematic monitoring, sensitive data, data processed on a large scale, matching or combining datasets, data concerning vulnerable subjects, innovative use of technology, processing that prevents users from exercising rights or using a service. One criterion plus significant risk may also trigger DPIA.
Common projects that need a DPIA: launching a customer analytics platform, deploying CCTV in a new location, implementing automated hiring screening, building a user scoring or segmentation system, using new AI tools for content generation or moderation, processing health data for a new service, rolling out employee monitoring software, introducing facial recognition anywhere. When in doubt, run the assessment — it is evidence of compliance even if the outcome is "low risk".
A pre-assessment screening questionnaire saves time. Build a 10-question checklist that covers the ICO criteria: are you using special category data? Is the processing at scale? Is it innovative? If any answer suggests risk, trigger a full DPIA. If all answers are clearly low-risk, document the screening and move on. This two-tier approach catches high-risk projects without burdening simple ones.
Consult the ICO for residual high risks. Article 36 requires prior consultation with the ICO if your DPIA concludes that residual risk remains high after mitigations. Few organisations trigger this, but when you do, the ICO has up to 14 weeks to respond. Factor this into project timelines. Common prior consultation scenarios: deploying facial recognition in public spaces, large-scale health data processing, cross-border data sharing for novel purposes.
DPIAs must be reviewed when processing changes. A DPIA is not a one-off document — if you change the processing (new purpose, new data categories, new technology), the DPIA must be updated. Establish a cadence: annual review minimum, immediate review on any material change. The ICO has fined organisations specifically for failing to update DPIAs when systems evolved.
Tip: If your team is debating whether processing needs a DPIA, that debate itself is a signal: run it. A completed DPIA that concludes "low risk" costs you a day of work. A missing DPIA on high-risk processing costs millions.
Tags: gdpr, dpia, article-35, risk-assessment, high-risk-processing
Tutorial
How to Conduct a DPIA Step by Step
A proper DPIA describes the processing, assesses necessity and proportionality, identifies risks, and documents mitigations.
Article 35(7) defines the minimum DPIA content: a systematic description of the processing, an assessment of necessity and proportionality, an assessment of risks to rights and freedoms, and the measures to address those risks. Use the ICO's published DPIA template as a starting point — it covers all four requirements in a structured format suitable for most projects.
Start with a clear description. Document what personal data you will process, whose data, for what purposes, how the data flows (collection, storage, use, sharing, deletion), which systems and third parties are involved, how long you will retain it, and the volume. A data flow diagram is extraordinarily helpful here — a picture of the lifecycle catches issues that narrative text hides.
Assess necessity and proportionality. Necessity: is this processing genuinely needed for the stated purpose, or could you achieve the same outcome with less data? Proportionality: is the intrusion proportionate to the benefit? These are separate tests. Processing may be necessary for a purpose but still disproportionate (e.g. collecting full medical history when only blood type is needed). Document your reasoning for both.
Identify risks to individuals. Brainstorm everything that could go wrong: data breach exposing information, inaccurate data leading to wrong decisions, discrimination from biased algorithms, unnecessary tracking causing distress, inability to exercise rights, loss of control over personal data, financial loss, reputational damage, physical harm. For each risk, assess likelihood (low/medium/high) and severity (minimal/significant/severe).
Design mitigations for each risk. Common mitigations: minimisation (collect less data), pseudonymisation or anonymisation, encryption at rest and in transit, access controls, staff training, contractual safeguards with processors, automated deletion, clear privacy notices, user controls (opt-outs, preferences). For each mitigation, document what it addresses and who owns implementation. Track residual risk after mitigation.
Consult stakeholders. Article 35(9) requires you to seek the views of data subjects "where appropriate". For significant projects, this might mean user research, focus groups, or a public consultation. For smaller projects, your customer-facing team's insights often substitute. Also consult: your DPO, legal counsel, information security, and any processors who will handle the data. Document who was consulted and what feedback you incorporated.
Sign-off and publish. The DPIA should be signed off by a decision-maker with authority to approve or halt the project — typically the product owner, a senior executive, or the DPO. Some organisations publish redacted DPIAs voluntarily as a transparency measure. The ICO does not require publication but praises organisations that do. At minimum, the DPIA must be available to the ICO on request and to internal auditors.
Review periodically and on change. A DPIA is a living document. Set a review cadence (annual minimum) and specific triggers: new data categories, new purposes, new processors, major system changes, significant incidents. Every review should update the risk assessment and mitigation status. Close out mitigations as they are implemented and note any that were descoped or deferred — and why.
Tip: Do not write DPIAs in legal prose. Use plain language, structured sections, and diagrams. A DPIA that project managers and engineers can actually read is a DPIA that gets followed; a 40-page legal document gets filed and ignored.
Tags: gdpr, dpia, article-35, risk-assessment, methodology
Tutorial
Do You Need a Data Protection Officer (DPO)?
A DPO is mandatory in three specific scenarios — outside those, appointing one voluntarily is a valid choice but not required.
Article 37(1) mandates a DPO in three scenarios: processing is carried out by a public authority or body (except courts acting judicially); your core activities consist of regular and systematic monitoring of data subjects on a large scale; or your core activities consist of large-scale processing of special category data or criminal conviction data. If any apply, DPO is not optional.
"Core activities" means activities essential to your main business, not administrative or support functions. A hospital's core activity is patient care — processing health data is part of that, so DPO required. A manufacturing firm's core activity is making widgets — HR data processing is administrative, not core, so DPO may not be required. The test is whether the processing is inextricable from what the organisation does.
"Large scale" is not precisely defined but the European Data Protection Board factors it in four ways: number of data subjects (absolute or as proportion of relevant population), volume and range of data, duration of processing, and geographical scope. A regional hospital processes data on a "large scale" even if smaller than a national bank. A small consultancy processing a few hundred employee records probably is not large scale.
"Regular and systematic monitoring" includes online tracking, profiling, geolocation, analytics, data-driven marketing, CCTV, behavioural advertising, connected devices (IoT), and loyalty programmes. Many digital businesses trigger DPO requirements here without realising it — running a typical e-commerce site with behavioural tracking and segmentation on a customer base above a few thousand users likely qualifies.
Voluntary DPO appointment is common and wise. Even if not mandatory, many mid-sized organisations appoint a DPO (or equivalent privacy lead) to centralise compliance responsibility, provide internal expertise, and signal seriousness to the ICO and customers. Once appointed — voluntarily or mandatorily — the DPO must meet all Article 38 requirements: independence, resources, access to top management, protection from dismissal for doing the role.
The DPO can be an employee or an external contractor. Article 37(6) allows either. External DPOs (fractional, often via specialist firms) suit organisations too small for a full-time role but too complex to absorb the work in an existing role. Cost typically £500-£3,000 per month for SME fractional DPOs in the UK. The DPO must be easily reachable for data subjects and the ICO regardless of their employment model.
Expertise requirements matter. Article 37(5) requires DPOs to have "expert knowledge of data protection law and practices". There is no mandatory qualification but common credentials include: CIPP/E (IAPP), Practitioner Certificate in Data Protection (PC.dp) from PDP Training, CIPT for technical DPOs. The DPO should be able to engage with both legal text and technical architecture — a generalist lawyer or compliance officer without GDPR specialism is unlikely to meet the bar.
Conflict of interest disqualifies many internal candidates. The DPO cannot hold a role that determines purposes and means of processing — so CEO, CIO, head of marketing, head of HR are all conflicted and cannot also be DPO. A dedicated compliance role, head of risk, or general counsel (if not setting processing purposes) can usually serve. A small-company IT manager cannot be DPO over systems they administer.
Tip: If you are uncertain whether you need a mandatory DPO, appoint one voluntarily. The extra overhead is modest compared to the regulatory risk of wrongly concluding you do not need one.
Tags: gdpr, dpo, article-37, governance, compliance-roles
Tutorial
DPO Responsibilities and How to Work With One
The DPO advises, monitors, trains and liaises with the ICO — they are not responsible for compliance decisions, but for the advice behind them.
Article 39 defines the DPO's minimum tasks: informing and advising the controller and employees of their obligations; monitoring compliance with UK GDPR and internal policies; providing advice on DPIAs and monitoring their performance; cooperating with the ICO; and acting as the contact point for the ICO and data subjects. Note what is missing: the DPO does not "own" compliance. The controller remains responsible.
The DPO advises, management decides. If the DPO advises against deploying a new analytics tool and management proceeds anyway, the controller is liable for the decision — but the DPO has discharged their obligation. This distinction protects DPOs from being scapegoated for executive choices. Document advice given in writing; if it is ignored, that is the organisation's risk to carry, not the DPO's.
Independence is legally protected. Article 38(3) forbids dismissing or penalising the DPO for performing their tasks. An organisation cannot fire the DPO for flagging a major compliance gap or for advising against a profitable-but-risky project. The DPO reports directly to the highest management level — typically CEO or a board committee — bypassing middle management who might have conflicting incentives.
Provide adequate resources. Article 38(2) requires sufficient resources for the DPO to perform their tasks: budget, staff, access to systems, training. A part-time DPO with no budget and no assistant cannot effectively monitor compliance across a 500-person organisation. The ICO has criticised organisations for appointing DPOs without giving them the means to operate. Treat the DPO as a genuinely resourced function, not a compliance tickbox.
DPOs need information access. They must be able to review any processing activity, audit any system, interview any employee, and observe any decision affecting personal data. Build this access into your operating model: standing invitation to product planning meetings, read access to all data-related systems, automatic cc on contracts involving data processing, visibility of security incidents. Access denied is Article 38 breach.
Publish the DPO contact details. Article 37(7) requires you to publish the DPO's contact information and communicate it to the ICO. Include a specific email address (dpo@yourdomain is standard) and optionally a postal address. Do not make users chase or route through general support. Data subjects have a right to contact the DPO directly about any issue concerning their personal data.
Train the organisation. Core DPO task: educate employees on their obligations. This means induction training for new joiners, refresher training annually, role-specific training for teams handling high volumes of data (customer service, HR, marketing), and targeted training after incidents or regulatory updates. Training records are evidence of Article 24 accountability — keep them.
Maintain the DPO-ICO relationship. When things go wrong (breaches, complaints), the DPO is the single point of contact with the ICO. A DPO who has never engaged with the ICO is at a disadvantage in a real incident. Attend ICO events, respond promptly to ICO queries, and treat the relationship as professional rather than adversarial. Organisations that cooperate constructively with the ICO routinely receive lighter penalties than those that stonewall.
Tip: Hold a monthly 30-minute DPO-to-CEO check-in. It creates a formal record of top-level awareness of data protection matters, which becomes evidence of governance if the ICO ever investigates. And it usually catches issues early.
Tags: gdpr, dpo, article-39, responsibilities, governance
Tutorial
UK–EU Data Transfers After Brexit
The EU–UK adequacy decision allows free flow of data between the UK and EEA — but it is not permanent and comes with conditions.
In June 2021 the European Commission adopted two adequacy decisions for the UK — one under GDPR and one under the Law Enforcement Directive. These decisions allow personal data to flow from the EEA to the UK without additional safeguards because the UK has been assessed as providing an "essentially equivalent" level of data protection. This removes the need for Standard Contractual Clauses (SCCs) when EEA controllers send data to the UK.
The decision is time-limited. The original adequacy decision was due to expire in June 2025. In December 2024 the European Commission confirmed its intent to renew, and the renewal was formally adopted — but adequacy decisions must be reviewed every four years and can be suspended at any time if UK law diverges from EU standards. Organisations relying on adequacy need to monitor developments and have a Plan B.
In the reverse direction (UK to EEA), the UK government treats all EEA countries as adequate. The Data Protection Act 2018 section 17A established transitional "adequacy regulations" for the EEA that remain in force. So data transfers from UK controllers to EEA recipients can happen freely under UK GDPR Article 45. This is the simpler direction in most practical scenarios.
Check the onward transfer chain. If you send data from the UK to an EEA processor who then forwards it to the US, the US transfer still needs a separate safeguard — adequacy does not cascade. Review processor locations in your ROPA and map the full data flow. A UK customer's data sent to a Dutch marketing agency who uses Salesforce (US-hosted) requires UK-US transfer safeguards at the final hop.
Personal data can still flow under the pre-Brexit arrangement for non-commercial purposes (for example, law enforcement cooperation) under separate legal frameworks. Commercial data flows rely on the GDPR/UK GDPR adequacy decisions. Do not conflate the two; the Law Enforcement Directive adequacy covers policing data, not customer databases.
If adequacy lapses (expires or is withdrawn), you need alternative safeguards immediately. The most common are SCCs under Article 46, or Binding Corporate Rules (BCRs) for intra-group transfers. Keep your SCC templates ready in the document management system — do not wait for a regulatory crisis to start drafting them. Many UK-EU organisations have dormant SCCs signed and ready as a fallback.
The UK Government published the International Data Transfer Agreement (IDTA) and UK Addendum to the EU SCCs in 2022. These are the UK-specific replacements for the old EU SCCs. If you need UK-outward transfer safeguards (beyond EEA adequacy countries), the IDTA is the primary tool. It is free to use, published by the ICO, and can be executed via signature like any contract.
Keep transfer records in your ROPA. Article 30 requires you to document "transfers of personal data to a third country or an international organisation" including the identification of the country and documentation of suitable safeguards. A simple column in your ROPA spreadsheet noting "EEA — adequacy regulations" or "US — IDTA" is sufficient. Regulators will ask for this during audits.
Tip: Monitor the ICO's international transfers page at ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/international-transfers. It tracks adequacy status changes, new IDTA versions, and emerging risks. Subscribe to the ICO newsletter for alerts.
Tags: gdpr, international-transfers, uk-eu, adequacy, brexit
Tutorial
Transfers to Third Countries Outside Adequacy
For transfers to countries without adequacy, you need Article 46 safeguards plus a transfer impact assessment — and the Schrems II ruling raised the bar significantly.
Chapter V of UK GDPR governs transfers to third countries. A third country is any country outside the UK and the EEA. For data to flow there legally you need one of: an adequacy regulation (Article 45), appropriate safeguards (Article 46 — mainly SCCs, IDTA, or BCRs), or a specific derogation (Article 49). Most commercial transfers rely on Article 46 safeguards. Without one of these, the transfer is unlawful.
Current UK adequacy regulations as of April 2026 cover: the EEA (all member states plus Iceland, Liechtenstein, Norway), Gibraltar, Andorra, Argentina, Canada (commercial organisations under PIPEDA), Faroe Islands, Guernsey, Isle of Man, Israel, Jersey, New Zealand, Republic of Korea, Switzerland, Uruguay, and the US (under the UK Extension to the Data Privacy Framework, for certified organisations only). The ICO publishes the current list.
The US situation is unusual. The UK Extension to the EU-US Data Privacy Framework (DPF) provides adequacy — but only for US companies that are DPF-certified. Check the DPF list at dataprivacyframework.gov before relying on adequacy for a specific vendor. If they are not listed, you need alternative safeguards (typically the IDTA or the EU SCCs with UK Addendum).
Schrems II (CJEU, July 2020) invalidated the EU-US Privacy Shield and established that SCCs alone are not enough where the destination country has surveillance laws that exceed EU/UK privacy standards. You must also conduct a Transfer Impact Assessment (TIA) and potentially implement supplementary measures. This ruling remains binding in the UK despite Brexit; the ICO requires TIAs for post-Schrems-II transfers.
A TIA must cover: the nature of the data, the lawful basis for transfer, the destination country's legal regime (especially government access powers), the specific recipient, the technical and contractual safeguards in place, and the likelihood of harmful access. Document the conclusion: transfer can proceed, transfer can proceed with additional safeguards, or transfer must be suspended. Free TIA templates are available from the ICO, IAPP, and EDPB.
Supplementary measures commonly used: end-to-end encryption with keys held in the UK/EEA, pseudonymisation at source, data minimisation, contractual commitments to challenge government requests, transparency reports from the vendor, policies to notify the data exporter of any access requests. The EDPB's "Recommendations 01/2020" lists technical, contractual, and organisational measures — use them as a checklist.
Derogations under Article 49 are narrow. Examples: explicit consent for the transfer (after being informed of the risks), necessary for a contract with the data subject, public interest, legal claims, vital interests. These are "occasional" and "non-repetitive" exceptions — you cannot build your SaaS architecture around them. The ICO has specifically warned against using derogations as a workaround for bulk transfers.
Document every third-country transfer in your ROPA. For each transfer record: data categories, destination country, recipient, Article 46 safeguard used, date of TIA (if applicable), and review date. During an investigation the ICO will request this and gaps cost you. For complex multi-hop transfers (UK → EU processor → US sub-processor), document each hop separately with its own safeguard.
Tip: Before you sign any contract with a non-UK/non-EEA vendor, require them to complete your TIA questionnaire. If they refuse or cannot answer basic questions about government access requests, that alone is usually disqualifying.
Tags: gdpr, international-transfers, schrems-ii, third-countries, transfer-impact
Tutorial
Standard Contractual Clauses, IDTA, and Adequacy Explained
SCCs, the IDTA, BCRs and adequacy are the main tools for lawful international transfers — each has specific uses, triggers, and maintenance obligations.
Adequacy decisions (Article 45) are government-to-government determinations that a third country provides equivalent data protection. When adequacy is in place, transfers flow without additional safeguards — treat the third country as if it were part of the UK for transfer purposes. The UK maintains its own adequacy regulations; they do not automatically match EU adequacy decisions (they mostly overlap, but not always).
Standard Contractual Clauses (SCCs) are pre-approved contract templates that impose GDPR-level protections on the recipient. The EU Commission's 2021 SCCs are a modular set covering four transfer scenarios: controller-to-controller, controller-to-processor, processor-to-processor, and processor-to-controller. You pick the relevant modules, complete the annexes (data categories, recipients, technical measures), and sign. Free to use.
The UK International Data Transfer Agreement (IDTA) is the UK equivalent to EU SCCs, published by the ICO in March 2022. It is shorter and more practical than the EU SCCs, covering any transfer scenario in a single document. The UK Addendum to the EU SCCs is an alternative — if you already have EU SCCs signed with a vendor, adding the UK Addendum makes them work for UK transfers too. Use whichever is simpler.
Binding Corporate Rules (BCRs) are for intra-group transfers within a multinational corporate group. They require ICO approval (a lengthy process, typically 12-24 months) but once in place they cover all transfers between group entities globally. BCRs are mostly used by very large organisations — the approval overhead rarely makes sense for under 5,000 employees. If you are considering BCRs, plan for a two-year project.
SCCs require annexes, which many organisations forget. The clauses themselves are just a few pages, but the annexes are where the substance lives: annex I lists the parties, annex II describes the transfer (categories of data subjects, data, purposes, retention), annex III specifies technical and organisational measures, annex IV lists any sub-processors. An SCC signed with empty annexes is not valid. Every annex must be filled.
SCCs and IDTA both require regular review. As processors change sub-processors, as your data processing evolves, as the regulatory landscape shifts, you need to update. Best practice: annual review of all SCCs and IDTAs, with triggers for immediate update on any material change. Keep a register of executed transfer agreements with renewal dates and version tracking.
The IDTA has a specific structure that differs from SCCs. It uses "tables" rather than annexes. Table 1 covers the parties, Table 2 the data and purposes, Table 3 security arrangements, Table 4 additional protections. The ICO provides a free editable template — download from ico.org.uk and complete in Word. Do not improvise clauses; deviations from the template create legal uncertainty.
When a safeguard is broken (vendor changes country, sub-processor added without notice, breach affects transferred data), you may need to suspend the transfer. Article 46 safeguards are only valid while the actual conditions they describe hold true. Build contract clauses that require prompt notification of any change, and have a documented suspension procedure ready. Cutting data flow is painful but sometimes the only compliant option.
Tip: For most UK organisations working with international vendors, the UK Addendum to the EU SCCs is the least-friction option. Vendors commonly already have EU SCCs signed; adding a short UK Addendum takes minutes instead of weeks.
Tags: gdpr, sccs, idta, bcr, adequacy, international-transfers
Tutorial
Age of Consent for Online Services (UK GDPR Section 9)
In the UK, children aged 13 and over can consent to online services themselves — below 13, you need verifiable parental consent.
Article 8 of UK GDPR sets the default age of consent for information society services at 16, but allows member states to lower it. The UK has set the age at 13 via section 9 of the Data Protection Act 2018. So for UK users, children aged 13 and over can consent to online services (social media, gaming, messaging, most apps) without parental involvement. Below 13, verifiable parental consent is required under Article 8(2).
The age limit applies only to consent-based processing in "information society services". If you rely on another lawful basis — legitimate interests, contract, legal obligation — the Article 8 age rule does not directly apply. But the ICO's Age Appropriate Design Code (AADC) imposes separate child-protection obligations that apply regardless of lawful basis for any service "likely to be accessed by children". Both regimes apply in parallel.
Age estimation must be proportionate to risk. You do not need full verification for every user — a simple age gate (date of birth entry) is often acceptable for low-risk services. High-risk processing (profiling, advertising targeting, data sharing) needs stronger verification: email confirmation with parent, capacity-based checks, age estimation using AI, or third-party verification services like Yoti. The AADC has detailed guidance on matching verification to risk.
If your service is not intended for under-18s but children access it anyway, you are still covered by the AADC. "Directed at children" is not the only test. The AADC applies to services "likely to be accessed by children" — which includes most general-purpose digital services (social media, video platforms, shopping sites with any youth appeal). Assume children are present unless you have robust age verification that excludes them.
Privacy settings for children should default to high. The AADC requires defaults that are maximum-privacy: geolocation off, profiling off, public sharing disabled, friend requests from strangers off. If a child-user wants to change these, they can — but the default must protect. Meta, TikTok and others have been pressured into these defaults by the ICO specifically under the AADC.
Marketing to children needs extra care. Behavioural profiling for marketing is explicitly flagged as a high-risk activity for children under the AADC. Default position: do not profile children for marketing. Even with consent (which a 13-year-old can give), the ICO expects you to question whether the processing is in the child's best interests. "Best interests of the child" is a guiding principle throughout the AADC.
Age self-declaration has known failures. Children routinely enter fake ages to access services — this is well-documented. If your service has features that should be age-gated, you cannot rely solely on self-declaration for compliance. The ICO has investigated services where age gates were trivially bypassed and found them non-compliant. Use age assurance techniques proportional to the risk of harm.
Document your age-assurance approach in a DPIA. The AADC requires a DPIA for any service likely to be accessed by children. The DPIA should describe your age assurance method, the basis for choosing it, the risks it mitigates, and residual risks. Keep it updated as your service changes. The ICO has used DPIA quality as a key indicator of AADC compliance in its 2023-2024 audits of social media and gaming platforms.
Tip: Read the ICO's Age Appropriate Design Code in full before designing any service that children might use. It runs to 15 standards and is the most detailed regulatory guidance on children's data in the world.
Tags: gdpr, children, age-of-consent, age-13, online-services
Tutorial
Getting Verifiable Parental Consent for Under-13s
Below 13, you need "verifiable" parental consent — and "verifiable" is a practical test, not a box to tick.
Article 8(2) of UK GDPR requires controllers to make "reasonable efforts to verify" that consent is given or authorised by the holder of parental responsibility. The ICO has been explicit that a simple "I am a parent" checkbox is not verification. What is required depends on the nature and risks of the processing — stronger verification for higher-risk activities.
Low-risk verification methods: email to a verified parent address with a reply-to-confirm, SMS with confirmation code, submission of a parent's credit card details (small charge refunded, proves adult control), postal confirmation with hand-signed form. These work for services where processing is modest and risks low — say, a basic educational platform with no profiling.
Higher-risk verification methods: upload of ID documents (passport, driving licence), video verification with liveness checks, third-party verification services (Yoti, Verifymy, Persona), knowledge-based authentication using data only a real parent would know. Use these for services involving significant data collection, profiling, advertising, or sharing with third parties.
The verification relates to consent for a specific processing activity. If you later want to add new processing — for example, extending a basic account to include behavioural advertising — you need fresh parental consent for the new activity. Consent is not a blank cheque for future changes. Many services fail this on product updates; track what was consented to and re-verify when scope changes.
Provide clear information to both parent and child. Two separate notices: a plain-language explanation for the parent about what you do with the child's data, and an age-appropriate notice for the child themselves. The AADC requires child-friendly explanations at the child's reading age (use icons, simple words, short sentences). Do not assume the parent will explain to the child.
Allow easy withdrawal of consent. Parents must be able to withdraw consent as easily as it was given. If consent was given via a web form, there should be a web form to withdraw. If via credit card confirmation, there should be an account-settings option. Withdrawal triggers deletion of data collected under that consent (unless another lawful basis applies). Delays in processing withdrawal are ICO complaints waiting to happen.
Keep verification records. For each parental consent obtained, record: date, method used, parent identifier (email, phone, or ID), child identifier, what was consented to, version of privacy notice seen. Retain for the duration of the processing plus limitation period. The ICO has asked for these records during investigations of children's services; absence of records is treated as absence of consent.
Reassess verification when the child ages. When a child reaches 13, they gain their own right to consent. You can continue processing under the original parental consent, but best practice is to invite the now-13-year-old to review and take ownership of their account. Some services automatically transition consent to the child at 13 with notification to the parent; this is a model the ICO has endorsed.
Tip: Never assume Facebook/Instagram/YouTube account ownership proves age. Many under-13s have parent-approved social media accounts; none of those services verify ages in ways useful for GDPR compliance. Build your own age and consent process.
Tags: gdpr, children, parental-consent, article-8, under-13, verification
Tutorial
PECR: The Rules You Actually Need for Marketing
PECR sits alongside GDPR and covers cookies, electronic marketing, and nuisance calls — with its own consent rules and ICO enforcement.
The Privacy and Electronic Communications Regulations 2003 (PECR) is a separate UK regulation that works alongside UK GDPR. PECR covers four main areas: marketing by electronic means (email, SMS, phone, post-style messages), cookies and similar technologies, security of public electronic communications services, and privacy of customer data held by telcos. For most businesses, marketing rules and cookie rules are where PECR bites.
PECR uses the GDPR definition of consent. When PECR requires consent — which is most marketing to individuals and all cookies beyond strictly necessary — the standard is UK GDPR consent: freely given, specific, informed, unambiguous, and a clear affirmative action. Pre-ticked boxes, implicit consent from continued use, and bundled consents all fail both regimes.
Email and SMS marketing to individuals always requires consent, unless the "soft opt-in" exception applies (see the soft opt-in tutorial). Text "individuals" in PECR includes sole traders and some partnerships. Emailing a managing director of a limited company at their work email is B2B and outside strict consent rules — but emailing sarah.smith@soletrader.co.uk is B2C and needs consent.
Marketing calls: different rules for live vs automated. Automated calls (recorded message delivered by machine) always require prior consent. Live calls to subscribers registered on the Telephone Preference Service (TPS) are prohibited without consent. Live calls to subscribers not on TPS are allowed without consent but with an opt-out offer. Check TPS before every campaign.
Postal marketing is not covered by PECR — it falls under UK GDPR's general rules. You can send postal marketing under legitimate interests without prior consent, provided you offer opt-out, honour suppression lists, and respect any recorded objections. The Mail Preference Service (MPS) is not a statutory list but organisations that ignore it face ICO scrutiny.
PECR fines are separate from GDPR fines and can be applied simultaneously. The maximum under PECR is £500,000 per breach (not per-affected-subject). The ICO has fined dozens of organisations under PECR for nuisance call/text campaigns — examples include CPS Advisory Ltd (£50,000, 2022), Valca Vehicle Ltd (£80,000, 2022), and Numberclose Ltd (£200,000, 2021). The ICO's PECR enforcement page is updated monthly.
Keep evidence of consent for PECR purposes. The burden is on you to show you obtained valid consent. Minimum evidence: user identifier (email, phone), date of consent, method (web form, in-store, inbound call), what the user saw at the point of consent (page URL, banner wording, script). The ICO's first question in any PECR investigation is "show me your consent records for these numbers/emails".
Update your processes for the Data (Use and Access) Act 2025 changes. The Act, passed in 2025, amended PECR to align more closely with UK GDPR terminology and added enhanced fines under PECR up to 4% of global turnover for the most serious breaches. The changes commenced in stages through 2025-2026. Check the ICO's PECR guidance for current penalties and compliance obligations as these evolve.
Tip: Before launching any electronic marketing campaign, run it past the PECR checklist: do I have consent (or soft opt-in), is the audience on any preference service, does my message identify the sender, is there an easy opt-out? Miss any one and you are at risk.
Tags: gdpr, pecr, marketing, electronic-marketing, ico
Tutorial
The Soft Opt-In Exception for Email and SMS Marketing
You can email existing customers about similar products without prior consent — but only if you meet four strict conditions.
Regulation 22(3) of PECR contains the "soft opt-in" — an exception that lets you send marketing emails or SMS without prior consent if: the recipient's contact details were obtained in the course of a sale or negotiations for a sale; the marketing is for similar products or services to those they bought or considered; the recipient was given a simple opt-out opportunity when their details were first collected; and every subsequent communication offers the same easy opt-out. All four conditions must be met.
"In the course of a sale" is narrower than most organisations assume. A completed purchase clearly qualifies. A user who abandoned a checkout without buying is in "negotiations for a sale" and arguably qualifies. A newsletter signup with no purchase intent does not qualify. Free trials are a grey area — if the user provided card details and entered a paid relationship, yes; if they signed up for a free product only, probably no.
"Similar products or services" is the biggest compliance failure point. A user who bought a kettle gives you soft opt-in for other kitchen appliances — arguably. A user who bought a kettle does not give soft opt-in for insurance, holidays, or clothing. The ICO interprets "similar" narrowly. If you sell across categories, do not assume one purchase grants soft opt-in across your whole catalogue.
The opt-out at collection must be simple. A clear, unticked box on the order form: "Tick this box if you do NOT want to receive marketing about similar products" satisfies the rule. A checkbox buried in Ts & Cs does not. An opt-out offered only in the email confirmation (after the transaction) does not meet the "at the time their details were collected" requirement.
Every subsequent email must offer easy unsubscribe. A one-click unsubscribe link at the bottom of every marketing email, functional, leading to an immediate opt-out (no login required, no "are you sure" multi-step process). Regulation 22(3)(c) uses the phrase "simple means of refusing" — the ICO reads this as single-click. Using the phrase "we will remove you within 28 days" is not compliant.
Soft opt-in does not apply to third-party marketing. If you collected details for your own marketing, you cannot soft opt-in to partners, subsidiaries, or marketing-to-affiliates without explicit consent for third-party marketing. Each legal entity needs its own consent basis. ICO enforcement actions have repeatedly hit organisations that stretched soft opt-in to share data with "partners".
Soft opt-in does not apply to charities, political parties, or non-commercial organisations. PECR Regulation 22(3) specifies "in the course of a sale or negotiations for a sale of a product or service". A charity collecting names at an event cannot use soft opt-in; they need express consent for marketing. The RSPCA and other charities have been fined for treating event signups as consent to marketing.
Record which messages went under soft opt-in vs explicit consent. If a user later complains, you need to show the legal basis for each message. Build it into your marketing platform: a "consent basis" field on every subscriber record, with valid values including "consent_marketing", "soft_optin_purchase_2026-03-15", and "legitimate_interests_b2b". Audit regularly to catch drift.
Tip: If in doubt, get express consent at checkout with a clear opt-in box. Soft opt-in is narrower than the average marketer thinks, and ICO fines for overreach outweigh the small conversion benefit of skipping explicit consent.
Tags: gdpr, pecr, soft-opt-in, email-marketing, sms-marketing
Tutorial
Valid Marketing Consent Under UK GDPR and PECR
Marketing consent must be freely given, specific, informed, unambiguous, and a clear affirmative action — stricter than pre-2018 rules.
Under UK GDPR Article 4(11) and Recital 32, consent must be: freely given (no coercion, no bundling with essential services), specific (to a defined purpose — not a catch-all), informed (the user knows who, what, how long, and their rights), and unambiguous, indicated by a clear affirmative action. Pre-ticked boxes, opt-out mechanisms, and continued use of services do not constitute valid consent. This applies to marketing consent just as it does to all consent.
Consent must be a positive action. An empty checkbox that the user ticks is fine. A pre-ticked checkbox the user leaves is not (Planet49 case, 2019). A long paragraph of Ts & Cs with marketing buried at paragraph 12 is not. A sign-up form with small-print "by signing up you agree to receive marketing" is not. Every consent needs a deliberate, separate, affirmative action.
Granularity matters. "I agree to receive marketing" is too broad. Better: "I agree to receive emails about product updates" and separately "I agree to receive emails about partner offers" and separately "I agree to phone calls about renewals". Each channel and each purpose should have its own checkbox. Bundling means users cannot exercise meaningful choice — which fails the "specific" test.
Identify the controller. Consent must be informed of the identity of the controller (you). If marketing will come from named partners, those partners must be listed specifically — not "our trusted partners". The ICO fined Verso Group £80,000 in 2020 for using vague "carefully selected third parties" language in consent forms. List them, or do not claim consent covers them.
Provide clear withdrawal mechanisms. Article 7(3) requires that withdrawing consent is as easy as giving it. A user who subscribed via a web form should be able to unsubscribe via a web form — not by writing a letter or calling a premium-rate number. Most mature marketing platforms offer one-click unsubscribe links that immediately update subscription status; that is the standard.
Re-consent after meaningful change. If you significantly change the marketing (new categories, new partners, new purposes) you need fresh consent for the change. You cannot rely on old consent covering new activity. Re-consent campaigns ahead of major product launches are now standard practice — done well, they can refresh engagement and cleanse out-of-date lists simultaneously.
Consent expires functionally if not reinforced. There is no legal expiry, but ICO guidance and industry practice treat consent as getting stale over time. If a user has not engaged with your marketing in 2 years, their consent is probably no longer valid in the "informed and unambiguous" sense — they may have forgotten they signed up. Re-permission campaigns every 18-24 months keep lists fresh and defensible.
Document everything. For every consent: user identifier, timestamp, source (page URL or channel), wording shown at the point of consent, version of privacy notice, and withdrawal history. This record is your defence in any ICO complaint. Mature marketing platforms do this automatically; older databases often do not. Back-fill where you can; where you cannot, consider whether to continue marketing on that consent.
Tip: The simplest test: would the user remember giving you consent if asked today, and would they stand by the decision? If not, the consent was probably not valid to begin with. Review your historic opt-in lists with that lens.
Tags: gdpr, pecr, marketing-consent, opt-in, consent, email-marketing
Tutorial
ICO Fines: How They Are Calculated and What Triggers Them
The ICO can fine up to £17.5m or 4% of global turnover — but most fines are smaller, and the calculation follows a published methodology.
UK GDPR sets two fine tiers. The lower tier is up to £8.7m or 2% of global annual turnover (whichever is higher) — applied to breaches of controller/processor obligations like Article 25 (data protection by design), Article 28 (processor contracts), Article 30 (ROPA), and Article 33 (breach notification). The upper tier is up to £17.5m or 4% of global turnover — for breaches of user rights, lawful basis, consent, and international transfer rules.
The ICO published its data protection fining guidance in March 2024. It sets out a 5-step methodology: (1) assess seriousness based on nature, gravity and duration; (2) identify starting point as a percentage of turnover; (3) consider aggravating and mitigating factors; (4) apply effectiveness, proportionality and dissuasiveness test; (5) assess economic circumstances. This structured approach makes fines more predictable than before 2024.
Aggravating factors commonly cited in ICO decisions: prolonged duration of breach, number of data subjects affected, inadequate response to complaints, failure to cooperate with ICO, previous similar breaches, deliberate or negligent conduct, disregard for ICO warnings. Each aggravator can increase the fine substantially. H&M's €35m fine in 2020 was heavily influenced by aggravators — systematic employee surveillance and management complicity.
Mitigating factors that reduce fines: prompt breach notification, cooperation with ICO investigation, remedial measures taken, self-reporting, victim assistance (e.g. credit monitoring for affected customers), DPO appointment and quality, mature compliance programme, DPIAs and documentation in place. British Airways' initial £183m fine was reduced to £20m partly due to cooperation and COVID-19 economic impact.
Notable UK fines provide calibration. British Airways £20m (2020, security breach affecting 400,000 customers). Marriott £18.4m (2020, security breach of 339 million records). Clearview AI £7.5m (2022, unlawful scraping of biometric data). TikTok £12.7m (2023, processing children's data without consent). Equifax £500,000 (2018, under pre-GDPR rules but still relevant). Monetary penalties above £1m typically follow major incidents affecting millions of data subjects.
Smaller fines are far more common. PECR fines for nuisance marketing typically range £40,000-£500,000. The ICO fines dozens of organisations under PECR each year — the total number of GDPR monetary penalties above £1m is still under 20. For most UK SMEs, the risk is not a headline-grabbing multi-million-pound fine but a £50,000-£200,000 penalty that nonetheless sinks the business.
The ICO prefers enforcement other than fines. Fines are a last resort. More common enforcement: reprimand (public notice of breach without monetary penalty), enforcement notice (legal order to do or stop something), assessment notice (right to audit the organisation). In 2023 the ICO issued roughly 400 enforcement actions of various kinds, of which only a small fraction were monetary fines. Cooperation genuinely reduces outcomes.
Appeal routes exist. Monetary penalty notices can be appealed to the First-tier Tribunal (General Regulatory Chamber). The tribunal reviews the ICO's reasoning and can uphold, reduce, or quash fines. Experian's appeal in 2023 resulted in the fine being overturned on specific grounds. Fines are rarely escaped entirely on appeal but are occasionally reduced. Timelines are tight (28 days to appeal), so organisations must be ready.
Tip: If you receive a preliminary notice of intent (PNI) from the ICO, engage a specialist data protection lawyer immediately. Written representations at the PNI stage are your best chance to reduce the eventual fine — most fines are decided in the representation phase, not at formal notice stage.
Tags: gdpr, ico, fines, enforcement, penalties
Tutorial
Class Actions and Group Litigation for Data Breaches
Lloyd v Google (2021) limits "loss of control" damages but group claims for material damage or distress continue to succeed in UK courts.
UK GDPR Article 82 entitles users to compensation for material or non-material damage caused by a breach. "Material damage" is financial loss — stolen money, costs of identity protection, lost income. "Non-material damage" is emotional distress, anxiety, reputational harm. Both are recoverable, though UK courts have been stricter than EU courts about quantifying non-material damage.
The Supreme Court's Lloyd v Google decision (November 2021) killed off "loss of control" class actions. Lloyd argued that the mere loss of control over personal data was itself compensable damage for everyone in a class, without evidence of individual harm. The Supreme Court said no — users must prove actual damage or distress individually. This significantly raised the bar for US-style class actions in the UK.
Group Litigation Orders (GLOs) and representative actions remain viable when claimants can show individual damage. Recent examples: the easyJet breach (9 million customers, 2020) spawned a GLO; the British Airways breach led to a group settlement in 2021 covering 16,000+ affected customers; the Marriott breach led to multiple group claims. These succeed where individual users can show specific harm, not just "loss of control".
Representative actions under CPR 19.6 are still possible for non-damage remedies. A single representative can bring a claim on behalf of others with the "same interest" — for declarations, injunctions, or (in theory) damages where a single formula applies to all. Post-Lloyd, representative actions for damages are rare but still used for non-monetary relief like forcing deletion or changing practices.
Claim values for individual data protection cases typically range £500-£10,000. The JD v Hallmark Care Homes case awarded £6,000 for a distressing disclosure of an employee's personal data. Vidal-Hall v Google (pre-GDPR) awarded smaller sums for distress from tracking. Very serious cases — involving identity theft, financial loss, or significant distress — can reach £20,000+. Courts assess each case on its facts.
Litigation funders are active in this space. Firms like Harcus Parker, Hausfeld, and Bott & Co run data protection group claims on "no win no fee" bases with funding backing. If 50,000+ users are affected by a breach, expect funder-backed litigation to emerge within months. For organisations, the cost of defending group claims often exceeds any ICO fine — and settlements are frequent.
Joint and several liability can be severe. Article 82(4) makes controllers and processors jointly and severally liable for damage where both are involved. A cloud vendor and their customer can both be sued for the same breach; one might pay the full damages and seek contribution from the other. Have clear contractual allocation of liability in your processor agreements and maintain cyber insurance with sufficient cover.
Insurance is essential. Most cyber insurance policies cover data protection claims, but check coverage for: legal defence costs, damages awarded or settled, notification costs, PR and crisis management, regulatory fines (rarely insurable in the UK for intentional breaches but sometimes for negligence). Review your cyber insurance annually with a broker who understands the current GDPR litigation landscape.
Tip: Post-Lloyd, prevention matters more than ever for group claim risk. A breach that previously would have triggered £50 per user in class damages now triggers £0 for many — but a handful of seriously affected users can still win individually at £5,000+ each. Reduce the number of serious harms, not just the breach count.
Tags: gdpr, litigation, class-actions, lloyd-v-google, compensation, civil-claims
Tutorial
A step-by-step technical guide to implementing a cookie consent banner that meets UK GDPR and PECR standards without breaking site performance.
Start with a cookie inventory. Run a scan with CookieBot or OneTrust (free tier works for small sites) to enumerate every cookie, pixel, and localStorage item your site sets. Categorise each as: strictly necessary, functional, analytics, or advertising. Strictly necessary means genuinely essential — session cookies, CSRF tokens, load balancer cookies. Analytics (including Google Analytics) is not strictly necessary. Build a spreadsheet of the full inventory before writing any banner code.
Choose your CMP approach: self-build or third-party. Third-party CMPs (CookieBot, OneTrust, CookieYes, Usercentrics) give you a certified solution, auto-blocking, cookie lists, and compliance reporting. Expect £10-£200/month for small sites. Self-build gives you full control and zero ongoing fees but requires engineering effort and legal review. For most SMEs, a third-party CMP is the better value.
Default-deny is the starting state. Before the user makes any choice, set default consent to denied for all non-essential categories. For Google Consent Mode v2, this means: ad_storage=denied, analytics_storage=denied, ad_user_data=denied, ad_personalization=denied, functionality_storage=granted, security_storage=granted. Fire this as a gtag() call before any tag loads.
Block non-essential tags until consent. Wrap every non-essential tag (GA, Facebook Pixel, LinkedIn Insight, Hotjar, etc.) in logic that only fires on consent. If using Google Tag Manager, use Consent Settings on each tag and tie to consent state. If hand-rolling, check a JS variable (window.cookieConsent.analytics === true) before calling tracker libraries. Do not load the tracker scripts themselves until consent — some trackers fire on script load even if you never call them.
Design the banner UI for compliance. Equal prominence for Accept and Reject — same button size, same colour intensity, same position. No nudging ("Accept all to get the best experience"). Granular controls accessible in one click via a "Manage preferences" button. Link to the cookie policy. Keep the banner dismissible only by making a choice — auto-dismiss after timer or scroll is not valid consent.
Store consent decisions with a version hash. When a user makes a choice, record: timestamp, consent configuration (functional/analytics/advertising per category), banner version, policy version. Store in a first-party cookie (one year expiry) and optionally also on your server for logged-in users. The hash should change when you update the cookie list or policy, triggering a fresh prompt.
Test thoroughly in incognito mode. Open your site fresh in a new incognito window. Check DevTools > Application > Cookies and localStorage before clicking anything — there should be no non-essential cookies. Click "Reject all" — verify that no non-essential cookies appear afterwards. Click "Accept all" in a fresh window — verify that analytics/advertising cookies now set. Repeat after clearing and re-consenting.
Monitor compliance continuously. New cookies appear when you add vendors, plugins, or embed widgets. Set a monthly scan with your CMP or an independent scanner. Review the cookie inventory, categorise new entries, and update your policy. Large sites automate this via CI integration — every deployment triggers a scan and alerts on new trackers. Drift is the main compliance failure mode for cookie banners over time.
Tip: The single biggest source of cookie banner non-compliance is "Accept All" being one click while "Reject" takes two or more. Design the rejection path first and make sure it is literally a single button click from the main banner.
Tags: gdpr, pecr, cookies, implementation, technical-guide, cookie-banner
Tutorial
A practical checklist for turning a compliant privacy policy draft into a published, versioned, and properly-linked live document.
Start from a compliant draft that covers all Article 13/14 disclosures — see the "Writing a GDPR-Compliant Privacy Policy" tutorial for content requirements. Before publishing, have the draft reviewed by: your DPO (for completeness), legal counsel (for accuracy and defensibility), your product team (for alignment with actual processing), and optionally the ICO self-assessment tool (for regulatory alignment). Track review feedback in a shared document.
Publish at a stable, predictable URL. Use /privacy or /privacy-policy — avoid /pp, /legal/privacy-v2, or dynamic URLs. Search engines, users, and regulators all expect to find it in the obvious place. Link from every page footer, from signup forms, from checkout, from account settings, from marketing emails. An accessible privacy policy must be no more than two clicks from any page.
Structure with navigation. A modern privacy policy is typically 3,000-8,000 words. Break it into clearly-labelled sections with a sticky table of contents or collapsible sections. Common structure: summary, who we are, what data we collect, why and under what lawful basis, who we share with, international transfers, retention, your rights, how to contact us, changes to this policy, glossary. Users should be able to jump to the section they need.
Version and date the document. At the top, show "Last updated: [date]" and a version number. Maintain an archive of previous versions accessible from the policy — either inline (collapsible "previous versions" section) or via links to /privacy/v1, /privacy/v2, etc. When the ICO or a court asks what your policy said on a specific date, you must be able to answer. Git-backed CMS makes this trivial.
Implement change notifications for material changes. When you make substantive changes (new purposes, new recipients, new lawful bases, new retention periods), notify existing users by email in advance — typically 30 days. Minor changes (typo fixes, clarifications) can be noted in a "what changed" summary at the top of the policy without direct notification. Document your reasoning for material-vs-minor classification.
Link from every collection point. At every form where you collect personal data, include a short just-in-time notice with a link to the relevant section of the privacy policy. "We use your email address to send order updates. See our privacy policy section on communications [link] for details." This is required under Article 13's "at the time data are obtained" rule and good UX practice.
Provide accessible formats. The privacy policy is a document that users have a right to understand — that means it must be accessible. Ensure it passes WCAG 2.1 AA: readable by screen readers, sufficient colour contrast, logical heading hierarchy, skip links for sections. Offer a plain-text version or Easy Read version for users with cognitive impairments — the ICO has explicitly praised organisations that offer Easy Read privacy notices.
Review annually and on trigger. Schedule an annual review of the privacy policy to catch drift — new processors, new purposes, new countries. Trigger immediate reviews on: new product launches, processor changes, legal updates (ICO guidance, case law, legislative changes), mergers or acquisitions. Maintain a review log showing what was checked and what was changed.
Tip: Your privacy policy is only as good as its accuracy. A beautifully written document that does not reflect reality is a ticking compliance bomb. Pair every annual review with an operational audit — are you still doing what the policy says?
Tags: gdpr, privacy-policy, implementation, compliance-docs, publishing
Tutorial
Building a Data Breach Response Capability
Practical playbook for standing up breach detection, response, notification and documentation from scratch.
Assemble an incident response team before the first incident. Core members: DPO (compliance lead), CISO or head of IT (technical lead), head of legal, head of communications, an executive sponsor (CEO or COO), and a dedicated note-taker. For larger organisations add: HR (for employee data breaches), customer service (for customer-facing communications), a third-party digital forensics retainer. Get this team together quarterly for tabletop exercises.
Create a breach response playbook. Document each step of the process: initial triage (within 1 hour of awareness), technical containment (within 4 hours), preliminary risk assessment (within 24 hours), ICO notification decision (within 48 hours), user notification decision, post-incident review. Each step has owners, inputs, outputs, and decision criteria. Keep the playbook updated and practised — a playbook no one has read is useless at 3am on a Saturday.
Build detection and alerting. Technical controls that surface incidents: SIEM (Security Information and Event Management) alerts, database activity monitoring, DLP (data loss prevention) tooling, endpoint detection, anomaly alerts on authentication and access patterns. Non-technical: breach reporting email (breach@), staff training, clear escalation paths, customer complaint monitoring. The majority of breaches are discovered by staff reports, not technical detection.
Pre-draft notification templates. Time is everything in a breach response — having templates ready saves critical hours. Template: ICO notification (per the online form fields), data subject notification email (plain language, covering nature/consequences/mitigation/contact), internal employee briefing, website notice banner, press statement (reviewed by legal and comms). Store templates in a shared location accessible to the response team.
Set up a breach register now, even if empty. The register is an Article 33(5) legal requirement and is the source of truth for every incident. Structure: incident ID, dates (occurred, detected, contained, reported, closed), category, cause, affected data and subjects, risk assessment, actions taken, ICO notified (y/n + date), subjects notified (y/n + date), root cause, lessons learned. Use a spreadsheet or a dedicated tool — the format matters less than having one.
Establish relationships with third parties before you need them. A breach often requires: external forensic investigators (CrowdStrike, Mandiant, NCC Group in the UK), external data protection counsel, a PR crisis firm, a secure notification platform for mass user notification. Have agreements in place and contact numbers saved — trying to onboard a forensic firm mid-incident delays everything. Cyber insurance brokers often have panel firms you can call.
Run tabletop exercises quarterly. A scenario: "An employee reports their laptop with unencrypted customer data was stolen on a train. What do you do?" Walk the team through each playbook step, identifying gaps and timing delays. Another scenario: "A security researcher emails saying they found an unsecured database backup online." These exercises reveal procedural gaps that real incidents will expose at worse moments.
Close the loop after every incident. Within 30 days of containment, conduct a post-incident review covering: what happened, how we detected it, how we responded, what we did well, what we should have done better, what systemic changes are needed. Update the playbook, update policies, update technical controls. A breach register entry that ends "no lessons learned" is itself a compliance weakness — every incident should improve the system.
Tip: Practice your breach response in August and December — the two times of year when key team members are most likely to be on holiday. Real breaches do not wait for convenient timing, and your response plan must work when only half the team is available.
Tags: gdpr, breach-response, implementation, incident-response, playbook
Tutorial
Learn what every metric in the Site Audit tool means, how scores are calculated, and how to prioritise your fixes for maximum SEO impact.
Run your first audit
Enter any URL in the Site Audit tool and click Analyse. The crawler fetches the live page in real time — no cached data — and runs over 70 checks across HTML structure, SEO fundamentals, performance, security and accessibility. Results appear within seconds and are grouped by severity so you know immediately what to tackle first. You can audit any URL on the web, not just your own site — auditing competitors is a useful exercise to see what they are doing right.
Understand the score
Your SEO Score is calculated as: 100 minus (Critical issues multiplied by 15) minus (Warnings multiplied by 5) minus (Info items multiplied by 1). A score of 80 or above is healthy and means the page has solid technical foundations. A score of 60 to 79 means there are issues worth addressing. Below 60 means significant problems that are actively suppressing your rankings. The score is not the goal — fixing the underlying issues is. But the score gives you a single number to track progress over time.
Work through Critical issues first
Critical issues have the biggest score impact at 15 points each, but more importantly they actively harm your rankings or prevent the page from being indexed at all. The most common Critical issues are: missing title tag (Google has nothing to show in search results), no meta description (hurts click-through rate), missing H1 tag (weakens on-page relevance signal), broken or missing canonical tag (creates duplicate content risk), no HTTPS (security warning shown to users, ranking penalty), and noindex on a page that should be indexed (page removed from Google entirely). Each Critical issue has an exact fix described in the Learning Hub — click through to find the specific solution.
Address Warnings next
Warnings do not break SEO but they hold rankings back and compound over time. Common warnings include: thin content under 300 words (Google may consider the page low quality), missing alt text on images (lost image search opportunity and accessibility failure), slow Time to First Byte over 600ms (server is too slow, delays everything else), missing Open Graph tags (shared links on social media look broken), and heading hierarchy errors (skipping from H1 to H3 confuses content structure). Warnings cost 5 points each — a page with 10 warnings loses 50 points before any Critical issues are counted.
Understand Info items
Info items are observations worth knowing but not urgent. They cost 1 point each and represent opportunities rather than problems. Examples: a page that could benefit from FAQ schema but does not have it, images that could be further compressed, or minor heading style suggestions. Tackle Info items last, or use them as a checklist for polishing pages that are otherwise well optimised. Many Info items are optional — the tool flags them for awareness, not as mandatory fixes.
Prioritise by page importance
Fix issues on your most commercially important pages first. A 95-scoring blog post from three years ago matters less than a 60-scoring service page you are actively trying to rank. Identify your top 10 most important pages — homepage, main product and service pages, highest-traffic landing pages — and audit these first. Once these are in good shape, work through the rest of the site systematically. Sorting audit results by page traffic (if your analytics is connected) makes prioritisation automatic.
Track progress with Score History
After each fix, re-run the audit and check Score History to see your improvement trend over time. A consistently rising score indicates you are building good technical foundations. A sudden score drop after a deployment is a red flag — something in that deployment broke something. Score History makes regressions visible immediately rather than letting them silently damage rankings for weeks before you notice.
Run audits after every deployment
Any site update can introduce regressions. A new plugin, theme change, CMS upgrade or content restructure can break canonical tags, introduce noindex tags, create redirect chains or break schema markup. Make running the Site Audit a standard part of your deployment checklist, not an afterthought. Five minutes of checking after deployment can prevent weeks of ranking recovery.
Tip: Audit your most important pages first: homepage, top product and service pages, and any page you are actively promoting or paying to drive traffic to. A perfect score on a page nobody visits is worth less than an 80 on your main service page.
Tags: seo, audit, technical, html, errors, site-audit
Tutorial
A complete walkthrough of how Google Lighthouse scoring works, what each metric means, and the fastest ways to move the needle on your score.
What the PageSpeed score actually measures
The Performance score in Google Lighthouse is a weighted average of six lab metrics, not a direct measurement of how fast your page feels to real users. The weights are: LCP 25%, TBT 30%, CLS 15%, FCP 10%, Speed Index 10%, TTI 10%. TBT (Total Blocking Time) has the highest weight at 30%, which means JavaScript performance has the single biggest impact on your score. The score runs on a simulated mid-range Android device on a simulated 4G connection — which is why mobile scores are almost always 20 to 40 points lower than desktop scores.
Lab data vs field data — why both matter
Lab data is what Lighthouse measures in a controlled environment on a test device. Field data (called CrUX — Chrome User Experience Report) is what real Chrome users actually experience on your site. Google uses field data for ranking decisions, not lab scores. A site can score 95 in Lighthouse but have poor field data if real users are on slower connections or older devices. Always cross-reference your Lighthouse score with the Core Web Vitals field data shown alongside it, and with the Core Web Vitals report in Google Search Console.
Test mobile first, always
Google uses mobile-first indexing, meaning it crawls and ranks your site based on the mobile version. Desktop scores are typically 20 to 40 points higher than mobile scores for the same page. A desktop score of 90 with a mobile score of 42 is a real problem because most of your visitors are on mobile and Google is using the mobile experience for ranking. Always test on mobile first. A good target is mobile above 75 and desktop above 90, though these are guidelines not hard rules.
The Opportunities section — where to focus
The Opportunities section in Lighthouse shows specific improvements with estimated time savings in seconds. Sort by impact and work top to bottom. The most common high-impact opportunities are: Eliminate render-blocking resources (JS and CSS in the head delaying rendering), Properly size images (serving oversized images to small screens), Remove unused JavaScript (loading code that is never executed), Serve images in modern formats (WebP and AVIF instead of JPEG and PNG), and Reduce unused CSS. Each opportunity links to documentation explaining exactly how to implement the fix.
The Diagnostics section — root causes
Diagnostics do not directly affect the score but reveal the underlying problems causing your Opportunities to fail. Avoid enormous network payloads, Minimise main-thread work, Reduce JavaScript execution time, Keep request counts low — these are the root causes behind multiple Opportunities. Fixing a Diagnostic often improves several Opportunities simultaneously. For example fixing main-thread JavaScript usually improves both TBT and TTI.
The three fastest wins for most sites
For the majority of websites, these three changes produce the biggest score improvements per hour of effort. First: eliminate render-blocking JavaScript by adding defer or async attributes to script tags — this often adds 10 to 20 points by itself. Second: compress and convert images to WebP and add explicit width and height attributes — images are typically 60 to 80 percent of page weight. Third: enable server-side caching to reduce TTFB — a slow server makes every other metric worse because nothing can load until the server responds.
Verify improvements with real user data
After making performance improvements, check your Core Web Vitals in Google Search Console. Field data updates every 28 days as a rolling average, so improvements take time to appear in GSC. In the meantime, use the CrUX data shown alongside your Lighthouse score and check WebPageTest.org for real-device testing from real locations. A score improvement in Lighthouse that does not translate to field data improvement suggests your hosting or CDN is the bottleneck rather than your code.
Tip: A PageSpeed score of 90+ does not guarantee fast real-world performance. Always cross-reference with Core Web Vitals field data in Google Search Console. The field data is what Google actually uses for rankings — not the lab score.
Tags: performance, pagespeed, cwv, speed, lcp, tbt, fcp
Tutorial
Learn exactly which schema types unlock rich results in Google, how to implement them correctly, and how to test and debug your markup.
Why schema markup matters
Schema.org JSON-LD markup tells Google what your content is about in a structured, unambiguous way — not just text that Google has to interpret, but explicit declarations. This enables rich results in search: star ratings, FAQ dropdowns, recipe cards, event listings, product prices, job posting details and more. Rich results take up significantly more space in the search results page than plain blue links, and studies consistently show they increase click-through rates by 20 to 30 percent. Beyond rich results, schema also helps your content appear in featured snippets and Knowledge Panels.
Schema.org types vs Google-supported types
Schema.org defines over 800 types covering everything from MedicalCondition to TouristAttraction. However Google only supports a subset of these for rich results in Search. The types that unlock visual enhancements are: Product, Review, AggregateRating, FAQPage, HowTo, Recipe, Event, LocalBusiness, Article, BlogPosting, NewsArticle, BreadcrumbList, VideoObject, JobPosting, Course, Dataset, SoftwareApplication, Book, and a few others. Using types outside this supported list — like WebPage, isRelatedTo, or custom extensions — adds no rich result value and wastes implementation effort.
Choose the right type precisely
Match schema to your content as specifically as possible. Use BlogPosting not just Article for blog posts — it is a subtype with more specific meaning. Use Plumber not LocalBusiness for a plumbing company. Use NewsArticle not Article for news content. Use FAQPage only when your page actually contains visible Q and A content — not as a trick to earn FAQ rich results on a page without real questions. The wrong type means no rich result even if your markup is otherwise perfect.
Use our AI Schema Generator
Our Schema Generator reads your live page content and writes complete JSON-LD automatically with all required and recommended properties populated from what is actually on your page. This avoids the most common implementation mistake: writing schema that describes content that is not actually on the page. Google requires schema to match visible page content, and a mismatch is a policy violation that can result in rich results being removed or a manual action against your site.
Required vs recommended properties
Required properties are the minimum for eligibility — missing even one means no rich result will appear even if everything else is perfect. Recommended properties increase the richness of the result and the likelihood of it appearing. For Product schema: name, image and description are required for a basic snippet, with offers (containing price, priceCurrency and availability) required for price display. For FAQPage: mainEntity containing Question objects each with name and acceptedAnswer containing an Answer with a text property. For LocalBusiness: name, address and telephone at minimum.
JSON-LD format and correct placement
Always use JSON-LD format — Google recommends it over Microdata and RDFa because it does not require touching your visible HTML. Place JSON-LD in a script tag with type="application/ld+json" inside the page head section. Multiple separate schema blocks on one page are fine — use a separate script block for each type. Do not try to combine everything into one object unless the types are genuinely nested (like an Offer inside a Product). Multiple schema types on one page is normal and expected for complex pages.
Test before deploying, monitor after
Use our Schema Debugger and Google Rich Results Test (search.google.com/test/rich-results) before publishing. Fix every error — invalid schema is silently ignored by Google with no warning to you. After deployment, check Google Search Console Enhancements section for your schema type — it reports errors and warnings across your whole site as Google crawls it. Rich results typically appear within one to three weeks of correct implementation and Google crawling the updated page.
Tip: Add schema in a JSON-LD script block in your page head. Never use Microdata — JSON-LD is easier to maintain, does not require changes to your visible HTML structure, and is the format Google explicitly recommends in their developer documentation.
Tags: schema, structured-data, rich-results, rich, html, seo
Tutorial
Answer Engine Optimisation explained — how ChatGPT, Perplexity and Google AI Overviews decide what to cite, and exactly how to optimise your content to be selected.
How AI search selects sources to cite
AI search engines including ChatGPT Search, Perplexity AI and Google AI Overviews do not rank pages the same way traditional search does. They select sources based on a combination of factors: clear factual answers near the top of the page (the AI extracts the most direct answer, not the most detailed one), strong E-E-A-T signals including named authors with verifiable credentials, structured data that explicitly labels content types, direct question-and-answer format content, and established domain authority from trusted backlinks. Vague, promotional or thin content is almost never cited regardless of its traditional search ranking.
Write answer-first content
The single most impactful change you can make for AEO is to answer the main question on each page within the first one or two sentences. AI models extract the clearest answer, not the most comprehensive one. Think of it like a newspaper headline: the key information comes first, the detail follows. For a page about how to fix a CLS issue, the first sentence should say something like "CLS is fixed by adding width and height attributes to images and reserving space for ads before they load" — then the page expands on each fix in detail. Pages that bury the answer under an introduction and background context are rarely cited.
Add FAQ schema to every important page
FAQPage schema directly feeds structured question-and-answer data into AI retrieval pipelines. Add at least five to eight genuine FAQs per important page using our Schema Generator. Each question should be phrased as a complete natural language question that someone would actually type or speak: "How long does it take to fix a CLS score?" rather than "CLS fix time". Each answer should be self-contained and factual — two to four sentences that answer the question completely without requiring the reader to read the rest of the page to understand the answer.
Improve your E-E-A-T signals
AI models use E-E-A-T signals to assess whether content is trustworthy enough to cite. The most important signals: named author bios with real credentials (qualifications, years of experience, published work), citations and links to authoritative external sources within the content, visible publication and update dates on every page, links from the author page to their LinkedIn and other professional profiles, and an About page that explains who runs the site and why they are qualified to write about the topic. Without these signals, AI models treat content as anonymous and unverifiable — which makes it far less likely to be cited.
Create an llms.txt file
Some AI crawlers including those from Anthropic and others respect a /llms.txt file — a plain text file in your root directory that explains what your site covers, who it is for, and which pages are most authoritative. Think of it as a sitemap for AI crawlers rather than search engine crawlers. Keep it concise: two paragraphs explaining what the site does and who it serves, followed by a list of your most important URLs with one-sentence descriptions. Run our AEO Checker to get a custom llms.txt generated automatically from your site content.
Structure content for machine extraction
AI models extract structured content far more reliably than dense prose. Use clear heading hierarchy with H2 for main topics and H3 for subtopics. Write definitions immediately after introducing a new term. Use numbered lists for processes and steps, bullet points for features and options. Use tables for comparisons. Avoid passive voice and jargon. Keep sentences under 20 words where possible. The easier your content is for a human to scan, the easier it is for an AI model to extract and cite accurately.
Build topical authority clusters
AI models favour sources that demonstrate comprehensive coverage of a topic, not just a single page. A site with 20 interlinked articles on web performance — covering LCP, CLS, INP, TTFB, image optimisation, JavaScript performance, server configuration and CDN setup — is more likely to be cited as an authority than a site with one excellent page on the topic. Build content clusters around your core topics with clear internal linking between related articles. Each article should link to the others and to a central pillar page that covers the topic at a high level.
Tip: Check your AEO score monthly using our AEO Checker. AI search visibility changes faster than traditional SEO — models update their retrieval systems regularly and pages that are cited today may need refreshing in three months if competitors publish more authoritative and better-structured content.
Tags: aeo, ai-search, ai, perplexity, chatgpt, schema, eeat
Tutorial
A practical guide to diagnosing and fixing each of the three Core Web Vitals that directly affect your Google rankings.
What Core Web Vitals are and why Google uses them
Core Web Vitals are three real-user experience metrics that Google incorporated into its ranking algorithm in 2021 as part of the Page Experience update. They measure aspects of page experience that users actually feel: how fast the page loads (LCP), how stable the layout is (CLS), and how responsive the page is to interaction (INP). Unlike Lighthouse lab metrics, Core Web Vitals are measured from real Chrome user data collected from actual visitors. This means your score varies by device, connection speed and geography — a fast page on a fibre connection in London may be a slow page on a 3G connection in rural Scotland.
LCP — Largest Contentful Paint
LCP measures how long it takes for the largest visible element in the viewport to fully load. This is usually the hero image, but it can be a large heading, a video thumbnail or a background image. The target is under 2.5 seconds. Between 2.5 and 4 seconds needs improvement. Over 4 seconds is poor and will negatively affect rankings. The most common fixes: preload the hero image with fetchpriority="high", serve it in WebP or AVIF format (30 to 50 percent smaller than JPEG), use a CDN to serve it from a location near the user, remove render-blocking scripts above the fold that delay rendering, and reduce TTFB so the server responds faster. Never add loading="lazy" to your LCP image — that is one of the most common and most damaging mistakes.
CLS — Cumulative Layout Shift
CLS measures unexpected layout movement — elements jumping around as the page loads. The most common cause is images without explicit width and height attributes. When the browser does not know how much space to reserve for an image, it allocates nothing, then shifts everything down when the image loads. Other causes: web fonts swapping from fallback to custom font, ads injected above existing content, cookie banners pushing content down, and embeds loading without reserved space. The target is under 0.1. The fix for images is simple: add width and height to every img tag. Add CSS: img { max-width: 100%; height: auto; } to keep them responsive. This single fix resolves the majority of CLS issues on most sites.
INP — Interaction to Next Paint
INP replaced FID (First Input Delay) as a Core Web Vital in March 2024. While FID only measured the delay before the first interaction, INP measures all interactions throughout the page lifetime — every click, tap and keyboard press. The target is under 200ms response time. Between 200 and 500ms needs improvement. Over 500ms is poor. The main cause of poor INP is JavaScript blocking the browser main thread. When JavaScript is running, the browser cannot respond to user input. Fixes: break up long JavaScript tasks into smaller chunks that yield to the browser between them, defer non-critical scripts, remove unused JavaScript, and avoid large DOM trees with thousands of elements.
Use field data not just lab data
Lighthouse gives you lab data — what would happen on a clean controlled test device. The Core Web Vitals report in Google Search Console shows field data — what real users actually experience on your site. Both matter but they serve different purposes. Lab data tells you what to fix. Field data tells you whether your fixes are actually reaching real users. A page can pass Lighthouse and fail field data if real users are on slower devices or connections than the Lighthouse test simulates. Always check field data in GSC after making performance improvements.
Prioritise mobile
Core Web Vitals thresholds are the same for mobile and desktop but mobile is much harder to pass because mobile devices have less processing power and mobile connections are slower. Google uses mobile-first indexing, so mobile performance matters most for rankings. Test on a real mid-range Android device, not just Chrome DevTools device emulation. DevTools emulation does not accurately simulate mobile CPU throttling. Real device testing regularly reveals performance issues that emulation misses.
Monitor continuously not just occasionally
Core Web Vitals can regress after any deployment. A theme update, new plugin, JavaScript change or CMS version upgrade can introduce CLS, increase LCP time, or add JavaScript that hurts INP — often without the developer realising. Set up monitoring so that CWV issues are detected immediately rather than after they have been affecting rankings for weeks. Run the Core Web Vitals tool after every significant deployment. The GSC Core Web Vitals report updates on a 28-day rolling average so regressions appear there with a delay — your own monitoring catches them sooner.
Tip: Run the Core Web Vitals tool after every major deployment. A CWV regression after a site update is one of the most common causes of sudden ranking drops and one of the most frequently missed because there is often a weeks-long delay between the change and the ranking impact becoming visible in your analytics.
Tags: cwv, lcp, cls, inp, performance, speed, tbt, fcp
How-To
Step-by-step fixes for every common CLS cause — images, fonts, ads, iframes and dynamic content — with copy-paste code examples.
Fix images without dimensions — the most common cause
The browser needs to know how much space to reserve for an image before it downloads. Without explicit dimensions the browser allocates no space, then shifts everything down when the image loads. The fix: add width and height attributes to every img tag matching the image's natural dimensions. Then add CSS so images stay responsive: img { max-width: 100%; height: auto; }. The HTML attributes tell the browser the aspect ratio to reserve space for. The CSS ensures the image scales down on smaller screens without breaking the layout.
Example:
<img src="product.webp" width="800" height="600" alt="Product name" style="max-width:100%;height:auto">
Fix web font FOUT and FOIT
FOUT (Flash of Unstyled Text) happens when the browser shows a fallback font, then swaps to the web font when it loads — causing text to jump. FOIT (Flash of Invisible Text) happens when the browser hides text completely until the font loads. Both cause CLS. The fix is two-part: add font-display: swap to your @font-face declarations so text is always visible using the fallback font, and preload critical fonts so they load earlier and reduce the swap delay.
Example:
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
@font-face { font-family: "Main"; src: url("/fonts/main.woff2"); font-display: swap; }
Reserve space for ads before they load
Ads are one of the most common causes of CLS on content sites. When an ad loads into a slot without reserved space, it pushes all the content below it down — sometimes by hundreds of pixels. The fix: always define a minimum height for ad containers equal to the tallest ad format that can appear in that slot. This reserves the space even before the ad loads.
Example:
.ad-slot { min-height: 250px; background: #f5f5f5; }
.ad-leaderboard { min-height: 90px; }
For responsive ads use aspect-ratio:
.ad-slot { aspect-ratio: 970/250; width: 100%; }
Fix late-loading iframes
YouTube embeds, Google Maps, Vimeo and other iframes often load after the page and cause layout shift if no space is reserved for them. The fix is a CSS aspect-ratio wrapper that reserves the exact space before the iframe loads.
Example:
.iframe-wrap { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; }
.iframe-wrap iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0; }
The 56.25% padding-bottom creates a 16:9 aspect ratio container. For other ratios divide height by width: a 4:3 video uses padding-bottom: 75%.
Fix cookie banner CLS
Cookie consent banners that load after the page and push content down are a very common CLS source, especially on sites using third-party consent management platforms that load their banner asynchronously. The fix: change the banner position so it overlays content instead of pushing it.
Example:
.cookie-banner { position: fixed; bottom: 0; left: 0; right: 0; z-index: 9999; }
The banner now appears as an overlay at the bottom of the screen without shifting any existing content. Alternatively, render the banner server-side in the initial HTML response so it is present from the first paint — eliminating the shift entirely.
Fix CSS animations that cause layout shift
Animating layout properties like margin, padding, top, left, width or height forces the browser to recalculate the layout on every animation frame, which both causes layout shift and is CPU-intensive. The fix: only animate transform and opacity. These properties use the GPU compositor thread and do not trigger layout recalculation.
Example:
BAD: @keyframes slide { from { margin-left: 0 } to { margin-left: 200px } }
GOOD: @keyframes slide { from { transform: translateX(0) } to { transform: translateX(200px) } }
The visual result is identical but the GOOD version causes no CLS and uses far less CPU.
Tip: Use our CLS Debugger tool to automatically identify all CLS contributors on any page. It records the page loading, measures each layout shift, identifies the elements causing them, and gives each one an individual CLS score. This turns a vague overall CLS number into a prioritised list of specific elements to fix.
Tags: cls, performance, technical
How-To
Everything you need to do to images to pass Core Web Vitals, improve PageSpeed, fix alt text for accessibility, and implement modern formats correctly.
Convert to WebP or AVIF for massive size reduction
WebP is typically 25 to 35 percent smaller than JPEG at the same visual quality, and it supports transparency (unlike JPEG) so it can replace PNG too. AVIF is even better — up to 50 percent smaller than JPEG — with excellent quality at low file sizes. Use the picture element to serve modern formats with JPEG or PNG fallback for older browsers.
Example:
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Description" width="800" height="600">
</picture>
All modern browsers support WebP. AVIF support is now above 90% globally. The fallback img is only used by very old browsers.
Always add width and height attributes
Every img tag must have explicit width and height attributes matching the image natural dimensions. This allows the browser to pre-allocate the correct space before the image downloads, preventing layout shift (CLS). Without these attributes the browser allocates no space, then shifts everything down when the image loads — a direct contribution to your CLS score.
In CSS always add: img { max-width: 100%; height: auto; }
This keeps images responsive (scaling down on smaller screens) while the HTML attributes tell the browser the aspect ratio to reserve space for.
Lazy load images below the fold
Images below the fold — not immediately visible without scrolling — should load only when the user scrolls near them. This reduces the amount of data downloaded on initial page load, improving LCP and overall load time.
Add to any below-the-fold image: loading="lazy"
Example: <img src="photo.webp" loading="lazy" width="800" height="600" alt="...">
Critical rule: NEVER add loading="lazy" to your hero image or any image visible above the fold without scrolling. Lazy loading the LCP image is one of the most common and most damaging performance mistakes — it delays the most important visible element and directly increases your LCP time by 1 to 2 seconds.
Write descriptive alt text for every content image
Alt text serves three purposes: it describes images to screen readers for visually impaired users (a legal accessibility requirement in many countries), it tells Google what the image shows so it can be indexed in Google Image Search, and it displays as text if the image fails to load. Write alt text that specifically describes what is in the image.
BAD: alt="image" (screen readers already say "image")
BAD: alt="photo1.jpg" (the filename, meaningless)
BAD: alt="running shoes buy cheap running shoes online" (keyword spam)
GOOD: alt="Red Nike Air Max Pegasus 40 running shoes, side view"
Decorative images (dividers, backgrounds) should have empty alt text: alt="" — this tells screen readers to skip them entirely.
Compress to target file sizes
Regardless of format, compress images to the smallest file size that maintains acceptable visual quality. Target sizes: hero images under 200KB, product images under 100KB, thumbnail images under 40KB. Use Squoosh (squoosh.app) for manual compression — it is free, runs in the browser, supports WebP and AVIF conversion, and shows you a side-by-side quality comparison at different compression levels. For WordPress: use an image compression plugin set to compress automatically on upload and convert to WebP.
Tip: Use our Image Optimisation Checker to audit every image on a page in one click. It flags: missing alt text, wrong format (JPEG where WebP should be used), missing dimensions, file size over 100KB, and missing lazy loading on below-the-fold images. Fix images over 100KB first — these give the biggest performance gains per fix.
Tags: images, performance, accessibility, cwv, image-optimisation
How-To
Practical techniques to make your content easier to read, reduce bounce rate, and signal quality to Google — without losing depth or authority.
Target a Flesch Reading Ease score of 60 or above
The Flesch Reading Ease score measures how easy a piece of text is to read on a scale of 0 to 100. A score of 60 to 70 corresponds to an 8th to 9th grade reading level — appropriate for most web content. Higher scores are easier to read. Lower scores are harder. To improve your score: use shorter sentences (under 20 words), use shorter words (one to two syllables where possible), and avoid unnecessary jargon. Check your score with our Readability Checker on any URL.
Use subheadings every 200 to 300 words
Subheadings (H2 and H3 tags) serve two purposes: they help readers scan and navigate the page (most people scan before committing to reading), and they help Google understand the structure and topics covered on the page. Every main section should have an H2 heading. Sub-sections within that get H3 headings. Include your secondary keywords naturally in two or three subheadings per page. Write subheadings as complete, descriptive phrases — not single words. "How to Fix Slow Server Response Time" tells more to both the reader and Google than "Server Issues".
Keep paragraphs short and use white space generously
On the web, long blocks of text are intimidating. Readers scan before they read, and a wall of text signals "this will take too long". Keep paragraphs to three or four sentences maximum. Use white space between paragraphs — it makes content feel less dense and more approachable. Break any list of items into bullet points or numbered lists rather than embedding them in a long sentence. A paragraph that would be fine in a book or academic paper often needs to be split into two or three shorter paragraphs for web reading.
Use active voice and direct language
Passive voice is harder to process and puts distance between the writer and the reader. Compare: "Mistakes were made by the development team" (passive, evasive) versus "The development team made mistakes" (active, clear). Aim for 80 percent or more active voice in your content. Active voice also tends to produce shorter sentences because you eliminate the auxiliary verbs that passive constructions require. Our Readability Checker flags passive voice sentences and suggests active alternatives.
Lead with the answer, then expand
Most readers come to a page looking for a specific answer. If they do not find it quickly they leave — and a high bounce rate tells Google the page did not satisfy the searcher's intent. Structure your content using the inverted pyramid: lead with the most important information first, then supporting details, then background and context. For SEO specifically: answer the main search query in the first one or two paragraphs, then expand with detail, examples and nuance. This structure also makes your content much more likely to be featured in AI search citations and Google featured snippets.
Tip: Write your first draft naturally without worrying about readability, then do a dedicated readability editing pass. In that pass: split every sentence over 20 words, break every paragraph over 4 lines into two, convert every list embedded in a sentence into bullet points, and replace every passive construction with active. This systematic approach is faster than trying to write for readability from scratch.
Tags: content, readability, on-page, onpage
Case Study · +48 PageSpeed points | Bounce rate 67% to 44% | Revenue per session +33%
Case Study: E-Commerce Site Goes from 34 to 82 PageSpeed
How an e-commerce store improved their PageSpeed score by 48 points and reduced bounce rate by 23% through systematic performance fixes over 8 weeks.
Starting point
A mid-size e-commerce store selling home furnishings had a PageSpeed mobile score of 34. Their mobile bounce rate was 67% — meaning nearly two thirds of mobile visitors left without viewing a second page. Revenue per session on mobile was £1.40. The site had 47 product images served as uncompressed JPEGs with no width or height attributes, three render-blocking JavaScript files in the page head, and a server response time of 1.4 seconds. LCP was 6.8 seconds on mobile — well into the Poor range.
Fix 1: Image format conversion — plus 8 points
All 47 product images were converted from JPEG to WebP using an image optimisation plugin. Average image file size dropped from 180KB to 62KB — a 66% reduction. The product listing pages that previously loaded 2.5MB of images now loaded under 900KB. PageSpeed score jumped from 34 to 42. LCP improved from 6.8 seconds to 4.9 seconds — still in the Poor range but a significant improvement from image compression alone.
Fix 2: Add image dimensions — plus 7 points
All img tags were given explicit width and height attributes via theme template editing. The CLS score dropped from 0.42 (Poor) to 0.04 (Good) in a single change — product grids had been causing significant layout shift as product images loaded. PageSpeed score improved from 42 to 49. Bounce rate on the product listing pages began declining immediately — layout stability alone improved user experience measurably.
Fix 3: Defer render-blocking JavaScript — plus 14 points
Three third-party scripts were identified as render-blocking: a live chat widget, an analytics tag manager, and a product review widget. All three were moved to deferred loading — they now load after the page renders rather than blocking it. The First Contentful Paint improved from 4.1 seconds to 2.3 seconds. PageSpeed score went from 49 to 63. LCP improved to 3.1 seconds. Mobile bounce rate dropped to 58% from 67%.
Fix 4: Server-side caching — plus 19 points
Server-side page caching was enabled via the hosting control panel — each page is now built once and served as a static HTML file for subsequent visitors rather than being rebuilt from the database on every request. TTFB dropped from 1.4 seconds to 180ms. LCP improved from 3.1 seconds to 1.9 seconds — now in the Good range (under 2.5 seconds). Final PageSpeed score: 63 to 82. All three Core Web Vitals passed for the first time.
Business result
Eight weeks after completing all fixes: mobile bounce rate dropped from 67% to 44%. Revenue per session on mobile increased from £1.40 to £1.87 — a 33% improvement. The total revenue increase from mobile traffic alone more than paid for the development time within the first month. Organic search rankings for product category pages improved for 23 keywords as Google updated its Core Web Vitals assessment of the site.
Tip: Prioritise fixes in this order for maximum impact per hour of effort: server response time first (caching), then render-blocking scripts, then image format conversion, then image dimensions, then lazy loading. This order consistently produces the biggest score improvements most quickly across different types of sites.
Tags: performance, pagespeed, ecommerce, woocommerce
Case Study · +28% CTR | +97 monthly organic clicks | FAQ rich results on 4 queries
Case Study: FAQ Schema Increases CTR by 28%
A B2B SaaS company added FAQ schema to their pricing page and saw click-through rate improve by 28% within 6 weeks as FAQ rich results began appearing in Google.
The problem
A pricing page for a project management SaaS tool ranked at position 4 for its target keyword "project management software pricing". Despite a solid position, the page had an average CTR of 2.1% — significantly below the typical 5 to 7% expected at position 4. Competitors at positions 5 and 6 were generating more clicks despite ranking lower, because their results had FAQ rich results that took up substantially more space in the SERP.
Analysis
The page already contained eight common questions about pricing in the body copy: questions about free trials, monthly vs annual billing, team seat pricing, cancellation policy and data export. These were written as flowing text, not as explicit question-and-answer pairs, and had no schema markup. Adding FAQPage schema to surface these existing questions as rich results required only schema markup changes, not content rewriting.
Implementation
The FAQ content was restructured as visible Q&A pairs on the page, then FAQPage schema was generated using our Schema Generator from the page URL. The JSON-LD block containing eight question-and-answer pairs was added to the page head. Google Rich Results Test confirmed valid FAQPage markup within 24 hours. The page was submitted for re-indexing via Google Search Console URL Inspection.
Results after 2 weeks
FAQ rich results appeared in Google search for the primary target keyword and three related queries. The search result now displayed the page title and description plus two expandable FAQ items below — taking up roughly three times the vertical space of a standard result. Competitors at positions 5 and 6 no longer had the visual advantage that had been helping them outperform their ranking.
Results after 6 weeks
Average click-through rate improved from 2.1% to 2.7% — a 28% relative improvement. Total impressions stayed flat, confirming the CTR gain came from the richer result appearance rather than ranking improvement. Monthly organic clicks to the pricing page increased from 340 to 437. The pricing page became the second highest converting organic landing page on the site, up from fourth.
Tip: FAQ schema works best on pages that are already ranking on page one. It will not improve your ranking position but it will dramatically improve CTR once you are visible — by increasing the space your result occupies and showing searchers that your page directly addresses their questions before they click.
Tags: schema, rich-results, rich, ctr, b2b
Case Study · CLS 0.38 to 0.04 | Google Discover traffic 89% recovered | Full recovery in 12 weeks
Case Study: News Site Fixes CLS and Recovers Google Discover Traffic
A regional news publisher saw Google Discover traffic collapse after ad network changes caused CLS to spike to 0.38. Here is how they diagnosed and fixed the problem.
The incident
A regional news publisher switched ad networks in February to a new provider offering higher CPMs. Within three weeks, Google Discover traffic — which had been generating 180,000 monthly visits — dropped by 62%, falling to 68,000 monthly visits. Google Search Console showed a Page Experience signal warning flag. Investigation revealed that CLS had spiked from 0.05 (Good) to 0.38 (Poor) according to the CrUX field data — the new ad network was injecting ads differently from the previous one.
Diagnosis with CLS Debugger
Running the CLS Debugger on article pages identified three separate issues contributing to the 0.38 CLS score. First: 34 ad slots across article pages had no reserved minimum height — ads loaded into containers with no predefined size and pushed content down. Second: a sidebar widget loaded asynchronously 0.8 seconds after the main content, shifting the article body. Third: a cookie consent banner from the new ad network was injecting itself at the top of pages rather than as a fixed overlay, pushing all content down on every page load.
Fix 1: Ad slot reserved heights
All ad containers were given minimum heights matching the ad formats served: 250px for standard MPU ads, 90px for leaderboard ads, 600px for half-page ads. CSS was added: .ad-slot { min-height: 250px; background: #f8f8f8; }. This alone reduced CLS from 0.38 to 0.21 — the largest single fix. The background colour on ad containers also improved the visual experience during the ad loading period.
Fix 2: Sidebar skeleton placeholder
A CSS skeleton placeholder was added for the sidebar at its exact final dimensions. The sidebar content loaded into the pre-allocated space rather than appearing and expanding from zero height. CLS dropped from 0.21 to 0.09.
Fix 3: Cookie banner repositioning
The cookie consent banner CSS was updated from a top-of-page position to a fixed bottom overlay: position: fixed; bottom: 0; left: 0; right: 0; z-index: 9999. The banner now overlays content rather than pushing it. CLS dropped from 0.09 to 0.04 — well within the Good threshold. Total time from diagnosis to all three fixes deployed: 4 days.
Recovery
CrUX field data updated over the following 28 days as the rolling average adjusted to the new lower CLS. Google Discover traffic began recovering after four weeks and reached 89% of pre-incident levels after eight weeks. Full recovery to previous levels took twelve weeks. The lesson: Google Discover is highly sensitive to CLS and Page Experience signals. Any change that degrades CLS — particularly ad network changes — should be tested on a staging environment first.
Tip: If you run display advertising, monitor CLS every time you change ad networks, update ad sizes, or add new ad placements. Even a small CLS increase from ads can suppress Google Discover traffic significantly within weeks of the change appearing in field data.
Tags: cls, news, google-discover, discover, ads
Tutorial
How to build a deliberate internal link structure that passes authority to your most important pages, improves crawlability and keeps users on site longer.
Why internal links matter more than most people realise
Internal links distribute PageRank — Google's measure of page authority — across your site. Pages with more internal links pointing to them tend to rank higher because they accumulate more authority from the rest of the site. Internal links also help Google discover and index your content. New pages with no internal links may take weeks or months to be crawled and indexed, simply because Googlebot has no path to follow to reach them. Internal linking is entirely within your control, costs nothing, and can deliver ranking improvements within weeks of implementation.
Map your pillar pages first
Before adding internal links, identify your ten most commercially important pages. These are your main product and service pages, category pages, high-value landing pages, and cornerstone content guides. Write them down. These are your pillar pages — the pages that deserve the most internal link authority. Every other page on your site should be linking toward these, either directly or through intermediate hub pages. Think of your pillar pages as the centre of a wheel and every other page as a spoke.
Link from high-traffic content to pillar pages
Your blog posts and informational content pages typically attract significant organic traffic for long-tail keywords. This traffic is valuable in itself, but these pages also accumulate authority from their rankings that can be passed to your pillar pages through internal links. Every blog post you publish should contain at least one contextual internal link to a relevant pillar page, using descriptive anchor text that includes the target page's primary keyword. "Our guide to fixing Core Web Vitals" is strong anchor text. "Click here" passes nothing useful to Google.
Find and fix orphan pages immediately
An orphan page has no internal links pointing to it. Google may never crawl it because Googlebot has no path to follow to reach it. Even if it is in your sitemap, without internal links it accumulates no authority from the rest of your site. Use our Site Crawler to find orphan pages. For each one: identify three to five existing pages that cover related topics and add contextual internal links from those pages to the orphan. After fixing, submit the orphan URL for indexing via Google Search Console.
Breadcrumb navigation creates automatic linking
Breadcrumb navigation — the "Home > Category > Subcategory > Page" trail at the top of pages — automatically creates internal links across your entire site. Every page links to its parent category, which links to its parent category, creating a hierarchy of links that both helps users navigate and passes authority up through your site structure. Breadcrumbs also enable BreadcrumbList schema which shows the breadcrumb path directly in Google search results. Most CMS platforms support breadcrumbs natively or via a simple plugin.
Use descriptive, varied anchor text
Anchor text — the clickable words in a link — tells Google what the linked page is about. Exact match anchor text (using the target page's primary keyword word for word) is powerful but using it on every link looks manipulative. Vary your anchor text naturally: exact match for two or three of your most important internal links to each pillar page, partial match (using related phrases) for others, and descriptive text for the rest. Think about how a human editor would naturally reference another page when writing — that natural variation is what Google expects to see.
Audit internal links monthly
Run the Internal Links tool every month to find: new orphan pages created by recent content publishing, broken internal links caused by deleted or moved pages, pages receiving far fewer internal links than their commercial importance warrants, and redirect chains in internal links (linking to a URL that redirects to another URL — update to link directly to the final destination). Build internal link checking into your monthly SEO maintenance routine. New content should never be published without adding at least two or three internal links to it from existing pages on the same day it goes live.
Tip: Every time you publish new content, spend five minutes finding three to five existing pages to link from and three to five to link to. This five-minute habit prevents orphan accumulation, builds authority immediately, and is far faster than fixing internal links retrospectively after months of neglect.
Tags: internal-links, seo, architecture
Tutorial
Experience, Expertise, Authoritativeness and Trustworthiness — explained with concrete steps to improve your E-E-A-T signals and rank better for competitive queries.
What E-E-A-T actually is and is not
E-E-A-T is not a ranking factor in the traditional sense — Google has confirmed it is not a direct signal that an algorithm scores. It is a framework that Google's human Quality Raters use to evaluate whether pages are genuinely useful. Quality Raters score pages against Google's Search Quality Evaluator Guidelines and their scores are used to train and evaluate Google's ranking algorithms. Pages that rate highly for E-E-A-T tend to rank better because they align with what the algorithms have been trained to reward. Think of it as a description of what good content looks like, not a metric to game.
Experience — first-hand knowledge of the topic
The first E was added to the framework in December 2022. It distinguishes between genuine first-hand experience with a topic and theoretical knowledge about it. A review of a hotel written by someone who stayed there demonstrates Experience. A review scraped from other reviews does not. A DIY guide written by someone who has actually done the task with real photos demonstrates Experience. An AI-generated guide rewriting Wikipedia does not. Add first-hand evidence to your content: real photographs, actual data from your own experience, specific situations you have encountered, real case studies from your work or clients.
Expertise — demonstrated subject matter knowledge
Every content page should have a named author whose credentials are relevant to the topic. The author bio should mention specific qualifications, years of experience, organisations they belong to, and other publications they have written for. For medical content this means qualified medical professionals. For legal content this means qualified solicitors or barristers. For financial content this means regulated financial advisors. For technical content this means demonstrable technical credentials. The credentials need to be plausible and verifiable — anyone can claim to be an expert, so Google's Quality Raters look for corroborating evidence.
Authoritativeness — reputation within the field
Authoritativeness is largely about what others say about you, not what you say about yourself. External links from authoritative sites in your field, mentions in industry publications, citations of your research or data, positive reviews on established platforms, Wikipedia mentions, and press coverage all build authoritativeness. This is the hardest E-E-A-T component to build quickly because it depends on your real-world reputation. Digital PR — proactively getting your content and expertise mentioned by established publications — is the most effective accelerant.
Trustworthiness — accuracy and transparency
Trustworthiness is the most important component of E-E-A-T according to Google's guidelines. A page can have strong Experience, Expertise and Authority but still score poorly for Trustworthiness if it is inaccurate, misleading, or lacks basic transparency signals. Add: a clear privacy policy and terms of service, a physical address and phone number on your contact page, an About page that explains who runs the site and why, author bylines and dates on all content, sources and citations for factual claims, and customer reviews from verified platforms.
Add Person and Organization schema
Schema markup makes your E-E-A-T signals machine-readable for Google rather than just human-readable. Add Person schema to author pages with: name, jobTitle, knowsAbout (list of topic areas), sameAs (links to LinkedIn, Twitter, Wikipedia if present, other publication profiles). Add Organization schema to your About page or homepage with: name, url, logo, foundingDate (only if stated on the page), address, sameAs (links to Companies House, LinkedIn company page, Crunchbase, Trustpilot, major directory listings). These sameAs links explicitly connect your website to your real-world identity.
YMYL sites must prioritise E-E-A-T above everything else
YMYL stands for Your Money or Your Life — pages covering health advice, medical information, financial decisions, legal guidance, or safety-critical topics. Google's Quality Raters are specifically instructed to hold YMYL pages to a much higher E-E-A-T standard than other content, because errors on these pages can cause real harm to real people. If your site covers YMYL topics, E-E-A-T work is not optional — it is the most important ranking work you can do. Anonymous content, generic advice, and missing credentials on YMYL pages are evaluated very harshly in quality rater assessments.
Tip: Run our E-E-A-T Checker quarterly and track your score over time. Build E-E-A-T improvements into a standing quarterly review: Q1 add author bios and credentials, Q2 earn three press mentions, Q3 add schema markup, Q4 update all content with fresh data and examples. Consistent improvement compounds.
Tags: eeat, trust, authority, content
Tutorial
A systematic, tool-by-tool process for auditing a site technical SEO from crawlability and indexation through to performance and structured data.
Step 1: Crawlability check
Start with the Robots and Sitemap tool. Check robots.txt is not blocking important pages, directories or resources (CSS, JavaScript, images). Verify your XML sitemap exists, is accessible at /sitemap.xml or /sitemap_index.xml, contains all pages you want indexed, does not contain pages with noindex tags, and is submitted in Google Search Console. Check that the sitemap URL is referenced at the bottom of robots.txt so all crawlers can find it automatically.
Step 2: Indexation check
Search Google for site:yourdomain.com and count the results. Compare this to your actual page count. A large discrepancy — say you have 500 pages but Google shows 80 — indicates crawl budget issues, widespread noindex tags, or technical barriers preventing crawling. For specific suspicious pages, search site:yourdomain.com/specific-page — no result means not indexed. Check Google Search Console Pages report for a full breakdown of indexed vs excluded pages with reasons for each exclusion.
Step 3: On-page technical audit
Run the Site Audit on your homepage and five most important pages. Work through Critical issues first: missing title tags, missing H1, missing meta descriptions, noindex on important pages, missing canonical tags, broken canonicals pointing to 404s, HTTPS issues. Then address Warnings. Pay special attention to: duplicate title tags across multiple pages (keyword cannibalism risk), canonical chains (A canonicals to B which canonicals to C), and pages with both a canonical pointing elsewhere and content worth ranking.
Step 4: Page Speed audit
Run PageSpeed analysis on mobile for: homepage, your most important product or service page, and a typical blog post. Scores below 50 on mobile need urgent attention. Note which Opportunities appear consistently across multiple pages — fixing these in a shared template or plugin fixes them everywhere simultaneously. The most impactful cross-page fixes are usually: enabling server-side caching, adding defer to global JavaScript includes, and optimising the theme's hero image handling.
Step 5: Core Web Vitals field data
Check Google Search Console Core Web Vitals report. This shows real-user field data for your pages grouped into Good, Needs Improvement and Poor. Pages marked Poor are being actively penalised in rankings. Pages marked Needs Improvement are below Google's threshold but not yet penalised. Focus on moving Poor pages to Good first. Field data updates on a 28-day rolling average — allow four weeks after any fix before expecting changes to appear in GSC.
Step 6: Schema and rich results audit
Run Schema Debugger on your homepage, main product or service page, and any pages where you expect rich results. Check Google Search Console Enhancements section for reports on each schema type you use. For e-commerce sites: check Product schema on product pages. For local businesses: check LocalBusiness schema. For blogs: check Article schema. For any page with FAQ content: check FAQPage schema. Errors in schema silently prevent rich results — they need to be fixed even when the page is otherwise performing well.
Step 7: Document, prioritise and schedule
Create a prioritised fix list ordered by: severity (Critical before Warnings), page importance (key landing pages before blog posts), and effort-to-impact ratio (quick wins first). Assign each fix to a specific person with a clear brief explaining what to change and why. Schedule a follow-up audit four weeks later to measure progress and catch any new issues introduced by the fixes. Track your overall Site Audit score over time — a rising trend confirms the work is building stronger foundations.
Tip: Run a full technical audit quarterly at minimum. Run a targeted audit — just Site Audit on key pages — after every significant deployment. Quarterly audits catch slow-building problems. Post-deployment audits catch immediate regressions. Both are necessary for sites that update regularly.
Tags: technical-seo, audit, checklist, crawlability
Tutorial
Everything you need to know about Open Graph, Twitter Cards and social meta tags — why they matter for SEO and traffic, and how to implement them perfectly.
Why social tags matter for SEO indirectly
Open Graph and Twitter Card tags are not direct ranking signals — Google has confirmed this. However, they dramatically affect what happens when your content is shared on social media. Without them, Facebook shows no image and picks random text for the description. LinkedIn uses your page title without the carefully crafted social description. WhatsApp and Slack show a broken or ugly preview card. This poor presentation reduces click-through rate from social shares, which reduces the traffic and engagement signals that do influence rankings indirectly.
The four required Open Graph tags
Every page on your site should have these four OG tags as a minimum. og:title — the headline shown when the page is shared. This can be different from your title tag and is often more conversational. og:description — two sentences summarising the page content, written to make someone want to click. og:image — the image displayed in the preview card, minimum 1200x630 pixels, ideally a custom image specific to the page rather than your logo. og:url — the canonical URL for this page, ensuring all shares point to the right version even if people copy a URL with tracking parameters.
Image specifications for every platform
The ideal OG image is 1200x630 pixels (1.91:1 ratio) and works correctly on Facebook, LinkedIn, WhatsApp, Slack and Discord. Twitter (now X) prefers 1200x600 pixels for summary_large_image cards — a slightly wider ratio. Always specify og:image:width and og:image:height alongside the og:image URL, because without these dimensions some platforms (particularly Facebook) may not display the image. Keep image file sizes under 8MB. Use JPG or PNG format — WebP is not consistently supported by all social platforms yet.
Additional tags that add value
Beyond the four required tags: og:type sets the content type — use "website" for your homepage and category pages, "article" for blog posts and news. og:site_name displays your brand name alongside the page title in some platforms. og:locale specifies the language and region (en_GB for British English). For articles add: article:published_time, article:modified_time, article:author and article:section. These give Facebook and LinkedIn additional context for categorising and promoting your content to relevant audiences.
Twitter Card types and implementation
Twitter reads its own meta tags separately from OG tags, though it falls back to OG tags for some properties. The most important Twitter tags: twitter:card specifies the card type — always use "summary_large_image" for content pages as it shows the largest image and gets highest engagement. twitter:title and twitter:description can differ from OG equivalents to optimise specifically for Twitter audiences. twitter:image can be the same as og:image or a cropped version optimised for Twitter's crop. twitter:site sets your Twitter handle for attribution.
Dynamic OG tags per page
Site-wide default OG tags are better than nothing but dramatically underperform per-page custom tags. Your homepage OG title describing your whole business is wrong for a product page or blog post. Set up your CMS to generate unique OG tags for every page automatically: for product pages, pull the product name, description and product image; for blog posts, pull the post title, excerpt and featured image; for category pages, pull the category name and description. Most SEO plugins handle this automatically once configured.
Test and force cache refresh
Use Facebook Sharing Debugger (developers.facebook.com/tools/debug) to preview exactly how any URL will appear when shared on Facebook, and to force Facebook to clear its cache of old OG data after you update tags. Use LinkedIn Post Inspector (linkedin.com/post-inspector) for LinkedIn previews. Use Twitter Card Validator for X (cards-dev.twitter.com/validator). After updating OG tags on a page, always clear the social platform caches — without this, the old cached version appears in shares for up to seven days even after you update the tags on your server.
Tip: Create custom OG images for your most important pages: homepage, main service pages, and any content you plan to actively promote on social media. Custom images specific to the content significantly outperform generic site images or logos. Tools like Canva make creating 1200x630 template-based OG images quick and consistent.
Tags: open-graph, social, meta-tags, twitter
Tutorial
How to handle URL changes, site migrations and redirect chains without losing ranking authority — and when to use 301, 302, canonical and noindex.
301 vs 302 — when to use each
A 301 redirect is permanent. It tells Google: this URL has moved forever, update your index to the new URL, and pass all the link equity from the old URL to the new one. A 302 redirect is temporary. It tells Google: this URL has moved for now, keep the old URL in your index because it might come back, pass less link equity. Use 301 for: permanent URL changes, HTTP to HTTPS migration, www to non-www consolidation, domain name changes, site migrations. Use 302 for: genuine temporary redirects like seasonal campaign pages, A/B testing alternative versions, maintenance pages (though even these are usually better handled with server configuration rather than redirects).
Avoid redirect chains at all costs
A redirect chain is URL A redirecting to URL B which redirects to URL C which redirects to URL D. Each hop in the chain: reduces the link equity passed by approximately 15% (though Google has not confirmed exact numbers), adds 100 to 500 milliseconds of load time per hop (a three-hop chain can add over a second to page load time), and risks crawlers giving up before reaching the final destination on very long chains. Use our Redirect Checker to map all chains on your site. Fix by updating each redirect to point directly to the final destination — A to D directly, skipping B and C.
Canonical tags — how and when to use them
A canonical tag (<link rel="canonical" href="..."> in the page head) tells Google which version of a page is the authoritative master. Use canonicals for: URL parameter variants (?sort=price, ?colour=red, ?utm_source=email all canonical to the clean URL), paginated pages (page 2, 3, etc. canonical to themselves, not to page 1), content syndicated to other sites (the original canonicals to itself), and multiple URL paths to the same content (e.g. /products/shoes and /sale/shoes showing the same page). Every page should have a self-referencing canonical — even pages with no duplicates — as insurance against URL parameter contamination.
Common canonical mistakes
Canonical pointing to a 404 page: Google ignores it, no consolidation happens. Canonical pointing to a redirect: Google follows the chain but it is inefficient — point canonicals directly to the final URL. Canonical pointing to a noindex page: contradictory signals, unpredictable behaviour. Every page canonicaling to the homepage: this tells Google your entire site is just one page and catastrophically dilutes all your page authority. Mixing HTTP and HTTPS in canonicals: always use https:// in canonical URLs after migrating to HTTPS. Using relative URLs in canonicals: always use absolute URLs including the full domain.
URL structure best practices
URL structure decisions made early are hard to change later because every change requires redirects. Follow these principles from the start: use hyphens not underscores to separate words (Google treats underscores as word joiners, not separators), use lowercase always, include the primary keyword for the page, keep URLs short (under 60 characters is ideal), use directory structure that reflects your content hierarchy (/products/shoes/running/ not /p/52/7/), avoid dates in URLs for content you want to remain relevant long-term, and avoid session IDs and tracking parameters in canonical URLs.
Site migration checklist
Migrating to a new domain or URL structure requires careful preparation. Six weeks before: crawl the current site and document every URL, its traffic, rankings and backlinks. Four weeks before: build the redirect map, mapping every old URL to its new equivalent. Two weeks before: build and test the new site with redirects in place. Launch day: switch DNS, immediately verify all redirects are working, submit updated sitemap to GSC, set up new GSC property if domain changed. Weeks one to six post-launch: daily GSC check for 404 errors, fix any within 24 hours.
When to use noindex instead of redirect or canonical
Use noindex (a meta robots tag with noindex value) when you want a page to remain accessible to users but be removed from Google's index entirely. Common uses: thank-you pages, order confirmation pages, admin pages, staging pages, pages with duplicate content that cannot be resolved with canonicals. Never combine noindex with a canonical — they contradict each other. Never add noindex to a page that already has a canonical pointing elsewhere — the noindex removes the page from the index regardless of the canonical.
Tip: Never redirect an entire site to the homepage using a catch-all redirect. Redirect each URL to its most relevant equivalent on the new site. Mass homepage redirects are interpreted by Google as soft 404s — the page is technically accessible but returns irrelevant content — and strip all link equity from those URLs rather than passing it to the homepage.
Tags: redirects, canonical, url-structure, url, migration
Tutorial
How to identify the right keywords for your business, map them to pages, prioritise which to target first, and use data to track performance over time.
Start with search intent, not search volume
The most common keyword research mistake is optimising for search volume without understanding intent. A keyword with 10,000 monthly searches where the intent is informational will drive traffic but not conversions if your page is transactional. Every keyword has one of four intents: informational (the user wants to learn something — how to, what is, guide to), navigational (the user wants to find a specific website or brand), commercial (the user is researching before buying — best, reviews, vs, alternatives), or transactional (the user is ready to act — buy, price, book, hire, quote). Your page content must match the intent of the keyword you are targeting, or you will not rank.
Build your seed keyword list
Write down every phrase a potential customer might use to find your business. Think about: the problems your product or service solves, the outcome customers are looking for, the specific names of your products or services, your geographic location if relevant, and the questions customers ask before they buy. Aim for 20 to 40 seed keywords without filtering. Quality of thinking at this stage determines the quality of your entire keyword strategy. Then expand each seed using Google autocomplete (type the keyword and look at the suggestions), People Also Ask boxes in search results, and Related searches at the bottom of the results page.
Keyword mapping — one primary keyword per page
Keyword mapping means assigning specific keywords to specific pages. The golden rule: one primary keyword per page, supported by two to four semantically related secondary keywords. Two pages targeting the same primary keyword create keyword cannibalism — they compete with each other for rankings and Google struggles to determine which page is more relevant. Neither ranks as well as a single strong, focused page would. Use our Keyword Gap tool to find cannibalisation across your site. When you find it: either merge the two pages into one comprehensive page, or differentiate them to target clearly different intents.
Long-tail keywords are your fastest wins
Long-tail keywords are specific, longer phrases with lower search volume but much higher intent and much lower competition. "Running shoes" is short-tail — dominated by Nike, Adidas and major retailers with enormous domain authority. "Minimalist trail running shoes for wide feet UK" is long-tail — a specific, high-intent search where a small specialist retailer can realistically rank. New sites and small sites should focus almost entirely on long-tail keywords initially. As you rank for long-tail terms, your domain authority grows, and you can gradually target shorter, more competitive terms.
Map keywords to the buyer journey
Different stages of the buyer journey require different content types and target different keywords. Top of funnel (awareness): the customer knows they have a problem but does not know solutions yet — target informational keywords like "how to improve website speed". Middle of funnel (consideration): the customer is comparing options — target commercial keywords like "best website speed optimisation tools" or "pagespeed vs gtmetrix". Bottom of funnel (decision): the customer is ready to buy — target transactional keywords like "website speed audit tool" or "pagespeed optimisation service". Build content for all three stages to capture customers at every point in their journey.
Use our Keyword Analysis tool
Enter any page URL in our Keyword Analysis tool to see which keywords the page currently uses and how prominently, identify gaps in keyword coverage compared to competing pages, and find keyword cannibalisation issues. For pages targeting specific keywords, use the tool to check: is the primary keyword in the title tag, H1, first paragraph, and at least two subheadings? Are secondary keywords naturally distributed through the body text? Is the keyword density in a natural range (0.5 to 1.5 percent is typical — much higher reads as stuffing)?
Refresh existing content before creating new
Before writing a new page for a keyword, check whether you already have a page that ranks for it — even at position 15 or 20. Updating and expanding an existing page that has some authority built up is almost always faster to results than creating a new page from scratch. A page ranking at position 12 can often be moved to position 5 with: updated content adding depth and freshness, additional internal links pointing to it, improved schema markup, and any technical issues fixed. Save new page creation for keywords where you have nothing ranking at all.
Tip: The best keywords for a new or small site are specific, local or niche — terms where established authority is not required to rank. Win in specific niches first, build authority, then expand to broader more competitive terms. Trying to rank for head terms from day one wastes resources and produces no results.
Tags: keywords, keyword, content, strategy, seo
Tutorial
Google SEO Fundamentals: How Google Finds, Crawls and Ranks Your Pages
A complete plain-English guide to how Google Search actually works — crawling, indexing, ranking and content quality — and what that means for every decision you make about your site.
How Google discovers new pages
Google uses automated programs called crawlers — primarily Googlebot — to continuously explore the web by following links from page to page. The vast majority of pages Google discovers and indexes are found through links rather than through sitemap submissions. When another indexed page links to your page, Googlebot follows that link and visits your page. This is why getting your first few external links is so important for a new site — without links, Googlebot may never find your pages even if they are technically perfect. Submit a sitemap to Google Search Console to supplement link discovery, but do not rely on it alone.
Crawling vs indexing — a crucial distinction
Crawling is Googlebot visiting your page and downloading its HTML content. Indexing is Google analysing that content, deciding it is worthy of inclusion, and storing it in the search index. These are two separate steps and a crawled page is not guaranteed to be indexed. Google may choose not to index pages it considers too thin (insufficient content to be useful), duplicate (content nearly identical to another indexed page), or low quality (content that does not meet the helpfulness standard). Check Google Search Console Pages report to see exactly which pages are indexed and which are excluded, with the specific reason for each exclusion.
How Google reads and understands your content
Google reads your HTML and extracts: the title tag (what you named the page), the H1 tag (the main heading users see), all other headings in hierarchy order, the body text and its structure, the alt text on images, the anchor text of links, the canonical URL, meta description, and structured data in JSON-LD format. Google also executes JavaScript — it renders your page in a headless Chromium browser so JavaScript-rendered content is visible to Googlebot. However JavaScript content is typically processed in a second crawl wave which can delay indexing by days or weeks. For important content, prefer server-side rendering or static HTML over JavaScript rendering.
What makes Google rank one page higher than another
Google uses hundreds of signals. The most impactful: relevance (how well the page content matches the specific query — semantic match not just keyword match), authority (the quantity and quality of other trusted pages linking to your page), page experience (Core Web Vitals, mobile-friendliness, HTTPS, absence of intrusive interstitials), content quality (E-E-A-T signals, depth, accuracy, freshness), and user intent match (does your page format match what users actually want for this query — are they looking for a list, a video, a product page, or a how-to guide). No single signal dominates — Google weighs all of them together.
Content quality is the foundation
Google has been increasingly explicit that creating genuinely helpful, people-first content is the most important thing you can do for SEO. Content should be unique — not copied or paraphrased from other sources. It should be accurate — factually correct and up to date. It should be comprehensive — covering the topic sufficiently for the searcher to accomplish their goal. It should demonstrate genuine expertise — showing that the author has real knowledge and experience with the topic. Content written primarily to rank in search rather than to help the reader tends to be identified and downgraded. Write for your reader first, optimise for search second.
Site structure and URL organisation
Google recommends using descriptive, human-readable URLs that include words related to the page content. A URL like /seo-tools/keyword-research/ tells both users and Google what the page covers before anyone clicks on it. A URL like /p/1234/variant/7/ tells nobody anything. Group topically related pages in logical directories — /blog/ for blog content, /services/ for services, /products/ for products. This helps Google learn how often different sections of your site change and crawl them at appropriate frequencies. Clean URL structure also makes it easier for users to understand where they are on your site.
Technical requirements Google cares about most
Google has relatively few hard technical requirements but several strongly recommended practices. Hard requirements: pages must be accessible to Googlebot (not blocked by robots.txt or login walls), pages must return a 200 status code to be indexed, and content must not be hidden from Googlebot using JavaScript tricks or CSS hiding. Strongly recommended: HTTPS (a confirmed ranking signal since 2014), mobile-friendly design (mobile-first indexing means Google primarily uses the mobile version), fast loading (Core Web Vitals are ranking signals), and no intrusive interstitials that block content (a negative ranking signal).
What Google explicitly says does not matter
Google has published guidance dispelling common SEO myths. These factors have confirmed minimal or zero impact on rankings: the meta keywords tag (not read at all), keyword stuffing (actively penalised), keywords in the domain name (negligible ranking impact, barely matters), exact minimum or maximum content length (no magic word count), number and order of headings (heading order is not a ranking signal per se), and E-E-A-T as a direct algorithmic signal (it is a quality evaluation framework, not a scored ranking factor). Focus on genuine quality rather than these commonly over-emphasised factors.
Tip: Set up Google Search Console for every site you manage. It is completely free. It shows you exactly how Google currently sees your site, reports crawling and indexing errors, surfaces security issues, and shows your performance data for every keyword you rank for. It is the single most important tool for understanding and improving your relationship with Google Search.
Tags: seo, google, crawling, indexing, ranking, fundamentals
Tutorial
Bing SEO: How to Rank on the Second Largest Search Engine
Bing powers 28% of US searches, serves 140 million daily active users, and backs ChatGPT Search. Here is how Bing works differently from Google and how to optimise for it.
Why Bing deserves serious attention in 2025
Bing and the Microsoft Search Network account for approximately 28% of all US desktop search queries and around 12% of global desktop searches. Bing serves 140 million daily active searchers who do not use Google. The typical Bing user in the US is an educated professional aged 35 to 54 with a household income above $75,000 — a highly valuable demographic for many businesses. Beyond direct Bing traffic, OpenAI ChatGPT Search now uses Bing as its search backend, meaning that Bing indexing is increasingly critical for AI search visibility across multiple platforms including Bing, ChatGPT, Cortana and Yahoo.
Key difference: desktop-first indexing
Google uses mobile-first indexing — it crawls and ranks primarily based on your mobile site version. Bing takes the opposite approach: it uses desktop-first indexing. This does not mean you should neglect mobile — users on all devices access Bing and a poor mobile experience hurts engagement signals. But for Bing, your desktop version drives the primary understanding of your content. Ensure your desktop site is technically perfect and comprehensively optimised, then ensure the mobile version is also fully functional.
Bing ranking factors and how they differ from Google
Bing is more transparent about its ranking factors than Google. Bing explicitly weights: content quality and relevance (Bing rewards exact-match keywords more than Google — include your target keyword naturally in the title, H1, opening paragraph and meta description), backlink quality (Bing is very strict about link schemes and can de-index sites participating in link farms or paid link schemes faster than Google), user engagement signals (dwell time and click-through rate are explicitly acknowledged signals), page load speed (directly stated as a ranking factor), social media signals (Bing acknowledges indirect social influence), and local factors for location-sensitive queries.
Set up Bing Webmaster Tools properly
Bing Webmaster Tools (bing.com/webmasters) is Bing's equivalent of Google Search Console and it is free. Create an account and verify your site — you can import verification from Google Search Console automatically if you are already verified there. Submit your sitemap. Explore the dashboard: the Search Performance report shows clicks, impressions, CTR and average position for your keywords. The URL Inspection tool shows whether specific pages are indexed and any crawl issues. The Site Audit tool identifies technical SEO issues. The Keyword Research tool shows Bing-specific search volumes. The Backlinks report shows your backlink profile as Bing sees it.
Content optimisation specifically for Bing
Bing places more emphasis on exact-match keywords than Google does. Include your primary keyword verbatim in the title tag, H1, first paragraph, and at least two subheadings. This does not mean keyword stuffing — density above 2% is still counterproductive. Bing also places more weight on established, older content compared to Google. A page that has been indexed and accumulating positive engagement signals for two years may rank better on Bing than a newer page with higher quality content. Refreshing and updating older content specifically helps Bing rankings in ways it is less impactful for Google.
Social signals and Bing
Unlike Google (which denies using social signals directly), Bing acknowledges that social media activity influences its algorithm indirectly. Content that receives genuine social engagement tends to earn natural backlinks and citations, and Bing's algorithm detects and rewards this pattern. An active social media presence, shareable content, and consistent posting can benefit Bing rankings in ways that are separate from their direct traffic value. Microsoft owns LinkedIn, which Bing integrates with — LinkedIn presence and authority has a closer relationship with Bing rankings than with Google rankings.
Bing AI features and Copilot integration
Bing has deeply integrated AI through Copilot (formerly Bing Chat) and generative search. Bing's generative search organises AI-powered summaries of search results into sections with source links. Content that performs well in traditional Bing search also tends to be cited in Bing AI features — the same quality signals apply. Bing recently launched Copilot in Webmaster Tools, an AI assistant that analyses your specific site data and provides personalised SEO recommendations. This is a genuinely useful feature that Google Search Console does not yet have an equivalent for.
Schema markup on Bing
The same JSON-LD schema markup you implement for Google works for Bing. Bing supports schema.org structured data and uses it to understand content and power rich results in Bing Search. Bing Webmaster Tools reports schema errors separately from Google Search Console, so both need to be checked after implementing or updating schema. Bing particularly values LocalBusiness schema for local search results, Product schema for shopping queries, FAQPage schema for question-based queries, and Article schema for news and blog content.
Tip: The additional effort required to optimise specifically for Bing is small because most Google best practices also work for Bing. Read the Bing Webmaster Guidelines at bing.com/webmasters/help/webmaster-guidelines-30fba23a — they are more detailed and explicit than Google's guidelines and provide a clear checklist of what Bing rewards and what it penalises.
Tags: bing, seo, microsoft, ai-search, webmaster
Tutorial
Schema.org defines 800+ types but Google supports a specific subset for rich results. This guide covers every Google-supported type, required properties, and what each one unlocks.
How the schema.org hierarchy works
Schema.org organises its types in a class hierarchy. Everything inherits from the base type "Thing". Thing branches into CreativeWork, Organization, Person, Place, Product, Event, Action and others. CreativeWork branches into Article, Book, Recipe, HowTo, FAQPage, VideoObject, MusicRecording and many more. Each subtype inherits all the properties of its parent type and adds its own specific properties. This means a BlogPosting (which extends Article, which extends CreativeWork, which extends Thing) has access to all properties from all four levels. Always use the most specific applicable type — it signals more precisely what your content is and increases rich result eligibility.
Article, BlogPosting and NewsArticle for content sites
Use Article for general editorial content where a more specific type does not apply. Use BlogPosting for blog posts — it is more specific than Article and more accurately describes the content type. Use NewsArticle for news content — it enables eligibility for the Top Stories carousel in Google Search. Required properties for all three: headline (the article title), image (at minimum one image URL, minimum dimensions 1200x628 pixels for rich results), datePublished (ISO 8601 format: 2024-03-15), author (a Person or Organization object with name). Recommended: dateModified, publisher (Organization with name and logo), description, url. Enables: author bylines in Google Discover, article carousel in Discover, Top Stories eligibility for NewsArticle.
Product for e-commerce pages
Product schema enables price, availability and star ratings to appear directly in search results without the user clicking through. Required for a basic product snippet: name, image, description. Required for price display: offers (containing an Offer object with price as a number, priceCurrency as ISO 4217 code like GBP or USD, and availability as a schema.org URL like https://schema.org/InStock). Required for rating display: aggregateRating (containing ratingValue as a number not a string, and reviewCount as a number). Never use AggregateOffer for a single product page — use a single Offer object. Keep price and availability updated dynamically — stale data causes policy violations and can result in rich results being removed.
FAQPage for question-and-answer content
FAQPage schema enables expandable Q&A boxes directly under your search result, potentially doubling the visual space your result occupies on the page. Required structure: mainEntity containing an array of Question objects, each with name (the question text as a complete sentence) and acceptedAnswer containing an Answer object with text (the answer as a complete self-contained response). Rules: all questions and answers must be visible on the page as content users can read. You cannot hide FAQ content with CSS. Questions must have exactly one accepted answer. Do not use FAQ schema for promotional content or links to products — Google specifically prohibits this use.
HowTo for instructional content
HowTo schema can unlock step-by-step instruction displays in Google Search with each step shown individually. Required: name (the title of the how-to guide), step (an array of HowToStep objects each with name and text). Recommended: totalTime using ISO 8601 duration format (PT30M for 30 minutes, PT1H30M for 90 minutes), estimatedCost (with currency), supply (list of supplies needed), tool (list of tools needed). Each HowToStep can include an image showing that specific step — this creates rich visual instructions in search results. All steps must be visible on the page.
LocalBusiness and its 200+ subtypes
LocalBusiness has over 200 specific subtypes covering virtually every type of physical business. Always use the most specific available subtype: Plumber not LocalBusiness for a plumbing company, Dentist not MedicalBusiness for a dental practice, Restaurant not FoodEstablishment for a restaurant, HairSalon not BeautySalon for a hair salon. Required for local rich results: name, address (a PostalAddress object with streetAddress, addressLocality, postalCode, addressCountry in ISO alpha-2 format like GB), telephone. High-value recommended properties: geo (GeoCoordinates with latitude and longitude as numbers not strings), openingHoursSpecification (day-by-day hours), aggregateRating, priceRange, sameAs (array of links to GBP, Facebook, Yelp, directories).
Event for event listings
Event schema enables event rich results showing date, location and ticket information directly in search results. Required: name (the event name), startDate (ISO 8601 including time: 2024-06-15T19:00), location (a Place object with name and address, or VirtualLocation for online events). Recommended: endDate, description, offers (with price and availability), organizer (Organization or Person), image, eventStatus using schema.org URLs (EventScheduled, EventCancelled, EventRescheduled, EventPostponed), eventAttendanceMode (OfflineEventAttendanceMode, OnlineEventAttendanceMode, or MixedEventAttendanceMode). For cancelled or rescheduled events always update eventStatus immediately to avoid misleading searchers.
BreadcrumbList, WebSite and SoftwareApplication
BreadcrumbList shows your page hierarchy as a breadcrumb trail in Google search results, replacing the URL. Required: itemListElement (an array of ListItem objects each with position as a number, name as the breadcrumb label, and item as the full URL). Add to every page to consistently show breadcrumbs in results. WebSite schema enables the sitelinks search box that appears for branded searches. Required: name, url. Recommended: potentialAction (SearchAction). SoftwareApplication displays app ratings and category in search results. Required: name, operatingSystem, applicationCategory. Recommended: aggregateRating, offers with price. Avoid on SoftwareApplication: applicationSubCategory (not Google-supported), browserRequirements (not supported).
Tip: Always validate new schema with Google Rich Results Test (search.google.com/test/rich-results) before deploying. Not all schema types unlock visual rich results — some improve Google understanding without changing how the result looks. The Rich Results Test shows exactly which rich result types your schema qualifies for, so you know what to expect.
Tags: schema, structured-data, rich-results, json-ld, types
Tutorial
JavaScript SEO: How to Ensure Google Indexes Your JS-Rendered Content
JavaScript-heavy sites can be invisible to search engines if not built correctly. Here is exactly how Google handles JavaScript, what goes wrong, and how to fix it.
How Google handles JavaScript
Google can execute JavaScript and render pages similarly to a web browser — it uses a headless version of Chromium to render JavaScript-heavy pages. However JavaScript rendering happens in a second crawl wave, separate from the initial HTML crawl. The first wave crawls raw HTML within seconds of Googlebot visiting. The second wave renders JavaScript, but this can take days or even weeks. During this delay, JavaScript-rendered content is invisible to Google. For time-sensitive content or pages where indexing speed matters, this delay is a significant problem.
The server-side rendering solution
Server-side rendering (SSR) generates the complete HTML on the server before sending it to the browser. Googlebot receives a fully rendered page in the first HTTP response without needing to execute any JavaScript. This eliminates the crawl delay and makes content available for indexing immediately. For React applications: Next.js provides SSR out of the box. For Vue applications: Nuxt.js. For Angular: Angular Universal. If full SSR is not feasible, consider static site generation (SSG) for content that does not change often — it pre-generates HTML at build time with the same indexing benefit.
Dynamic rendering as a middle ground
Dynamic rendering serves pre-rendered static HTML to crawlers while serving the JavaScript version to users. Googlebot receives the fast-indexable static version while users get the full interactive experience. This is implemented with a reverse proxy that detects the user agent — serving the pre-rendered version to crawlers and the JavaScript version to browsers. Tools like Rendertron or Prerender.io provide the pre-rendering service. Google acknowledges dynamic rendering as a valid workaround but considers it a transitional solution rather than a long-term recommendation.
What JavaScript patterns cause indexing failures
Several JavaScript patterns commonly cause content to be missed by Google. Lazy loading without intersection observer: content that loads only on scroll may not be seen by Googlebot which has a limited scroll simulation. Infinite scroll: content that only appears after user scrolling actions may never be indexed — use paginated URLs instead. Client-side routing with incorrect meta tag handling: single-page applications that update content without properly updating the title, meta description and canonical tags for each route. Soft navigation: pages that update content dynamically without triggering a new page load — each logical page needs a distinct URL to be individually indexable.
Testing JavaScript rendering
Google Search Console URL Inspection tool is the most reliable way to test how Google actually renders your page. Enter any URL and click "Test Live URL" — it shows a screenshot of how Googlebot renders the page and highlights any resources that failed to load. Compare this screenshot to how the page looks in a browser. Any content missing in the GSC screenshot is content Googlebot cannot see. Also check the "Page Resources" section which shows which JavaScript files were blocked, failed to load, or timed out during rendering.
JavaScript SEO checklist
For any JavaScript-heavy site, check these items. Each route in your application has a unique URL (not just hash fragments). Title tags, meta descriptions and canonical tags update correctly when navigating between routes. Structured data (JSON-LD) is present in the initial HTML response, not injected by JavaScript after rendering. Navigation links use proper href attributes so Googlebot can follow them (not just JavaScript click handlers). Images have alt text set in HTML not just applied by JavaScript after load. Server-side or static rendering is used for content that needs to be indexed promptly.
Internal linking in JavaScript applications
Googlebot discovers pages by following links. In JavaScript applications, navigation often uses JavaScript-only click handlers rather than actual href attributes in anchor tags. Googlebot cannot follow JavaScript-only links — it only follows href attributes. Ensure all navigation links in your application include proper href attributes: <a href="/page"> not just <div onclick="navigate()">. For React Router, Vue Router and Angular Router: all navigation components should generate standard anchor tags with href attributes in the rendered HTML. Check the rendered HTML source (not the JavaScript source) to verify href attributes are present.
Tip: Use Google Search Console URL Inspection with "Test Live URL" to see exactly what Googlebot sees when it renders any page on your site. A five-second check comparing the Googlebot screenshot to the browser view instantly reveals whether JavaScript content is being indexed correctly.
Tags: javascript, crawling, indexing, technical, rendering
Tutorial
The specific SEO challenges and opportunities for e-commerce sites — from product schema and category page optimisation to handling out-of-stock products and faceted navigation.
Category pages are your highest-value ranking opportunity
For most e-commerce sites, category pages (also called collection pages or listing pages) represent the biggest ranking opportunity. They can rank for high-volume commercial keywords like "running shoes" or "wooden dining tables" that individual product pages cannot compete for. Category pages should have: a unique H1 targeting the primary category keyword, at least 200 to 300 words of unique descriptive content above or below the product grid (not just a grid of products with no text), breadcrumb schema, and internal links to the most important products in the category. Avoid duplicate content across similar category pages by writing unique descriptions for each.
Product page optimisation fundamentals
Each product page should target the specific product name plus key descriptive attributes as its primary keyword. The title tag format that works well: Product Name + Key Attribute + Brand Name (example: "Merrell Moab Speed Trail Running Shoes | ShoeStore UK"). The H1 can be slightly different — more descriptive and including additional attributes. Product descriptions should be unique — never copy manufacturer descriptions verbatim as this creates duplicate content across hundreds of retailers. Write descriptions that answer the actual questions customers have before buying: sizing, materials, use cases, warranty, delivery.
Product schema implementation
Every product page needs Product schema with: name, image (multiple if possible), description, sku or mpn for product identification, brand (as an Organization or Brand object), offers containing price, priceCurrency, availability and priceValidUntil. For sites with customer reviews: aggregateRating with ratingValue and reviewCount. Availability must be kept dynamically updated — showing InStock for out-of-stock products is a policy violation that can result in rich results being removed and a manual action against your site. If you use a platform like Shopify or WooCommerce, a schema plugin handles this automatically.
Handling out-of-stock and discontinued products
Out-of-stock products: keep the page live with availability set to OutOfStock in schema. Update the buy button to show "Notify me when back in stock". Do not redirect to the category page — the product page retains its SEO value and backlinks. If the product is temporarily out of stock, keeping it indexed maintains the ranking built up while in stock. Discontinued products with significant traffic or backlinks: keep the page live and redirect visitors to the most similar available product. Discontinued products with minimal value: 301 redirect to the closest category page, not the homepage.
Faceted navigation — the biggest technical challenge
Faceted navigation (filter menus that create URLs like /shoes?colour=red&size=42&brand=nike) is one of the most common sources of duplicate content and crawl budget waste for e-commerce sites. Each filter combination creates a new URL with nearly identical content. The solutions: use canonical tags on filtered pages pointing to the base category URL, add noindex to filtered pages that have no unique SEO value, use JavaScript for filtering that updates the display without changing the URL, or implement hreflang-style parameter handling in Google Search Console. Never simply block filtered URLs in robots.txt — this prevents crawling but not indexing if external links point to them.
Pagination for product listings
Large category pages with pagination (page 2, page 3, etc.) present indexing decisions. Google recommends treating each paginated page as its own independent page rather than using canonical tags pointing all pagination to page 1 (which was previously recommended but is now discouraged). Each paginated page should be accessible to Googlebot, have a unique title tag indicating the page number, and be linked from pagination navigation. For very large catalogues (thousands of products across hundreds of paginated pages), use crawl budget wisely: ensure your most important product pages get linked from as early in the pagination as possible.
Site architecture for e-commerce
E-commerce site architecture profoundly affects which pages rank. The ideal structure: Homepage links to all top-level category pages. Category pages link to subcategory pages and feature the most important individual products. Product pages link back to their parent category and related products. This creates a clear hierarchy that passes authority from the homepage down through categories to products. Every product page should be reachable within three to four clicks from the homepage — pages buried deeper in the hierarchy are crawled less frequently and rank less well.
Tip: Google has specific e-commerce SEO documentation at developers.google.com/search/docs/specialty/ecommerce that covers product data sharing, structured data for commerce, and launch checklists. Read it as a supplement to this guide — it reflects Google's current specific guidance for online retailers.
Tags: ecommerce, product, schema, category, seo
Tutorial
Local Link Building: How to Earn Backlinks That Boost Local Rankings
Backlinks from locally relevant and authoritative sources are one of the strongest signals for Maps 3-pack rankings. Here is exactly how to earn them systematically.
Why local backlinks matter differently from general backlinks
For local search rankings, the relevance and local authority of a backlink matters as much as its general authority. A backlink from the Sheffield Chamber of Commerce is more valuable for a Sheffield business than a backlink from a national news site, even if the national site has higher domain authority. Local backlinks signal to Google that you are a genuine, established presence in your local community. They also tend to be easier to earn than national backlinks because you have a genuine local connection — you are competing for the same backlinks as local competitors, not national ones.
Local business directories — the foundation
Start with the major directories that every local business should be listed in. In the UK: Google Business Profile (mandatory), Bing Places, Apple Maps Connect, Facebook Business, Yell, Yelp, Thomson Local, Checkatrade (for tradespeople), Trustpilot, FreeIndex, TouchLocal. Each listing should have your exact NAP (Name, Address, Phone) matching your website and GBP. Each directory listing earns a backlink to your site and a local citation. A consistent set of 20 to 30 high-quality directory listings is more valuable than 200 inconsistent or low-quality ones.
Chamber of Commerce and business associations
Join your local Chamber of Commerce and any relevant trade associations or business networks. Membership typically includes a backlink from the organisation's website — which carries high local authority as an established, trusted local institution. Chambers of Commerce websites have been around for decades and have accumulated significant authority. The membership fee is usually modest relative to the SEO value of the backlink. Also consider: Federation of Small Businesses (FSB), industry-specific trade associations, professional body directories (if applicable to your profession).
Local press and news sites
Local news sites (regional newspapers, community news blogs, local business magazines) carry strong local authority signals. Getting your business mentioned in a local news story earns a high-value local backlink. Strategies for earning local press coverage: send press releases for genuinely newsworthy events (business milestones, charity work, new services, unusual achievements), offer yourself as a local expert for comment on stories relevant to your industry, sponsor local events that get press coverage, or run a campaign that has a local angle (charity fundraising, community initiatives).
Sponsorships and community involvement
Sponsoring local sports teams, community events, school fetes, charity events and cultural activities typically earns backlinks from the sponsored organisation's website and from press coverage of the event. These backlinks are genuinely earned rather than bought — the sponsorship serves a real purpose and the backlink is a natural acknowledgment. Local sports clubs, schools and charities often have older, well-established websites with genuine local authority. Look for sponsorship opportunities where the organisation's website is well-established and gets regular updates.
Local supplier and partner cross-linking
If you supply products to local businesses, or use suppliers and partners who serve the same local market, reciprocal linking can make sense when both sites genuinely serve each other's visitors. A local brewery and a local pub that stocks their beer naturally mention each other. A local photographer and a wedding venue naturally reference each other. These links have genuine editorial merit — they are not link exchanges for SEO purposes but natural business relationships made visible online. Curate these relationships rather than pursuing random reciprocal links.
Resource pages and local guides
Many local and industry websites have resource pages or local guides that list recommended local businesses. Find these by searching Google for: "Sheffield best [your business type]", "Sheffield recommended [your service]", or "[your city] local business directory [your industry]". Reach out to the page editor and explain why your business would be a good addition to their resource page. Success rate is low but the backlinks earned are highly relevant and valuable. Also consider creating your own local resource guides — a "Best places to visit in Sheffield" guide from a Sheffield tourism business naturally attracts links and local authority.
Tip: Consistency beats volume in local link building. Five genuinely earned, locally relevant backlinks from established sources are worth more than 50 spammy directory submissions. Focus on quality and genuine community involvement rather than trying to accumulate the most links.
Tags: local-seo, link-building, backlinks, authority
Tutorial
Content Strategy for SEO: How to Plan, Create and Maintain a Site That Ranks
A systematic approach to planning what content to create, how to structure it, how to keep it fresh, and how to measure whether it is working.
Start with a content audit before creating anything new
Most sites with more than 50 pages have content that is underperforming, thin, outdated, or cannibalising other pages. Before adding new content, audit what you already have. Use our Site Audit to identify thin content (under 300 words), and Google Search Console to see which pages receive zero organic traffic. Pages with zero traffic that have been live for more than six months are strong candidates for: updating and improving if the topic is valuable, merging with a related page if the content overlaps, or removing with a redirect if the content adds no value. Pruning low-quality content can improve your overall site quality score in Google's algorithm.
The content pillar and cluster model
Organise your content around pillar pages and topic clusters rather than creating individual disconnected articles. A pillar page covers a broad topic comprehensively at a high level. Cluster pages cover specific subtopics in depth and link back to the pillar page. Example: a pillar page on "Website Speed Optimisation" covers the topic overview. Cluster pages go deep on specific subtopics: "How to Fix LCP", "How to Reduce CLS", "Server Response Time Explained", "Image Optimisation Guide". All cluster pages link to the pillar page and the pillar page links to all cluster pages. This structure tells Google that you have comprehensive authority on the topic.
Editorial calendar and publishing cadence
Consistent publishing at a sustainable cadence beats irregular bursts of content. Google monitors publishing frequency and a site that publishes consistently is crawled more frequently than one that publishes randomly. Determine a realistic cadence: for most business sites, one to four well-researched pieces per month is more sustainable and effective than five blog posts per week that reduce in quality. Use an editorial calendar to plan topics three months ahead, assign each piece to a specific author with a deadline, and build in time for review, SEO optimisation and internal linking before publication.
Content freshness and the importance of updating
Google's Freshness algorithm rewards recently updated content for queries where freshness matters. Even for evergreen topics, updating content signals to Google that the page is being maintained and reflects current best practices. Build a content refresh schedule: review your highest-traffic pages every six months and update them with current data, new examples, updated screenshots, and any changes in best practice. Even small updates — adding a new section, updating statistics, adding a FAQ block — can trigger re-crawling and re-evaluation. Show the update date prominently on the page.
Writing for search intent, not just keywords
Every piece of content should be planned around a specific search intent, not just a keyword. Before writing, ask: what is the user trying to accomplish when they search this query? Are they trying to learn (informational intent — create a guide or explanation)? Are they trying to compare options (commercial intent — create a comparison or review)? Are they ready to buy or hire (transactional intent — create a product or service page with strong calls to action)? Are they looking for a specific website (navigational intent — not usually worth targeting with content)? The format, length and structure of your content should be designed around the intent.
Measuring content performance
Track content performance with Google Search Console and Google Analytics together. In GSC: for each important page, track impressions (how often the page appears in search results), average position (how high in results it appears), and clicks. A page with high impressions but low CTR has a title or meta description problem. A page with good CTR but low average position needs more authority (backlinks, internal links). In Analytics: track organic traffic to each page, time on page (longer is better), bounce rate (lower is better for informational content), and conversions from organic traffic. Set up monthly reporting to catch trends early.
Tip: The most underused content strategy is simply updating existing content. Before creating any new piece, check whether you already have a page on the topic that could be expanded. Updating a page that already has some authority built up consistently produces faster ranking improvements than creating a new page on the same topic.
Tags: content, strategy, seo, keywords, planning
Tutorial
Mobile SEO: Why Mobile-First Indexing Changes Everything
Google uses your mobile site to determine your rankings for all users. Here is what mobile-first indexing means, what it requires, and how to audit your mobile performance.
What mobile-first indexing means in practice
Mobile-first indexing means Google primarily uses the mobile version of your website for crawling, indexing and ranking — even when the user searching is on a desktop. If your mobile site has less content than your desktop site, fewer internal links, different structured data, or slower performance, your rankings for all users are determined by the weaker mobile version. Google switched all websites to mobile-first indexing in 2023. There is no longer a separate "desktop index" for new sites — there is one index, based primarily on the mobile experience.
Mobile-first does not mean mobile-only
Mobile-first indexing does not mean only mobile users matter or that you should neglect desktop. It means the mobile version is the primary version Google evaluates. The ideal setup is a responsive design — a single website that adapts its layout to different screen sizes. With responsive design, the same HTML content is served to all devices and there is no risk of the mobile version having less content than desktop. Separate mobile sites (m.yourdomain.com) and dynamic serving (different HTML based on user agent) both create more complexity and more risk than a well-implemented responsive design.
Content parity between mobile and desktop
If you have a separate mobile site or use dynamic serving, ensure the mobile version has complete content parity with desktop. Every piece of text, every image, every internal link, and every structured data block present on desktop must also be present on mobile. Common failures: accordions or tabs that hide content on mobile (hidden content may not be indexed), images that load differently on mobile (missing alt text in mobile version), navigation that simplifies to fewer links on mobile (reducing crawlable internal links), and structured data that is only present in the desktop template.
Mobile performance — Core Web Vitals on real devices
Core Web Vitals thresholds are the same for mobile and desktop but mobile is significantly harder to pass. Mobile devices have less processing power than desktop computers, mobile connections are slower than typical broadband, and Lighthouse simulates a mid-range Android device on a 4G connection as its mobile test condition. A Lighthouse mobile score of 90+ is an excellent achievement. Below 75 on mobile needs attention. Test your site on real mobile devices — Chrome DevTools emulation does not accurately simulate mobile CPU throttling. Use WebPageTest.org to test from real mobile devices at real locations.
Mobile usability requirements
Google has specific mobile usability requirements that affect ranking. Clickable elements must not be too close together — minimum touch target size is 48x48 pixels with adequate spacing between targets. Text must be readable without zooming — minimum 16px font size for body text. Content must not be wider than the screen requiring horizontal scrolling. Viewport must be configured with <meta name="viewport" content="width=device-width, initial-scale=1">. No Flash or other non-mobile-compatible technologies. Check mobile usability issues in Google Search Console under Experience > Mobile Usability.
Images on mobile — size and format
Mobile users on 4G or 5G connections still benefit from smaller images because mobile devices have less processing power and smaller screens. Use the srcset attribute and sizes to serve appropriately sized images to each viewport. A 2400px wide image served to a 375px mobile screen wastes bandwidth and slows loading. The picture element with WebP or AVIF source and JPEG fallback serves modern formats to supporting browsers. Lazy loading (loading="lazy") for below-the-fold images is particularly valuable on mobile where users scroll through more content relative to screen size.
Tip: Use Google Search Console Mobile Usability report to find mobile-specific issues across your entire site in one place. It categorises issues by type and shows which URLs are affected. Fix the issue type affecting the most URLs first — often a single template change fixes the issue across hundreds of pages simultaneously.
Tags: mobile, seo, indexing, cwv, technical
Tutorial
Voice Search SEO: How to Optimise for Spoken Queries
Voice search queries are longer, more conversational, and often local. Here is how voice search works and what changes in your SEO approach when people speak rather than type.
How voice search queries differ from typed queries
When people type a search query they use short fragments: "pizza delivery Sheffield". When they speak a query they use complete natural language sentences: "What pizza places deliver to Sheffield city centre right now?" Voice queries are typically three to five times longer than typed queries, use natural conversational language, are often questions (who, what, where, when, why, how), and are more likely to be local (adding "near me" or specific locations). Optimising for voice search means optimising for question-based, conversational, local content.
Featured snippets are voice search answers
When someone asks a voice question to Google Assistant, Siri (via Bing) or Alexa, the answer read out is almost always pulled from the featured snippet position in search results. Earning the featured snippet for a query means your content becomes the voice answer for that query. Featured snippets typically come from pages that: directly answer the question in a concise paragraph (40 to 60 words is the ideal featured snippet length), use the question as a heading and the answer in the paragraph immediately below, and rank in the top five results for the query.
Optimise for question-based queries
Research the questions your potential customers ask using Google's People Also Ask boxes for your main keywords, Answer the Public (answerthepublic.com), and your own customer service records. Create content that directly answers these questions. Structure each answer with the question as an H2 or H3 heading and the concise answer as the first paragraph below that heading. The more naturally your content is structured as question-and-answer pairs, the better it performs for both voice search and featured snippets.
Local voice search optimisation
Voice searches are three times more likely to be local than typed searches. "Near me" voice queries and "open now" queries have exploded. Optimise for local voice search by: keeping your Google Business Profile completely up to date (especially hours — voice assistants answer "is [business] open now?" directly from GBP), adding local keywords to your content naturally, implementing LocalBusiness schema with openingHoursSpecification and geo coordinates, and ensuring your NAP is consistent across all directories so voice assistants have consistent data to draw from.
FAQ schema and voice search
FAQPage schema marks up your question-and-answer content explicitly for search engines and AI systems to extract and use. Voice assistants can pull answers from FAQ schema to answer spoken questions. Add FAQ schema to every page where you have genuine Q&A content, making sure the questions are written in the natural language your customers actually use when speaking. Technical or formal question phrasing is less likely to be matched by natural language voice queries than conversational phrasing.
Page speed matters even more for voice
Voice search results tend to come from pages that load faster than average. When someone asks a voice question and hears an answer, the underlying source page loaded and was processed by the search engine quickly. Google has found that pages serving as voice answers average load times of under two seconds. Prioritise page speed for any content you want to rank for voice queries. Core Web Vitals improvement, server response time reduction, and mobile performance optimisation all contribute to voice search eligibility.
Tip: Think about voice search as being for the most specific, local, and immediate queries. Optimise your most locally-relevant content and your most direct question-and-answer content for voice. Track your featured snippet performance in Google Search Console by looking for queries where you rank position one — these are your current and potential voice answers.
Tags: voice-search, aeo, local-seo, seo, featured-snippets
Tutorial
Hosting, CDN and Server Configuration for SEO
Your hosting infrastructure determines your baseline performance. Here is how to choose the right hosting, configure your server correctly, and use a CDN to serve content faster globally.
Why hosting quality directly affects rankings
Your web hosting determines your TTFB (Time to First Byte) — how quickly the server responds to a page request. TTFB is the foundation of all other performance metrics. A TTFB of 1.5 seconds adds 1.5 seconds to your LCP, FCP, and every other timing metric before any optimisation can help. Google wants TTFB under 800ms and ideally under 200ms. Cheap shared hosting commonly produces TTFB of 1 to 3 seconds. Managed WordPress hosting or VPS typically produces TTFB of 100 to 300ms. The hosting quality difference alone can mean 15 to 20 PageSpeed points.
Hosting types and their trade-offs
Shared hosting: your site shares a server with hundreds or thousands of others. Cheapest option (£3 to £15/month). TTFB is unpredictable and often slow. Traffic spikes on other sites slow yours down. Suitable only for very low-traffic or non-SEO-critical sites. VPS (Virtual Private Server): dedicated virtual server resources. More expensive (£20 to £100/month). Consistent performance. Requires server administration knowledge. Managed WordPress hosting (WP Engine, Kinsta, Pressable): VPS-equivalent performance with server management handled for you. Higher cost (£25 to £150/month). Best option for WordPress sites where SEO performance matters. Dedicated server: an entire physical server for one site. Most expensive but maximum performance. Usually overkill for most sites.
Content Delivery Networks — how they work and why you need one
A CDN stores copies of your site's static files (images, CSS, JavaScript, often the entire HTML) on edge servers located in data centres worldwide. When a visitor in Sydney requests your UK-hosted site, without a CDN their request travels to the UK and back — adding 300 to 400ms of latency. With a CDN, the request is served from the nearest Sydney edge server — reducing that latency to under 20ms. CDNs also provide DDoS protection, handle traffic spikes without affecting your origin server, and often include optimisation features like automatic image compression and Brotli compression.
Server-side caching configuration
Caching prevents your server from rebuilding pages from scratch on every request. Without caching, every page request triggers: database queries to fetch content, template rendering to build HTML, plugin execution to add features — all taking 500ms to 2 seconds. With full-page caching, the first request builds the page, saves the HTML, and every subsequent request serves the pre-built HTML in under 10ms. For WordPress: install a page caching plugin and enable page cache in settings. For other platforms: check your hosting provider's caching options or configure caching at the server level using tools like Varnish or Nginx FastCGI cache.
Compression — Gzip and Brotli
HTTP compression reduces the file size of text-based assets (HTML, CSS, JavaScript) during transfer between server and browser. Gzip typically reduces text file sizes by 60 to 80%. Brotli (a newer compression algorithm) achieves 15 to 25% better compression than Gzip. Both are supported by all modern browsers. Enable in Apache .htaccess: AddOutputFilterByType DEFLATE text/html text/css application/javascript. Enable in Nginx: gzip on; gzip_types text/html text/css application/javascript. Most CDN providers enable Brotli compression automatically. Check if compression is working using our Page Speed tool — it flags missing compression.
HTTPS configuration for performance
HTTPS adds a small overhead for the initial connection (the TLS handshake) but enables HTTP/2 which more than compensates through connection multiplexing. Enable HTTP/2 on your server — it allows multiple requests over a single connection simultaneously, eliminating the bottleneck of sequential HTTP/1.1 requests. Enable HSTS (HTTP Strict Transport Security) to tell browsers to always use HTTPS — this eliminates the HTTP-to-HTTPS redirect on repeat visits. Configure OCSP stapling to speed up certificate validation. These are server-level settings your hosting provider or system administrator can configure.
Tip: If your TTFB is consistently over 800ms after enabling caching, the problem is your hosting tier not your code. Upgrading hosting typically produces an immediate TTFB improvement of 60 to 80% with zero code changes. Run a TTFB test before and after any hosting change using WebPageTest.org to measure the exact improvement.
Tags: hosting, server, cdn, ttfb, performance
Tutorial
Backlinks from trusted, relevant sites are one of the strongest ranking signals. Here is how link building actually works, what makes a backlink valuable, and how to earn them without risk.
Why backlinks remain a critical ranking signal
Google confirmed in the Antitrust case documents in 2024 that links remain one of the top three ranking signals alongside content quality and user engagement. The logic is simple: when a trustworthy website links to yours, they are endorsing your content to their audience. The more authoritative sites that endorse you, the more Google trusts your site. Buying links, exchanging links, or earning links through low-quality directories has diminishing value as Google's link spam algorithms improve. Links earned naturally through genuinely valuable content and real relationships are increasingly the only reliable strategy.
What makes a backlink valuable
Not all backlinks are equal. A backlink from a highly authoritative, relevant site in your industry is worth far more than hundreds of links from low-quality directories or irrelevant sites. The factors that determine link value: domain authority of the linking site (how much trust Google has built up for that domain), topical relevance (a gardening website linking to another gardening site is more valuable than an unrelated site), anchor text (the words used in the link — descriptive and varied is ideal), link placement (editorial links within body content are more valuable than footer or sidebar links), and dofollow vs nofollow (dofollow passes link equity, nofollow does not but still has brand value).
Content-led link building — create things worth linking to
The most sustainable link building strategy is creating content so genuinely useful, interesting or unique that other sites naturally link to it. Types of content that earn natural links: original research and data (surveys, studies, unique statistics), comprehensive guides (the most complete resource on a specific topic in your industry), free tools and resources (calculators, templates, checklists), visual content (infographics, charts, diagrams that others embed), and contrarian or surprising takes on conventional wisdom. The question to ask before creating link-worthy content: "Why would another website want to link to this specifically?"
Digital PR — the most scalable link earning strategy
Digital PR means generating press coverage and backlinks through PR activities rather than pure SEO tactics. Strategies: newsjacking (responding quickly to breaking news in your industry with expert commentary), data-led PR (commissioning or conducting research that journalists will reference), expert commentary (being available to journalists as a named source), and press releases for genuinely newsworthy business events. Tools like HARO (Help A Reporter Out), Qwoted and ResponseSource connect journalists seeking expert sources with businesses. A single successful Digital PR placement in a national publication or major industry site can earn dozens of follow-on links as the story is picked up.
Broken link building
Broken link building finds backlinks on other sites that point to pages that no longer exist (returning 404 errors), then creates replacement content and contacts the linking site to suggest updating the link to your resource. The process: use a tool to find broken links in your industry, create or identify content on your site that matches what the broken link was pointing to, contact the site owner to let them know the link is broken and suggest your content as a replacement. This is more effective than cold outreach for general link requests because you are solving a real problem for the site owner.
Competitor backlink analysis
Analysing where your competitors get their backlinks reveals link opportunities you can pursue. Use our Backlink Checker to see the backlink profiles of your main competitors. Look for: sites that link to multiple competitors but not to you (clear gap), resource pages and roundups that include competitors (you can pitch to be included), guest post opportunities (if competitors have guest posts on a site, you may also be able to contribute), and directories or associations that competitors are listed in (if they are legitimate, you should be there too). Focus on the overlapping patterns — links that multiple successful competitors share are high-confidence opportunities.
Tip: Never buy links from link farms, PBNs (private blog networks), or link brokers. Google's link spam algorithms have become sophisticated enough to detect and discount paid links reliably, and manual actions against sites buying links still happen regularly. The risk is not worth any short-term benefit. Earn links through genuine value creation.
Tags: link-building, backlinks, authority, seo
Tutorial
Crawl budget is the number of pages Googlebot crawls on your site within a given time period. For large sites, managing it correctly determines which pages get indexed and how often.
What crawl budget is and when it matters
Crawl budget refers to the number of pages Googlebot crawls on your site within a set period. For most small sites under a few hundred pages, crawl budget is not a concern — Google crawls everything. Crawl budget becomes critically important for: large e-commerce sites with tens of thousands of product pages, news sites publishing hundreds of articles per day, sites with large amounts of dynamically generated URLs (filtered pages, search result pages, URL parameters), and sites where certain pages need to be indexed urgently. If Google is spending crawl budget on low-value pages it has none left for your important ones.
Identify crawl waste on your site
Crawl waste is Googlebot time spent on URLs that should not be crawled. Common sources: URL parameters that create duplicate content (?sort=price&filter=red), session IDs appended to URLs, search result pages (/search?q=trainers), admin and login pages, thank-you and confirmation pages, printer-friendly versions of pages, and paginated pages beyond a reasonable depth. Use Google Search Console's Crawl Stats report to see how many pages Googlebot is crawling per day, which pages are being crawled most frequently, and whether Googlebot is finding crawl errors.
robots.txt to control crawl access
Use robots.txt to explicitly block Googlebot from crawling pages that waste crawl budget and should not be indexed. Block: /wp-admin/, /wp-login.php, /cart/, /checkout/, /order-received/, /search/, /tag/ (if tag archive pages add no unique value), and any URL patterns created by faceted navigation that create duplicate content. Use the Disallow directive with specific paths rather than broad patterns that might accidentally block important pages. Test your robots.txt using the robots.txt tester in Google Search Console before making it live.
URL parameter handling in Search Console
Google Search Console has a URL Parameters tool that lets you tell Google how to handle specific URL parameters. You can mark a parameter as one that "changes page content" (crawl all URLs with this parameter) or "does not change page content" (crawl only one version). For common e-commerce parameters like sort order, colour filter, size filter — if they do not create genuinely different content, tell Google to treat them as duplicates. This directly reduces crawl waste on filtered pages without blocking them entirely.
Internal linking supports crawl efficiency
Googlebot discovers pages by following links. Pages with many internal links are crawled more frequently because Googlebot encounters their URLs more often. Prioritise important pages by giving them more internal links. Conversely, reduce internal links to low-value pages to reduce how often Googlebot visits them. Check your XML sitemap — only include URLs you want indexed. The sitemap tells Google which pages you consider important; including low-value URLs signals that everything is important, which dilutes the signal.
Crawl frequency and page freshness
Google crawls pages more frequently when they change often. A blog that publishes daily is crawled more frequently than a static service page that never changes. If you need certain pages indexed quickly after updates — product prices, news articles, event details — ensure those pages are crawled frequently by: linking to them from frequently-updated pages, submitting individual URLs via Search Console URL Inspection for urgent re-indexing, and using the IndexNow protocol (supported by Bing and increasingly by other engines) to notify search engines immediately when content changes.
Tip: For most sites under 1,000 pages, crawl budget is not a limiting factor. Spend your SEO time on content quality and backlinks rather than crawl budget optimisation. Focus on crawl budget only when GSC Crawl Stats show Googlebot is visiting many more pages than you have worth indexing, or when important new pages take weeks to appear in search results.
Tags: crawl-budget, crawling, indexing, technical, seo
Tutorial
Serving different countries or languages requires specific technical implementations. Here is how hreflang, country targeting, and international site structure work.
When international SEO applies to you
International SEO becomes necessary when you target users in multiple countries or language regions. Signs you need it: you are getting significant traffic from countries you are not targeting, you have content translated into multiple languages, you sell to customers in different countries with different pricing, or you have separate country-specific domains or subdomains. If you serve only one country in one language, international SEO is not relevant to you.
URL structure options for international sites
There are three main approaches. Country-code top-level domains (ccTLDs): separate domains for each country (example.co.uk, example.de, example.fr). Strongest geographic signal to Google. Most expensive and complex to maintain. Subdomains: (uk.example.com, de.example.com). Strong geographic signal. Easier to manage than ccTLDs. Subdirectories: (example.com/uk/, example.com/de/). Easiest to implement and maintain. Shares domain authority across all regions. Google's own documentation acknowledges all three as valid approaches. For most businesses, subdirectories are the practical recommendation.
Hreflang implementation
Hreflang tags tell Google which language and country version of a page to show to users in each locale. Add in the head section of every language variant: <link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/page">. The language code uses ISO 639-1 format (en, fr, de, es). The country code uses ISO 3166-1 Alpha-2 format (GB, US, FR, DE). Always use lowercase for language, uppercase for country, separated by a hyphen. Add x-default for users whose locale does not match any variant: <link rel="alternate" hreflang="x-default" href="https://example.com/page">.
The return-link requirement
The most common hreflang implementation error. Every page referenced in a hreflang set must reference all other pages in the same set. If your English page points to the French page via hreflang, the French page must also reference the English page with hreflang. And both must reference themselves with their own hreflang tags. If any page in the set is missing these return references, Google may ignore the entire hreflang implementation for that page. Use our Hreflang Checker to automatically validate that all pages in each set have complete reciprocal references.
Translating content versus creating content for each market
Machine translation (Google Translate, DeepL) is not sufficient for pages you want to rank. Machine-translated content is detectable, often unnatural, and does not demonstrate the E-E-A-T signals that help content rank in each market. Human translation plus local adaptation is the minimum standard. Ideally, create content written natively by writers who understand the specific cultural context and search behaviour of each market. Search queries for the same topic can differ significantly between countries — British users searching for "trainers" are looking for what Americans call "sneakers". Keyword research needs to be done separately for each language market.
Geo-targeting in Google Search Console
If you use a generic top-level domain (.com, .net, .org) and serve specific countries, set geographic targeting in Google Search Console. In GSC: go to your property, then International Targeting, then Country. Select the primary country you target. This is a relatively weak signal compared to ccTLDs or hreflang, but it gives Google additional context when it cannot infer your target country from your URL structure or content. This setting applies at the domain level — use separate GSC properties for subdirectories targeting different countries and set targeting separately for each.
Tip: Use our Hreflang Checker to validate your implementation across all language variants before considering it complete. Missing return links — the most common implementation error — are invisible without automated checking across multiple pages simultaneously.
Tags: hreflang, international, seo, multilingual
Tutorial
Review Strategy: How to Earn Reviews That Build Rankings and Trust
Customer reviews affect local rankings, click-through rates, and conversion rates. Here is a systematic approach to earning more reviews ethically and making them work harder for your business.
Why reviews matter for SEO and conversion
Reviews affect your business in three distinct ways. For local search rankings: Google reviews are one of the strongest signals for Maps 3-pack positioning — both quantity and recency matter. For click-through rates: star ratings displayed in search results (via schema markup) increase CTR by 15 to 20% compared to results without stars. For conversion rates: 93% of consumers read online reviews before making a purchasing decision and businesses with higher average ratings convert more visitors into customers. Investing in a systematic review strategy affects all three simultaneously.
Google Business Profile reviews — the most important
Google reviews are the most directly impactful for local search rankings. To get your Google review link for sharing with customers: go to business.google.com, select your listing, click Share Profile, and copy the review link. This link takes customers directly to the review dialog without requiring them to search for your business. The review link can be shared via email, text message, WhatsApp, on receipts, on after-service surveys, and on your website as a visible call to action.
The right way to ask for reviews
The most effective review request is personal, timely and easy. Personal: ask each customer directly rather than sending a mass email to your entire customer list. Timely: ask when the experience is fresh — within 24 to 48 hours of a positive interaction. Easy: provide the direct link so customers do not have to search. The right moment to ask varies by business: after a positive customer service call, when a project is completed successfully, when a product has been delivered and used for a few days, after a positive in-store experience. Train all customer-facing staff to ask personally — a personal verbal request followed by a text with the link converts at much higher rates than an automated email.
Responding to reviews — positive and negative
Respond to every review — positive and negative — within 24 hours. Responding to positive reviews thanks the customer and shows you are engaged with your customers. Responding to negative reviews demonstrates professionalism, gives you the opportunity to resolve issues, and shows potential customers that you take concerns seriously. For negative reviews: acknowledge the issue, apologise sincerely without being defensive, explain what went wrong if appropriate, and offer to resolve it offline (provide contact details). Never argue with a negative reviewer in the public response. The response is written for the thousands of future customers reading it, not for the one reviewer.
Third-party review platforms beyond Google
Beyond Google, build reviews on platforms relevant to your industry. For UK businesses: Trustpilot (widely trusted, good SEO value), Feefo (verification system, strong in retail), Checkatrade (for trades and home services), Which? Trusted Traders (for trades). For all businesses: Facebook reviews, Bing Places. Industry-specific: Tripadvisor for hospitality and tourism, Houzz for interior design and building, Bark for services. Multiple review platforms create multiple sources of social proof that appear in branded search results. Aggregate your ratings using AggregateRating schema to display them in Google search results.
Review schema for rich results
Add AggregateRating schema to your homepage and service pages to display your star rating in Google search results. This requires: aggregateRating containing ratingValue (your average rating as a number), reviewCount (total number of reviews), and bestRating (usually 5). Reviews must be from your own site or clearly aggregated from third-party platforms following Google's policies — you cannot simply claim a rating without the underlying reviews being accessible. Use our Schema Generator to create correctly formatted AggregateRating schema and validate it with our Schema Debugger before publishing.
Tip: Consistency beats campaigns. Asking every customer for a review as part of your standard post-service process earns more reviews over time than occasional email campaigns to your full database. Build the review request into your workflow — make it automatic and habitual rather than a periodic effort.
Tags: reviews, local-seo, eeat, schema, trust
Tutorial
A complete walkthrough of the five signals Google uses to rank businesses in the Maps 3-pack — schema, NAP, GBP, reviews and proximity — with exact implementation steps.
What the Maps 3-pack is and why it matters
The Google Maps 3-pack (sometimes called the Local Pack) is the block of three local business listings that appears at the top of Google search results for local queries like "plumber Sheffield" or "Italian restaurant near me". It appears above organic search results and typically above paid ads, making it the most prominent position in local search. For local businesses it is the single highest-value position in Google because it displays your business name, rating, address, phone number and opening hours — everything a customer needs to contact or visit you — without them needing to click through to your website.
Signal 1: Google Business Profile
Your GBP listing must be claimed, verified and fully completed. Without a verified GBP you cannot appear in the 3-pack regardless of how good your website is. Fill every field completely: choose the most specific primary category available (Plumber not Home Services), add secondary categories for all relevant services, write a keyword-rich business description, add all services with descriptions, set accurate opening hours including special holiday hours, upload at least 10 high-quality photos, enable messaging if you can respond promptly, and answer the Q&A section. Post GBP updates weekly — active, maintained profiles consistently rank higher than dormant ones.
Signal 2: LocalBusiness schema on your website
Structured data on your website creates a machine-readable connection between your website and your GBP listing. Google uses it to algorithmically confirm your business details rather than having to extract them from page text (which can be ambiguous). Your LocalBusiness JSON-LD should include: exact business name matching your GBP, full address using PostalAddress type, telephone number in E.164 format, geo coordinates (latitude and longitude as numbers), openingHoursSpecification (day by day), aggregateRating if you have reviews, and sameAs array linking to your GBP, Facebook, Yelp and relevant directories.
Signal 3: NAP consistency
NAP stands for Name, Address, Phone. Google cross-references your business NAP across your website, GBP listing, and directory listings. Inconsistencies — even minor ones like "Street" vs "St", "+44" vs "0", "Ltd" vs "Limited", or a slightly different phone number format — dilute your local authority signal. Establish one canonical version of your NAP and use it identically everywhere. Run our Local SEO Checker to audit consistency across 20+ directories simultaneously. Fix the largest inconsistencies first — phone format and address abbreviation differences are the most common.
Signal 4: Reviews
Google reviews are a direct local ranking signal. Three aspects matter: quantity (more reviews signal more established presence and more customer interactions), recency (recent reviews signal an active, current business — a burst of reviews two years ago and nothing since is less powerful than a steady stream), and rating (higher average ratings correlate with higher 3-pack positions, though the relationship is not simply linear). Respond to all reviews promptly. Build review requests into your post-service workflow. Aim for at least one new review per month as a minimum sustainable pace.
Signal 5: Proximity
Proximity to the searcher is a factor you cannot directly control — Google ranks businesses closer to the searcher's location higher for "near me" queries. You can maximise your proximity signal by: adding accurate geo coordinates in your LocalBusiness schema (use GeoCoordinates type with latitude and longitude as numbers), setting an appropriate service area in GBP for service-area businesses (those that travel to customers rather than receiving customers at a fixed location), and ensuring your address in schema and GBP is absolutely precise.
Local content signals on your website
Beyond the technical signals, your website content sends local relevance signals. Mention your town, city, county and region naturally throughout your content. Reference local landmarks, local events, and local context in your blog and case studies. Build local backlinks from directories, press, community sites and local business associations. Create location-specific pages if you serve multiple areas. Internal linking from local content to your main service pages reinforces local topical authority.
Tip: The fastest path to improving your Maps ranking is to fix LocalBusiness schema and ensure NAP consistency — both are purely technical fixes that can be implemented in a few hours and take effect within weeks. Run our Local SEO Checker and work through every failed check systematically before moving to harder-to-control signals like reviews and proximity.
Tags: local-seo, local, schema, nap, gbp, maps
Tutorial
How to write, implement and validate LocalBusiness JSON-LD from scratch — including all required and recommended fields, opening hours, geo coordinates and sameAs links.
Why LocalBusiness schema is the foundation of local SEO
Without LocalBusiness schema, Google has to extract your business name, address, phone number and hours from your page text — a process that is error-prone and slow. With schema, you provide this information in a machine-readable format that Google can process instantly and reliably. Schema connects your website to your Google Business Profile algorithmically, strengthens your local authority signal, and provides the structured data that local rich results (knowledge panels, map results) rely on. It is the highest-impact single technical change for most local businesses.
Required fields — the absolute minimum
At minimum, your LocalBusiness JSON-LD must include: @context set to "https://schema.org", @type set to the most specific applicable subtype (Plumber, Restaurant, Dentist, HairSalon — never just LocalBusiness unless no specific subtype applies), name (exactly matching your GBP business name), address (a PostalAddress object containing streetAddress, addressLocality, postalCode, and addressCountry in ISO alpha-2 format — "GB" not "United Kingdom", "US" not "United States"), and telephone (your primary business phone number).
High-impact recommended fields
Beyond the minimum: geo (a GeoCoordinates object with latitude and longitude as decimal numbers — use right-click on Google Maps to get precise coordinates, enter as numbers not strings), url (your website URL), image (a photo of your premises), openingHoursSpecification (day-by-day hours as OpeningHoursSpecification objects with dayOfWeek, opens and closes in 24-hour format), priceRange (one to four pound or dollar signs reflecting your price level), aggregateRating (if you have genuine customer reviews), and sameAs (an array of URLs for your GBP listing, Facebook page, Yelp listing, and major industry directories).
Choose the most specific subtype
Schema.org has over 200 LocalBusiness subtypes. Using a specific subtype is always better than using the generic LocalBusiness type because it signals your exact business category to Google. Some examples: AccountingService, Attorney, AutoRepair, Bakery, BarOrPub, BeautySalon, BicycleStore, BookStore, CafeOrCoffeeShop, Campground, ChildCare, Chiropractor, ClothingStore, ComputerStore, DaySpa, Dentist, ElectronicsStore, EmergencyService, FinancialService, Florist, FoodEstablishment, GeneralContractor, GroceryOrSupermarket, HairSalon, HardwareStore, Dentist, Library, LodgingBusiness, MusicStore, NightClub, Optician, Pharmacy, Physician, Plumber, RealEstateAgent, Restaurant, SportsClub, TattooParlor. If your type is not in this list, search schema.org/LocalBusiness for the complete hierarchy.
Opening hours specification
The openingHoursSpecification property uses an array of OpeningHoursSpecification objects, one per day (or one for a group of days with the same hours). Each object contains: dayOfWeek (as a schema.org URL or just the day name: "Monday"), opens (time in 24-hour format: "09:00"), closes (time: "17:30"). For businesses open until midnight: closes "23:59" (not "24:00"). For businesses open 24 hours: opens "00:00" closes "23:59". For businesses closed all day (e.g. Sundays): omit that day from the array rather than including it with identical opens and closes times. For seasonal or holiday hours, add a validFrom and validThrough date range to the specification.
Validate and maintain the schema
After creating your LocalBusiness schema: paste it into our Schema Debugger to check for errors and missing required fields, then test with Google's Rich Results Test (search.google.com/test/rich-results). Add the JSON-LD to your homepage in a script tag with type="application/ld+json" inside the head section. Also add it to your contact page. Submit both pages for indexing via Google Search Console after adding schema. Set a quarterly reminder to review the schema against your current GBP data — any time you change address, phone, hours or business name, update the schema the same day.
Tip: Use our Schema Generator with your contact page URL to automatically generate complete LocalBusiness JSON-LD populated from your actual page content. Then validate with our Schema Debugger and cross-reference against your GBP listing to ensure every field matches exactly.
Tags: schema, local-seo, local, nap
Tutorial
How to Run and Interpret a Site Audit
Learn what every metric in the Site Audit tool means, how scores are calculated, and how to prioritise your fixes for maximum SEO impact.
Run your first audit
Enter any URL in the Site Audit tool and click Analyse. The crawler fetches the live page — no cached data — and runs 70+ checks.
Understand the score
Your SEO Score is calculated as: 100 − (Critical issues × 15) − (Warnings × 5) − (Info items × 1). A score of 80+ is healthy. Focus on Critical issues first.
Work through Critical issues
Critical issues have the biggest score impact. Common ones: missing title tag, no meta description, missing H1, broken canonical, no HTTPS. Each comes with an exact fix.
Address Warnings next
Warnings don't break SEO but hold rankings back. Thin content (under 300 words), missing alt text, slow TTFB — these compound over time.
Track progress with Score History
After each fix, re-run the audit and check Score History to see your improvement trend over time.
Tip: Run audits on your most important pages first: homepage, top product/service pages, and any pages you're actively trying to rank.
Tags: seo, audit, technical, html, errors, site-audit
Tutorial
Understanding & Improving Your PageSpeed Score
A complete walkthrough of how Google's Lighthouse scoring works, what each metric means, and the fastest ways to move the needle.
What the score measures
The Performance score is a weighted average of 6 lab metrics. LCP (25%), TBT (30%), CLS (15%), FCP (10%), SI (10%), TTI (10%). TBT and LCP have the most weight.
Test both mobile and desktop
Mobile scores are typically 20–40 points lower than desktop. Google uses mobile-first indexing, so always check mobile first.
Fix the biggest wins first
Eliminate render-blocking resources (defer JS, preload fonts), serve images in WebP/AVIF, and enable compression. These three changes can add 15–25 points.
Read the Opportunities section
Each opportunity shows estimated savings in seconds. Sort by impact and work top to bottom. "Reduce unused JavaScript" and "Remove render-blocking resources" are the most common big wins.
Verify with Real User data
Lab scores can differ from real user experience. Check the CrUX data shown alongside your score for real-world LCP, FID/INP and CLS values.
Tip: A PageSpeed score of 90+ doesn't guarantee fast real-world performance. Always cross-reference with Core Web Vitals field data.
Tags: performance, pagespeed, cwv, speed, lcp, tbt, fcp
Tutorial
Schema Markup: From Zero to Rich Results
Learn exactly which schema types unlock rich results in Google, how to implement them correctly, and how to test and debug your markup.
Why schema matters
Schema.org JSON-LD markup tells Google what your content is about, enabling rich results: star ratings, FAQs, recipe cards, event listings, product prices. These can increase CTR by 20–30%.
Choose the right type
Match schema to your content: Product for e-commerce, Article for blog posts, FAQPage for support pages, LocalBusiness for location pages, Event for events. Wrong type = no rich result.
Use our AI Schema Generator
Go to Schema Generator → paste your URL → click Generate. The AI reads your page and writes complete schema with all required and recommended properties filled in.
Required vs recommended properties
Required properties are the minimum for eligibility. Recommended properties increase the chance of a rich result appearing. Always fill both. For Product: name, image, description, offers are required.
Test before deploying
Use Google's Rich Results Test (search.google.com/test/rich-results) and Schema Debugger tab in this tool. Fix any errors before publishing — invalid schema is ignored.
Tip: Add schema in a JSON-LD script block in your page head section. Never use Microdata — JSON-LD is Google's preferred format and easier to maintain.
Tags: schema, structured-data, rich-results, rich, html, seo
Tutorial
AEO: How to Get Cited by AI Search Engines
Answer Engine Optimisation explained — how ChatGPT, Perplexity and Google AI Overviews decide what to cite, and exactly how to optimise for them.
How AI search selects sources
AI search engines prioritise pages with: clear factual answers, strong E-E-A-T signals, structured data, direct Q&A format content, and established authority. Vague or promotional content is rarely cited.
Write answer-first content
Lead with a direct 2–3 sentence answer to the likely question, then expand. AI models extract the clearest answer, not the most detailed one. Think: "What is X?" answered in the first paragraph.
Add FAQ schema
FAQPage schema directly feeds structured Q&A into AI training and retrieval. Add at least 5–8 FAQs per important page using our Schema Generator.
Improve your E-E-A-T signals
Add author bios with credentials, cite sources, show publication dates, link to your LinkedIn/social profiles. These are direct signals AI models use to assess credibility.
Create an llms.txt file
Some AI crawlers respect /llms.txt — a plain text file explaining what your site is about and which pages are most important. Run our AEO Checker to get a custom llms.txt generated for your site.
Tip: Check your AEO score monthly. AI search visibility changes faster than traditional SEO as new models update their training data.
Tags: aeo, ai-search, ai, perplexity, chatgpt, schema, eeat
Tutorial
Core Web Vitals: Fix LCP, CLS and INP
A practical guide to diagnosing and fixing each of the three Core Web Vitals metrics that directly affect your Google rankings.
LCP — Largest Contentful Paint
LCP measures how long the largest visible element takes to load. Target: under 2.5s. Common fixes: preload the hero image with <link rel="preload">, serve it in WebP, use a CDN, and remove render-blocking scripts above the fold.
CLS — Cumulative Layout Shift
CLS measures unexpected layout movement. Target: under 0.1. Main causes: images without width/height attributes, web fonts swapping in, ads without reserved space. Use our CLS Debugger to identify culprits.
INP — Interaction to Next Paint
INP replaced FID in 2024. It measures how quickly the page responds to all user interactions. Target: under 200ms. Fix by: breaking up long JavaScript tasks, using requestIdleCallback, and avoiding large DOM sizes.
Use field data, not just lab data
Lab data (Lighthouse) shows what would happen on a clean test device. Field data (CrUX) shows what real users experience. Both matter — Google uses field data for rankings.
Prioritise mobile
CWV thresholds are the same for mobile and desktop, but mobile is harder to pass. Test on a mid-range Android device, not just desktop. Most CWV failures are mobile-only.
Tip: Run the Core Web Vitals tool after every major deployment. A CWV regression after a site update is one of the most common causes of sudden ranking drops.
Tags: cwv, lcp, cls, inp, performance, speed, tbt, fcp
How-To
How to Fix Cumulative Layout Shift (CLS)
Step-by-step fixes for every common CLS cause — images, fonts, ads, iframes and dynamic content — with copy-paste code examples.
Fix images without dimensions
The most common CLS cause. Add width and height to every img tag:
<img src="hero.jpg" width="1200" height="630" alt="...">
Or use CSS aspect-ratio:
img { aspect-ratio: 16/9; width: 100%; height: auto; }
Fix web font FOUT/FOIT
Add font-display: swap to @font-face declarations and preload critical fonts:
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
@font-face { font-family: "Main"; src: url(...); font-display: swap; }
Reserve space for ads
Never inject ads into the page flow without a placeholder. Add a min-height to ad containers:
.ad-slot { min-height: 250px; background: #f5f5f5; }
Fix late-loading iframes
Wrap iframes in an aspect-ratio container:
.iframe-wrap { position: relative; padding-bottom: 56.25%; height: 0; }
.iframe-wrap iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
Fix CSS animations that shift layout
Only animate transform and opacity — these are compositor-only and don't cause layout shifts:
/* BAD */ @keyframes slide { from { margin-left: 0 } to { margin-left: 100px } }
/* GOOD */ @keyframes slide { from { transform: translateX(0) } to { transform: translateX(100px) } }
Tip: Use our CLS Debugger tool to automatically identify all CLS risk factors on any page before manually checking the code.
Tags: cls, performance, technical
How-To
How to Fully Optimise Images for SEO & Speed
Everything you need to do to images to pass Core Web Vitals, improve PageSpeed, fix alt text for accessibility, and implement modern formats.
Convert to WebP or AVIF
WebP is typically 30% smaller than JPEG. AVIF is up to 50% smaller. Use <picture> for fallback:
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Description">
</picture>
Add width and height attributes
Always specify dimensions to prevent CLS:
<img src="hero.webp" width="1200" height="630" alt="Hero image">
The browser pre-allocates space before the image loads.
Lazy load below-the-fold images
Add loading="lazy" to any image not visible on initial load:
<img src="photo.webp" loading="lazy" width="800" height="600" alt="...">
Never lazy-load the hero/LCP image — it will hurt LCP.
Write descriptive alt text
Alt text serves screen readers and Google image search. Be specific:
BAD: alt="image1.jpg"
BAD: alt="photo"
GOOD: alt="Custom handmade leather wallet in tan brown, open showing card slots"
Compress without quality loss
Target: JPEG under 100KB for product images, under 200KB for hero images. Use an image compression tool — free browser-based options are widely available. For production, use a CDN that auto-serves optimal formats and sizes.
Tip: Use our Image Optimisation Checker to audit every image on a page in one click — it flags missing alt text, wrong format, missing dimensions, file size issues and lazy loading all at once.
Tags: images, performance, accessibility, cwv, image-optimisation
How-To
How to Improve Content Readability for SEO
Practical techniques to make your content easier to read, reduce bounce rate, and signal quality to Google — without sacrificing depth.
Target a Flesch score of 60+
A Flesch Reading Ease of 60–70 is ideal for most web content. This roughly corresponds to an 8th–9th grade level. Use shorter sentences (under 20 words) and common vocabulary.
Use subheadings every 300 words
H2 and H3 subheadings help readers scan and help Google understand your content structure. Each H2 should introduce a distinct subtopic. Include target keywords naturally in 2–3 subheadings.
Keep paragraphs short
No paragraph should exceed 3–4 sentences on web. White space improves perceived readability. Break long explanations into numbered lists or bullet points.
Use active voice
Passive voice is harder to process. Compare:
Passive: "The page was indexed by Google."
Active: "Google indexed the page."
Aim for 80%+ active voice in your content.
Add a summary or TL;DR
Add a 2–3 sentence summary at the top of long articles. This is extracted by AI search engines for citations, improves scroll depth, and reduces bounce for users who are just scanning.
Tip: Use our Readability tool to get your Flesch score, sentence length analysis and specific improvement suggestions for any page URL.
Tags: content, readability, on-page, onpage
Case Study · +48 PageSpeed | −23% Bounce Rate | +33% Revenue/Session
Case Study: E-Commerce Site Goes from 34 to 82 PageSpeed
How an ecommerce store improved their PageSpeed score by 48 points and reduced bounce rate by 23% through systematic performance fixes.
Starting point
A mid-size ecommerce store had a PageSpeed mobile score of 34. Bounce rate was 67%. Revenue per session was £1.40. The site had 47 product images with no dimensions, 3 render-blocking scripts, and was serving JPEG images.
Fix 1: Image format conversion (+8 points)
All product images converted from JPEG to WebP using an image optimisation plugin. Average image size dropped from 180KB to 62KB. PageSpeed score jumped from 34 to 42.
Fix 2: Add image dimensions (+7 points)
All img tags given explicit width and height attributes via the ecommerce theme customisation. CLS score dropped from 0.42 to 0.04. PageSpeed: 42 → 49.
Fix 3: Defer render-blocking JS (+14 points)
Three third-party scripts (chat widget, analytics, review widget) moved to defer loading. Configured script deferral via the server or a performance plugin. PageSpeed: 49 → 63.
Fix 4: Enable server-side caching (+19 points)
Server-side object caching added to hosting. TTFB dropped from 1.4s to 180ms. LCP improved from 4.8s to 1.9s. Final PageSpeed: 63 → 82.
Business result
Bounce rate dropped from 67% to 44%. Revenue per session increased from £1.40 to £1.87 (+33%). Organic traffic improved 18% over the following 3 months as rankings improved.
Tip: Prioritise fixes in this order for maximum impact: server response time → render-blocking scripts → image format → image dimensions → lazy loading.
Tags: performance, pagespeed, ecommerce, woocommerce
Case Study · +28% CTR | +97 monthly organic clicks | FAQ rich results on 4 queries
Case Study: FAQ Schema Increases CTR by 28%
A B2B SaaS company added FAQ schema to their pricing page and saw a 28% CTR improvement within 6 weeks as FAQ rich results appeared in Google.
The problem
A pricing page ranked #4 for its target keyword but had a 2.1% CTR — below average for position 4 (typical is 5–7%). Competitors at positions 5 and 6 were getting more clicks due to richer SERP features.
Analysis
The page had 8 common pricing questions buried in the copy but no FAQ schema. Competitors at positions 5 and 6 had FAQ rich results showing below their listings, making their results take up more screen space.
Implementation
Used the Schema Generator to generate FAQPage schema from the URL. 8 Q&As were structured into the JSON-LD block. Schema was added to the <head>. Google Search Console's Rich Results Test confirmed valid markup within 24 hours.
Results after 2 weeks
FAQ rich results appeared in Google SERPs for the target keyword and 3 related queries. The listing now occupied approximately 3× the SERP real estate of a standard result.
Results after 6 weeks
CTR improved from 2.1% to 2.7% (+28%). Impressions stayed flat, confirming the gain was from CTR not ranking. Monthly organic clicks increased from 340 to 437.
Tip: FAQ schema works best on pages that already rank on page 1. It won't improve rankings, but it will improve CTR once you're visible.
Tags: schema, rich-results, rich, ctr, b2b
Case Study · CLS 0.38 → 0.04 | Google Discover traffic +89% recovery
Case Study: News Site Fixes CLS and Recovers Google Discover Traffic
A regional news publisher saw Google Discover traffic collapse after ad network changes caused CLS to spike to 0.38. Here's how they diagnosed and fixed it.
The incident
After switching ad networks, Google Discover traffic dropped 62% in 3 weeks. Google Search Console showed a "Page Experience" signal warning. CLS had spiked from 0.05 to 0.38 according to CrUX field data.
Diagnosis with CLS Debugger
Running the CLS Debugger on article pages found 3 issues: ad slots without reserved height (34 ad units), a sidebar that loaded 0.8s after page content, and a cookie consent banner that pushed content down.
Fix 1: Ad slot heights
All ad containers given min-height equal to the ad format served (250px for standard banners, 90px for leaderboards). This alone reduced CLS from 0.38 to 0.21.
Fix 2: Sidebar skeleton
A CSS skeleton placeholder was added for the sidebar matching its final dimensions. Sidebar loaded into the pre-allocated space. CLS: 0.21 → 0.09.
Fix 3: Cookie banner position
Cookie banner repositioned from top-of-page (pushing content down) to fixed bottom overlay (no layout shift). CLS: 0.09 → 0.04.
Recovery
After 4 weeks with CLS at 0.04, Google Discover traffic recovered to 89% of pre-incident levels. Full recovery took 8 weeks. Lesson: CLS failures from ads are the most common cause of Google Discover traffic loss.
Tip: If you run display advertising, CLS should be monitored every time you change ad networks or update ad sizes. Even a small CLS increase can trigger Google Page Experience signals.
Tags: cls, news, google-discover, discover, ads
Tutorial
Internal Linking Strategy That Boosts Rankings
How to build a deliberate internal link structure that passes authority to your most important pages, improves crawlability and keeps users on site longer.
Why internal links matter
Internal links distribute PageRank (link equity) across your site. Pages with more internal links pointing to them tend to rank higher. They also help Google discover and index content faster.
Identify your pillar pages
Pillar pages are your most commercially important pages — main product/service pages, category pages, or cornerstone content. Every internal link strategy starts by mapping these.
Link from high-traffic content to pillar pages
Use your blog posts and informational content to link to your pillar pages using descriptive, keyword-rich anchor text. Never use "click here" as anchor text.
Fix orphan pages
An orphan page has no internal links pointing to it — Google may never find it. Use our Site Crawler to find orphan pages, then add at least 2–3 internal links to each.
Use breadcrumb navigation
Breadcrumbs create automatic internal links across your whole site. They also enable BreadcrumbList schema which improves rich results. Most CMS platforms support these with a plugin.
Audit with Internal Links tool
Run the Internal Links tool monthly to catch broken links, orphan pages and thin link structures before they impact rankings.
Tip: Aim for at least 3 internal links to every important page. New pages especially need internal links — without them, Google may take weeks to discover them.
Tags: internal-links, seo, architecture
Tutorial
E-E-A-T: How to Build Trust Signals Google Rewards
Experience, Expertise, Authoritativeness and Trustworthiness — explained with concrete steps to improve your E-E-A-T signals and rank better for competitive queries.
What E-E-A-T actually is
E-E-A-T is not a ranking factor — it's a framework Google's Quality Raters use to evaluate pages. Pages with strong E-E-A-T tend to rank better because they align with what Google rewards algorithmically.
Add author bios with credentials
Every content page should have a named author with a bio that demonstrates relevant credentials. Link to their LinkedIn, other publications, and qualifications.
Add Person and Organization schema
Use our Schema Generator to create Person schema for authors and Organization schema for your company. Include sameAs links to Wikipedia, LinkedIn, Crunchbase and other authoritative profiles.
Build your About and Contact pages properly
Google checks About pages for E-E-A-T signals. Include company history, team bios, physical address, phone number and registered company details.
Get cited by third parties
Press coverage, industry directories, Wikipedia mentions and backlinks from authoritative sites all boost your E-E-A-T. This is the hardest part — but digital PR is the most effective method.
Add first-hand experience to content
The first E (Experience) means showing personal experience with the topic. Add case studies, photos, real data, and first-person accounts to content where relevant.
Tip: YMYL (Your Money or Your Life) pages — health, finance, legal — are evaluated most strictly for E-E-A-T. If you operate in these sectors, E-E-A-T should be your top priority.
Tags: eeat, trust, authority, content
Tutorial
Running a Full Technical SEO Audit: Checklist & Process
A systematic, tool-by-tool process for auditing a site's technical SEO from crawlability and indexation through to performance and structured data.
Step 1: Crawlability check
Start with Robots & Sitemap tool. Verify robots.txt isn't blocking important pages, your XML sitemap is submitted to Google Search Console, and all key URLs are included.
Step 2: Indexation check
Search Google for site:yourdomain.com. Compare the page count to your actual page count. Large discrepancies suggest crawl budget issues or accidental noindex tags.
Step 3: Site Audit
Run the Site Audit on your homepage and 5 most important pages. Work through Critical issues first, then Warnings. Pay special attention to missing canonical tags and broken redirects.
Step 4: Page Speed audit
Run PageSpeed on mobile for your homepage, top landing page, and a typical blog post. Scores below 50 on mobile need urgent attention.
Step 5: Core Web Vitals
Check the Core Web Vitals tool for real-user data. Lab scores and real-user scores can differ significantly — both matter.
Step 6: Schema audit
Run Schema Debugger on key pages. Check for errors, warnings and missing recommended properties. Run Google's Rich Results Test on product and FAQ pages.
Step 7: Document and prioritise
Create a prioritised fix list: Critical issues first, then things that unlock rich results, then performance improvements. Assign each fix to a developer with a clear brief.
Tip: Run this full audit quarterly and track scores over time using the Score History tool. A sudden score drop often signals a deployment that introduced regressions.
Tags: technical-seo, audit, checklist, crawlability
Tutorial
Open Graph & Social Meta Tags: Complete Guide
Everything you need to know about Open Graph, Twitter Cards and social meta tags — why they matter for SEO, traffic and brand, and how to implement them perfectly.
Why social tags matter for SEO
While OG tags aren't a direct ranking factor, they dramatically affect click-through rate when content is shared. A compelling image + title on social drives clicks — which signals engagement to Google.
The required OG tags
Every page needs at minimum: og:title, og:description, og:image, og:url and og:type. Missing any of these causes social platforms to use fallback content that rarely looks good.
Image dimensions and format
The optimal OG image is 1200×630px (1.91:1 ratio), under 8MB, in JPG or PNG format. Twitter prefers 1200×600px for summary_large_image cards. Always include og:image:width and og:image:height.
Twitter Card types
Use twitter:card="summary_large_image" for content with images. Use "summary" for pages without a suitable image. "app" and "player" are for specific use cases.
Dynamic OG tags
Product pages, blog posts and event pages should have unique OG tags generated dynamically per page — not the same site-wide defaults. Template your CMS to generate these from page title, description and featured image.
Test and debug
Use Facebook's Sharing Debugger to preview and clear cache. Use Twitter's Card Validator for Twitter. Use our OG Generator to create and preview tags before deploying.
Tip: After updating OG tags, social platforms cache the old version. Paste your URL into Facebook's Sharing Debugger and click "Scrape Again" to force a refresh.
Tags: open-graph, social, meta-tags, twitter
Tutorial
Redirects, Canonicals and URL Structure for SEO
How to handle URL changes, site migrations and redirect chains without losing ranking authority — and when to use 301, 302, canonical and noindex.
301 vs 302 redirects
301 is permanent — it passes ~99% of link equity to the destination. 302 is temporary — it passes less equity and tells Google the original URL might come back. Always use 301 for permanent URL changes, site migrations and HTTP→HTTPS.
Avoid redirect chains
A redirect chain is A→B→C→D. Each hop loses a small amount of link equity and slows page load. Use our Redirect Checker to find chains, then update all redirects to point directly to the final destination.
Canonical tags explained
A canonical tag tells Google which version of a page is the "master". Use it for paginated content, URL parameter variants (e.g. ?colour=red), and any duplicate content. Point canonicals to the https:// www version.
Self-referencing canonicals
Every page should have a self-referencing canonical — even pages with no duplicates. This prevents Google from choosing its own canonical if it discovers multiple paths to the page.
Site migrations
When migrating domains: set up 301 redirects for every old URL, submit a new sitemap, update all internal links to the new URLs, and update any external links you control. Monitor GSC for 404 errors for 6 months after migration.
Tip: Never redirect an entire site to the homepage — redirect each URL to its equivalent new URL. Mass homepage redirects are treated by Google as soft 404s and strip link equity.
Tags: redirects, canonical, url-structure, url, migration
Tutorial
Keyword Research to Content Strategy: End-to-End
How to identify the right keywords for your business, map them to pages, prioritise which to target first, and use the Keyword Analysis tool to track performance.
Seed keywords and intent
Start with 5–10 seed terms that describe your core product or service. For each, identify search intent: informational (how to), navigational (brand name), commercial (best/reviews), or transactional (buy/price).
Expand with questions
Use "People also ask" in Google for your seed terms. These question-based queries map to FAQ schema and often have rich result opportunities with lower competition than head terms.
Keyword mapping
Assign one primary keyword and 2–3 secondary keywords to each page. Two pages targeting the same keyword creates "keyword cannibalism" — they compete with each other and neither ranks well.
Prioritise low-competition, high-intent terms
New sites should target long-tail, high-intent keywords first. "Buy [product] [city]" converts better than "[product]" and is far easier to rank for initially.
Use our Keyword Analysis tool
Run the Keyword Analysis tool on your pages to see what keywords you're already ranking for, identify gaps, and find cannibalisation issues. Focus improvements on keywords ranking 5–20 — these are easiest to move to page 1.
Tip: Don't obsess over search volume. A 50-search/month keyword with clear buying intent is worth more than a 5,000/month informational keyword that never converts.
Tags: keywords, keyword, content, strategy, seo
Tutorial
Local SEO: How to Rank in the Google Maps 3-Pack
A complete walkthrough of the five signals Google uses to rank businesses in the Maps 3-pack — schema, NAP, GBP, reviews and proximity — with exact implementation steps for each.
What the Maps 3-pack is
The Maps 3-pack is the block of three local businesses Google shows at the top of results for local queries like "plumber Sheffield" or "Italian restaurant near me". It is the single most valuable position for local businesses — it appears above organic results and often above paid ads.
Signal 1: Google Business Profile (GBP)
Your GBP listing must be claimed, verified and fully completed. It is the primary signal — without a verified GBP you cannot appear in the 3-pack at all. Fill every field: category, services, hours, photos, description. Keep it updated weekly.
Signal 2: LocalBusiness schema on your website
Structured data connects your website to your GBP listing and supplies name, address, phone, opening hours and geo coordinates to Google algorithmically. Use our Schema Builder to generate complete LocalBusiness JSON-LD. Missing schema forces Google to guess — and it often gets it wrong.
Signal 3: NAP Consistency
Your Name, Address and Phone must be identical across your website, GBP and every directory listing — Yell, Yelp, Thomson Local, Facebook, etc. Even minor differences like "St" vs "Street" dilute local authority. Use our Local SEO Checker to audit NAP consistency across 20+ directories.
Signal 4: Reviews
Quantity, recency and diversity of Google reviews are a strong Maps ranking signal. Ask every customer for a review using your Google Business Profile short link. Respond to every review — positive and negative. Aim for a steady stream rather than a one-off burst.
Signal 5: Proximity
Google biases results toward businesses physically closest to the searcher. You cannot change your location, but you can maximise your service area in GBP and use areaServed in your schema. Proximity matters most for "near me" queries.
Tip: The fastest way to improve your Maps ranking is to fix your LocalBusiness schema and ensure NAP consistency. Run the Local SEO Checker and work through every failed check — most take under 10 minutes each.
Tags: local-seo, local, schema, nap, gbp, maps
Tutorial
LocalBusiness Schema: Complete Implementation Guide
How to write, implement and validate LocalBusiness JSON-LD from scratch — including all required and recommended fields, opening hours, geo coordinates and sameAs links.
Why LocalBusiness schema matters
LocalBusiness JSON-LD explicitly tells Google your business name, address, phone, hours and coordinates in a machine-readable format. Without it, Google has to guess from your page text — and guesses can be wrong, harming your Maps ranking and knowledge panel data.
Required fields
At minimum include: @context, @type (LocalBusiness or a subtype like Plumber, Restaurant), name, address (with streetAddress, addressLocality, postalCode, addressCountry), telephone. These four are the absolute baseline for Maps ranking.
High-impact recommended fields
Add these for maximum impact: geo (latitude and longitude), openingHoursSpecification, aggregateRating, sameAs (linking to GBP and directories), priceRange, url. Each adds ranking signal weight.
Use a specific subtype where possible
Instead of generic LocalBusiness, use the most specific subtype: Plumber, Electrician, Restaurant, Dentist, HairSalon, etc. See schema.org/LocalBusiness for the full list. Specific types unlock richer results.
Add to your page head
Place your schema in a JSON-LD script block inside your page head section. Do not use Microdata. Validate with our JSON-LD Checker and Google's Rich Results Test before publishing.
Keep it in sync
Any time you change your address, hours or phone number, update the schema immediately. Inconsistency between your schema and GBP confuses Google and can suppress your listing.
Tip: Use our Schema Builder to generate complete LocalBusiness JSON-LD with all fields in under 2 minutes. Paste it into your page head and validate with the JSON-LD Checker.
Tags: schema, local-seo, local, nap
How-To
How to Fix NAP Consistency Across Directories
Name, Address and Phone must be identical everywhere your business is listed. Here is how to audit your NAP, find inconsistencies and fix them systematically.
What NAP consistency means
NAP stands for Name, Address and Phone. Google cross-references your NAP across your website, Google Business Profile, and directory listings. Inconsistencies — even minor ones like "Ltd" vs "Limited" or "+44" vs "0" — dilute your local authority score.
Start with your source of truth
Decide on the single canonical version of your business name, address and phone number. This exact format must appear identically on your website, in your LocalBusiness schema, and in your GBP. Write it down before touching any directories.
Audit your schema
Run the Local SEO Checker on your website. Check that name, streetAddress, addressLocality, postalCode and telephone in your schema match your source of truth exactly. Fix any discrepancies in your JSON-LD first.
Audit your GBP
Log into business.google.com and check every field. Your business name in GBP must match your schema name exactly. Address format must match. Phone must be in the same format — use E.164 (+441142000000) in schema and the local format (0114 200 0000) visibly on page.
Fix directory listings
Use the directory check links in your Local SEO Checker results. For each directory: log in, update your NAP to exactly match your source of truth. Pay special attention to Yell, Yelp, Thomson Local (UK), Yellow Pages (US/AU/CA), Bing Places and Apple Maps Connect.
Prevent future drift
Never use different name formats on different platforms. If you move or change your phone number, update every listing the same day — start with GBP and your website schema, then work through directories systematically.
Tip: The most common NAP inconsistency is the phone number format. Pick one format and use it everywhere. For schema use E.164 international format (+441142000000). For on-page display use the local format your customers recognise.
Tags: nap, local-seo, local, gbp
How-To
How to Find Your Google Business Profile Link
Four methods to get your GBP URL for your sameAs schema array and your on-page footer link — including how to extract coordinates from a Maps URL.
Method 1 — Google Search (easiest)
Go to google.com and search your exact business name + town. Your Knowledge Panel will appear on the right (desktop) or top (mobile). Click "View on Google Maps". Copy the URL from the address bar — this is your GBP link.
Method 2 — GBP Dashboard
Go to business.google.com and sign in. Select your listing. Click "View on Maps" or "View on Search". Copy the URL — this is your canonical GBP link and is always the most reliable.
Method 3 — Short g.page link
In your GBP dashboard go to Your Business Profile → Share profile. Google gives you a short link like https://g.page/your-business-name. This works in your sameAs array and is easier to remember.
Method 4 — Short maps.app.goo.gl link
Open your listing in Google Maps on mobile and tap Share → Copy link. This gives you a short link like https://maps.app.goo.gl/XXXXXXX. Fully valid as a sameAs URL and recognised by the Local SEO Checker.
Add it in two places
Add your GBP link to your LocalBusiness schema sameAs array AND as a visible "Find us on Google Maps" link in your footer. The on-page link counts as a local signal that the checker looks for.
Extract coordinates from a Maps URL
If you only have a Maps URL and need your latitude and longitude: paste the full URL into a browser. Look for the section starting with @ — e.g. @53.3811,-1.4701. The first number is latitude, the second is longitude. Paste these into your schema geo property.
Tip: Add your GBP link in both your schema sameAs array and your page footer. The Local SEO Checker awards points for both separately — missing either costs you ranking signal.
Tags: gbp, local-seo, local, schema
How-To
How to Add Geo Coordinates to Your LocalBusiness Schema
Step-by-step: get precise latitude and longitude from Google Maps and add them to your LocalBusiness schema geo property for Maps proximity scoring.
Why geo coordinates matter
The geo property in your LocalBusiness schema gives Google your exact physical location algorithmically. Without it, Google relies on your address text — which can be imprecise. Coordinates improve proximity matching for "near me" queries.
Step 1 — Open Google Maps
Go to maps.google.com and search your business address. Click on the result to open the place panel.
Step 2 — Get your coordinates
Right-click exactly on your business location pin on the map. The coordinates appear at the top of the right-click menu (e.g. 53.3811, -1.4701). Click them to copy to clipboard. The first number is latitude, the second is longitude.
Step 3 — Add to your schema
In your LocalBusiness JSON-LD, add a geo object: { "@type": "GeoCoordinates", "latitude": 53.3811, "longitude": -1.4701 }. Use numbers not strings — do not wrap the values in quotes.
Step 4 — Verify accuracy
Make sure your pin is on your actual premises — not on the road outside or a nearby landmark. Open the coordinates in a maps app to confirm the pin location before adding to schema. Inaccurate coordinates can harm proximity scoring.
Tip: Use decimal degrees format (53.3811, not 53° 22' 52"N). Google Maps right-click always gives decimal degrees. Never use degrees/minutes/seconds in schema.
Tags: schema, local-seo, local, maps
How-To
How to Embed Google Maps on Your Contact Page
Adding a Google Maps iframe to your contact page is a direct local SEO signal. Here is exactly how to generate the embed code and add it correctly.
Why a Maps embed matters
A Google Maps iframe on your page is a direct on-page local signal. The Local SEO Checker checks for maps.google.com or google.com/maps/embed in your HTML. It signals to Google that your physical location is prominently featured on your website.
Generate your embed code
Go to maps.google.com and search your business name or address. Click Share → Embed a map. Choose your map size. Click Copy HTML. You will receive an iframe embed code — the src URL starts with https://www.google.com/maps/embed. Copy the entire iframe element.
Add it to your contact page
Paste the iframe code into your contact page HTML. The ideal position is below or beside your address — somewhere visible without scrolling on desktop. Wrap it in a div with a fixed height so it does not cause layout shift (CLS).
Responsive sizing
Add width="100%" to the iframe and remove any fixed pixel width. Set a fixed height (e.g. height="350"). This makes it responsive on mobile without causing CLS issues.
Verify the checker sees it
Run the Local SEO Checker after adding the embed. The "Google Maps embed" check should show as passed. If it still fails, confirm the iframe is in the page HTML (not loaded via JavaScript after page load) as the checker reads static HTML.
Tip: Add your Maps embed above the fold on your contact page — not hidden at the bottom. Visible, prominent location signals carry more weight than buried ones.
Tags: maps, local-seo, local, gbp
How-To
Step-by-step fixes for missing title tags, duplicate titles, missing meta descriptions and incorrect canonical tags — the most common Site Audit critical issues.
Fix a missing title tag
Add <title>Primary Keyword — Brand Name</title> in your <head>. Keep it 50–60 characters. Every page must have a unique title. In WordPress: use your SEO plugin to set this per-page.
Fix a duplicate title
Two pages with the same title confuse Google about which to rank. Identify duplicates in your Site Audit results, then write unique titles for each. Use the page's primary keyword, not your brand name.
Fix a missing meta description
Add <meta name="description" content="Your 150–160 character description here"> in your <head>. Meta descriptions don't directly affect rankings but improve click-through rate from SERPs significantly.
Fix a missing or incorrect canonical
Every page needs <link rel="canonical" href="https://yourdomain.com/exact-url/"> in the <head>. The canonical must exactly match the URL as it appears in your sitemap — including trailing slash if used.
Fix noindex on important pages
If your Site Audit shows a noindex tag on a page that should be indexed, remove <meta name="robots" content="noindex"> or the equivalent in your CMS. Check your robots.txt isn't blocking it too.
Tip: After fixing meta tags, submit the affected URLs for re-indexing in Google Search Console using URL Inspection > Request Indexing.
Tags: meta-tags, title, description, canonical
How-To
How to Find and Fix Broken Links & 404 Errors
How to identify broken internal and external links using the Site Crawler, prioritise fixes, and implement the right solution for each type.
Find broken links with Site Crawler
Run the Site Crawler on your domain. Filter results by status code 404. The crawler shows you both the broken URL and the page it was linked from — fix from there.
Fix broken internal links
For internal 404s: either restore the page, set up a 301 redirect to the closest equivalent page, or update the link to point to the correct URL. Never leave a 404 on a page that previously had links pointing to it.
Fix broken external links
External 404s (links you send to other sites) harm user experience. Update them to a working URL, use a Wayback Machine link if the content has been removed, or remove the link entirely.
Rescue link equity from 404s
If a page that has external backlinks returns 404, set up a 301 redirect to the most relevant equivalent page. This rescues the link equity that would otherwise be lost.
Set up a custom 404 page
A custom 404 page keeps users on your site when they hit a dead end. Include your navigation, a search bar, and links to your most popular pages. This reduces bounce rate from accidental 404s.
Tip: Check for new 404s weekly, especially after publishing new content or deleting pages. Google Search Console's Coverage report also surfaces 404s that Googlebot has found.
Tags: broken-links, broken, 404, redirects, crawl
How-To
How to Improve Mobile PageSpeed by 30+ Points
The most impactful, actionable fixes for poor mobile PageSpeed scores — ordered by typical score improvement so you tackle the biggest wins first.
Eliminate render-blocking resources
Scripts and stylesheets that load in <head> block the page from rendering. Add defer or async to non-critical JS. Move non-critical CSS to load asynchronously. This single fix often adds 10–20 points.
Serve properly sized images
Serving a 2000px wide image to a 375px mobile screen wastes bandwidth. Use srcset and sizes attributes, or your CDN's image resizing feature. Convert all images to WebP.
Enable text compression
Enable GZIP or Brotli compression on your web server or CDN. This reduces HTML, CSS and JS file sizes by 60–80%. Most hosting providers have this as a one-click setting in cPanel.
Reduce server response time (TTFB)
If your Time to First Byte is over 600ms, focus here before anything else. Solutions: enable server-side caching, upgrade hosting, use a CDN, or enable a page caching plugin in WordPress.
Remove unused CSS and JS
Page builders and themes load CSS/JS for features you don't use. Tools like PurgeCSS remove unused CSS automatically. Manually defer or remove unused third-party scripts (chat widgets, unused analytics etc).
Tip: Test on a real 4G mobile connection, not just Lighthouse. Tools like WebPageTest let you test on real devices from real locations — scores often differ significantly from lab results.
Tags: pagespeed, mobile, performance, lcp
How-To
A complete local SEO fix guide — from Google Business Profile optimisation to LocalBusiness schema, NAP consistency and local link building.
Complete your Google Business Profile
Go to business.google.com and ensure: business name exactly matches your signage, primary category is as specific as possible, address is correct and consistent, phone number matches your website, and all opening hours are accurate.
Add LocalBusiness schema
Use our Schema Generator to create a LocalBusiness schema with your exact NAP (Name, Address, Phone). Include openingHours, geo coordinates, and sameAs links to your GBP, Facebook and Yelp profiles.
NAP consistency across the web
Your business name, address and phone number must be identical on every directory — Google, Bing, Yelp, Yell, Checkatrade etc. Even small variations (St vs Street, Ltd vs Limited) create inconsistency signals.
Get local citations
Submit your business to local and industry directories. Each citation is a trust signal. Prioritise: Yelp, Yell, Trustpilot, industry-specific directories, and your local Chamber of Commerce.
Generate and respond to reviews
Reviews are one of the strongest local ranking factors. Create a process to ask every satisfied customer for a review. Respond to every review — positive and negative — within 24 hours.
Tip: The Local Pack (the 3 map results) is driven primarily by proximity, relevance and prominence. You can't control proximity, but schema, citations and reviews control relevance and prominence.
Tags: local-seo, google-business, local-business, schema
Case Study · +41% organic traffic recovered | 12 pages back to page 1 | 3 pages ranking higher than before
Case Study: Internal Link Audit Recovers Ranking Drops
A SaaS company's product pages dropped from page 1 to page 2–3 after a site redesign. An internal link audit revealed orphaned pages were losing all link equity.
The problem
After a site redesign, 12 key product pages dropped from positions 2–5 to positions 15–25. Traffic fell 41% over 8 weeks.
Diagnosis
The redesign removed a sidebar widget that linked to all product pages from every blog post. The product pages effectively became orphans — no internal links remained.
Fix 1: Internal link audit
Used Site Crawler to map the internal link graph. Confirmed: product pages had 0 internal links post-redesign. Pre-redesign each had 40–80 links from blog content.
Fix 2: Content linking strategy
Identified 25 blog posts with high traffic and topical relevance to each product. Added 2–3 contextual internal links per post pointing to the relevant product page with descriptive anchor text.
Fix 3: Breadcrumb schema
Added BreadcrumbList schema and visible breadcrumbs to all product pages, creating additional internal link paths from every product page up to category pages.
Recovery timeline
Rankings began recovering within 3 weeks. By week 8, all 12 pages were back on page 1. Three were now ranking higher than pre-redesign.
Tip: Always run an internal link audit before and after any site redesign. Losing internal links is one of the most common — and most easily missed — causes of post-redesign ranking drops.
Tags: internal-links, authority, saas, ranking-drop, ranking
Case Study · Traffic +140% vs update low | 40% above pre-update peak | New competitive YMYL rankings
A personal finance blog lost 60% of traffic after Google's Helpful Content Update. By rebuilding E-E-A-T signals they recovered and grew beyond previous peaks.
The incident
A personal finance site lost 63% of organic traffic over 6 weeks following a Google Helpful Content Update rollout. The site had strong backlinks and good technical SEO but anonymous authors.
Root cause analysis
The site had no author bios, no About page, no company registration details, no author schema, no named experts. Every article said "Staff Writer" with no credentials. Classic E-E-A-T failure for YMYL content.
Fix 1: Author infrastructure
Hired 3 qualified finance writers. Created detailed author pages with qualifications, LinkedIn profiles, and previous publications. Added Person schema to each author page.
Fix 2: Content audit and rewrite
Identified 40 thin, low-E-E-A-T articles. Rewrote each with first-hand examples, data citations, expert quotes, and clear author attribution.
Fix 3: About and trust pages
Created a comprehensive About page with company registration, physical address, team photos and bios, editorial policy, and fact-checking process.
Fix 4: Schema and external signals
Added Organization schema with sameAs links to Companies House, LinkedIn and industry press coverage. Ran digital PR campaign to get 8 mentions in mainstream finance publications.
Results
4 months after the update: traffic recovered to 90% of pre-update levels. 6 months: traffic 40% above pre-update peak. Site now ranks for competitive YMYL terms it never ranked for before.
Tip: For YMYL sites, E-E-A-T is not optional. Google's Quality Raters explicitly evaluate whether authors have relevant credentials and whether the site is transparent about who runs it.
Tags: eeat, helpful-content, helpful, finance, ymyl, recovery
Case Study · 98.3% rankings maintained | 0 critical technical errors | +8% organic traffic post-migration
Case Study: Site Migration with Zero Ranking Loss
How an e-commerce brand migrated from Magento to Shopify with 1,200 URLs — and maintained 98% of their organic traffic through careful technical preparation.
Pre-migration audit
Six weeks before migration: full crawl of existing site documenting all URLs, their traffic, rankings, and inbound links. This became the redirect map source of truth.
Redirect mapping
Every one of the 1,200 existing URLs mapped to a corresponding new Shopify URL. No redirect chains — all pointed directly to the final destination. Edge cases (discontinued products) redirected to the closest category page.
Technical preparation
New site built and reviewed against a 40-point technical SEO checklist: canonical tags, sitemap, robots.txt, meta tags, schema, page speed, HTTPS before launch day.
Launch day process
Launched at 11pm on a Tuesday (low traffic). Immediately verified: all redirects working, sitemap submitted to GSC, new property set up in GSC, Bing Webmaster Tools updated.
Post-launch monitoring
Daily GSC checks for 6 weeks. Site Crawler run every 3 days to catch broken links introduced by the new platform. Any 404s fixed within 24 hours.
Results
After 8 weeks: 98.3% of rankings maintained. The 1.7% loss was discontinued product pages. Overall organic traffic was up 8% due to improved PageSpeed on the new platform.
Tip: The biggest migration risk is redirect errors. Test every redirect individually before launch using our Redirect Checker tool. One wrong redirect on a high-authority URL can cost months of traffic.
Tags: migration, redirects, technical-seo, ecommerce
Tutorial
Your title tag is the headline Google shows in search results. Get it wrong and nobody clicks. This guide shows you exactly what to write and what to avoid.
What even is a title tag?
Think of your title tag like the label on a tin of beans. It tells Google (and people searching) exactly what your page is about. It appears as the blue clickable headline in Google search results. If you don't set one, Google will just use your page heading — which might be fine, or might be terrible.
How long should it be?
Google shows roughly 50–60 characters before cutting it off with "...". Type your title here: if it's longer than 60 characters, expect it to get chopped. Shorter is usually better. Aim for 50–60 characters. Our tool flags anything outside this range.
What to put in it
Include your main keyword near the start. Include your brand name at the end (especially on inner pages). Be specific: "Buy Red Running Shoes for Women | ShoeStore UK" beats "Running Shoes" every time. Ask yourself: would I click this in search results?
What to avoid
Don't stuff keywords: "Running Shoes Women Running Shoes Buy Running Shoes" looks spammy and Google will ignore it. Don't use the same title on every page — Google needs to tell your pages apart. Don't use ALL CAPS (it looks shouty and gets ignored).
Fix it in your CMS
WordPress: use your SEO plugin — it adds a title field to every page. Shopify: edit in Online Store > Pages. Squarespace: page settings > SEO tab. Plain HTML: change the text between <title> and </title> in your page's <head> section.
Tip: Write your title for humans first, Google second. If a real person would click it, Google will probably rank it.
Tags: title, meta, on-page, onpage, seo
Tutorial
A meta description doesn't directly affect rankings — but it massively affects whether people click your result. Here's how to write ones that work.
What is a meta description?
It's the 2–3 line summary that appears under your title in Google search results. Google doesn't always use what you write (it sometimes picks text from your page instead), but writing a good one increases the chance Google uses it — and that people click.
The magic length
Aim for 150–160 characters. Too short and you're wasting the space. Too long and Google cuts it off mid-sentence with "..." which looks unprofessional. Use our Meta Analyzer to check your length instantly.
What makes a great one
Answer the searcher's question before they even click. Include your main keyword (Google bolds keywords that match the search). Add a call to action: "Find out how", "Get a free quote", "Browse 500+ styles". Make a promise you can keep on the page.
Real examples
BAD: "We offer services for all your needs." GOOD: "Professional plumbers in Manchester — same-day callouts, transparent pricing, 5-star rated. Get a free quote in 60 seconds." The good one tells you WHO (Manchester), WHAT (plumbers), WHY (same-day, transparent, 5-star), and WHAT TO DO (get quote).
How to add it
Add this inside your page's <head> section: <meta name="description" content="Your description here">. In WordPress use your SEO plugin. In Shopify edit in the SEO section of each page/product.
Tip: If Google keeps ignoring your meta description and using other text from the page, it means your description doesn't match what searchers are looking for. Rewrite it to directly answer the most common search intent for that page.
Tags: meta, description, ctr, on-page, onpage
Tutorial
Canonical Tags Explained (Without the Jargon)
Canonical tags solve duplicate content problems. If you've got multiple URLs showing the same content, Google gets confused — canonicals tell it which version to index.
The problem they solve
Imagine your homepage is available at three different URLs: https://example.com, https://www.example.com, and https://example.com/index.html. Google sees three separate pages with identical content. It doesn't know which one to rank. Your link equity (ranking power) gets split three ways. Canonicals fix this.
How a canonical works
You add one line of code to the <head> of your page: <link rel="canonical" href="https://example.com/preferred-url">. This tells Google: "Whatever URL you used to find this page, THIS is the official version. Index this one, not the others."
When you need one
Always add a self-referencing canonical (a page pointing to itself). This protects you from duplicate content caused by URL parameters like ?sort=price or ?utm_source=email. Also use canonicals when you syndicate content to other sites — the original should canonical to itself.
Common mistakes
Canonical pointing to a 404 page (Google ignores it). Canonical pointing to a redirect (Google prefers direct references). Every page canonicaling to the homepage (this tells Google your whole site is just one page). Mixing http and https in canonicals.
Check yours now
Run our Site Audit — it flags missing canonicals, broken canonicals, and canonical chains. The Meta Analyzer also shows your canonical URL directly.
Tip: Every page on your site should have a canonical tag, even if it points to itself. It's cheap insurance against duplicate content problems from URL parameters, session IDs, and tracking codes.
Tags: canonical, duplicate-content, duplicate, technical
Tutorial
robots.txt tells search engine crawlers which pages to visit and which to skip. Get it wrong and you could accidentally hide your entire site from Google.
What is robots.txt?
It's a plain text file that lives at yoursite.com/robots.txt. Every time a search engine crawler visits your site, it checks this file first. Think of it as a sign on your door: "Crawlers welcome, but please don't go in the kitchen." It's a request, not a lock — bad bots ignore it.
Basic syntax
User-agent: * means "all crawlers". Disallow: /admin/ means "don't crawl anything in the admin folder". Allow: / means "you can crawl everything". Sitemap: https://yoursite.com/sitemap.xml tells crawlers where your sitemap is.
What to block
Good things to block: /admin/, /login/, /cart/, /checkout/, /thank-you/, search result pages (?s=, ?q=), duplicate filtered pages. These pages waste crawl budget and add no SEO value.
The most dangerous mistake
Disallow: / blocks your ENTIRE site from Google. This is sometimes accidentally added during development and left on in production. Our Robots & Sitemap Tester checks for this. It's more common than you'd think.
Check yours now
Visit yoursite.com/robots.txt in a browser. Or use our Robots & Sitemap tool which checks for common errors including accidental full-site blocks, syntax errors, and missing sitemap references.
Tip: robots.txt controls crawling, not indexing. Even if you block a page in robots.txt, Google can still index it if other sites link to it. Use noindex meta tags to prevent indexing, and robots.txt to control crawling.
Tags: robots, crawling, technical, indexing
Tutorial
An XML sitemap is a list of all your important pages that you send directly to Google. It helps Google discover and crawl pages faster — especially on large or new sites.
Why sitemaps matter
Google discovers pages by following links. If a page has no links pointing to it, Google might never find it. A sitemap bypasses this problem — you hand Google a complete list of your pages directly. Essential for new sites, large e-commerce stores, and pages with thin internal linking.
What to include
Include: all publicly accessible pages you want indexed. Exclude: thank-you pages, login pages, admin pages, duplicate content pages, any page with a noindex tag. Don't submit pages you don't want Google to index — that sends mixed signals.
How to generate one
WordPress: most SEO plugins auto-generate sitemaps at /sitemap.xml or /sitemap_index.xml. Shopify: automatically creates one at /sitemap.xml. Other platforms: use an online XML sitemap generator. Then check it with our Robots & Sitemap tool.
Submit to Google
Go to Google Search Console > Sitemaps > Enter your sitemap URL > Submit. Google will then crawl it and tell you how many pages were found vs how many were indexed. If there's a big gap, that tells you pages have indexing problems.
Keep it updated
Modern CMS platforms update sitemaps automatically when you add/remove pages. If yours doesn't, set a monthly reminder to regenerate it. An outdated sitemap with deleted pages causes Google to waste crawl budget on 404 errors.
Tip: After submitting your sitemap, check Google Search Console weekly for the first month. The "Coverage" report shows exactly which pages Google is and isn't indexing — and why.
Tags: sitemap, crawling, indexing, technical
Tutorial
H1, H2, H3 Headings: The Right Way to Structure Your Pages
Heading tags (H1, H2, H3) aren't just for making text bigger — they tell Google what your page is about and help users scan your content. Here's how to use them properly.
Think of headings like a book
Your H1 is the book title. H2s are the chapter titles. H3s are the section titles within chapters. H4s and below are sub-sections. This hierarchy helps Google understand the structure of your content and helps users find what they need quickly.
The H1 rules
Every page needs exactly ONE H1 tag. It should clearly state what the page is about. Include your main keyword. The H1 doesn't have to be identical to your title tag — but they should be closely related. Missing H1 is a Critical issue in our Site Audit because it's one of the strongest on-page SEO signals.
H2s are your main sections
Use H2s for the main sections of your page. Include relevant keywords naturally — don't force them. Ask: if someone only read the headings, would they understand the page? Good H2s: "How to Fix Slow Page Speed", "Common Causes of High CLS", "Tools You'll Need". Bad H2: "More Information".
Don't skip levels
Don't jump from H1 to H4. Don't use headings just to make text bigger (use CSS for that). Don't put your entire page content in headings. Our HTML Checker flags heading hierarchy errors.
Multiple H1s — is it really a problem?
Technically Google can handle multiple H1s, but it's still bad practice. It confuses the hierarchy and weakens the signal of what your page is primarily about. Our Site Audit flags this as a warning. Fix it by changing secondary H1s to H2s.
Tip: Write your headings first, then fill in the content underneath. If your headings don't tell a coherent story, your page structure is probably confusing to both users and Google.
Tags: headings, h1, on-page, onpage, content, structure
Tutorial
Internal links are links from one page on your site to another. Used strategically, they boost the authority of your most important pages and help Google understand your site structure.
Why internal links matter
When a page on your site links to another, it passes some of its authority (Google calls this PageRank) to the linked page. So if your homepage — which has lots of external links pointing to it — links to your services page, it gives that services page a rankings boost.
Link to your most important pages
Identify your 5–10 most commercially important pages (product pages, service pages, key landing pages). Make sure these are linked from your homepage, your navigation, and from relevant blog posts. The more internal links a page gets, the more authority it accumulates.
Use descriptive anchor text
Anchor text is the clickable words in a link. "Click here" tells Google nothing. "Our guide to fixing Core Web Vitals" tells Google exactly what the linked page is about. Use natural, descriptive anchor text that includes the target page's keywords — but don't over-optimise.
Find orphan pages
An orphan page is a page with no internal links pointing to it. Google can't easily find it, and it gets no authority from your other pages. Use our Internal Links tool to find orphans. Every published page should have at least one internal link pointing to it.
Fix broken internal links
A broken internal link (one that goes to a 404 page) wastes authority and frustrates users. Run our Site Crawler to find all broken internal links. Fix them by updating the link destination or redirecting the dead URL.
Tip: The best time to add internal links is when publishing new content. As you write each new page, actively look for 3–5 existing pages you can link to and from. Build this habit and your site structure will improve automatically over time.
Tags: internal-links, authority, seo, structure
Tutorial
HTTPS & SSL: Why Every Site Needs It and How to Check Yours
HTTPS is a confirmed Google ranking factor and a trust signal for users. If your site still runs on HTTP, here's what that means and how to fix it.
HTTP vs HTTPS — what's the difference?
HTTP = HyperText Transfer Protocol. The data sent between your browser and the website is unencrypted — anyone on the same network could technically intercept it. HTTPS = the S stands for Secure. Data is encrypted. Google confirmed in 2014 that HTTPS is a ranking signal. Chrome now shows "Not Secure" warnings on HTTP sites.
Check if you have it
Look at your site URL. Does it start with https:// (secure) or http:// (not secure)? Our Site Audit flags HTTP as a Critical issue. You can also check by clicking the padlock icon in your browser's address bar.
How to get SSL
Most web hosts include free SSL via Let's Encrypt. In cPanel: look for "SSL/TLS" or "Let's Encrypt". In Plesk: Domains > your domain > SSL/TLS Certificates. Via your CDN provider: SSL is typically included. Contact your host if you can't find it — it should be free.
After enabling HTTPS
You must redirect all HTTP traffic to HTTPS. Without a redirect, some users will hit the HTTP version. Add this to your .htaccess file: RewriteEngine On / RewriteCond %{HTTPS} off / RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]. Or use your CMS settings.
Mixed content warnings
Even after switching to HTTPS, some images, scripts or stylesheets might still load over HTTP — this is "mixed content" and causes browser warnings. Use our Site Audit to detect mixed content issues. Fix by updating all resource URLs to https://
Tip: After switching to HTTPS, update your Google Search Console property (add the HTTPS version as a new property), update your sitemap URL, and update any hardcoded HTTP links in your content.
Tags: https, ssl, security, technical
Tutorial
Why Google Isn't Indexing Your Pages (And How to Fix It)
If Google isn't indexing your pages, they won't appear in search results. Here are the most common causes and exactly how to diagnose and fix each one.
Check if your pages are indexed
Search Google for site:yoursite.com. This shows all indexed pages. If you have 50 pages but Google only shows 10, you have an indexing problem. For specific pages: search site:yoursite.com/your-page-url. No result = not indexed.
Check for noindex tags
The most common cause. Look for this in your page's <head>: <meta name="robots" content="noindex">. If it's there, Google will not index the page. Also check your CMS settings — WordPress has a "Discourage search engines" option that adds noindex to your whole site.
Check robots.txt
Visit yoursite.com/robots.txt. If you see Disallow: / it blocks your entire site. Our Robots & Sitemap tool checks for this automatically. Note: robots.txt blocks crawling, not indexing — but if Google can't crawl, it can't index either.
Check canonical tags
If your page has a canonical pointing to a different URL, Google will index the canonical target, not the page itself. This is correct behaviour — but if your canonical is wrong, the wrong page gets indexed.
Request indexing in Google Search Console
Go to Google Search Console > URL Inspection > paste your URL > Request Indexing. This queues the page for crawling. Don't do this for every page — use it for important new pages or pages you've just fixed.
Tip: Google Search Console's Coverage report is your best friend for indexing issues. It shows exactly which pages are indexed, which are excluded, and why. Check it monthly.
Tags: indexing, noindex, crawling, search-console, console
Tutorial
LCP measures how long it takes for the biggest visible element on your page to load. It's the most important Core Web Vital. Here's what causes a poor LCP and exactly how to fix it.
What LCP actually measures
LCP times how long until the largest element in the visible part of the page (the "viewport") has fully loaded. This is usually a hero image, a large heading, or a background image. Good: under 2.5 seconds. Needs improvement: 2.5–4 seconds. Poor: over 4 seconds. It measures what the user actually sees, not background processes.
Find your LCP element
Open Chrome DevTools (F12) > Performance tab > record a page load > look for "LCP" in the timeline. Or run our Page Speed tool — it shows you which element is your LCP and how long it took. Common LCP elements: hero images, large banner images, H1 text, video thumbnails.
Fix slow LCP: image optimisation
If your LCP is an image (most common): compress it and convert to WebP format (30–50% smaller than JPEG), add width and height attributes to prevent layout shift, use a CDN to serve it faster. These changes alone often cut LCP by 1–2 seconds.
Fix slow LCP: server response time
If your server is slow (TTFB over 600ms), your LCP will be slow no matter how optimised your images are. Solutions: upgrade your hosting, enable server-side caching, use a CDN, enable GZIP/Brotli compression on your server.
Fix slow LCP: render-blocking resources
CSS and JavaScript files that load in your <head> before the page renders can delay LCP. Solutions: add defer or async to non-critical JavaScript. Move non-critical CSS to load after the page. Inline critical CSS directly in the <head>. Our Page Speed tool shows exactly which resources are blocking rendering.
Tip: The single biggest LCP win for most sites is adding loading="eager" and fetchpriority="high" to the LCP image. This tells the browser to prioritise loading it above everything else.
Tags: lcp, cwv, performance, pagespeed
Tutorial
CLS measures how much your page layout shifts while loading. You know when you're about to click something and the page jumps and you click the wrong thing? That's CLS. Here's how to fix it.
What causes layout shift?
Images without dimensions: the browser doesn't know how much space to reserve, so when the image loads it pushes everything down. Ads and embeds that load after the page. Web fonts loading and swapping. Dynamically injected content (cookie banners, banners, chat widgets). Each shift contributes to your CLS score.
What's a good CLS score?
Good: under 0.1. Needs improvement: 0.1–0.25. Poor: over 0.25. A score of 0 means no shifting at all. A score of 0.5 means significant, noticeable movement. Check yours with our Page Speed tool or use our CLS Debugger which shows exactly which elements are shifting.
Fix images without dimensions
Always add width and height to image tags: <img src="photo.jpg" width="800" height="600" alt="...">. This lets the browser reserve the right space before the image loads, preventing the page from jumping. This is the fix for 90% of CLS issues.
Fix web font CLS
When custom fonts load late, text can shift. Add font-display: swap to your @font-face declarations. Preload critical fonts: <link rel="preload" href="/fonts/myfont.woff2" as="font" type="font/woff2" crossorigin>. Or use system fonts instead of custom fonts for above-the-fold text.
Fix ad and embed shifts
Reserve space for ads before they load using min-height on the container. For cookie banners, load them before the main content or use position: fixed so they don't push content. For third-party embeds (Twitter, YouTube), use aspect-ratio CSS to maintain the correct proportions.
Tip: Use our CLS Debugger tool — it records your page loading and highlights exactly which elements shift, when they shift, and by how much. This turns a vague CLS score into a specific list of elements to fix.
Tags: cls, cwv, performance, layout
Tutorial
INP (Interaction to Next Paint): The Newest Core Web Vital Explained
INP replaced FID in March 2024 as a Core Web Vital. It measures how fast your page responds to clicks, taps and keyboard input. Here's what it means and how to improve it.
What is INP?
INP (Interaction to Next Paint) measures the time between a user clicking, tapping, or pressing a key, and the page visually updating in response. Good: under 200ms. Needs improvement: 200–500ms. Poor: over 500ms. A slow INP means your page feels sluggish and unresponsive — like buttons that don't react quickly.
Why it replaced FID
FID (First Input Delay) only measured the first interaction on a page. INP measures ALL interactions throughout the page's lifetime. A page that responds quickly to the first click but gets slow after that would pass FID but fail INP. INP is a much better measure of real-world interactivity.
What causes poor INP?
Too much JavaScript running on the main thread. Long-running event handlers (your click functions doing too much work). Third-party scripts hogging the processor. Large DOM trees with thousands of elements. Excessive CSS animations using CPU-intensive properties.
How to improve INP
Break up long JavaScript tasks (any task over 50ms blocks the browser). Defer non-critical JavaScript. Move heavy processing to Web Workers (background threads). Reduce third-party scripts — each one potentially degrades INP. Simplify your event handlers. Reduce DOM size.
Measure it
INP is only measurable with real users (it's a field metric, not a lab metric). Check your INP in our Page Speed tool under the CrUX real-user data section. In Chrome DevTools, use the Performance panel and look for long tasks. Google Search Console's Core Web Vitals report shows real-user INP for your site.
Tip: The biggest INP wins usually come from auditing your third-party scripts. Remove any you don't absolutely need. Even legitimate scripts like chat widgets, analytics, and social share buttons can add 200–400ms to your INP.
Tags: inp, cwv, performance, javascript
Tutorial
TTFB (Time to First Byte) is how long your server takes to start responding. A slow server makes everything else slow. Here's how to diagnose and fix it.
What is TTFB?
When your browser requests a page, TTFB is the time from sending that request to receiving the first byte of the response. Good: under 200ms. Acceptable: under 600ms. Poor: over 800ms. A high TTFB adds to every other metric — LCP, FCP, everything. Fix TTFB first, then worry about everything else.
Check your TTFB
Our Page Speed tool shows TTFB in the detailed metrics. You can also use WebPageTest.org which gives a detailed TTFB breakdown showing DNS time, connection time, and server processing time separately. This helps identify where the delay actually is.
Enable server caching
Without caching, every page request makes your server build the page from scratch (database queries, template rendering). With caching, the first request builds the page, saves it, and every subsequent request serves the saved version. WordPress: install a page caching plugin. Shopify and most hosted platforms cache automatically.
Use a CDN
A Content Delivery Network stores copies of your site on servers around the world. When a user in Australia visits your UK-hosted site, they're served from an Australian server. A CDN can cut TTFB by 200–400ms for international visitors by serving your content from edge locations near the user.
Upgrade your hosting
Cheap shared hosting means your site shares a server with potentially thousands of other sites. If they get traffic spikes, your site slows down. Moving to a VPS, managed WordPress hosting (WP Engine, Kinsta) or a dedicated server typically cuts TTFB by 50–80%.
Tip: Run your TTFB test at different times of day. If TTFB is fast at 3am but slow at 2pm, you're on an overloaded shared server. Time to upgrade your hosting.
Tags: ttfb, server, hosting, performance
Tutorial
Images are the number one cause of slow pages. This guide shows you how to compress, resize, and serve images in the right format — without making them look bad.
Why images slow your site down
Images are typically 60–80% of a page's total file size. A single unoptimised photo from a modern camera can be 8MB. The same image, optimised properly, can be 80KB — 100x smaller — with no visible quality difference. Multiply this across 20 images on a page and you see the problem.
Choose the right format
JPEG: best for photos. PNG: best for graphics with transparency. WebP: better than both for most uses — 25–35% smaller than JPEG, supports transparency. AVIF: even better compression than WebP but less browser support. Rule of thumb: use WebP for photos, SVG for logos and icons, PNG only when you need transparency on graphics.
Compress your images
Use a browser-based image compression tool — free options are widely available for PNG, JPEG and WebP. For WordPress: use an image optimisation plugin that compresses automatically on upload. Target: under 100KB for most images, under 200KB for hero images.
Serve the right size
Don't serve a 2000px wide image in a 400px column. Use the srcset attribute to serve different sizes: <img srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1200w" sizes="(max-width: 600px) 400px, 800px" src="medium.jpg">. WordPress and most modern CMS platforms do this automatically.
Add lazy loading
Images below the fold (not immediately visible) should load only when the user scrolls near them. Add loading="lazy" to image tags: <img src="photo.jpg" loading="lazy" alt="...">. This reduces initial page load time significantly. Don't use lazy loading on your LCP image (hero image) — that would slow it down.
Tip: Use our Image Optimisation tool to scan every image on a page. It shows file size, dimensions, format, and whether each image has proper alt text. Focus on images over 100KB first.
Tags: images, performance, webp, compression, image-optimisation
Tutorial
Structured data is code you add to your pages that helps Google understand your content better — and can earn you rich results in search (star ratings, FAQs, prices). Here's everything you need to know.
What is structured data?
It's extra information you add to your web pages that's written for search engines, not humans. Visitors don't see it — but Google reads it and uses it to understand your content better. Example: Google can read the text "★★★★☆ — 4 stars" but structured data lets you say explicitly: this is a rating of 4.2 out of 5 from 347 reviews. Then Google can display it as star ratings in search results.
The formats: JSON-LD vs Microdata
There are a few ways to write structured data. Google's recommended format is JSON-LD — it's a separate block of code in your page's <head>, so it doesn't mix with your visible HTML. Microdata is woven into your existing HTML. JSON-LD is far easier to manage — you can copy, paste and edit it without touching your page layout.
What can you get rich results for?
Reviews & ratings (star ratings in search). FAQs (expandable questions directly in search results). Recipes (cooking time, calories, ingredients in search). Products (price, availability, ratings). Events (dates, locations, ticket info). Articles (image, author, date). Local businesses (address, hours, phone). Each type has specific required properties.
Add your first schema
The easiest to start with: FAQPage schema. Find the most common questions about your product or service. Add them to your page as visible Q&A text. Then use our AI Schema Generator to automatically create the JSON-LD code. Paste it into your page's <head>. Test it with Google's Rich Results Test.
Test and validate
Use our Schema Debugger to validate your markup. Use Google's Rich Results Test (search.google.com/test/rich-results). Google Search Console's Enhancements section shows any structured data errors on your live site. Errors in structured data mean you won't get rich results even if the markup is present.
Tip: Start with FAQPage schema — it's the easiest to implement and can earn you expandable FAQ boxes in search results immediately. These can dramatically increase click-through rate by taking up more SERP real estate.
Tags: schema, structured-data, rich-results, rich, json-ld, json
Tutorial
FAQ schema adds expandable question-and-answer boxes directly under your search result. Here's how to implement it correctly and get it approved by Google.
What FAQ schema looks like in Google
When implemented correctly, your search result expands to show questions with toggle arrows. Users can click to reveal the answer without visiting your site. This doubles or triples the space your result takes up in search results — and increases clicks even when users can see the answer.
Google's rules for FAQ schema
The Q&As must be genuinely present as visible content on the page (you can't hide the text). Each question must have exactly one accepted answer. Don't use FAQ schema for advertising or promotional content. Don't mark up content that is not actually Q&A format. Google will ignore or penalise violations.
Write the questions
Think about what your customers actually ask. Check your site's search queries, your customer support emails, "People Also Ask" boxes in Google for your topic. Write natural questions, not keyword-stuffed ones. 3–5 questions per page is ideal. Keep answers to 2–3 sentences — short enough to read in search results.
Generate the code
Use our AI Schema Generator or Schema Markup tool. Select "FAQPage" as the type. Enter your questions and answers. Copy the generated JSON-LD code. Paste it inside the <head> section of your page (or use a a JSON-LD script block just before the closing body tag).
Verify and monitor
Test immediately using our Schema Debugger. Submit the page for indexing in Google Search Console. Check the Enhancements > FAQ section in GSC after a few days — it will show if Google has detected your schema and whether there are any errors. Rich results typically appear within 1–2 weeks.
Tip: FAQ schema works best on pages that already answer questions — service pages, product pages with common objections, "how it works" pages. Don't create a fake FAQ section just for the schema — Google is good at detecting this.
Tags: schema, faq, rich-results, rich, structured-data
Tutorial
Product schema helps Google display your product's price, availability, and star ratings directly in search results. Here's how to implement it for e-commerce pages.
What product rich results look like
Product schema can earn you: star ratings under your search result, price displayed in search results, availability status (In stock, Out of stock), and Google Shopping panel appearances. These significantly increase click-through rates — shoppers see the price before clicking.
Required properties
Minimum for product rich results: name (product name), image (at least one good quality image URL), description. For reviews: aggregateRating with ratingValue and reviewCount. For price: offers with price, priceCurrency, and availability. Missing required properties = no rich results.
Generate and add the markup
Use our Schema Generator — select "Product", enter your URL, and it generates the markup from your page content. WordPress + ecommerce plugins: a schema plugin can add product schema automatically. Shopify: most themes include basic product schema.
Review policies — important
Google has strict rules about review stars. You can only show aggregateRating if the reviews are on your own site and visible to users. You cannot add fake reviews or aggregate reviews from third-party platforms (unless using Google's specific third-party review policies). Violations can result in manual actions.
Check with our Schema Debugger
After adding schema, run our Schema Debugger on the product URL. It shows every property present, flags missing required fields, and warns about potential policy violations. Also check Google's Rich Results Test before submitting to GSC.
Tip: If you sell on an ecommerce platform, use a schema plugin rather than hand-coding it — product schema needs to update dynamically when prices and stock change. Hard-coded schema with wrong prices can hurt trust and trigger Google penalties.
Tags: schema, product, ecommerce, rich-results, rich
Tutorial
Keyword research is finding out what words your potential customers type into Google. Target the right words and you get traffic. Target the wrong ones and you're invisible. Here's how to do it.
Start with what you know
Write down every phrase a customer might search to find your business. Think about: the product or service you sell, the problem it solves, the type of customer who needs it, your location if relevant. Don't overthink it. Write 20–30 ideas. This is your seed list.
Expand with Google itself
Type each seed keyword into Google and look at: autocomplete suggestions (what people type), "People Also Ask" boxes, "Related searches" at the bottom of results. Each of these is a real keyword that real people search for. Free and incredibly accurate because it's Google's own data.
Understand search intent
Every search has an intent: Informational (How do I fix X?), Navigational (brand name searches), Commercial (best X, X reviews), Transactional (buy X, X price). Your page content must match the intent. A product page won't rank for "how do I fix X" — you need a guide for that.
Check keyword difficulty
Some keywords are dominated by huge brands — you won't rank for "running shoes" if you're a small business. Look for specific, longer keywords: "women's trail running shoes for wide feet UK". Longer, more specific keywords (long-tail keywords) have less competition and higher intent.
Use our Keyword Analysis tool
Enter any page URL in our Keyword Analysis tool. It shows which keywords appear on that page, their frequency, and how they compare to competitors. Use this to find keyword gaps — words your competitors rank for that you're not using. Then add those keywords naturally to your content.
Tip: The best keywords for a small site are specific, local, or niche. "Plumber London" is too competitive. "Emergency boiler repair East London" is specific enough to win. Start specific and expand as your authority grows.
Tags: keywords, keyword, research, seo, content
Tutorial
Content Quality: What Google's E-E-A-T Actually Means for Your Site
E-E-A-T stands for Experience, Expertise, Authoritativeness, and Trustworthiness. It's how Google's quality raters judge your content. Here's what it means in plain English and how to improve your scores.
What is E-E-A-T?
Google uses human quality raters to evaluate whether pages are genuinely helpful. They use E-E-A-T as a framework. Experience: does the author have first-hand experience? Expertise: do they know their subject? Authoritativeness: do others in the field respect this source? Trustworthiness: can you trust the information and the site? Higher E-E-A-T = better rankings, especially for health, finance, and legal topics.
Show real expertise
Write content that demonstrates genuine knowledge. Include specific details, real data, and personal experience. Avoid generic "tips" that anyone could write. If you're a plumber writing about boilers, mention specific models, real-world problems you've solved, and technical nuances. Surface-level content is increasingly penalised.
Build authoritativeness
Get mentions and links from respected sites in your industry. Have your authors publish elsewhere. Get quoted in trade publications. Earn reviews on trusted platforms. Authoritativeness is largely about reputation — what do others say about you? This takes time, but it's a multiplier on all your other SEO work.
Establish trust signals
Add an About page with real author bios. Show contact details (real address, phone). Display customer reviews. Include privacy policy, returns policy, terms. Add author bylines to all content. Add dates (and update them when content is refreshed). Show security badges if you take payments.
Check your E-E-A-T score
Use our E-E-A-T Checker to scan any page for trust and authority signals. It checks for author information, contact details, review signals, HTTPS, privacy policies, and more. Use it to find quick wins — often just adding author bios and contact info can significantly improve your score.
Tip: E-E-A-T matters most for YMYL (Your Money or Your Life) topics — health, finance, legal, safety. If your site covers any of these, invest heavily in demonstrating genuine expertise and trustworthiness. Google holds these pages to a much higher standard.
Tags: eeat, content, quality, authority
Tutorial
If your content is hard to read, people leave immediately. High bounce rates tell Google your page isn't useful. Here's how to write clearly, structure your content well, and keep people engaged.
Why readability matters for SEO
When people land on your page and immediately leave (high bounce rate), Google interprets this as your page not answering their question. Better readability = people stay longer = stronger signal that your page is useful = better rankings. Readable content also earns more shares, links, and return visits.
The most important readability fixes
Short paragraphs (3–4 lines max). Subheadings every 200–300 words so people can scan. Bullet points for lists (easier to read than long sentences). Simple words where possible. Active voice ("We fixed the problem") over passive ("The problem was fixed by us"). These changes make a massive difference with minimal effort.
Reading level
Most online content should target a reading age of 12–14 years (Flesch-Kincaid grade level 7–8). This doesn't mean dumbing down — it means clear communication. Even highly educated readers prefer plain, direct language online. Use our Readability Checker to check your Flesch reading ease score.
The inverted pyramid
Put the most important information first. Many readers only read the first paragraph. Journalists have used this structure for 150 years because it works: lead with the key point, then supporting details, then background. For SEO: answer the search query immediately, then expand and explain.
Check and improve
Use our Readability tool to analyse any page or paste text. It gives you a readability score, sentence length analysis, and specific suggestions. Pay attention to: very long sentences (over 20 words), passive voice overuse, complex word alternatives, and paragraph length.
Tip: Write your first draft naturally, then edit for readability. Read it aloud — if you stumble, your readers will too. Break long sentences at the natural pause point. Replace every "utilise" with "use" and every "commence" with "start".
Tags: readability, content, ux, bounce-rate, bounce
How-To
Your page has no title tag. This is a critical SEO issue — Google has nothing to show in search results except a guess. Here's how to add one in 5 minutes.
Why this is critical
Without a title tag, Google picks a random string of text from your page to use as the headline in search results. This usually looks terrible, confuses users, and hurts click-through rates. The title tag is one of the strongest on-page SEO signals — you're leaving significant ranking power on the table.
WordPress fix (2 minutes)
Install your SEO plugin if you haven't already. Edit the page. Scroll to the SEO plugin section at the bottom. Enter your title in the SEO Title field. Keep it 50-60 characters and include your main keyword near the start. Save. Done.
Shopify fix (2 minutes)
Go to Online Store > Pages (or Products/Collections). Click the page. Scroll to "Search engine listing". Click "Edit website SEO". Type your title in the "Page title" field. Save.
Plain HTML fix (1 minute)
Open your page's HTML file. Find the <head> section. Add this line: <title>Your Page Title Here — Brand Name</title>. Make sure it's inside the <head> tags. Save and upload the file.
Squarespace / Wix / Other builders
Every modern website builder has an SEO section. Look for: Page Settings > SEO tab, or click the page > Properties > SEO. There will be a "Title" or "SEO Title" field. If you can't find it, search your platform's help docs for "add meta title".
Tip: After adding title tags, re-run the Site Audit to confirm the fix. It usually takes Google 1–2 weeks to pick up the new title in search results.
Tags: title, on-page, onpage, critical, fix
How-To
Multiple pages on your site have identical title tags. Google can't tell your pages apart, and you're competing with yourself in search results. Here's how to find and fix them all.
Find all duplicate titles
Use our Site Crawler to scan your whole site. It lists every page title and flags duplicates. Alternatively, in Google Search Console go to Pages > Why pages aren't indexed, or use our Site Crawler to export all titles and sort them to find matches.
Prioritise which to fix first
Fix duplicate titles on your most important pages first: homepage, product/service pages, blog posts you're actively promoting. Category pages and tag archive pages often have templated titles — fix the template, not each page individually.
Fix templated pages in WordPress
If your blog archive pages all show "Posts | YourSite", edit your SEO plugin settings for Archives. Change the title template to include the category or tag name. This fixes all archive pages at once.
Write unique titles for key pages
Each page title should reflect that specific page's content and primary keyword. Homepage: "Brand Name — Main Service/Product | Location". Service pages: "Service Name — City | Brand". Blog posts: "Post Title | Brand". No two pages should have the same title.
Prevent future duplicates
Set up title templates in your SEO plugin for each page type: posts, pages, products, categories, tags. Good templates auto-generate unique titles from the content. Test new page types before launching to confirm titles are unique.
Tip: If you have an e-commerce site with hundreds of product pages, focus on your best-selling and most-visited products first. You can't rewrite 1,000 titles in a day — prioritise by traffic and revenue impact.
Tags: title, duplicate, on-page, onpage, fix
How-To
How to Fix: Thin Content (Under 300 Words)
Pages with very little content struggle to rank because they don't give Google much to work with. Here's how to identify thin pages and bulk them up with genuinely useful content.
Is thin content always a problem?
Not necessarily. A contact page with 150 words is fine. A product page with 80 words is a problem. The question is: does this page provide enough value to satisfy the searcher's intent? If someone searches for information and lands on a 100-word page, they'll probably leave immediately. That's the problem.
Find your thin pages
Our Site Audit flags pages under 300 words. But also look for pages with high bounce rates in Google Analytics — thin content and high bounce rate often go together. Sort your pages by bounce rate and look for correlation with short content.
Consolidate very thin pages
If you have 10 product pages each with 50 words, consider consolidating them into one comprehensive category page with 1,500 words covering all 10 products. Or redirect thin, low-value pages to a stronger related page. Fewer, better pages outperform many thin ones.
Expand thin content that should rank
For pages targeting specific keywords: add a proper introduction, expand the main content with specifics, add an FAQ section addressing related questions, include an image or two with alt text, add internal links to related content. Aim for at least 500 words for informational pages, 300+ for product pages.
Don't pad for the sake of it
Adding 500 words of meaningless filler is worse than staying at 100 words. Every sentence should add value. A tight 400-word page beats a bloated 1,000-word page every time. Write what's genuinely useful, not what hits a word count.
Tip: Run our Readability Checker on your expanded content. If you've added 500 words but the readability score is terrible, you've created thin content that's also hard to read — doubly bad. Quality and quantity both matter.
Tags: content, thin, on-page, onpage, fix
How-To
Alt text is the description you add to images. Without it, Google can't understand your images, screen readers can't describe them to blind users, and you're missing keyword opportunities.
What alt text actually is
Alt text (alternative text) is a text description of an image. It's used by: screen readers for visually impaired users (accessibility), Google to understand what the image shows (SEO), browsers when an image fails to load (shown as text instead). It looks like this: <img src="photo.jpg" alt="Red running shoes on white background">.
Find images without alt text
Our Site Audit and Image Optimisation tools flag all images without alt text. Every image without alt="" or alt="some text" is missing alt text. Note: purely decorative images should have alt="" (empty alt text) — this tells screen readers to skip them.
Write good alt text
Describe what you actually see in the image, specifically. "Image" or "Photo" is useless — screen readers already say "image". "Red Nike Air Max running shoes side view" is great. Include a keyword if it's genuinely relevant to the image. Don't keyword-stuff: "running shoes buy cheap running shoes UK cheap" is spam.
Fix in WordPress
Go to Media Library. Click any image. The "Alt Text" field is on the right side. Add the description. Save. This updates the alt text across all pages using that image. For images already placed on pages, click the image in the page editor, look for "Alt Text" in the block settings.
Fix in Shopify
Products: go to the product, click each product image, click "Edit alt text" (appears when you hover). You'll see a field to add your description. For collection images and theme images, use the theme editor or media settings.
Tip: Prioritise alt text for images that appear near your target keywords on the page — especially hero images and product images. These carry the most SEO weight. Decorative images (dividers, backgrounds) can have empty alt="" attributes.
Tags: images, alt-text, alt, accessibility, fix
How-To
Broken links (links to pages that return 404 errors) waste link authority, frustrate users, and make your site look abandoned. Here's how to find and fix them all.
The damage broken links do
When you link to a page that returns a 404, the link authority (ranking power) flowing through that link is lost. Users click it, get an error, and leave frustrated. Google crawls it, wastes crawl budget, and over time may view your site as poorly maintained. External broken links also embarrass you in front of anyone who follows them.
Find broken links
Use our Site Crawler — it follows every link on your site and reports any that return 4xx or 5xx status codes. Google Search Console > Pages > Not Found also shows broken pages Google has tried to crawl. For outbound broken links, use our Redirect Checker to test specific URLs.
Fix internal broken links
Update the link to point to the correct URL. If the target page no longer exists, link to the most relevant existing page instead. If the content is gone permanently and no replacement exists, remove the link entirely. Don't leave links pointing to 404 pages.
Fix broken pages with redirects
If you've deleted or moved a page that had links pointing to it (internal or external), set up a 301 redirect from the old URL to the most relevant existing page. This preserves the link authority. In WordPress: use a redirect plugin or add rules to .htaccess. In Plesk/Apache: add to .htaccess.
Fix broken external links
If you're linking to an external page that's returned a 404, either find the updated URL for that content, find an alternative source to link to, or remove the link if the content no longer exists anywhere. Linking to dead external pages is a minor trust signal issue.
Tip: Run our Site Crawler monthly to catch broken links before they accumulate. It's much easier to fix 5 broken links per month than 200 at the end of the year.
Tags: 404, broken-links, broken, crawling, fix
How-To
A redirect chain is when URL A redirects to URL B which redirects to URL C. Each hop loses link authority and slows down page loading. Here's how to detect and collapse redirect chains.
What is a redirect chain?
A redirect chain is A → B → C → D instead of A → D directly. Each redirect hop: loses some link authority (roughly 15% per hop, though Google hasn't confirmed exact numbers), adds 100–500ms of load time per hop, and can confuse crawlers. If the chain has 4+ hops, some crawlers give up entirely.
Find your redirect chains
Use our Redirect Checker — enter a URL and it shows the full redirect path. Our Site Crawler also detects redirect chains across your whole site. In Google Search Console, URLs with redirect issues sometimes appear in the Coverage report.
Fix the chains
Update each redirect in the chain to point directly to the final destination. Example: if A redirects to B and B redirects to C, update A to redirect directly to C. Then you can remove the B redirect (or keep it pointing to C for any direct links to B). Always redirect to the final destination in one step.
Fix redirect loops
A redirect loop is A → B → A, which creates an infinite loop. The browser eventually gives up with a "too many redirects" error. Fix by identifying which redirect creates the circular reference and removing or correcting it. Our Redirect Checker detects loops and shows you where the circle starts.
Prevent future chains
When you add a new redirect, always check if the destination already has a redirect. If yes, update your new redirect to point to the final destination directly. Build this into your process: when redirecting, always follow the chain to the end first.
Tip: The most common cause of redirect chains is URL migrations. When you switch from HTTP to HTTPS, then move the site, then change URL structure, you can accidentally create 3-hop chains. After any major migration, audit all redirects with our Redirect Checker.
Tags: redirects, 301, chains, fix
How-To
How to Fix: Slow TTFB (Server Response Over 600ms)
Your server is taking over 600ms to respond. This slows down everything else on your page — LCP, FCP, everything. Here's how to diagnose the cause and fix it.
Diagnose the cause
TTFB has three components: DNS lookup time, connection time, and server processing time. Use WebPageTest.org and look at the waterfall — it breaks TTFB into these three components. If DNS is slow: switch to a faster DNS provider. If connection is slow: you need a CDN. If server processing is slow: you need caching or better hosting.
Enable caching (fastest fix)
WordPress: install a page caching plugin. Configure page caching — this stores a pre-built version of each page so the server doesn't regenerate it on every request. This can cut TTFB from 800ms to 100ms overnight. Best single fix for WordPress sites.
Add a CDN
A CDN routes your traffic through edge servers close to your users, reducing TTFB by 200-400ms for international visitors. Set up via your hosting control panel or a CDN provider — most offer a free tier. Once active, static assets are served from the nearest edge location automatically.
Optimise your database
Slow database queries are a common cause of high server processing time. WordPress: use a query debugging plugin to identify slow queries. Common fixes: add indexes to key tables, delete spam comments, clean up post revisions. Database cleanup plugins can automate this.
Upgrade hosting
If you're on shared hosting and TTFB is consistently over 1 second, no amount of optimisation will fully fix it. Consider: managed WordPress hosting (Kinsta, WP Engine — £25–50/month), a VPS (Linode, DigitalOcean — £10–20/month with more control), or at minimum a better shared hosting tier.
Tip: After making changes, always clear your caching plugin and test TTFB again in a private browser window. Cached responses look fast even without server improvements — you need to test the uncached response to see real TTFB.
Tags: ttfb, server, hosting, performance, fix
How-To
Your page has no H1 heading. This is a critical SEO issue — the H1 is one of the most important on-page ranking signals. Here's how to add one properly.
Why H1 is critical
The H1 tag is your page's main heading — it tells Google and users what the page is about. It's one of the strongest on-page SEO signals after the title tag. Pages without H1 tags miss a major ranking opportunity and often have unclear content structure. Google's own documentation emphasises heading hierarchy for understanding page content.
Find pages missing H1s
Our Site Audit flags every page missing an H1 tag. You can also use our HTML Checker on specific pages. In Chrome, right-click > View Page Source and search for "<h1" — if nothing highlights, there's no H1.
Write a good H1
Your H1 should: clearly state what the page is about, include your primary keyword naturally, be different from but related to your title tag, be the first and most prominent heading on the page. Don't just copy your title tag — the H1 is what visitors see on the page, the title tag is what they see in Google.
Add it to your page
HTML: add <h1>Your Heading Here</h1> as the first heading on the page. WordPress (Gutenberg editor): make sure your first block is a "Heading" block set to H1. WordPress (Classic editor): in the format dropdown, your page title usually automatically creates an H1 — check this hasn't been overridden by your theme. Shopify: the product/page name is usually the H1 — check your theme's template.
Common H1 mistakes to avoid
Multiple H1s (use only one per page). H1 hidden with CSS display:none (Google ignores hidden content). Logo or site name as H1 (it should be the page topic, not your brand). H1 that's just one word (be specific). Using a div with a class to make text look like H1 without actually being H1 in the HTML.
Tip: If your CMS theme automatically generates an H1 from your page title, don't add another one manually — that would create two H1s. Check your theme documentation to understand how headings are generated before editing.
Tags: h1, headings, on-page, onpage, fix, critical
How-To
How to Find and Fix Mixed Content
Use the console to find every insecure resource, rewrite the URLs at source including stored content, then add a CSP backstop.
1. Let the browser console find them
Open DevTools and load the page. Browsers report every mixed-content resource by exact URL and say whether it was blocked or merely flagged. This beats grepping because it catches resources injected by scripts and by third-party embeds that never appear in your source files.
2. Grep the codebase as well
grep -rn 'http://' ./public --include="*.html" --include="*.css" --include="*.js" | grep -vE 'xmlns|schema\.org|w3\.org|http-equiv'. The exclusions matter — namespace declarations and schema identifiers contain http:// but are not resource requests, and rewriting them breaks things for no benefit.
3. Check your CSS files specifically
Stylesheets reference images and fonts via url(http://...) and are easy to overlook because they are not in the HTML. grep -rn 'url(http://' ./public --include="*.css" catches these.
4. Rewrite the URLs to https://
Nearly every reputable host now serves HTTPS, so the fix is usually just the scheme. Verify each rewritten URL actually loads over HTTPS before shipping — a few ancient third-party endpoints still do not support it, and those need replacing rather than rewriting.
5. Clean stored and CMS content
On CMS sites the offenders are usually inside post bodies and widget settings, not templates. WordPress: wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guid --dry-run first, then without --dry-run. Leave guid alone or feed readers treat every post as new.
6. Check form actions
A form on an HTTPS page posting to an http:// action sends everything the user typed in clear text, and the page still shows a padlock so nobody notices. grep -rn 'action="http://' across your templates and fix every instance.
7. Add upgrade-insecure-requests as a backstop
Add the directive to your CSP so browsers retry any remaining http:// resource over HTTPS automatically. Treat it as a safety net while you clean the source, not as the fix — it relies on the destination supporting HTTPS, and where it does not you get a missing resource instead of an insecure one.
8. Re-test the pages most likely to carry legacy content
Old blog posts, product descriptions, embedded videos, imported pages. The homepage is usually the cleanest page on the site. Confirm the console is clear and the padlock shows without warnings.
Tip: Set up a CSP report endpoint afterwards. Mixed content returns through content editors pasting old URLs and through new third-party embeds, and reports tell you it has happened before a customer notices the broken padlock.
Tags: mixed-content, security, howto, fix
How-To
Render-blocking resources are CSS and JavaScript files that prevent your page from displaying until they've fully loaded. They delay LCP, FCP, and everything the user sees. Here's how to fix them.
What render-blocking means
When your browser loads a page, it processes HTML top-to-bottom. When it hits a script or stylesheet link tag, it stops rendering the page and downloads and parses that file first. Only then does it continue. So if you have 5 JavaScript files in your head section, the user sees nothing until all 5 have loaded.
Fix render-blocking JavaScript
Add the defer attribute to non-critical script tags. This downloads the script in parallel but executes it after the HTML is parsed. Or add the async attribute for scripts that don't depend on each other. Move scripts to just before the closing body tag as a last resort.
Fix render-blocking CSS
CSS is harder — browsers need CSS to render the page without unstyled flashes. Solution: extract "critical CSS" (the CSS needed to render the above-the-fold content) and inline it in the <head>. Load the rest of your CSS asynchronously. Tools: Critical CSS generator at criticalcss.com, or the critters package for automated extraction.
Identify what's blocking
Our Page Speed tool shows "Eliminate Render-Blocking Resources" in the opportunities section with specific files listed. Chrome DevTools > Performance tab > record page load > look for yellow (scripting) and purple (rendering) in the timeline. The waterfall shows which files are causing the delay.
WordPress-specific fixes
In your performance plugin: look for settings to defer JavaScript and optimise CSS delivery. These handle the technical implementation automatically. Be careful — deferring some scripts can break functionality. Test thoroughly after enabling.
Tip: Don't defer everything blindly. Some scripts (jQuery, Bootstrap JavaScript) need to load in a specific order. Defer non-essential scripts first (analytics, chat widgets, social share buttons) and test that the page still functions correctly.
Tags: performance, javascript, css, render-blocking, render, fix
How-To
How to Fix: High CLS (Cumulative Layout Shift) Score
Your page layout is shifting after load, causing a poor user experience. Our CLS Debugger shows exactly what's shifting. This guide tells you how to fix each type of shift.
Identify what's shifting
Use our CLS Debugger tool — enter your URL and it records the page loading and highlights every element that shifts, with a score for each one. Focus on the elements with the highest individual CLS contribution. Common culprits: images without dimensions, late-loading fonts, ads, cookie banners, dynamically injected content.
Fix images: add explicit dimensions
The fix: add width and height attributes to every <img> tag: <img src="photo.jpg" width="1200" height="800" alt="...">. Add aspect-ratio in CSS: img { aspect-ratio: 3/2; }. This reserves the right space before the image loads so nothing shifts when it appears. This is the most common CLS fix.
Fix font swap CLS
When web fonts load late, text jumps from the fallback font to the web font. Fix: add font-display: optional (no font swap) or font-display: swap with size-adjust to match fallback font metrics. Preload critical fonts in <head>: <link rel="preload" href="font.woff2" as="font" crossorigin>. Consider using system fonts to eliminate this entirely.
Fix cookie banner and popup CLS
Cookie banners that push content down cause massive CLS. Fix: use position: fixed or position: sticky for cookie banners so they overlay content instead of pushing it. Or load the banner above the fold in the initial HTML before any shifts can occur. Popups should always overlay, never push content.
Fix ad CLS
Reserve space for ads before they load: .ad-container { min-height: 250px; }. Use aspect-ratio CSS for responsive ad containers. Never inject ads above existing content after the page has loaded. Use static ad slots in your layout rather than dynamically appended ones.
Tip: After fixing CLS issues, use our CLS Debugger again to confirm the score has improved. Then check Google Search Console's Core Web Vitals report — real-user CLS data updates every 28 days, so it takes about a month to see improvement in GSC.
Tags: cls, cwv, layout, fix, images, mobile
How-To
JavaScript files that are loaded but not used on a page are pure waste — they delay loading with no benefit. Here's how to identify and remove unused JS.
Find unused JavaScript
Chrome DevTools: open F12 > Coverage tab (if not visible, press Ctrl+Shift+P and type "Coverage"). Click the record button and reload the page. The Coverage panel shows every JS and CSS file with the percentage used vs unused. Files showing 70%+ unused are good candidates for removal or code-splitting.
Identify which plugins/scripts to remove
Our Page Speed tool shows "Remove Unused JavaScript" with specific files listed. Common offenders: jQuery UI (loaded by WordPress but only a small part is used), ecommerce cart scripts on non-shop pages, social share buttons, chat widgets, old Google Analytics (use GA4 instead), plugins you're not actively using.
Remove unused WordPress plugins
Every active plugin potentially adds JavaScript. Deactivate any plugin you don't actively use. Consider whether you need jQuery-heavy plugins — there are often lighter alternatives. Check if any active plugins load scripts site-wide that should only load on specific pages.
Limit scripts to relevant pages
Contact form scripts shouldn't load on every page — only on the contact page. ecommerce cart scripts shouldn't load on the homepage. In WordPress: script management plugins let you disable specific scripts on specific pages. This reduces unused JavaScript without removing the functionality entirely.
Consider code splitting
If you're a developer: bundle large JavaScript files with code splitting so only the code needed for the current page is loaded. Webpack and Vite both support this. For third-party scripts: use dynamic imports to load scripts only when needed (e.g., load the chat widget only when the user scrolls to the bottom).
Tip: Before removing any JavaScript, test your page thoroughly. Some scripts that appear unused during a crawl are actually needed for specific user interactions. Always test in staging before making JavaScript changes on a live site.
Tags: javascript, performance, pagespeed, fix, unused
How-To
How to Fix: Poor First Contentful Paint (FCP)
FCP measures how long until the first piece of content appears on screen. Google wants under 1.8 seconds. A poor FCP makes your page feel broken before it has even loaded.
What causes poor FCP
The three main causes are: 1) Render-blocking resources (CSS and JS files in the head that delay rendering), 2) Slow server response time (high TTFB means nothing can load until the server responds), 3) Large HTML payload that takes time to download before the browser can start rendering.
Eliminate render-blocking resources
Add the defer attribute to non-critical JavaScript. Move scripts to just before the closing body tag. For CSS: extract critical above-the-fold CSS and inline it in your page head, then load the rest asynchronously using a preload onload swap.
Reduce server response time
FCP cannot start until the server sends the first byte. Target TTFB under 600ms. Enable server-side caching (object caching or a page caching plugin). Use a CDN to serve your HTML from a location near the user. Upgrade your hosting if TTFB is consistently over 1 second.
Preload critical resources
Tell the browser to start downloading critical resources early: <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>. Preload your hero image, critical fonts and key CSS. Every resource preloaded is one less thing blocking the first paint.
Reduce initial HTML size
Strip unused code from your HTML. Remove inline scripts that can be deferred. Minimise the amount of HTML in the initial response — the browser cannot start rendering until it has received and parsed the opening tags. Enable GZIP or Brotli compression on your server to reduce transfer size.
Tip: FCP and LCP often have the same root causes. Fixing render-blocking resources and server response time will improve both metrics simultaneously. Always test on mobile — FCP is typically 2-3x slower on mobile than desktop.
Tags: fcp, performance, pagespeed, cwv, fix
How-To
How to Fix: High Total Blocking Time (TBT)
TBT measures how long the main thread was blocked by JavaScript tasks over 50ms, preventing the page from responding to user input. High TBT makes pages feel unresponsive and sluggish.
Understand what TBT measures
The browser has a single main thread that handles both JavaScript execution and user interactions. Any JavaScript task that runs for more than 50ms blocks user input for the duration of that task. TBT adds up all the time above 50ms across all tasks during page load. A score above 600ms is poor. The main culprit is always JavaScript.
Find long tasks in DevTools
Open Chrome DevTools > Performance tab. Record a page load. Look for red triangles in the timeline — these mark long tasks. Click on a task to see which script caused it. The Call Tree and Bottom-Up tabs show which functions consumed the most time. This tells you exactly which JavaScript to optimise.
Break up long JavaScript tasks
Instead of running one large function, break it into smaller chunks using setTimeout or scheduler.postTask: function processInChunks(items) { const chunk = items.splice(0, 50); processChunk(chunk); if (items.length) setTimeout(() => processInChunks(items), 0); }. This yields control back to the browser between chunks, allowing user interactions to be processed.
Remove unused JavaScript
Every JavaScript file loaded on a page increases TBT — even if unused. Use Chrome DevTools Coverage tab to find files with 70%+ unused code. Remove unused plugins, defer third-party scripts (analytics, chat widgets, social buttons), and use code splitting so only the JavaScript needed for the current page loads.
Defer third-party scripts
Third-party scripts (analytics, chat, ads, review widgets) are a major TBT source because you cannot control their code quality. Load them with defer or async. Better: use a facade pattern — show a static placeholder until the user interacts, then load the real widget. Partytown is a library that runs third-party scripts in a web worker to keep the main thread free.
Tip: TBT in Lighthouse is a lab metric. The real-world equivalent is INP (Interaction to Next Paint). Fix TBT and INP improves automatically. Target TBT under 200ms for a good score.
Tags: tbt, javascript, performance, pagespeed, fix
How-To
How to Fix: Slow Time to Interactive (TTI)
TTI measures when a page becomes fully interactive — able to respond to user input reliably. A slow TTI means users can see your page but cannot use it, leading to frustration and high bounce rates.
What TTI actually measures
TTI is the point at which: the page has displayed useful content (FCP has occurred), event handlers are registered for visible elements, and the page responds to user interactions within 50ms. The main difference between FCP and TTI is JavaScript execution time — the page can look loaded but still be unusable while JS is running.
Reduce JavaScript execution time
TTI is almost entirely driven by JavaScript. Audit your JS bundle size using Chrome DevTools > Coverage. Remove unused code. Split large bundles into smaller chunks that load on demand. Minify and compress all JavaScript files. Removing 100KB of JavaScript can improve TTI by 1-2 seconds on a mid-range mobile device.
Defer non-critical JavaScript
Identify which JavaScript is needed for the page to be interactive above the fold and which can be deferred. Analytics, social share buttons, chat widgets, video players — all can load after TTI. Use defer on all non-critical scripts. This lets the page become interactive faster while non-essential features load in the background.
Minimise main thread work
The main thread must be free for the page to become interactive. Reduce the total JavaScript parsing, compilation and execution time. Avoid large frameworks for simple functionality — vanilla JavaScript is faster to parse. Use Web Workers for CPU-intensive tasks like data processing, so the main thread remains responsive.
Preload critical resources
TTI starts from FCP — the faster FCP occurs, the earlier TTI can potentially be reached. Preload your critical JavaScript with <link rel="preload" href="main.js" as="script"> so it starts downloading immediately. This reduces the gap between FCP and TTI because the critical JS is ready to execute sooner.
Tip: TTI is being phased out by Google in favour of INP (Interaction to Next Paint) as a Core Web Vital. However, fixing TTI issues (primarily JavaScript) fixes INP as well. The underlying problem is always the same: too much JavaScript blocking the main thread.
Tags: tti, javascript, performance, pagespeed, fix
How-To
How to Fix: Poor Speed Index Score
Speed Index measures how quickly the visual content of your page is populated — it captures how fast the page looks complete to a user watching it load. A poor Speed Index means the page builds up slowly, piece by piece.
What Speed Index measures
Speed Index is calculated by capturing a video of the page loading and measuring how quickly the visible content fills in over time. A page that appears mostly complete after 1 second scores much better than one that gradually loads over 5 seconds even if the final result is identical. Google weights it at 10% of the performance score.
Eliminate render-blocking resources
Render-blocking CSS and JavaScript delay when visual content can start appearing. Move non-critical JS to defer or async. Inline critical above-the-fold CSS directly in the <head> so the browser can render the page immediately without waiting for external stylesheets. This is the single biggest Speed Index win.
Optimise images above the fold
Images that are visible without scrolling (above the fold) have a massive impact on Speed Index because they are part of the visible progress. Compress them aggressively, serve in WebP or AVIF format, and add <link rel="preload" as="image"> for the hero image so it starts loading as early as possible.
Reduce server response time
Nothing can render until the server responds. A slow TTFB pushes every visual element back by the same amount. Target TTFB under 600ms. Enable server-side page caching, use a CDN, and optimise database queries. A 500ms TTFB improvement translates directly into a 500ms Speed Index improvement.
Avoid large layout shifts during load
Elements that jump around during loading are captured by the Speed Index video and count against the score. Set explicit dimensions on images and videos, avoid dynamically injecting content above existing content, and use CSS skeleton screens instead of blank spaces while content loads. A stable loading experience scores better.
Tip: Speed Index improvements almost always come from the same fixes as LCP and FCP. If you fix render-blocking resources, optimise above-the-fold images and reduce TTFB, your Speed Index will improve automatically alongside the other metrics.
Tags: speed, performance, pagespeed, fix
How-To
How to Fix: Unminified CSS and JavaScript
Unminified files contain whitespace, comments and long variable names that serve no purpose in production. Minifying them reduces file size, reduces transfer time and improves page speed with minimal effort.
What minification does
Minification removes: whitespace and line breaks, code comments, long variable and function names (replaced with single letters), and unnecessary semicolons. A typical CSS file can shrink by 20-40% and a JavaScript file by 30-60%. This reduces the amount of data the browser needs to download before the page can load.
Minify in WordPress
In your performance plugin: enable Minify CSS and Minify JavaScript in the optimisation settings. Most modern caching and performance plugins include this. Test after enabling — occasionally minification breaks specific scripts that rely on their original formatting.
Minify in build tools
If you use a build process: Webpack uses TerserPlugin for JS and css-minimizer-webpack-plugin for CSS automatically in production mode. Vite minifies by default in production. Rollup uses @rollup/plugin-terser. Simply building for production (not development) handles this automatically in all modern tools.
Minify manually or via CDN
For simple sites without a build process: use online tools like CSS Minifier (cssminifier.com) or JavaScript Minifier (javascript-minifier.com). Copy your code in, get the minified version out, and replace your file. Better: use your CDN provider — many offer auto-minification of CSS, JS and HTML automatically.
Enable GZIP or Brotli compression too
Minification and compression work together. Minification reduces the file size before compression. Compression (GZIP or Brotli) then reduces it further during transfer. Enable compression in your .htaccess: AddOutputFilterByType DEFLATE text/css application/javascript. Or use a CDN provider which typically enables Brotli automatically.
Tip: Always test your site after enabling minification. Some JavaScript files (particularly inline scripts or scripts that rely on specific whitespace) can break when minified. Test every interactive feature on the page — forms, sliders, menus — before making it live.
Tags: javascript, css, minify, performance, pagespeed, fix
How-To
CSS files that contain styles never used on the page add unnecessary weight. The browser must download, parse and process all CSS before rendering — unused rules waste all three.
Find unused CSS
Chrome DevTools: press F12 > More Tools > Coverage (or Ctrl+Shift+P and type Coverage). Click the record button and load the page. The Coverage panel shows each CSS file with a red bar showing unused percentage. A file showing 80%+ unused is a strong candidate for cleanup. Our Page Speed tool also identifies the specific files.
Remove unused CSS manually
For small sites: use PurgeCSS (purgecss.com) — it scans your HTML, JS and template files and removes CSS rules that are never referenced. Install via npm: npm install -g purgecss. Run: purgecss --css styles.css --content index.html --output purged.css. Always check the result — PurgeCSS can accidentally remove styles used by JavaScript interactions.
WordPress: remove plugin CSS
Many WordPress plugins load their CSS on every page even when only needed on specific pages. Use a script management plugin to disable specific CSS files on pages where they are not needed. A contact form plugin should only load its CSS on the contact page, not site-wide.
Use CSS frameworks carefully
Bootstrap, Tailwind and Bulma load hundreds of KB of CSS — most unused on any given page. If using Bootstrap: only import the components you actually use. If using Tailwind: use PurgeCSS in production (Tailwind includes this automatically via the purge option in tailwind.config.js). Unused framework CSS is the most common source of this issue.
Split CSS by page
For large sites: load only the CSS needed for each page. Use code splitting in your build tool to generate page-specific CSS files. WordPress: use wp_enqueue_style with conditional logic to only load CSS on relevant pages: if (is_page('contact')) { wp_enqueue_style('contact-form-css', ... ); }. This eliminates unused CSS entirely.
Tip: Be careful with dynamic CSS — styles applied by JavaScript after page load will not be caught by static analysis tools like PurgeCSS. Always test interactivity (dropdowns, modals, sliders) after removing CSS to ensure nothing has broken.
Tags: css, performance, pagespeed, fix
How-To
How to Fix: Enormous Network Payloads
Large network payloads — the total size of all files downloaded to load a page — directly cause slow load times and cost mobile users real money on data charges. Google flags pages over 1.6MB total transfer size.
Find your largest files
Chrome DevTools > Network tab > reload the page > click "Size" column to sort by file size. The largest files are your biggest opportunities. Also check the bottom status bar which shows total transfer size. Our Page Speed tool shows the specific files Lighthouse flags as oversized.
Compress images (biggest win)
Images typically account for 60-80% of page weight. Compress every image: use WebP or AVIF format (50-80% smaller than JPEG), use responsive images with srcset so mobile devices get smaller files, and lazy load below-the-fold images. A single unoptimised hero image can be 2-4MB — after compression it should be under 200KB.
Compress JavaScript and CSS
Enable Brotli or GZIP compression on your server — this reduces JS and CSS transfer size by 60-80% with zero code changes. In Apache .htaccess: AddOutputFilterByType DEFLATE text/css application/javascript text/html. In Nginx: gzip on; gzip_types text/css application/javascript. Many CDN providers enable Brotli automatically.
Remove unused code
Audit your JavaScript and CSS for unused code using the Coverage tab in Chrome DevTools. Remove unused plugins and libraries. Use tree-shaking in your build process to eliminate dead code. Switch from large libraries to lighter alternatives or native JavaScript methods — many popular libraries have much smaller modern equivalents.
Implement caching
Caching means returning visitors only download files that have changed. Set long cache lifetimes on static assets: Cache-Control: max-age=31536000, immutable for versioned files (e.g. app.abc123.js). For HTML: Cache-Control: no-cache (check for updates) with ETags. Returning visitors then load the page with near-zero download size.
Tip: Target a total page weight under 1.6MB for desktop and under 500KB for mobile. The single biggest impact action for most sites is image compression — convert all images to WebP and properly size them for their display dimensions. This alone can cut page weight by 60%.
Tags: javascript, css, performance, pagespeed, payload, fix
How-To
How to Fix: Font Display Issues Causing Layout Shift
When web fonts load late, browsers show a flash of invisible text (FOIT) or a flash of unstyled text (FOUT) — both harm CLS and user experience. The font-display CSS property controls exactly how fonts load.
Understand FOIT and FOUT
FOIT (Flash of Invisible Text): the browser hides text until the web font loads — users see blank space. FOUT (Flash of Unstyled Text): the browser shows a fallback font first, then swaps to the web font when it loads — users see a jump. Both are caused by web fonts loading slower than the HTML. font-display controls which behaviour occurs.
Add font-display: swap
The most common fix. Add font-display: swap to your @font-face declarations: @font-face { font-family: "MyFont"; src: url("font.woff2") format("woff2"); font-display: swap; }. This tells the browser to show the fallback font immediately and swap to the web font when it arrives. Eliminates invisible text but causes a visible swap.
Fix Google Fonts font-display
Google Fonts adds font-display via the URL: https://fonts.googleapis.com/css2?family=Open+Sans&display=swap. Change display=swap in your Google Fonts URL. For self-hosted Google Fonts: download the font files and host them yourself with @font-face including font-display: swap.
Preload critical fonts
Preloading starts the font download immediately rather than waiting until the CSS is parsed. Add to your <head>: <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>. Only preload fonts used above the fold — preloading too many fonts wastes bandwidth and can make things worse.
Reduce layout shift from font swap
Use font-display: optional if CLS from font swapping is severe — this only uses the web font if it loads within a short time window, otherwise uses the fallback with no swap. Or use the CSS size-adjust, ascent-override and descent-override properties to match your fallback font metrics to the web font, minimising the visual shift when the swap occurs.
Tip: Self-hosting fonts is almost always faster than loading from Google Fonts — one fewer DNS lookup, one fewer connection, and you can set your own caching headers. Use google-webfonts-helper.herokuapp.com to download and self-host any Google Font.
Tags: font, css, cls, performance, pagespeed, fix
How-To
How to Fix: Legacy JavaScript Slowing Modern Browsers
Legacy JavaScript uses polyfills and transpilation to support old browsers — but most of your users are on modern browsers that support ES6+ natively. Shipping this bloat to everyone wastes bandwidth and increases TBT.
What legacy JavaScript is
When developers write modern JavaScript (ES6+), tools like Babel transpile it to older syntax so it runs in Internet Explorer. This adds polyfills — extra code that implements modern features in old browsers. The problem: IE is dead (discontinued 2022). Most users are on Chrome, Safari or Firefox — all of which support ES6+ natively. The polyfills are dead weight.
Check your Babel config
In your babel.config.js or .babelrc, check your targets. If you see IE11 or old browser targets: @babel/preset-env with { targets: { ie: 11 } } — update this. Change to: { targets: { esmodules: true } } or specify modern browsers: { targets: { chrome: 90, firefox: 88, safari: 14 } }. This tells Babel not to transpile features these browsers already support.
Use module/nomodule pattern
Serve modern JS to modern browsers and legacy JS only to old browsers. Add type="module" to your main script (modern browsers only), and a nomodule fallback for old browsers: script type=module for modern browsers, script nomodule for legacy browsers. Modern browsers ignore nomodule, old browsers ignore type=module.. Modern browsers ignore nomodule. Old browsers ignore type="module". Both get the right code.
Audit third-party scripts
You cannot control the transpilation of third-party scripts. Check if the vendor provides a modern ESM version of their library. For npm packages: check if there is a /modern or /esm build. Replace outdated dependencies — many popular libraries from 2015-2018 ship heavily polyfilled code that has been rewritten in lighter modern versions.
Use browserslist to control output
Add a .browserslistrc file to your project: > 0.5%, last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, not dead, not IE 11. Build tools (Webpack, Vite, Babel, PostCSS, Autoprefixer) all read this file and use it to determine what syntax and prefixes to output. Removing IE 11 typically reduces JS output size by 15-25%.
Tip: Run npx browserslist in your project to see which browsers your current config targets. If IE 11 is in the list and you do not genuinely need to support it, remove it. This is the simplest way to reduce legacy JavaScript with no code changes.
Tags: javascript, performance, pagespeed, fix, unused
How-To
How to Fix: Inefficient Cache Lifetimes
Without proper caching, every page visit forces the browser to re-download all your files. Good cache policies mean returning visitors load pages almost instantly because files are served from their local cache.
Understand browser caching
When a browser downloads a file, it can store it locally (in cache). On the next visit, instead of downloading again, it serves the cached version. Cache-Control headers tell the browser how long to keep the file. If no cache headers are set, the browser may re-download files on every single page view — wasting bandwidth and slowing load times.
Set long cache for static assets
Static assets that do not change (versioned CSS, JS, images) should be cached for one year: Cache-Control: public, max-age=31536000, immutable. In Apache .htaccess: <FilesMatch ".(css|js|png|jpg|webp|woff2)$"> Header set Cache-Control "public, max-age=31536000, immutable" </FilesMatch>. The immutable flag tells the browser it will never change so skip revalidation.
Use cache busting for updates
Long cache lifetimes mean updates will not reach users until the cache expires. Solution: include a version hash in the filename: app.abc123.js. When you update the file, the hash changes, so the browser sees it as a new file and downloads it. All modern build tools (Webpack, Vite) do this automatically. Do not use query strings (?v=123) — CDNs often ignore them.
Set shorter cache for HTML
HTML files should have short cache lifetimes so users always get the latest content: Cache-Control: no-cache with ETags. This means the browser checks if the file has changed on each visit but only downloads it if it has. Set: Cache-Control: no-cache, must-revalidate plus ETag or Last-Modified headers for efficient revalidation.
WordPress caching plugins
In your performance plugin: look for Browser Caching settings and enable it, setting static files to cache for one year. Via your CDN: set the Cache TTL to one year for static assets (CSS, JS, images).
Tip: Run Google PageSpeed Insights and look for "Serve static assets with an efficient cache policy" in the opportunities section. It will list every file without proper caching and its current cache lifetime. Fix the files with 0 seconds or very short cache lifetimes first.
Tags: cache, performance, pagespeed, fix
How-To
How to Fix: Missing Preload and Preconnect Hints
Preload and preconnect tell the browser to start fetching critical resources early, before it would normally discover them. Used correctly, they can shave seconds off your LCP and FCP.
Preload vs preconnect — the difference
Preload (<link rel="preload">) fetches a specific file immediately — use for critical resources on your own server (hero image, main font, critical JS). Preconnect (<link rel="preconnect">) establishes a connection to a third-party domain early — use for CDNs, font providers, analytics. DNS-prefetch (<link rel="dns-prefetch">) is a lighter version of preconnect for less critical domains.
Preload your LCP image
Your LCP element (usually the hero image) should be preloaded so it starts downloading immediately: <link rel="preload" as="image" href="/hero.webp" fetchpriority="high">. Without preloading, the browser does not discover the hero image until it parses the HTML and CSS — often 500ms-2s into page load. Preloading moves this download to the very start.
Preload critical fonts
Fonts discovered late cause FOIT or FOUT. Preload fonts used above the fold: <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>. The crossorigin attribute is required for fonts even if self-hosted. Only preload 1-2 fonts — preloading too many competes for bandwidth with other critical resources.
Preconnect to third-party domains
Add preconnect hints for domains you will connect to: Google Fonts: <link rel="preconnect" href="https://fonts.googleapis.com"> and <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>. Google Analytics: <link rel="preconnect" href="https://www.googletagmanager.com">. This eliminates the DNS lookup and TCP handshake time when these resources are eventually requested.
Do not over-preload
Preloading too many resources is counterproductive — the browser has limited bandwidth and competing downloads hurt the resources that matter most. Only preload resources needed for the initial viewport. Lighthouse will flag unused preloads as a warning. As a rule: preload 1 hero image, 1-2 fonts, and 1-2 critical scripts. Nothing else.
Tip: Add fetchpriority="high" to your LCP image tag as well as preloading it: <img src="hero.webp" fetchpriority="high" alt="...">. This is a new browser hint that tells the browser to prioritise this image over others and can improve LCP by 200-500ms on its own.
Tags: preload, performance, pagespeed, lcp, fcp, fix
Tutorial
PageSpeed Metrics Explained: Every Score, Every Threshold
A complete reference guide to every metric in Google Lighthouse — what each one measures, what the good/poor thresholds are, how they are weighted in the score, and what causes each one to fail.
How the Performance score is calculated
The Performance score is a weighted average of six lab metrics: LCP 25%, TBT 30%, CLS 15%, FCP 10%, Speed Index 10%, TTI 10%. TBT has the most weight — improving JavaScript performance has the biggest score impact. The score is not linear: moving from 50 to 60 is easier than 80 to 90 because the scoring curve is steepest at the top.
FCP — First Contentful Paint (Good: under 1.8s)
Time until the browser renders the first piece of content (text, image, SVG or canvas). Measures perceived load start. Good: under 1.8s. Needs work: 1.8-3s. Poor: over 3s. Main causes of poor FCP: render-blocking resources, slow TTFB, large HTML. Weight in score: 10%.
LCP — Largest Contentful Paint (Good: under 2.5s)
Time until the largest visible element loads — usually the hero image or main heading. The most important Core Web Vital. Good: under 2.5s. Needs work: 2.5-4s. Poor: over 4s. Main causes: slow hero image, render-blocking resources, slow server, no CDN. Weight in score: 25%.
TBT — Total Blocking Time (Good: under 200ms)
Total time the main thread was blocked (individual tasks over 50ms) during page load. Correlates strongly with INP. Good: under 200ms. Needs work: 200-600ms. Poor: over 600ms. Main causes: large JavaScript bundles, too many scripts, third-party scripts. Weight in score: 30%.
CLS, Speed Index, TTI and TTFB
CLS (Cumulative Layout Shift) — visual instability score. Good: under 0.1. Poor: over 0.25. Weight: 15%. Speed Index — how quickly content visually fills in. Good: under 3.4s. Poor: over 5.8s. Weight: 10%. TTI (Time to Interactive) — when page is fully responsive. Good: under 3.8s. Poor: over 7.3s. Weight: 10%. TTFB (Time to First Byte) — server response time. Good: under 800ms. Poor: over 1.8s. Not directly weighted in score but affects all other metrics.
Tip: Never optimise for the Lighthouse score itself — optimise for the metrics that matter to real users. A score of 85 with good field data (CrUX) is better than a score of 95 with poor real-world performance. Always cross-reference Lighthouse results with your Core Web Vitals data in Google Search Console.
Tags: pagespeed, performance, lcp, fcp, cls, tbt, tti, ttfb, speed
How-To
Hreflang tells Google which language version of a page to show in which country. Missing or incorrect hreflang tags cause the wrong language version to appear in search results.
What hreflang does
Hreflang is an HTML attribute that tells Google: this page exists in multiple languages, and here is which URL to show for which language and country. Without it, Google may show your English page to French users or your US page to UK users — directly hurting click-through rate and conversions.
Basic hreflang implementation
Add hreflang link tags in the head of every page that has language variants. Each page must reference all variants including itself using link rel=alternate hreflang= tags for each locale (en-gb, fr, x-default) pointing to the corresponding page URL.
Fix the return link requirement
Every page referenced in a hreflang tag must return-reference back. If your English page points to the French page via hreflang, the French page must also point back to the English page. Missing return links are the most common hreflang error. Check all language variants have complete reciprocal linking.
Fix invalid language codes
Use ISO 639-1 language codes (en, fr, de, es) and ISO 3166-1 alpha-2 country codes (GB, US, FR, DE). Common mistakes: using en-EN instead of en-GB, using ENG instead of en, combining them wrong. Valid: en, en-GB, en-US, fr, fr-FR, fr-BE. Always use lowercase for language, uppercase for country.
Add x-default for unmatched users
The x-default tag specifies the fallback URL for users whose language does not match any variant. Add a link rel=alternate hreflang=x-default tag pointing to your main international page or language selector. This is typically your main international page or language selector. Without it, Google has no guidance for unmatched users.
Tip: Use our Hreflang Checker tool to automatically validate all hreflang tags on any page — it checks for missing return links, invalid codes and missing x-default in one scan.
Tags: hreflang, international, seo, multilingual, fix
How-To
Google Search Console or our Schema Debugger has flagged errors in your structured data. Here's how to understand each error type and fix it.
Missing required fields
The most common error. Every schema type has required properties — without them, you won't get rich results. Example: Product schema requires name, image, and offers for rich results. Check our Schema Debugger — it tells you exactly which required fields are missing for each schema type you're using.
Fix: add the missing fields
Look up the required fields for your schema type at schema.org or in our Schema Generator which documents required vs recommended fields. Add the missing fields to your JSON-LD. Example: missing "priceCurrency" in an Offer — add: "priceCurrency": "GBP". Re-test with our Schema Debugger until all required fields are present.
Wrong data types
Some fields require specific formats. Dates must be ISO 8601 format: "2024-01-15" not "15 January 2024". Ratings must be numbers: "ratingValue": 4.5 not "ratingValue": "4.5 out of 5". URLs must be absolute: "https://example.com/page" not "/page". Our Schema Debugger flags format errors.
Content not visible on page
Google requires that structured data accurately reflects visible page content. If your schema says a product costs £29.99 but the page shows £49.99, that's a violation. If your FAQPage schema includes questions not shown on the page, that's a violation. Keep schema and visible content perfectly in sync.
Use our Schema Debugger workflow
Step 1: Run our Schema Debugger on your page URL. Step 2: Fix each error shown. Step 3: Re-run the Debugger to confirm errors are resolved. Step 4: Use Google's Rich Results Test for final validation. Step 5: Submit the URL for indexing in Google Search Console. Step 6: Check GSC Enhancements in 2–3 weeks.
Tip: Don't try to game schema. If you mark up fake reviews, inflate ratings, or add schema for content not on the page, Google can issue a manual action against your site. Keep your schema honest and accurate.
Tags: schema, structured-data, fix, rich-results, rich
How-To
How to Fix: Keyword Cannibalization (Two Pages Competing for the Same Keyword)
When two of your pages target the same keyword, they compete against each other. Google gets confused about which to rank and often ranks neither well. Here's how to identify and fix it.
Identify cannibalising pages
Use our Keyword Gap tool to compare two pages targeting the same topic. Or search Google: site:yoursite.com "your keyword" — if multiple your pages appear, they might be cannibalising each other. Google Search Console > Performance > filter by keyword shows which URLs rank for each query.
Decide which page should win
Choose the stronger page: the one with more content, better backlinks, higher traffic, or more conversions. This becomes your "canonical" page for this topic. The other pages need to be dealt with.
Option 1: Merge the pages
Combine the content from both pages into one comprehensive page. This is often the best option when both pages have useful content. Write a single, more complete page, redirect the weaker URL to the stronger one, and update all internal links to point to the merged page.
Option 2: Differentiate the pages
If both pages need to exist, make them clearly different: target different keywords (one for "buy running shoes", one for "running shoe guide"), serve different intent (one transactional, one informational), or target different audiences (one for beginners, one for advanced runners).
Option 3: Redirect and consolidate
If the weaker page has little unique value, redirect it (301) to the stronger page. Update all internal links that pointed to the redirected URL. Over time Google will consolidate the authority of both pages into the remaining one, usually improving rankings for the surviving page.
Tip: Use our Internal Links tool after resolving cannibalization to make sure all internal links now point to the correct surviving page. Stray links pointing to the redirected URL are fine (they'll redirect) but updating them to point directly is best practice.
Tags: keywords, keyword, cannibalization, content, fix
How-To
E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness) affects how Google perceives your content quality. Here's a practical checklist to improve your E-E-A-T signals.
Add author bios to all content
Every article, guide, or blog post should have a named author with a bio. The bio should mention: their relevant qualifications, years of experience, other publications, and a photo. Link to their social profiles (especially LinkedIn) and to their other articles on your site. This establishes expertise.
Create a detailed About page
Your About page should tell the story of the business: when it was founded, by whom, what qualifications they have, what you stand for, who your team is, and what makes you different from competitors. Include photos of real people. Vague "we're passionate about..." text scores poorly for E-E-A-T.
Add clear contact information
A physical address, phone number, and email on a dedicated Contact page (and ideally the footer) is a major trust signal. Google's quality raters specifically check whether it's easy to contact the site owner. PO Box is acceptable; no address at all is a red flag.
Get reviews on third-party platforms
Google Reviews, Trustpilot, Feefo, and industry-specific review platforms build authoritativeness. You can't control what Google does with these, but positive reviews on established platforms are a strong third-party trust signal. Add review schema to display your aggregated rating in search.
Get mentioned by authoritative sources
Being quoted in industry publications, local news, trade magazines, and trusted websites builds authoritativeness. This is a longer-term strategy but has lasting impact. Write guest posts, respond to journalist requests (use HARO or Qwoted), speak at industry events, and contribute to reputable publications in your niche.
Tip: Run our E-E-A-T Checker monthly and track your score over time. After each batch of improvements (adding author bios, updating your About page, etc.), re-test to see what moved the needle. Build a quarterly E-E-A-T improvement habit.
Tags: eeat, trust, authority, content, fix
Case Study · +34% organic traffic | +41% impressions | 3 pages entered top 10 | 6 weeks to see results
Case Study: How Fixing Missing H1 Tags Increased Organic Traffic by 34%
A small UK accountancy firm's website had H1 tags missing on 23 of 28 pages. Here's exactly what we found, what we changed, and what happened to their rankings.
The problem
The site had been built on a custom WordPress theme where the developer had accidentally removed H1 tags from the template, replacing them with styled div tags that looked like H1s visually but weren't heading tags. This meant Google had no H1 on 23 pages. Our Site Audit flagged this as 23 Critical issues.
The fix
We updated the theme template to use proper <h1> tags instead of styled divs. We wrote optimised H1 text for each page, including the primary keyword and location (e.g., "Chartered Accountants in Leeds | Business Tax Specialists"). Total time to implement: 3 hours.
The results (6 weeks later)
Organic impressions increased 41% in Google Search Console. Organic clicks increased 34%. Three service pages that had never appeared in the top 10 for their target keywords entered positions 4, 7, and 9. The homepage moved from position 18 to position 6 for the firm's most important keyword.
Why it worked
H1 tags are one of Google's strongest on-page relevance signals. Without them, Google had to guess what each page was primarily about from context. Adding clear, keyword-rich H1 tags gave Google an explicit signal — and the rankings responded quickly. Technical fixes often produce faster results than content changes because they're clear signals, not gradual trust-building.
The lesson
Always run a technical SEO audit before starting content or link building work. A site with missing H1 tags can't fully benefit from great content or strong backlinks because Google is struggling with basic relevance signals. Fix the foundation first.
Tip: This type of technical fix — changing existing code rather than creating new content — often produces results in 2–6 weeks. Content changes take 2–6 months to show results because Google needs to re-evaluate the quality of new content.
Tags: h1, on-page, onpage, case-study, casestudy, seo
Case Study · +22% mobile conversion rate | LCP 8.2s → 1.9s | PageSpeed 28 → 71 | All Core Web Vitals passed
Case Study: How a 40-Point PageSpeed Improvement Increased Sales by 22%
An e-commerce store selling outdoor equipment had a mobile PageSpeed score of 28. Customers were abandoning the site before it loaded. Here's the complete optimisation story.
Starting point
The store had 12,000 SKUs with unoptimised product images averaging 2.3MB each. Mobile PageSpeed score: 28/100. LCP: 8.2 seconds. Mobile bounce rate: 78%. The client had been adding products for 3 years without any image optimisation process. Analytics showed users leaving within 3 seconds on mobile.
Phase 1: Image optimisation (Month 1)
We compressed all product images using an image optimisation plugin — 12,000 images, average reduction of 84%, from 2.3MB to 370KB. Converted to WebP format. Added lazy loading to all non-hero images. Added explicit width and height to all image tags. Result: LCP dropped from 8.2s to 4.1s. PageSpeed: 28 → 51.
Phase 2: JavaScript optimisation (Month 2)
Identified 6 plugins adding unnecessary JavaScript on every page (social share widgets, an unused chat plugin, an old cookie system). Removed 4 plugins entirely, replaced 2 with lighter alternatives. Deferred all non-critical JavaScript. Added a lightweight CDN. Result: LCP 4.1s → 2.8s. PageSpeed: 51 → 67.
Phase 3: Critical CSS and hosting upgrade (Month 3)
Extracted critical CSS and inlined it in <head> for above-the-fold rendering. Upgraded from shared hosting to managed ecommerce hosting. Enabled server-side object caching. Result: LCP 2.8s → 1.9s (now in "Good" range). PageSpeed: 67 → 71. All Core Web Vitals passed.
Business results (3 months after completion)
Mobile conversion rate: +22% (from 0.9% to 1.1%). Revenue from mobile: +31% (additional conversions + higher average order value from users who stayed longer). Organic rankings improved for 47 product category pages. Bounce rate: 78% → 51%. The PageSpeed investment paid for itself within the first month from increased conversions.
Tip: For e-commerce sites, focus on product listing and product detail pages first — these drive the most revenue. Homepage optimisation has less impact on conversions than optimising the pages where buying decisions are made.
Tags: pagespeed, ecommerce, cwv, performance
Case Study · +87% click-through rate | +94% organic clicks | +64% lead form submissions | Rich results in 11 days
Case Study: FAQ Schema Increased Click-Through Rate by 87%
A financial services company added FAQPage schema to 12 key service pages. Here's what happened to their impressions, clicks, and ultimately their leads.
The starting point
A UK mortgage broker had service pages ranking in positions 4–8 for their target keywords. Good positions, but click-through rates were disappointingly low (1.8% average). The pages were competing against comparison sites and banks with brand authority. They needed to stand out in search results without changing their rankings.
The strategy
We identified the top 5 questions customers asked before choosing a mortgage broker (from their sales team's knowledge and their FAQ page). Added these as visible Q&A content on each service page. Generated FAQPage schema for each page using our Schema Generator. Added the JSON-LD to the <head> of each page.
Implementation
The JSON-LD schema was added to 12 service pages over 3 days. Each page had 4–5 questions specifically relevant to that service type. We used Google's Rich Results Test to validate each page. Submitted all 12 pages for indexing in Google Search Console. Rich results appeared in Google within 11 days.
Results (90 days later)
Average CTR: 1.8% → 3.4% (+87%). Total organic clicks increased 94% despite average position improving only slightly (from 5.2 to 4.8). The expanded search result (taking up twice the visual space) dramatically improved visibility against competitors. Monthly lead form submissions from organic: +64%.
Why it worked so well
FAQ schema does something unique: it lets your result show the answer to the question in search results, which sounds counterintuitive but actually increases clicks. Users see the answer is relevant to them, which increases confidence that your page has what they need. They click to read more, not less.
Tip: FAQ schema works best when the questions on your page are the exact questions your target audience is searching for. Use Google's "People Also Ask" boxes for your target keywords to identify exactly which questions to answer.
Tags: schema, faq, ctr, rich-results, rich
Case Study · +340% traffic to rescued pages | 8 pages entered top 10 | +28% site-wide organic traffic | 12 weeks
Case Study: Internal Link Audit Rescued 47 Orphan Pages and Boosted Traffic 28%
A media site with 800+ articles had nearly 50 pages with zero internal links — invisible to Google. Here's how fixing internal linking structure recovered lost traffic.
The discovery
Running our Internal Links tool on an 800-page media site revealed 47 orphan pages (no internal links pointing to them) and 112 pages with only one internal link. These pages had been published but never properly integrated into the site's link structure. Analytics confirmed: orphan pages received an average of 8 monthly organic visits vs 340 for well-linked pages.
Prioritisation
Not all 47 orphans were worth fixing. We ranked them by: quality of content, target keyword search volume, and whether they had any external backlinks. 23 pages were worth fixing. 12 were merged with stronger related pages. 12 were left as-is (thin content that added no value).
The fix: systematic internal linking
For each of the 23 priority orphan pages: identified 5–8 existing pages that covered related topics, added contextual internal links from those pages using descriptive anchor text, added links from the site's category pages and tag pages, and added them to relevant "related articles" sections. Each orphan gained 6 internal links on average.
Results (12 weeks later)
The 23 fixed orphan pages collectively saw: organic traffic +340% (from 8 to 35 average monthly visits). 8 pages entered Google's top 10 for their primary keyword (none had been in top 10 before). Site-wide organic traffic increased 28% — the authority boost spread through the link structure to other pages too.
The ongoing process
We set up a monthly audit process: run our Internal Links tool, identify any new orphans (from recently published content), and add internal links immediately. The content team now adds 3–5 internal links per new article as standard practice. Orphan pages have dropped from 47 to consistently 0–3.
Tip: The easiest internal linking win is to link from your highest-traffic pages to your most commercially important pages. Your most visited pages pass the most authority — use that flow deliberately, not randomly.
Tags: internal-links, orphan-pages, orphan, case-study, casestudy, authority
Case Study · 65% traffic loss → full recovery in 6 months → 110% of original traffic | Multiple top-10 rankings gained
Case Study: E-E-A-T Improvements Recovered a Health Site from a Google Update
A health information site lost 65% of traffic in a Google core update. An E-E-A-T audit revealed why — and a systematic trust-building programme recovered it all within 6 months.
What happened
A health information site covering nutrition and supplements lost 65% of organic traffic after a Google core update. Investigation revealed the site had: no author bylines on any articles, no information about who ran the site, no references to sources or research, and some inaccurate health claims. Classic low E-E-A-T signals for a YMYL topic.
The E-E-A-T audit
Using our E-E-A-T Checker across all 140 articles revealed: 0 articles had author names, 0 articles cited any sources, the About page was 3 sentences long with no team information, no medical review process was documented, and several articles made claims without evidence. Google had essentially flagged this as an untrustworthy source.
Phase 1: Establishing authorship (Month 1–2)
Hired a registered nutritionist to review and co-author all health content. Added author bio boxes to every article with qualifications, credentials, and links to professional profiles. Created detailed author pages. Added "reviewed by" bylines where applicable. Updated the About page to tell the real story of the site with team photos.
Phase 2: Content quality and citations (Month 2–4)
Added references and citations to every factual claim (linking to NHS, published research, government health bodies). Removed or rewrote articles making unsubstantiated claims. Added a content review policy page. Updated all articles to include publish and last-reviewed dates. Added structured data for Article with author schema.
Phase 3: Results and lessons (Month 4–6)
Traffic recovery: 40% recovered by month 4, 90% by month 6, 110% (above pre-update) by month 8. The improvements also attracted natural backlinks from health publishers who now viewed the site as a citable source. The site now ranks for more competitive health terms it had never ranked for before.
Tip: If you're in a YMYL niche (health, finance, legal, safety) and you've been hit by a core update, a full E-E-A-T audit is always the first step. Content quality and technical SEO fixes rarely recover this type of traffic loss — trust signals are the issue.
Tags: eeat, core-update, update, health, trust
Case Study · LCP 9.2s → 1.8s | Enquiries 4 → 9/month (+125%) | Bounce rate 84% → 31% | PageSpeed 22 → 89
Case Study: A Local Business Doubled Enquiries by Fixing Page Speed
A local plumbing company's website took 9 seconds to load on mobile. Most visitors left before seeing any content. Here's how a focused speed optimisation project turned the site into their best sales tool.
The starting problem
A 3-person plumbing firm had a WordPress website that took 9.2 seconds to load on mobile (LCP) and 6.1 seconds on desktop. Google Analytics showed 84% mobile bounce rate. The site had a premium theme with 11 active plugins, unoptimised images from a photo shoot, and was on cheap shared hosting. Monthly enquiries from the website: 4.
Quick wins (Week 1)
Deactivated 5 unused plugins. Compressed all images (reducing average from 1.8MB to 180KB). Added WebP versions. Switched from shared hosting to a managed hosting plan with built-in caching. These changes alone cut load time to 4.2 seconds. PageSpeed went from 22 to 54.
Theme optimisation (Week 2)
The premium theme was loading 340KB of CSS with 90% unused. Replaced it with a lightweight custom child theme built on GeneratePress. Eliminated all page builder shortcodes that were generating render-blocking JavaScript. Added critical CSS inline. Load time: 4.2s → 2.6s. PageSpeed: 54 → 72.
Final polish (Week 3)
Added a CDN for image delivery. Implemented lazy loading on all below-fold images. Added font preloading. Configured browser caching headers. Final mobile LCP: 1.8 seconds. PageSpeed: 72 → 89. All three Core Web Vitals in "Good" range.
Business results (3 months later)
Mobile bounce rate: 84% → 31%. Monthly enquiries from website: 4 → 9 (+125%). Google local rankings improved as Google's Page Experience signals (Core Web Vitals) began reflecting the faster site. Total investment: 15 hours of optimisation work + £12/month better hosting. ROI: extraordinary.
Tip: For local businesses, speed optimisation often has a faster and more direct ROI than other SEO work. Every second of load time improvement can increase conversions by 7%. On a site converting at 0.5%, improving to 1% doubles your revenue from the same traffic.
Tags: pagespeed, local-seo, mobile, conversions
Case Study · Blog traffic 12,000 → 47,000/month | 43/50 target articles in top 10 | +280% trial signups from content
Case Study: Keyword Gap Analysis Found 200 Untapped Topics That Tripled Blog Traffic
A B2B software company was publishing content without a strategy. A keyword gap analysis against three competitors revealed 200+ topics their competitors ranked for that they completely ignored.
The situation
A project management software company published 2 blog posts per month, generating 12,000 monthly organic visitors. A competitor with a similar product and similar domain authority was getting 180,000 monthly visitors. Both companies had similar link profiles. The difference was entirely content strategy.
The keyword gap analysis
Using our Keyword Gap tool, we compared the company against their top 3 competitors. The analysis revealed: 1,847 keywords competitors ranked for in positions 1–10 that the company had zero content for. These weren't niche long-tails — 200+ had over 1,000 monthly searches. Pure missed opportunity.
Prioritisation framework
We scored each gap keyword by: monthly search volume (traffic potential), keyword difficulty (can we realistically rank?), business relevance (does the topic attract potential customers?), and content creation effort. The top 50 priorities were topics with 500+ searches, difficulty under 40, and high business relevance.
The content programme
We created a 6-month content calendar targeting the top 50 priority topics. Each piece was 1,500–3,000 words, genuinely expert content (written with input from the product team), included data and original insights, targeted a specific search intent, and linked to the product as a natural solution.
Results (12 months later)
Blog traffic: 12,000 → 47,000 monthly visitors. 43 of the 50 target articles ranked in top 10 for primary keyword. Trial signups from blog content: +280%. The content programme cost £2,500/month in content creation and generated an estimated £15,000/month in additional trial signups converting to customers.
Tip: The power of keyword gap analysis is that it shows you proven demand for topics you're ignoring. Competitors ranking for these keywords have validated that there's traffic to win. You're not guessing — you're targeting known opportunities.
Tags: keyword-gap, gap, content-strategy, strategy, b2b, case-study, casestudy
Case Study · +75% CTR on star-rated pages | +183% CTR on 4.5+ rated products | +18% organic revenue | 9 days to first rich results
Case Study: Product Schema Added Star Ratings to 500 Product Pages and Increased Revenue 18%
An online gift shop added product schema with aggregated ratings to their 500 product pages. Here's what happened to their search visibility and conversion rates.
The goal
The shop had 500 product pages ranking reasonably well (positions 4–15) but suffering from low click-through rates (average 1.2%). Their competitors were displaying star ratings in search results and getting significantly more clicks despite similar positions. The goal: implement product schema to earn star ratings in search results.
The implementation
The shop used an ecommerce platform. We configured a schema plugin to auto-generate Product schema from the product data. The schema included: product name, images, description, price, currency, availability, and most importantly — aggregateRating pulling from the built-in review system. All 500 pages updated automatically.
Validation and launch
Tested 20 random product pages with Google's Rich Results Test — all passed. Submitted the sitemap to GSC to accelerate crawling. First rich results appeared after 9 days. Within 3 weeks, 340 of 500 product pages were showing star ratings in search results.
Impact on click-through rate
Pages with star ratings: average CTR 1.2% → 2.1% (+75%). Products with 4.5+ star ratings: CTR jumped to 3.4% (+183%). Products with fewer than 10 reviews: minimal improvement (Google is cautious about products with few reviews). The lesson: star ratings help most when you have 15+ reviews and a rating above 4.0.
Business impact
Overall organic revenue increased 18% in the 3 months following the rich results rollout. The CTR improvement brought in the same additional traffic as a 15% ranking improvement would have — but without needing to improve rankings at all. Schema implementation was the entire value driver. Total cost of implementation: 6 hours of setup.
Tip: Product schema star ratings require real customer reviews on your own site. If you're not actively collecting reviews, start now — even 10–15 reviews per product can unlock star ratings. Add review request emails to your post-purchase flow immediately.
Tags: schema, product, rich-results, rich, ecommerce, case-study, casestudy
Case Study · +45% traffic in 8 weeks | +67% at 12 weeks | All product pages properly indexed | 2-hour fix for 4 months of damage
Case Study: Fixing Canonical Tags Recovered 45% of Lost E-commerce Traffic
An e-commerce site had incorrectly configured canonical tags pointing all product variant pages to the homepage. Google was indexing the homepage instead of product pages. Here's the full story.
The mystery
An outdoor equipment retailer noticed product pages were rarely appearing in search results — despite having strong content and several external backlinks pointing directly to product URLs. In Google Search Console, URL Inspection showed Google was selecting the homepage as the canonical for product pages. This meant Google was indexing the homepage instead of products.
Finding the cause
Our Site Audit revealed canonical tags on every product page pointing to the homepage (https://example.com/). This had been set during a developer update — they'd accidentally hardcoded the homepage URL as the canonical in the product page template. Every product variant had been telling Google for 4 months: "Don't index me, index the homepage instead."
The fix
Updated the product page template to use dynamic canonicals — each page's canonical now points to its own URL (self-referencing canonical). Where multiple URL variants existed (e.g., product with different colour parameters), the canonical was set to the main product URL. Deployed in 2 hours.
Re-indexing campaign
Submitted all product pages via Google Search Console's URL Inspection tool (prioritising best-selling products). Submitted the updated sitemap. Google began processing the canonical changes within 48 hours. Over the following 6 weeks, product pages gradually displaced homepage in search results for product-related queries.
Recovery
Week 4: product page traffic up 28%. Week 8: product traffic up 45%. Week 12: product traffic up 67% (now exceeding pre-problem levels as previously suppressed pages gained rankings they'd never had). Homepage traffic actually decreased slightly as it stopped monopolising product keyword rankings — and that was the correct outcome.
Tip: Always check canonical tags after any developer update or platform migration. A wrongly configured canonical template can silently destroy your organic visibility for weeks before anyone notices. Run our Site Audit after any significant site changes.
Tags: canonical, duplicate-content, duplicate, ecommerce, case-study, casestudy, technical
Case Study · 340 chains fixed | 1,847 backlinks now delivering full authority | +31% average position for priority pages | 3.5 hours of work
Case Study: A Redirect Audit Found 340 Authority-Leaking Chains After a Platform Migration
After migrating from Magento to Shopify, a retailer discovered 340 redirect chains that were leaking link authority. Here's how fixing them improved rankings within 6 weeks.
The migration context
A fashion retailer moved from Magento to Shopify with 2,400 product URLs changing format. The developer set up redirects, but because Shopify automatically creates its own URL structure and the redirect file was set up sequentially rather than with final destinations, chains formed: old Magento URL → intermediate Shopify URL → final Shopify URL. 340 two-hop chains existed across the site.
Discovery
Using our Redirect Checker on a sample of URLs revealed the pattern. We then ran a full site crawl and identified all 340 chains. On average, each chain was 2 hops (A → B → C). The most important affected URLs were category pages and best-selling product pages — the ones with the most external backlinks.
Impact assessment
The 340 chained URLs had a combined 1,847 external backlinks pointing to them. Each 2-hop chain was losing an estimated 15% of the authority of those backlinks. For the 50 most important URLs (by backlink count), we calculated significant authority loss impacting their ranking potential.
The fix
Updated all redirects to point directly to the final destination in one hop. For 340 redirects this took: 2 hours to compile the correct destination URLs, 1 hour to update the redirect list in Shopify's URL Redirects, 30 minutes of testing to verify all redirects now resolved correctly. Total: 3.5 hours.
Results
Weeks 1–3: no visible change (Google needed to re-crawl and process the redirect updates). Week 4: 12 category pages improved position by 2–5 places. Week 6: overall site rankings improved measurably. Best-selling product pages recovered authority. 3 months post-fix: an estimated 31% improvement in average position for the 50 priority pages.
Tip: Always audit redirects after any platform migration. Run our Redirect Checker on your 50 most important URLs (by external backlinks) within the first week after migration. Catching chains early means the authority loss is minimal.
Tags: redirects, migration, authority, case-study, casestudy
Tutorial
Everything you need to know about HTTP status codes, what each one means for SEO and users, how to force HTTPS, and how to fix the most common server response errors.
HTTP status codes explained
Every server response includes a status code. 2xx = success (200 OK is ideal). 3xx = redirect (301 permanent, 302 temporary). 4xx = client error (404 not found, 403 forbidden, 401 unauthorised). 5xx = server error (500 internal error, 503 service unavailable). Google handles each differently — 4xx pages lose rankings, 5xx pages are retried, 3xx pages pass authority depending on type.
301 vs 302 — which to use
A 301 (permanent redirect) passes approximately 99% of link equity to the new URL. Google updates its index to use the new URL. A 302 (temporary redirect) passes less equity and Google keeps the original URL indexed. Always use 301 for permanent URL changes, site migrations, and HTTP to HTTPS redirects. Only use 302 for A/B tests, maintenance pages, and genuinely temporary redirects.
Force HTTPS correctly
Every HTTP request must redirect to HTTPS. In Apache .htaccess: RewriteEngine On / RewriteCond %{HTTPS} off / RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]. In Nginx: server { listen 80; return 301 https://$host$request_uri; }. In WordPress: set the home and siteurl to https:// in Settings and add FORCE_SSL_ADMIN to wp-config.php.
Fix 404 Not Found errors
Pages returning 404 waste crawl budget and lose link equity. Use our Site Crawler to find all 404s. For moved pages: set up a 301 redirect to the most relevant equivalent. For deleted pages with backlinks: redirect to the closest relevant page or your homepage. For pages that never existed: check for typos in internal links using our Internal Links tool.
Fix 5xx server errors
500 errors mean your server crashed. Check your error log immediately: tail -f /var/log/nginx/error.log (Nginx) or /var/log/apache2/error.log (Apache). Common causes: PHP fatal errors, database connection failures, out of memory. 503 means the server is overloaded — add a Retry-After header so Googlebot knows when to try again. 502/504 mean your upstream app server crashed — restart it.
Tip: Set up Google Search Console alerts for coverage errors. You will be notified immediately when pages start returning 4xx or 5xx status codes so you can fix them before they affect rankings.
Tags: http, https, security, redirect, server, ssl
Tutorial
Internal links pass authority between your own pages, help Google understand your site structure, and keep users engaged longer. Here is the complete guide to using them strategically.
Why internal links matter more than most people think
Every internal link passes a portion of PageRank (link equity) from one page to another. Your homepage typically has the most authority from external backlinks. By strategically linking from the homepage and high-authority pages to your commercial pages, you boost those pages' rankings. Pages with no internal links pointing to them (orphan pages) get almost no authority and rank poorly.
Identify your most important pages
Before building links, map your pillar pages — the 5-10 pages you most want to rank. These are usually your main product or service pages, key landing pages, or cornerstone content. Every internal link strategy starts by funnelling authority toward these pages. Use our Internal Links tool to see the current link equity distribution across your site.
Use descriptive anchor text
Anchor text (the clickable words) is a relevance signal. "Click here" tells Google nothing. "Our guide to fixing Core Web Vitals" tells Google exactly what the linked page is about and reinforces its topical relevance. Use natural language that includes the target page main keyword. Avoid over-optimising — if every link uses the same exact-match anchor text it looks unnatural.
Fix orphan pages
An orphan page is a page with no internal links pointing to it. Google may never discover it, and it receives zero authority from your other pages. Use our Site Crawler to find orphan pages on your site. Once found, add links from topically related pages using descriptive anchor text. Orphan pages are one of the most common and easiest-to-fix structural SEO problems.
Audit and maintain regularly
Internal link structures break when pages are moved, deleted or renamed. Use our Site Crawler monthly to find broken internal links (links pointing to 404 pages). Fix them immediately — a broken internal link wastes the authority it was passing. After any site restructure, check all internal links still resolve correctly.
Tip: The best time to add internal links is when you publish new content. As you write each new page, actively identify 3-5 existing pages you can link to and from. Building this habit means your site structure improves automatically with every new piece of content.
Tags: links, internal-links, authority, seo, crawlability
How-To
How to Fix Mixed Content: HTTP Resources on an HTTPS Page
Mixed content happens when an HTTPS page loads images, scripts or stylesheets over HTTP. Browsers block active mixed content entirely and warn users about passive mixed content. Here is how to find and fix every instance.
What mixed content is and why it matters
When your page is served over HTTPS but references resources (images, scripts, CSS, fonts, iframes) over HTTP, that is mixed content. There are two types: Active mixed content (scripts, CSS, iframes loaded over HTTP) — browsers block this entirely. Passive mixed content (images, audio, video over HTTP) — browsers allow it but show a warning. Both damage trust and Google's assessment of your site's security.
Find all mixed content
Open Chrome DevTools (F12) on any page. Go to the Console tab — mixed content warnings appear as red errors showing the exact URL of the HTTP resource. For a full site audit: use our Site Audit tool which flags mixed content across all pages. In WordPress: the Better Search Replace plugin can find and fix HTTP references in the database.
Fix hardcoded HTTP URLs
Search your codebase and database for http:// references and update them to https://. In WordPress: use Better Search Replace to find and replace http://yourdomain.com with https://yourdomain.com in the database. For hardcoded links in templates: do a find-and-replace across all template files. Always test after replacing — some URLs may break if the destination does not support HTTPS.
Fix third-party resources
Check every external resource: CDN links, Google Fonts, embedded videos, social widgets, analytics scripts. Nearly all major services now serve over HTTPS — update any HTTP URLs to their HTTPS equivalent. For embedded iframes (YouTube, maps): ensure the embed URL uses https://. If a third-party service does not support HTTPS, replace it with one that does — an HTTP-only service is a security liability.
Add a Content Security Policy upgrade directive
Add this HTTP header as a safety net: Content-Security-Policy: upgrade-insecure-requests. This tells the browser to automatically upgrade HTTP resource requests to HTTPS. It catches any mixed content you miss. In Nginx: add_header Content-Security-Policy "upgrade-insecure-requests" always; In Apache: Header always set Content-Security-Policy "upgrade-insecure-requests".
Tip: After fixing mixed content, use Chrome DevTools Security tab on your most important pages to confirm the page is fully secure. The tab shows a green padlock and lists all resources — no HTTP references should appear.
Tags: mixed, https, security, ssl, http
Tutorial
Open Graph tags control how your content appears when shared on Facebook, LinkedIn, WhatsApp and other platforms. Missing or wrong OG tags mean ugly, low-engagement share cards. Here is exactly what to add and how.
Why OG tags matter
When someone shares your URL on Facebook, LinkedIn, WhatsApp, Slack or Discord, the platform reads your OG tags to build the preview card: title, description, image. Without OG tags: the platform guesses using page title and a random image, often with poor results. With good OG tags: your content gets a compelling, branded preview that dramatically increases click-through rate on shared links.
The four essential OG tags
Every page needs four core tags: og:title (the headline — keep under 60 chars), og:description (a compelling 100-160 char summary), og:image (your preview image — must be at least 1200x630px for best display, under 8MB, JPG or PNG), og:url (the canonical URL of the page). Without all four, share previews look incomplete or broken.
Set up the perfect OG image
The OG image is the most important element — it determines whether people stop scrolling. Optimal size: 1200x630px (1.91:1 ratio). Format: JPG or PNG (not WebP — not all platforms support it). Always include og:image:width and og:image:height tags so platforms can display it without waiting to download it. For articles: use a branded template with the article title overlaid on a consistent background. For products: use a clean product shot.
Add Twitter/X Card tags
Twitter has its own card system alongside OG tags. Add: twitter:card (use summary_large_image for the best display), twitter:title, twitter:description, twitter:image. Twitter will fall back to OG tags for title and description if its own tags are missing — but always set twitter:card explicitly. The summary_large_image card shows a large image above the text and gets significantly more engagement than the basic card.
Test and debug your OG tags
After adding OG tags, test them: Facebook Sharing Debugger (developers.facebook.com/tools/debug) — shows exactly how your page appears when shared and clears the cache. Twitter Card Validator (cards-dev.twitter.com/validator) — previews your Twitter card. LinkedIn Post Inspector (linkedin.com/post-inspector). Our OG Generator tool also lets you preview and edit tags before publishing.
Tip: Social platforms cache OG data aggressively. After updating your OG tags, always scrape your URL through Facebook's Sharing Debugger to force a cache refresh — otherwise the old preview will continue appearing for days or weeks.
Tags: og, open-graph, social, meta, twitter
Tutorial
Redirects done wrong leak link equity, slow your site and confuse search engines. Here is when to use each type, how to avoid chains and loops, and how to handle a full site migration correctly.
301 vs 302 — the real difference for SEO
301 (Moved Permanently): passes ~99% of link equity to the destination URL. Google updates its index to the new URL. Use for: permanent URL changes, HTTP to HTTPS, domain migrations, removing www. 302 (Found/Temporary): Google keeps the original URL indexed. Less equity passed. Use only for: A/B tests, maintenance pages, region detection, and genuinely temporary redirects. Using 302 when you mean 301 is one of the most common and costly SEO mistakes.
Redirect chains — why they destroy authority
A redirect chain is when URL A redirects to URL B which redirects to URL C. Each hop loses a small percentage of link equity and adds latency. A two-hop chain loses roughly 15% of the original equity. A three-hop chain loses ~25%. Use our Redirect Checker to test any URL — it shows every hop in the chain. Fix chains by updating all redirects to point directly to the final destination URL.
Redirect loops — how to diagnose and break them
A redirect loop is when URL A redirects to URL B which redirects back to URL A (or through other URLs back to itself). The page becomes completely inaccessible. Diagnosis: our Redirect Checker shows the loop. Common causes: CMS redirect plugins in conflict, force-www and force-non-www rules both active, HTTPS redirect misconfigured. Fix by identifying the conflicting rules in .htaccess or server config and removing the circular reference.
Implementing redirects correctly
In Apache .htaccess: Redirect 301 /old-page https://example.com/new-page. In Nginx: location /old-page { return 301 https://example.com/new-page; }. In WordPress: use the Redirection plugin or add rules to .htaccess. In Shopify: Online Store > Navigation > URL Redirects. Always test redirects immediately after adding them. Use curl -I to check the response code: curl -I https://example.com/old-page.
Site migration redirects — the complete checklist
When migrating domains or restructuring URLs: 1. Map every old URL to its new equivalent before launch. 2. Set up 301 redirects for all mapped URLs. 3. Update your XML sitemap to the new URLs. 4. Submit the new sitemap in Google Search Console. 5. Add the new domain as a property in Search Console. 6. Update your canonical tags. 7. Update all internal links to the new URLs. 8. Monitor Google Search Console for 404 errors for 3 months after migration.
Tip: After any redirect implementation, run our Redirect Checker on your 20 most important URLs to verify each resolves in a single hop with a 200 status code at the destination. Catching chains immediately after launch is far easier than finding them months later.
Tags: redirect, redirects, 301, migration, seo, links
Tutorial
Rich results are enhanced search listings with star ratings, FAQ dropdowns, prices, images and breadcrumbs. They dramatically increase click-through rates. Here is exactly which schema types unlock which rich results and how to implement them.
What rich results are and why they matter
Rich results are Google search listings enhanced with additional information beyond the standard blue link. Types: star ratings from aggregateRating schema, FAQ dropdowns from FAQPage schema, product prices from Product schema, recipe cards from Recipe schema, event dates from Event schema, breadcrumb trails from BreadcrumbList schema. Pages with rich results typically see 20-40% higher click-through rates than standard results at the same position.
Which schema unlocks which rich result
FAQPage schema: adds expandable Q&A pairs below your listing. Product schema with aggregateRating: star ratings and price in search results. Recipe schema: cooking time, calories and ratings in recipe carousels. Event schema: date, location and ticket link. HowTo schema: numbered steps listed below your result. BreadcrumbList schema: breadcrumb trail instead of URL. Article with datePublished: article dates in results. Each type has specific required properties that must be present.
Required properties for the most common types
FAQPage: mainEntity array with Question and acceptedAnswer for each pair (minimum 2 questions). Product: name plus offers with price and priceCurrency — add aggregateRating for stars. Article: headline, author with name, datePublished, image. Event: name, startDate, location. HowTo: name, step array with text. BreadcrumbList: itemListElement with each item, position and name. Missing required properties means no rich result even if the schema is technically valid.
Test before you deploy
Use Google's Rich Results Test (search.google.com/test/rich-results) — paste your URL or code and it shows exactly which rich results you are eligible for and any errors blocking eligibility. Use our JSON-LD Checker to validate the schema structure. Use our Schema Debugger to see what schema is currently on any live page. Fix all errors shown before publishing — invalid schema is silently ignored by Google.
After deployment — what to expect
After adding schema, it typically takes 1-4 weeks for Google to discover it and start showing rich results. Monitor in Google Search Console under Enhancements — it shows which pages have rich results and any errors. If eligible pages are not showing rich results after 4 weeks: re-test with the Rich Results Test, check for manual actions in Search Console, and ensure the pages have real user-generated content matching the schema (especially for reviews and ratings).
Tip: FAQPage schema is the highest-impact rich result for most sites — it requires no external reviews or user data, can be added to any informational page, and immediately increases SERP real estate. Start here before implementing any other schema type.
Tags: rich-results, rich, schema, structured-data, seo, ctr
Tutorial
A practical guide to everything Maps-related for local SEO — how to embed Google Maps correctly, find and add your Google Business Profile link, and understand what drives Maps 3-pack rankings.
Embedding Google Maps on your website
A Maps embed on your contact page is a confirmed local SEO signal. To generate: go to maps.google.com, search your business, click Share, select Embed a map, choose size, copy the iframe code. In your HTML: paste the iframe directly into your contact page. Add width="100%" and a fixed height (350px) to make it responsive. Wrap it in a div with overflow:hidden to prevent layout issues. Run our Local SEO Checker after adding it — the Google Maps embed check should immediately show as passed.
Finding your Google Business Profile URL
You need your GBP URL for your LocalBusiness schema sameAs array and for on-page footer links. Four ways to get it: 1. Search your business name in Google, find your Knowledge Panel, click View on Google Maps, copy the URL. 2. Log into business.google.com, select your listing, click View on Maps, copy the URL. 3. In GBP dashboard: Share profile gives you a short g.page/your-business link. 4. Open your listing in Google Maps on mobile, tap Share, copy the link (maps.app.goo.gl format). All formats are valid in your sameAs array.
GBP sameAs in schema — why both matter
Add your GBP link in two places: in your LocalBusiness schema sameAs array (machine-readable signal connecting your schema to your GBP listing) and as a visible "Find us on Google Maps" link in your footer (on-page local signal). Our Local SEO Checker awards points for both independently. Missing either costs you ranking signal. The schema sameAs link helps Google algorithmically connect your website data to your Maps listing.
What drives Maps 3-pack rankings
Google ranks businesses in the Maps 3-pack using three primary factors: Relevance (does your business match what was searched — controlled by GBP category, website content and schema), Distance (how close is the business to the searcher — not controllable but service area in GBP helps), Prominence (how well-known is the business — driven by reviews, citations and backlinks). You cannot change your location, but you can maximise relevance and prominence through schema, NAP consistency and reviews.
Maps embed common errors and fixes
Embed not loading: check the iframe src starts with https://www.google.com/maps/embed — http:// will be blocked as mixed content. Embed not detected by Local SEO Checker: the embed must be in the static HTML, not loaded by JavaScript after page load. Wrong location shown: generate a new embed from the correct pin location — right-click your exact premises in Google Maps, click the coordinates to copy them, then search those coordinates to generate the embed from the precise location.
Tip: Add your Maps embed above the fold on your contact page — not buried at the bottom. A prominent, visible Maps embed carries more local signal weight than one hidden below multiple sections. Make it large enough to be genuinely useful to visitors.
Tags: maps, local-seo, gbp, local, nap
How-To
How to Migrate from HTTP to HTTPS Without Losing Rankings
Switching to HTTPS is a confirmed Google ranking factor and essential for user trust. Done correctly it improves your SEO. Done wrong it can cause ranking drops that take months to recover. Here is the safe way to do it.
Get your SSL certificate
Most hosts provide free SSL via Let's Encrypt. In cPanel: look for SSL/TLS or Let's Encrypt under Security. In Plesk: Domains > your domain > SSL/TLS Certificates > Let's Encrypt. In Nginx: use Certbot (certbot --nginx -d yourdomain.com). In Apache: certbot --apache -d yourdomain.com. If using Cloudflare, enable SSL in the SSL/TLS settings — Full (Strict) mode is recommended.
Set up 301 redirects from HTTP to HTTPS
After installing SSL, all HTTP traffic must redirect to HTTPS. Apache .htaccess: RewriteEngine On / RewriteCond %{HTTPS} off / RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]. Nginx: add a server block for port 80 that returns 301 to the https:// equivalent. Verify the redirect works: curl -I http://yourdomain.com should show 301 Location: https://yourdomain.com.
Fix all internal URLs and resources
After switching, update all hardcoded URLs in your code and database to https://. In WordPress: use the Better Search Replace plugin to replace http://yourdomain.com with https://yourdomain.com across the database. Update your wp-config.php: define('SITEURL', 'https://yourdomain.com'); define('HOME', 'https://yourdomain.com');. Check all images, scripts, stylesheets and external embeds use https:// — any http:// resource causes mixed content warnings.
Update Google Search Console and Analytics
HTTPS and HTTP are treated as separate properties in Google Search Console. Add the HTTPS version as a new property and verify it. Submit your updated sitemap (https:// URLs) to the new property. Update your Google Analytics property to use https:// as the default URL. Update your sitemap to https:// URLs. Update any backlinks you control (social profiles, directory listings) to use the HTTPS URL.
Monitor for issues after migration
Check Google Search Console Coverage report daily for the first 2 weeks after migration — look for any 404 errors or mixed content warnings. Monitor rankings — a temporary small drop is normal as Google re-processes the redirects. Rankings should recover within 2-4 weeks if redirects are set up correctly. If traffic drops significantly after 4 weeks, check that all 301 redirects are in place and no pages are returning mixed content warnings.
Tip: Add the HSTS header after confirming your HTTPS migration is stable: Strict-Transport-Security: max-age=31536000; includeSubDomains. This tells browsers to always use HTTPS for your domain — even before receiving a redirect. Start with a short max-age (86400) for the first week, then increase to a year once you are confident HTTPS is working correctly.
Tags: https, ssl, http, redirect, security, migration
How-To
How to Fix HTML Document Setup Errors
DOCTYPE, charset, viewport, lang and deprecated elements are the foundation of every HTML page. Missing any one of them causes rendering failures, mobile usability issues and accessibility problems. Here is how to fix each one.
Add the DOCTYPE declaration
The DOCTYPE must be the very first line of your HTML file — before anything else including whitespace. Without it browsers enter quirks mode which causes unpredictable layout and broken CSS. Add exactly this as line 1:
<!DOCTYPE html>
This single line tells every browser to render in full standards mode. It is not a tag and needs no closing.
Declare the charset
Add the charset meta tag as the first element inside head, before the title tag. Without it browsers may misinterpret special characters causing garbled text, broken punctuation and failed form submissions:
<head>
<meta charset="UTF-8">
<title>Page Title</title>
</head>
UTF-8 supports every language and character set. Always use UTF-8 unless you have a specific reason not to.
Add the viewport meta tag
Without a viewport tag mobile browsers render at desktop width and scale it down — making your page tiny and unusable on phones. Google uses mobile-first indexing so this directly hurts rankings:
<meta name="color-scheme" content="light only"><meta name="viewport" content="width=device-width, initial-scale=1">
This single line tells mobile browsers to match the screen width and render at 1:1 scale. It is required on every page.
Set the HTML lang attribute
The lang attribute tells browsers, screen readers and search engines what language the page is in. Without it screen readers may use the wrong voice profile and Google may serve the page to the wrong audience:
<html lang="en">
Use the correct ISO language code for your content. For regional variants use: en-GB, en-US, fr-FR, de-DE. Always lowercase for language, uppercase for region.
Remove deprecated HTML elements
Elements removed in HTML5 signal poor code quality and can break in modern browsers. Common culprits: center, font, marquee, blink, bgcolor, align attributes, frameset, frame, noframes. Replace each with CSS:
center → text-align: center
font color="red" → color: red in CSS
bgcolor="#fff" → background-color: #fff in CSS
font size="3" → font-size in CSS
Run our HTML Checker to find all deprecated elements on any page.
Tip: These five items should be in every page template. Fix them once in your base template or CMS theme and every page on your site benefits automatically.
Tags: html, mobile, viewport, charset, doctype
How-To
How to Enable Gzip and Brotli Compression
HTTP compression reduces text file sizes by 60-80% before they are sent to the browser. It is one of the easiest performance wins available — zero downside, significant speed improvement. Here is how to enable it on any server.
Check if compression is already enabled
Before making changes check your current state. Open Chrome DevTools → Network tab → reload the page → click any HTML, CSS or JS file → look at Response Headers for content-encoding: gzip or content-encoding: br. If you see either, compression is already active. If not, follow the steps below for your server type.
Enable Gzip on Apache
Add this to your .htaccess file:
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json text/xml application/xml text/plain font/ttf font/otf image/svg+xml
</IfModule>
If mod_deflate is not available, check with your host. Most shared hosts have it enabled by default.
Enable Gzip on Nginx
Add this to your nginx.conf inside the http block:
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/html text/css application/javascript application/json text/xml text/plain image/svg+xml font/ttf;
Then reload Nginx: nginx -t && systemctl reload nginx
Enable Brotli for better compression
Brotli compresses 15-25% better than Gzip and is supported by all modern browsers. On Nginx with the ngx_brotli module:
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript application/json text/plain image/svg+xml;
On Apache use the mod_brotli module. If your CDN supports Brotli (Cloudflare, Fastly do by default) enable it there for the fastest results.
Verify compression is working
After enabling, verify with: curl -H "Accept-Encoding: gzip" -I https://yoursite.com and check the response for content-encoding: gzip. In Google PageSpeed Insights the "Enable text compression" audit will turn green. Typical savings: HTML 70-80% smaller, CSS 60-70% smaller, JavaScript 60-70% smaller. This directly improves LCP and TTFB scores.
Tip: Do not compress images, video or audio files — they are already compressed and running them through Gzip wastes CPU with no size benefit. Only compress text-based files: HTML, CSS, JS, JSON, XML, SVG.
Tags: compression, performance, server, speed, html, css, javascript
Tutorial
Answer Engine Optimisation is the practice of structuring content so AI search engines cite your site in their answers. Unlike traditional SEO, AEO rewards clarity, authority and direct answers over keyword density.
Understand how AI engines select sources
ChatGPT, Perplexity, Google AI Overviews and Claude all use similar signals to decide what to cite: factual accuracy, source authority, content clarity, structured data, and E-E-A-T signals. They prefer pages that directly answer a question in the first paragraph, have named credentialled authors, use schema markup, and are cited by other authoritative sources. Vague, promotional or thin content is almost never cited.
Structure content for direct answers
Lead every page and section with a direct 2-3 sentence answer to the question the page targets. AI models extract the clearest answer — not the most detailed one. Use the format: state the answer, then elaborate. Example: "LCP measures how long the largest visible element takes to load. Google considers under 2.5 seconds good. It is one of three Core Web Vitals used as ranking signals." Short, factual, specific.
Add FAQ sections with schema
FAQ sections are one of the most reliable AEO signals. AI engines frequently pull FAQ answers directly into their responses. Add a FAQ section to every key page answering the most common questions about the topic. Then add FAQPage JSON-LD schema so AI crawlers can parse the questions and answers programmatically:
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [{
"@type": "Question",
"name": "What is LCP?",
"acceptedAnswer": {"@type": "Answer", "text": "LCP is..."}
}]
}
Build E-E-A-T signals
AI engines weight pages with strong Experience, Expertise, Authoritativeness and Trustworthiness signals much more heavily. Concrete actions: add named author with credentials and photo to every article, add Person schema with sameAs links to LinkedIn and industry profiles, create a detailed About page covering your expertise and history, cite authoritative sources with outbound links, get your site mentioned and linked from authoritative sources in your industry.
Create an llms.txt file
llms.txt is an emerging standard for telling AI crawlers what your site is about and which pages are most important — like robots.txt but for AI. Create /llms.txt at your domain root:
# YourSite.com
> A site providing [clear description of your expertise]
## Key Pages
- [Homepage](https://yoursite.com): Overview
- [About](https://yoursite.com/about): Expertise and credentials
## Topics
Topics: list your main subject areas here
This helps GPTBot, ClaudeBot and PerplexityBot index your content correctly.
Use our AEO Checker tool
Our AEO Checker analyses any URL against all key AEO signals: answer-first content, FAQ presence, schema markup, E-E-A-T indicators, llms.txt, structured headings and content directness score. Run it on your most important pages and work through the recommendations in priority order. The checker scores each signal so you can focus effort where it has the most impact.
Tip: The fastest AEO win is usually adding a FAQ section with FAQPage schema to your top 5 pages. This takes under an hour per page and gives AI engines clearly structured question-answer pairs to cite directly.
Tags: aeo, ai, content, schema, eeat, authority
How-To
WCAG 2.1 compliance is not just a legal requirement — accessibility issues directly impact SEO. Screen reader users, keyboard-only users and people with visual impairments make up a significant portion of web traffic. Here is how to fix the most common errors.
Fix missing alt text on images (WCAG 1.1.1)
Every content image needs descriptive alt text. Every decorative image needs alt="" (empty, not omitted). Screen readers skip images with alt="" and announce the description for content images.
Content image: <img src="ceo.jpg" alt="Jane Smith presenting at Web Summit 2024">
Decorative: <img src="divider.png" alt="">
Never use "image of", "photo of" or a filename as alt text. Use our Accessibility Audit to find all missing alt text across a page.
Fix colour contrast failures (WCAG 1.4.3)
Normal text needs a 4.5:1 contrast ratio against its background. Large text (18pt or 14pt bold) needs 3:1. Common failures: grey text on white (#999 on white = 2.85:1 — fails), light blue links on white, text over images without an overlay. Check every text colour with the WebAIM Contrast Checker at webaim.org/resources/contrastchecker. The most common fix: darken grey text from #999 to #767676 (4.54:1 — passes).
Fix missing form labels (WCAG 4.1.2)
Every input needs a visible label linked with for/id or an aria-label. Placeholder text disappears when typing and is not announced reliably by screen readers:
WRONG: <input type="email" placeholder="Email">
RIGHT: <label for="email">Email address</label>
<input type="email" id="email" name="email">
For search inputs where a visible label is not appropriate: <input type="search" aria-label="Search the site">.
Fix keyboard navigation (WCAG 2.1.1)
Every interactive element — links, buttons, form inputs, custom dropdowns, modals — must be operable with keyboard alone using Tab, Shift+Tab, Enter and Space. Test by unplugging your mouse and navigating your entire site. Common failures: custom onclick divs with no tabindex, modals that trap focus, dropdowns that only work on hover. Fix: use native HTML elements (button, a, input) wherever possible — they handle keyboard automatically. For custom widgets add tabindex="0" and keyboard event listeners.
Fix missing semantic HTML (WCAG 1.3.1)
Screen readers use semantic elements to convey page structure. Using div for everything means users with assistive technology get no context. Replace:
div.nav → nav aria-label="Main navigation"
div.header → header
div.main-content → main
div.sidebar → aside
div.footer → footer
span onclick="..." → button
Never use a heading level (h1-h6) purely for visual styling — headings must reflect document structure. Use our HTML Checker to find semantic issues automatically.
Tip: Run our Accessibility Audit on your most important pages first — homepage, contact, key landing pages. Fix Critical errors before Warnings. Even fixing just the alt text and contrast issues on your top pages significantly improves both accessibility and SEO.
Tags: accessibility, wcag, html, alt, contrast, keyboard
How-To
How to Add FAQ Schema for Rich Results and AEO
FAQPage schema unlocks FAQ rich results in Google — accordion dropdowns directly in search results. It also makes your content much more likely to be cited by AI search engines. Here is how to implement it correctly.
When to use FAQPage schema
Use FAQPage schema on pages that contain a genuine FAQ section — a list of questions with direct answers. Do not use it on pages without visible FAQ content — Google will reject it. Best candidates: product pages with common questions, service pages, help/support pages, and any content page where you have added a Q&A section. Google shows FAQ rich results as expandable dropdowns beneath your search listing, increasing the space your result takes up and improving click-through rate.
Write the FAQ content first
Before adding schema write the actual FAQ section in your HTML. Each question should be a heading (h3 or h4) and each answer should be a paragraph below it. Questions should match what people actually search for — use our Keyword Analysis tool to find common questions in your niche. Answers should be 40-60 words — long enough to be useful, short enough for Google to display in a dropdown. Google limits FAQ rich results to 2 visible dropdowns so make your first two questions the most valuable.
Add the FAQPage JSON-LD
Add this script block in your page head or before the closing body tag:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is your returns policy?",
"acceptedAnswer": {
"@type": "Answer",
"text": "We accept returns within 30 days of purchase..."
}
},
{
"@type": "Question",
"name": "How long does delivery take?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Standard delivery takes 3-5 working days..."
}
}
]
}
</script>
Validate the schema
After adding, validate using our JSON-LD Checker and Schema Debugger tools. Common errors: missing @context, missing @type on Question or Answer, name field containing HTML tags (use plain text only), acceptedAnswer.text containing markup (plain text only). Also test in Google Rich Results Test at search.google.com/test/rich-results — it shows a preview of exactly how your FAQ will appear in search results.
Monitor in Search Console
After deploying, submit the page URL for indexing in Google Search Console. Check the Enhancements section — FAQ rich results appear there once Google has processed the schema. It typically takes 1-2 weeks. If errors appear in Search Console fix them immediately — Google will stop showing rich results for pages with schema errors. Use our Schema Debugger to diagnose any issues flagged by Search Console.
Tip: Combine FAQPage schema with HowTo schema on how-to pages for maximum rich result coverage. A page can have multiple schema types — just add separate JSON-LD script blocks for each type.
Tags: faqpage, schema, rich, faq, json, aeo, content
Tutorial
LSI Keywords: How to Use Semantic Keywords to Rank Better
LSI (Latent Semantic Indexing) keywords are terms semantically related to your main keyword. Using them naturally helps Google understand your content depth and improves rankings for a broader range of search queries.
What LSI keywords actually are
LSI keywords are words and phrases that naturally co-occur with your main topic. If your page is about "running shoes" then LSI keywords include: trainers, sole, cushioning, marathon, trail, road running, arch support, pronation. Google uses these co-occurrence patterns to judge whether content is genuinely about a topic or just repeating one keyword. Pages with rich semantic vocabulary rank better and for more queries than pages stuffed with one exact-match keyword.
Find LSI keywords for your topic
Four reliable sources: 1) Google autocomplete — type your keyword and note all suggestions. 2) Google "People also ask" — these are semantic questions around your topic. 3) Google related searches at the bottom of results. 4) Our Keyword Analysis tool — run it on a competitor page that ranks well and it surfaces the semantic terms they use. Also look at the bold words in Google snippets — Google bolds semantically related words, not just exact matches.
Add LSI keywords naturally
Do not force keywords in — write naturally about your topic and LSI terms appear organically. Deliberately include: synonyms (trainers alongside running shoes), related concepts (gait analysis, heel drop, stack height for a running shoe page), process words (lace, fit, break in), outcome words (comfort, speed, injury prevention). Aim for topical depth — cover the subject thoroughly and LSI terms appear naturally. Thin pages that repeat one keyword and nothing else score poorly for semantic richness.
Use LSI keywords in headings and subheadings
Semantic terms in H2 and H3 headings carry additional weight. Instead of H2: "Best Running Shoes" use H2: "How to Choose the Right Trainers for Your Gait". The semantic richness of your heading structure signals content depth to Google. Each H2 should introduce a distinct subtopic — use our Keyword Analysis tool to identify the subtopics competitors cover that you are missing.
Measure semantic coverage with our tools
Run our Keyword Analysis tool on your page and on the top 3 ranking competitors for your target keyword. Compare the semantic terms they use that you do not. These gaps are your LSI opportunities. Also run our Content Readability tool — pages with good semantic coverage typically also score better on readability metrics because natural language is semantically rich. Fix the gaps by adding sections or expanding existing content.
Tip: The fastest LSI improvement is usually adding a glossary section or FAQ section to a key page. Both naturally introduce semantic vocabulary and related terms in a structured way that both users and Google find valuable.
Tags: lsi, keywords, keyword, content, seo, strategy
Tutorial
HTML Document Structure: DOCTYPE, Charset, Viewport and Lang Explained
The foundation of every web page is its document structure. Getting DOCTYPE, charset, viewport and lang right ensures browsers render correctly, Google indexes properly, and screen readers work as expected.
The DOCTYPE declaration
The DOCTYPE declaration must be the very first line of every HTML file. <!DOCTYPE html> tells browsers to render in standards mode. Without it browsers enter quirks mode causing unpredictable layout and broken CSS. Google requires a valid DOCTYPE to process your page correctly.
The charset declaration
Add <meta charset="UTF-8"> as the very first element inside <head> — before the title tag. UTF-8 supports every language, symbol and emoji. Without it browsers may display garbled text for special characters. Google recommends UTF-8 for all web pages.
The viewport meta tag
Add <meta name="color-scheme" content="light only"><meta name="viewport" content="width=device-width, initial-scale=1"> inside <head>. Without this tag mobile browsers render at desktop width (around 980px) and scale down, making text tiny. Google uses mobile-first indexing — a missing viewport tag directly hurts mobile rankings.
The lang attribute
Add lang="en" to the opening html tag: <html lang="en">. For British English use lang="en-GB". This tells browsers, screen readers and search engines what language the page is in. Without it screen readers use the wrong pronunciation and Google has weaker language targeting signals.
The complete document structure
A correctly structured HTML document: <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="color-scheme" content="light only"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Page Title</title></head><body>content<div style="max-width:1100px;margin:0 auto;padding:16px 24px;border-top:1px solid #dbeafe;font-family:Outfit,sans-serif;"> <h3 style="font-size:11px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#94a3b8;margin:0 0 8px;">About aiwebpageseo</h3> <p style="font-size:13px;line-height:1.7;color:#94a3b8;margin:0;max-width:880px;">aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.</p> </div> </body></html>. Use our HTML Checker to verify document structure on any page.
Tip: Run our Site Audit on your homepage first — it checks all four structural elements in one pass and flags any that are missing or incorrect.
Tags: html, doctype, charset, viewport, lang, technical, seo
How-To
How to Fix a Missing DOCTYPE Declaration
A missing DOCTYPE causes browsers to enter quirks mode, breaking layout and CSS. This is a 30-second fix on any site.
What the error means
Your page is missing the <!DOCTYPE html> declaration or it is not the very first line of the file. Without it the browser enters quirks mode — a legacy compatibility mode causing layout bugs and broken CSS.
The fix
Add <!DOCTYPE html> as the absolute first line of your HTML file — before anything else including whitespace or comments. It must be on line 1 followed by <html lang="en">.
Verify the fix
Open your page in a browser, right-click and View Page Source. Confirm <!DOCTYPE html> is on the very first line. Our HTML Checker also flags missing or misplaced DOCTYPE declarations.
Tip: If your CMS generates pages automatically, the DOCTYPE is set in your theme or template file — usually the very top of header.php or your main layout template.
Tags: doctype, html, technical, fix
How-To
How to Fix a Missing or Incorrect Charset Declaration
Without a charset declaration browsers may display garbled text. The fix is a single meta tag added to your HTML head.
What the error means
Your page is missing <meta charset="UTF-8"> or is using a non-UTF-8 encoding. Without it browsers guess the encoding, which can cause special characters and accented letters to display as garbled characters.
The fix
Add <meta charset="UTF-8"> as the very first element inside your <head> tag — before even the title tag: <head><meta charset="UTF-8"><title>Your Title</title></head>.
Verify
Use our HTML Checker to confirm the charset is correctly declared. Chrome DevTools Network tab Response Headers should show Content-Type: text/html; charset=UTF-8.
Tip: Always save your HTML files as UTF-8 encoding in your code editor. In VS Code the encoding is shown in the bottom right corner of the editor window.
Tags: charset, encoding, html, fix, technical
How-To
How to Fix a Missing Viewport Meta Tag
Without a viewport tag mobile browsers render your page at desktop size. This directly hurts mobile rankings under Google mobile-first indexing.
What the error means
Your page is missing the viewport meta tag. Without it mobile browsers render at desktop width and scale down, making text tiny. Google uses mobile-first indexing — missing viewport directly damages your mobile search performance.
The fix
Add this tag inside your <head>: <meta name="color-scheme" content="light only"><meta name="viewport" content="width=device-width, initial-scale=1">. This enables all responsive CSS to work correctly on mobile.
What not to do
Do not add user-scalable=no or maximum-scale=1. These prevent users from zooming, which is a WCAG accessibility failure and is flagged by Google as a mobile usability issue.
Verify
Use Chrome DevTools device toolbar (F12, Ctrl+Shift+M) to preview at 375px width. No horizontal scrollbar should appear. Our Site Audit also confirms the viewport tag.
Tip: Google Search Console has a Mobile Usability report that flags pages with missing viewport tags. Check it after deploying your fix to confirm it is resolved across your whole site.
Tags: viewport, mobile, seo, fix, technical
How-To
How to Fix a Missing HTML lang Attribute
The lang attribute tells browsers, screen readers and search engines what language your page is in. Missing it fails WCAG and weakens international SEO.
What the error means
Your <html> tag is missing the lang attribute. This fails WCAG 3.1.1 Level A. Screen readers use lang to select the correct voice profile. Without it users with visual impairments hear the page in the wrong language. Google also uses lang for language targeting.
The fix
Add the lang attribute to your opening html tag: <html lang="en"> for English, <html lang="en-GB"> for British English, <html lang="fr"> for French. Use ISO 639-1 two-letter codes with optional ISO 3166-1 region codes: en-US, en-AU, fr-FR.
Verify
Run our HTML Checker or Site Audit to confirm the lang attribute is present. Google Search Console International Targeting report also surfaces lang-related issues.
Tip: Combining correct lang attributes with hreflang tags gives Google the strongest possible signal about which language version to show to which users.
Tags: lang, html, accessibility, seo, fix, technical
How-To
How to Fix Deprecated HTML Elements
Deprecated HTML elements like center, font, marquee and strike were removed in HTML5. Replace them with CSS equivalents.
Common deprecated elements
HTML5 removed: <center> (use CSS text-align:center), <font> (use CSS font-family/color), <strike> and <s> (use CSS text-decoration:line-through or semantic <del>), <marquee> (remove, use CSS animations), <blink> (remove), bgcolor attribute (use CSS background-color), align attribute (use CSS text-align).
The replacements
Center text: remove <center> add CSS text-align:center. Font styling: remove <font color="red"> add CSS color:red. Strikethrough: replace <strike>text</strike> with <span style="text-decoration:line-through">text</span> or <del>text</del> for deleted content.
Finding them
Use our HTML Checker which lists all deprecated elements with line numbers. Search your template files for: font, center, marquee, strike, blink, bgcolor, align= to find all instances.
Tip: Deprecated elements are most common in older sites built before HTML5 or migrated from legacy CMS platforms. A global find-and-replace across templates is the most efficient fix.
Tags: deprecated, html, css, fix, technical
Tutorial
Link Security: rel=noopener, noreferrer and target=_blank Explained
Links that open in new tabs without rel="noopener" expose your site to tabnapping. Understanding link security protects both your users and your site.
The tabnapping vulnerability
When you use target="_blank" the new page can access your original page via window.opener. A malicious site could redirect your original tab to a phishing page while the user reads the new tab. This is tabnapping. The fix: add rel="noopener" to prevent the new page accessing window.opener.
What rel=noopener does
rel="noopener" prevents the opened page accessing window.opener, blocking tabnapping. It also provides a performance benefit — without noopener the new page runs in the same process as your page. With noopener it runs in its own process.
What rel=noreferrer does
rel="noreferrer" prevents the browser sending the HTTP Referer header, so the destination site does not know which page sent the user. It also implies noopener. Use rel="noopener noreferrer" together on all external links that open in new tabs.
The correct pattern
Every external link opening in a new tab: <a href="https://external.com" target="_blank" rel="noopener noreferrer">Link text</a>. For internal links that open in new tabs, rel="noopener" alone is sufficient.
Tip: Use our Site Audit to detect all external links missing rel="noopener". Fix them in your link template or CMS link settings in one go.
Tags: security, noopener, noreferrer, links, html, technical
How-To
How to Fix External Links Missing rel="noopener"
External links with target="_blank" need rel="noopener noreferrer" to prevent the tabnapping security vulnerability.
Find affected links
Use our Site Audit which lists all external links missing rel="noopener". Look for <a> tags with target="_blank" but no rel="noopener" attribute.
Fix individual links
Add rel="noopener noreferrer" to any external link with target="_blank": <a href="https://external.com" target="_blank" rel="noopener noreferrer">Link text</a>. If rel already has values add to it: rel="nofollow noopener noreferrer".
Fix globally in your CMS
Most CMS platforms allow setting default link attributes. Look for an option to automatically add rel="noopener noreferrer" to all external links — the most efficient fix if you have many links.
Tip: Modern browsers now automatically apply noopener behaviour to target="_blank" links even without the attribute, but adding it explicitly is still best practice for older browser support.
Tags: noopener, security, links, fix, html
How-To
How to Add a Content Security Policy Header
Step-by-step CSP deployment for nginx, Apache, Cloudflare and Plesk, using report-only first so you do not break the site.
1. Inventory what your pages load
Open DevTools, Network tab, and load your busiest pages — homepage, a logged-in page, checkout, admin. Note every distinct domain serving scripts, styles, fonts, images, and every domain your JavaScript talks to via fetch or XHR. This list becomes your policy. Doing it from the homepage alone is the usual reason a CSP breaks checkout a week after deployment.
2. Write the report-only header first
Build the policy but send it under the Content-Security-Policy-Report-Only name. The browser evaluates it and reports violations without blocking anything. A sensible starting policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'.
3. Add it in nginx
In the server block: add_header Content-Security-Policy-Report-Only "default-src 'none'; script-src 'self'; ..." always; — the always keyword matters, or the header is omitted from error responses. Test the config with nginx -t before reloading, then systemctl reload nginx.
4. Or add it in Apache
.htaccess or the vhost: Header always set Content-Security-Policy-Report-Only "default-src 'none'; script-src 'self'; ..." — requires mod_headers enabled (a2enmod headers). Restart with systemctl restart apache2. Note that a policy set in .htaccess applies from that directory down, so a site with several .htaccess files can end up with inconsistent headers.
5. Or add it in Plesk or Cloudflare
Plesk: Websites & Domains, Apache & nginx Settings, Additional nginx directives — paste the add_header line there and Plesk writes it into the vhost config. Cloudflare: Rules, Transform Rules, Modify Response Header, add a static header. Cloudflare applies at the edge, which means it covers every origin behind it but also overrides what your origin sends.
6. Collect and read the violation reports
Add report-to csp-endpoint and a Reporting-Endpoints header naming a URL that accepts POSTed JSON. Also add report-uri as a fallback for browsers without full report-to support. Leave it running for at least a week of real traffic so seasonal and logged-in journeys are covered. Every violation is either a resource you forgot or an actual injection attempt.
7. Widen the policy for legitimate resources only
For each genuine violation, add the specific source — not a wildcard. If reports show your payment provider's script blocked, add that exact host to script-src. Resist the temptation to add 'unsafe-inline' to make reports stop: that removes most of the protection you are deploying.
8. Switch to enforcing and verify
When the reports are clean, rename the header from Content-Security-Policy-Report-Only to Content-Security-Policy. Confirm with curl -sI https://yoursite.com | grep -i content-security. Then paste the policy into Google's CSP Evaluator at csp-evaluator.withgoogle.com, which grades it and flags weaknesses you may not have noticed.
Tip: Keep the policy on one line in your config. Multi-line add_header values are a common source of silently malformed headers, and a malformed CSP may be ignored entirely — leaving you believing you are protected when you are not.
Tags: csp, security, howto, fix
How-To
How to Enable HTTP Strict Transport Security
Verify HTTPS everywhere, fix the redirect, then raise max-age in stages before considering preload.
1. Confirm HTTPS works on every host first
HSTS is sticky and effectively cannot be undone once cached. Check every page, asset, API endpoint and subdomain serves valid HTTPS. Any host that cannot will become unreachable in browsers that have seen the header, and no server-side change will fix that until max-age expires.
2. Fix the HTTP to HTTPS redirect
Port 80 must redirect to the same resource on the same host over HTTPS. In nginx: server { listen 80; return 301 https://$host$request_uri; }. In Apache: <VirtualHost *:80> ServerName site.example.org; Redirect permanent / https://site.example.org/ </VirtualHost>.
3. Never redirect across hosts in one hop
A redirect from http://example.com straight to https://example.org prevents HSTS ever being set for the first host, leaving the exact gap you are trying to close. Chain it instead: http://example.com to https://example.com first, then to https://example.org.
4. Start with a deliberately short max-age
Add: add_header Strict-Transport-Security "max-age=300" always; in nginx, or Header always set Strict-Transport-Security "max-age=300" in Apache. If something breaks, the effect expires in five minutes rather than years. Confirm the header appears with curl -sI on pages, assets and error responses.
5. Raise it to six months, then two years
Once stable, move to max-age=15768000 (six months, the recommended minimum), then max-age=63072000 (two years, the recommended value). Each increase commits you to supporting HTTPS on that host for the full duration, because browsers will refuse HTTP regardless of what you serve.
6. Add includeSubDomains only after testing them
This extends the policy to every subdomain. Enumerate them first — staging, mail interfaces, internal tools, legacy hosts — and confirm each serves valid HTTPS. Discovering an HTTP-only internal subdomain after a two-year policy is cached is a genuinely painful outage with no quick remedy.
7. Add upgrade-insecure-requests alongside
Set the CSP directive upgrade-insecure-requests so any stray http:// resource URL in your pages is retried over HTTPS automatically. This is aimed at sites with large volumes of legacy content where rewriting every historical URL is impractical.
8. Consider preload last, if ever
Submitting at hstspreload.org bakes your domain into browsers so HTTPS is enforced on a user's very first visit. It requires includeSubDomains, the preload token, and max-age of at least 31536000. Removal takes months to propagate through browser releases, so treat it as permanent.
Tip: The order that avoids outages is: HTTPS everywhere, then redirect, then short max-age, then long, then includeSubDomains, then preload. Skipping straight to preload is the classic way to find an HTTP-only subdomain the hard way.
Tags: hsts, security, howto, fix
How-To
How to Stop Your Site Being Framed (Clickjacking)
Set CSP frame-ancestors plus X-Frame-Options, then prove it with a real iframe test.
1. Decide who, if anyone, may frame you
Three options: nobody, only your own pages, or a named list of partners. Most sites want nobody. If a partner embeds a widget of yours, get the exact origin from them — you will list it explicitly rather than opening framing up generally.
2. Add frame-ancestors to your CSP
This is the authoritative modern control. Add frame-ancestors 'none' to block all framing, or frame-ancestors 'self' to allow same-origin only. Important: this directive does not inherit from default-src, so it must be stated explicitly even in an otherwise strict policy.
3. Add X-Frame-Options for older browsers
nginx: add_header X-Frame-Options "DENY" always; Apache: Header always set X-Frame-Options "DENY". Use SAMEORIGIN instead if you frame your own pages. Browsers that understand frame-ancestors ignore this header when both are present, so there is no conflict.
4. Do not use ALLOW-FROM
The ALLOW-FROM value of X-Frame-Options was never widely supported and is obsolete. If you need to permit specific external origins, that is only expressible through CSP frame-ancestors with those origins listed.
5. Name partner origins precisely
frame-ancestors https://partner.example.com https://app.partner.example.com — full origins including scheme. Never use a wildcard because one integration needs framing; that removes the protection for everyone. If the partner set is open-ended, protect the sensitive actions instead of the framing.
6. Apply it everywhere, prioritising state-changing pages
The pages that matter are the ones where a single click does something: payment confirmation, settings changes, permission grants, account deletion, admin actions. Static marketing pages carry far less risk. If a blanket policy is impractical, cover those first.
7. Test with a real iframe, not just curl
Save a local file containing <iframe src="https://yoursite.com" width="800" height="600"></iframe> and open it in a browser. A protected site shows an empty frame and a console message naming the directive that refused. Checking the header exists proves it is sent; this proves it works.
8. Add SameSite cookies as a second layer
Set your session cookie to SameSite=Lax or Strict. Framed inside an attacker's page, requests may then arrive without the session cookie and therefore unauthenticated, which blunts clickjacking even where framing itself is somehow permitted.
Tip: Set both headers. frame-ancestors is authoritative in modern browsers, X-Frame-Options covers older clients, and because modern browsers ignore the older header when both are present there is no downside to sending both.
Tags: xframe, security, howto, fix
Tutorial
Landmark elements like main and nav allow screen reader users to jump directly to content. Skip navigation links help keyboard users bypass repetitive menus. Both are required for WCAG compliance.
What ARIA landmarks are
ARIA landmarks define regions of a page for screen reader navigation. Key elements: <header> (site header), <nav> (navigation), <main> (primary content), <aside> (secondary content), <footer> (site footer). Screen reader users can jump between landmarks rather than tabbing through every element.
The main element
Every page should have exactly one <main> element wrapping the primary content. Screen reader users can jump directly to <main> to skip headers and navigation. WCAG 1.3.1 requires this landmark for Level A compliance.
The nav element
Wrap navigation menus in <nav>. For multiple navigation areas use distinct aria-label values: <nav aria-label="Main navigation">, <nav aria-label="Breadcrumb">. Screen reader users can list all navigation landmarks and jump to the one they need.
Skip navigation links
A skip navigation link is a visually hidden link at the top of the page jumping to main content. Implementation: <a class="skip-link" href="#main-content">Skip to main content</a> with CSS to hide until focused: .skip-link { position:absolute; transform:translateY(-100%); } .skip-link:focus { transform:translateY(0); }. Add id="main-content" to your <main> element.
Tip: The most impactful single accessibility improvement for keyboard users is adding a skip navigation link. It takes under 5 minutes and immediately benefits all keyboard-only users.
Tags: aria, landmarks, accessibility, nav, main, wcag, skip, navigation
How-To
How to Fix Missing Landmark Elements and Skip Navigation
Adding main, nav landmarks and a skip navigation link brings your page into WCAG compliance.
Add the main element
Wrap your primary page content: <main id="main-content">...your page content...</main>. Exactly one <main> per page, placed directly inside <body>.
Add nav elements
Wrap navigation menus: <nav aria-label="Main navigation"><ul><li><a href="/">Home</a></li></ul></nav>. Use distinct aria-label values for multiple navigation areas.
Add a skip navigation link
Add as first element inside <body>: <a href="#main-content" class="skip-link">Skip to main content</a>. CSS: .skip-link { position:absolute; left:-9999px; } .skip-link:focus { position:static; }.
Verify
Press Tab on your page — the skip link should be the first focusable element. Use our Accessibility Audit to confirm all landmark requirements are met.
Tip: Check landmark structure in Chrome DevTools Accessibility tab — each landmark should appear as a named region in the accessibility tree.
Tags: landmarks, main, nav, accessibility, fix, wcag, skip
How-To
How to Fix outline:none on :focus
Removing the focus outline makes keyboard navigation invisible. This fails WCAG 2.4.7. Replace with a visible custom focus indicator.
Why this is critical
The focus outline is the only visual indicator for keyboard users of which element is active. Removing it with outline:none makes the page completely unusable for keyboard-only users. Fails WCAG 2.4.7 Focus Visible (Level AA) — a legal requirement in many jurisdictions.
Find all instances
Search your CSS for: outline:none, outline:0, outline: none. Our CSS Checker flags all instances.
Replace with custom focus style
Instead of removing, replace: *:focus { outline: 2px solid #005fcc; outline-offset: 2px; }. Choose a colour meeting 3:1 contrast ratio against the background.
Use :focus-visible
Show focus rings only for keyboard navigation: *:focus:not(:focus-visible) { outline: none; } *:focus-visible { outline: 2px solid #005fcc; outline-offset: 2px; }. Hides ring on mouse clicks while keeping it for keyboard users.
Tip: Never use outline:none in a CSS reset. Modern resets like sanitize.css preserve focus outlines. Replacing that one line in a reset fixes the issue globally.
Tags: focus, outline, accessibility, css, fix, wcag, keyboard
How-To
How to Fix Positive tabindex Values
Positive tabindex values create unpredictable keyboard navigation. Replace with tabindex="0" or remove them.
What positive tabindex does
tabindex="1", tabindex="2" etc. override natural DOM tab order creating disconnected, confusing keyboard navigation. WCAG 2.4.3 requires a logical focus order.
The fix
Remove all positive tabindex values. The natural DOM order is correct in almost all cases. Use tabindex="0" to make non-focusable elements focusable in natural order. Use tabindex="-1" for programmatic focus only (modals, error messages).
Fix the real problem
Positive tabindex is usually added to fix broken tab order. The real fix is reordering the HTML source so DOM order matches visual order.
Tip: Browser DevTools Accessibility panel shows the tab order. Press Tab repeatedly and watch which elements receive focus to verify the sequence is logical.
Tags: tabindex, keyboard, accessibility, fix, wcag
How-To
How to Fix Focusable Elements with aria-hidden="true"
Elements that are both keyboard-focusable and aria-hidden create a confusing experience. Screen reader users tab to an invisible element.
What the problem is
aria-hidden="true" hides an element from the accessibility tree. But if still focusable via Tab, keyboard users land on an element the screen reader cannot announce — creating silent, confusing focus.
Option 1 — Remove aria-hidden
If the element should be accessible to keyboard users, remove aria-hidden="true" entirely.
Option 2 — Remove from tab order
If it should be hidden from both screen readers and keyboard users (decorative icon inside a labelled button), add tabindex="-1": <span aria-hidden="true" tabindex="-1">★</span>.
Tip: Tab through your page with a screen reader. Any time you land on an element not announced, it is likely focusable with aria-hidden="true".
Tags: aria, hidden, accessibility, fix, wcag, keyboard
How-To
How to Fix iframes Missing title Attribute
Every iframe needs a title so screen reader users know what the embedded content contains.
The fix
Add a descriptive title to every iframe: <iframe src="https://www.youtube.com/embed/xxx" title="Introduction video — how our product works"></iframe>.
Common examples
YouTube: title="Product demo video". Google Map: title="Map showing our London office". Payment form: title="Secure payment form".
For hidden iframes
Technical iframes (tracking) that should be hidden: <iframe src="tracking.html" title="" aria-hidden="true" style="display:none"></iframe>.
Tip: Third-party embed code from YouTube, Google Maps etc. does not include titles by default — add them manually after pasting embed code.
Tags: iframe, title, accessibility, fix, wcag
How-To
How to Fix Buttons with No Accessible Name
Buttons must have an accessible name so screen readers can announce their purpose. Icon-only buttons are the most common offender.
Fix with visible text
Add visible text inside the button: <button type="button">Search</button>.
Fix icon-only buttons
Add aria-label: <button type="button" aria-label="Search"><svg aria-hidden="true">...</svg></button>. Add aria-hidden="true" to the icon.
Fix input type=submit
Add value attribute: <input type="submit" value="Send Message">.
Tip: Close buttons (X) in modals are the most common offender. Always add aria-label="Close": <button aria-label="Close dialog" type="button">×</button>.
Tags: buttons, accessibility, fix, wcag, aria, label
How-To
How to Fix Auto-Playing Media Without Mute
Auto-playing audio without mute fails WCAG 1.4.2. Always mute auto-playing video and avoid auto-playing audio.
Fix for video
Add the muted attribute: <video autoplay muted loop playsinline>. The muted attribute is required for autoplay to work in modern browsers anyway.
Fix for audio
Remove autoplay from audio elements. There is almost no legitimate use case for auto-playing audio on a web page.
Provide controls
Always add the controls attribute: <video controls autoplay muted>. Gives users a visible play/pause button.
Tip: autoplay muted loop playsinline is the correct pattern for decorative background videos. This plays silently without blocking the page.
Tags: autoplay, video, audio, accessibility, fix, wcag
How-To
How to Fix Missing autocomplete Attributes on Form Inputs
The autocomplete attribute helps browsers fill form fields correctly and improves accessibility for users with cognitive disabilities.
Common autocomplete values
name, given-name, family-name, email, tel, organization, street-address, postal-code, country, current-password, new-password, username.
Implementation
Add to input elements: <input type="email" id="email" name="email" autocomplete="email">. For login forms: autocomplete="username" on email, autocomplete="current-password" on password.
When to use autocomplete=off
Only for truly one-time fields like verification codes or CAPTCHAs. Never on standard login, registration or checkout fields.
Tip: Using autocomplete="new-password" on password creation fields triggers the browser password generator, improving security for new accounts.
Tags: autocomplete, forms, accessibility, fix, wcag, html
Tutorial
CSS is render-blocking by default. Understanding how CSS affects LCP, FCP and CLS helps you prioritise the right optimisations.
CSS is render-blocking
When a browser encounters a <link rel="stylesheet"> tag it stops rendering until the CSS is fully downloaded and parsed. A large CSS file or slow delivery directly delays FCP and LCP. Google PageSpeed Insights flags this under Eliminate render-blocking resources.
@import creates sequential loading
@import inside CSS forces sequential downloading — the browser cannot discover the imported file until it has downloaded the parent. Replace all @import with <link rel="stylesheet"> tags in HTML so files download in parallel.
Critical CSS
Critical CSS is the minimal CSS needed to render above-the-fold content. Inlining it directly in <style> in <head> and loading the full stylesheet asynchronously allows the page to start rendering without waiting for the full CSS file — one of the most impactful FCP improvements.
Large CSS and parse time
CSS files over 100KB add significant parse time on every page. The browser processes every rule even if only a fraction applies. Use PurgeCSS to remove unused rules — sites commonly have 70-90% unused CSS from large frameworks.
CSS and Cumulative Layout Shift
Missing width/height on images causes layout shift. Always set explicit dimensions or aspect-ratio in CSS. Custom fonts without font-display:swap cause FOIT (invisible text) then layout shift when the font loads. Add font-display:swap to all @font-face declarations.
Tip: The single biggest CSS performance win for most sites is removing unused CSS. Run our CSS Checker on key pages and check the unused CSS percentage.
Tags: css, performance, lcp, fcp, cls, render-blocking, critical-css
How-To
How to Fix @import Rules in CSS
@import creates sequential stylesheet loading that delays rendering. Replace with parallel <link> tags in HTML.
The fix
Replace @import in CSS with <link rel="stylesheet"> in HTML: instead of @import url("fonts.css") in CSS, add <link rel="stylesheet" href="fonts.css"> in HTML. Multiple link tags download simultaneously.
For Google Fonts
Use the recommended pattern: <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=...&display=swap">. Never use @import for Google Fonts.
Exception for build tools
If using webpack, Vite or Parcel, @import in CSS source files is compiled into a single output file at build time. This does not cause a runtime performance issue.
Tip: Search your CSS files for @import to find all instances, then move each to a <link> tag in your HTML head.
Tags: import, css, performance, render-blocking, fix, fcp
How-To
Large CSS files delay rendering. Split into critical and non-critical CSS and remove unused rules.
Identify unused CSS
Open Chrome DevTools, press Ctrl+Shift+P and type Coverage. Run Coverage while loading your page — unused CSS is highlighted in red. Our CSS Checker also identifies CSS file size and flags oversized files.
Inline critical CSS
Extract above-the-fold CSS and inline in <style> in <head>. Then load the full stylesheet asynchronously: <link rel="stylesheet" href="main.css" media="print" onload="this.media='all'">.
Split by page type
For large sites with very different page types, split CSS into separate files per page type. Each page loads only the CSS it needs.
Tip: The Coverage tool in Chrome DevTools showing you use only 45KB of a 500KB CSS file is the most compelling argument for CSS optimisation investment.
Tags: css, performance, fix, unused, critical-css, lcp, fcp
How-To
How to Fix HTTP URLs in CSS Files
HTTP resource URLs in CSS trigger mixed content warnings on HTTPS pages and may be blocked by browsers.
Find all HTTP URLs
Search your CSS for url("http:// and url('http://. Our CSS Checker automatically flags all HTTP resource URLs in background-image, @font-face src and @import.
The fix
Change all http:// URLs to https:// in your CSS. If the external resource does not support HTTPS, consider hosting it yourself on your HTTPS server.
Tip: After fixing HTTP URLs in CSS, re-run our Site Audit to confirm no mixed content warnings remain.
Tags: https, mixed-content, css, fix, security
How-To
How to Fix Vendor Prefix Overuse in CSS
Excessive vendor prefixes bloat CSS. Use Autoprefixer to manage prefixes automatically.
What vendor prefixes are
Vendor prefixes (-webkit-, -moz-, -ms-) were used for CSS properties before standardisation. Most are now unnecessary — modern browsers support standard properties without prefixes.
Use Autoprefixer
Autoprefixer is a PostCSS plugin that adds only the prefixes your target browsers actually need. Remove all manual prefixes and let Autoprefixer add what is required based on your browserslist.
Remove manually
Search your CSS for -webkit-, -moz-, -ms-. Check each at caniuse.com — if standard property has >97% browser support without prefix, remove the prefixed version.
Tip: A typical modern browserslist: > 0.5%, last 2 versions, Firefox ESR, not dead. This removes prefixes for browsers with under 0.5% global usage.
Tags: vendor-prefix, css, fix, performance, autoprefixer
Tutorial
CSS Specificity, Cascade and Why !important Creates Problems
Understanding CSS specificity is the key to writing maintainable stylesheets. Specificity determines which rule wins when multiple rules target the same element.
How specificity is calculated
CSS specificity is a three-part score: (ID count, class count, element count). ID selector (#header) scores (1,0,0). Class selector (.nav) scores (0,1,0). Element selector (div) scores (0,0,1). Inline styles always win. !important overrides everything.
Why !important is problematic
!important removes an element from the normal cascade. To override it you need another !important with higher specificity — starting a specificity war. The codebase becomes impossible to maintain. Only legitimate uses: utility classes (.hidden) and overriding third-party styles you cannot edit.
Why ID selectors cause problems
ID selectors (#header) have specificity (1,0,0) — far higher than class selectors. To override them you need another ID selector or !important. Style with classes exclusively — use IDs only for JavaScript hooks and anchor links.
Deeply nested selectors
Selectors like nav ul li a span are brittle — restructuring HTML breaks the CSS. BEM naming eliminates nesting: .nav__link instead of nav ul li a. BEM selectors have specificity (0,1,0) regardless of component complexity.
Tip: Browser DevTools Styles panel shows exactly which rules apply and strikes through overridden declarations. Use it to understand and fix specificity conflicts.
Tags: css, specificity, important, cascade, selectors, quality
How-To
How to Fix Excessive !important in CSS
Excessive !important indicates unresolved specificity conflicts. Fix the underlying specificity instead.
Find all instances
Search your CSS for !important. Our CSS Checker counts all instances. More than 10 in a stylesheet is a warning sign.
Increase specificity instead
Instead of .button { color: red !important; } write .parent .button { color: red; }. Adding a parent selector increases specificity without !important.
Legitimate uses to keep
Keep !important for utility classes (.hidden { display:none !important }), and overriding third-party styles you cannot edit. Document with a comment: /* utility — intentional !important */.
Tip: CSS Cascade Layers (@layer) are the modern solution to specificity wars. Define layer order to control which rules win without !important hacks.
Tags: important, css, specificity, fix, quality
How-To
How to Fix Deeply Nested CSS Selectors
Selectors nested 3+ levels deep are brittle and hard to maintain. Flatten using BEM naming or direct class selectors.
The BEM approach
BEM eliminates nesting: instead of nav ul li a use .nav__link. Block (.nav), Element (.nav__link), Modifier (.nav__link--active). Every selector has specificity (0,1,0) regardless of complexity.
Flatten existing selectors
For each deeply nested selector: add a direct class to the target element and style that class. nav ul li a { color: blue } becomes .nav__link { color: blue } where .nav__link is added to the anchor tag.
Tip: Start BEM adoption on new components and refactor old ones gradually as they cause specificity problems.
Tags: css, selectors, specificity, bem, fix, quality
How-To
How to Fix ID Selectors in CSS
ID selectors have very high specificity making styles hard to override. Replace with class selectors.
The fix
For each ID selector in CSS, add a class with the same name to the HTML element and replace the ID selector with the class: #header { } becomes .site-header { } and the element gains class="site-header". Keep the id for JavaScript and anchor links.
Finding ID selectors
Search your CSS for #. Our CSS Checker flags all ID selectors with their count.
Tip: Adopt the rule "IDs are for JavaScript, classes are for CSS" from the start. This prevents the ID specificity problem entirely.
Tags: css, id-selectors, specificity, fix, quality
How-To
How to Fix Duplicate CSS Selectors and Properties
Duplicate selectors and properties add file weight and make it unclear which rules apply.
Finding duplicates
Our CSS Checker lists duplicate selectors and properties. The stylelint no-duplicate-selectors and declaration-block-no-duplicate-properties rules catch these automatically.
Merging duplicate selectors
Combine into a single rule block: instead of .btn { color: blue; } later .btn { padding: 10px; } write .btn { color: blue; padding: 10px; }.
Intentional duplicate properties
Some duplicates are intentional browser fallbacks: color: #f00; color: rgba(255,0,0,0.9). Add a comment: /* fallback for older browsers */ so future developers know it is deliberate.
Tip: Add stylelint to your workflow to catch duplicates as you write CSS rather than during an audit.
Tags: css, duplicate, fix, quality, maintenance
Tutorial
Responsive CSS: Media Queries, Mobile-First and CSS Grid
Google uses mobile-first indexing. Sites without responsive CSS rank poorly on mobile. This covers media queries, common responsive patterns and modern CSS layout.
Why responsive CSS matters for SEO
Google uses mobile-first indexing — it crawls and ranks the mobile version of your page. Large fixed widths (width:1200px) prevent content adapting to narrower screens causing horizontal scrolling. Mobile usability is a confirmed Google ranking signal.
Mobile-first media queries
Write for small screens by default, then use min-width queries for desktop: .container { width:100%; padding:16px; } @media (min-width: 768px) { .container { max-width:1200px; margin:0 auto; } }. Mobile-first loads only necessary styles on mobile.
Fixing large fixed widths
Replace width:1200px with max-width:1200px; width:100%. Or use: width: min(1200px, 100%). Use CSS Grid for layouts that naturally reflow: display:grid; grid-template-columns:repeat(auto-fit, minmax(280px, 1fr)).
CSS Grid and @supports fallbacks
CSS Grid has over 97% browser support. If supporting IE11: @supports (display: grid) { .grid { display:grid; grid-template-columns:repeat(3, 1fr); } }. Elements outside @supports receive the fallback layout.
prefers-reduced-motion
Add for users with vestibular disorders: @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }. Our CSS Checker flags sites with animations but no prefers-reduced-motion rule.
Tip: Google Search Console Mobile Usability report shows pages with mobile rendering issues. Use it alongside our Site Audit to prioritise responsive fixes.
Tags: responsive, css, media-queries, mobile, grid, fix, technical
How-To
How to Fix Large Fixed Pixel Widths in CSS
Fixed pixel widths above 600px break on mobile causing horizontal scroll.
Find fixed widths
Our CSS Checker flags all width declarations using fixed pixel values above 600px. Search for width: followed by values like 800px, 960px, 1200px.
The fix
Change width:1200px to max-width:1200px; width:100%. Or use: width: min(1200px, 100%).
Verify
Use Chrome DevTools device toolbar (F12, Ctrl+Shift+M) at 375px width. No horizontal scrollbar should appear.
Tip: Google Search Console Mobile Usability report shows pages with content wider than the screen. Check after fixing to confirm resolution across your site.
Tags: css, responsive, mobile, fix, width, media-queries
How-To
How to Fix Missing Media Queries in a Stylesheet
A stylesheet without media queries cannot respond to different screen sizes.
Add breakpoints
Common breakpoints: @media (max-width: 767px) { /* mobile */ } @media (min-width: 768px) { /* tablet+ */ } @media (min-width: 1024px) { /* desktop */ }.
What to put in breakpoints
Switch multi-column layouts to single column on mobile, change fixed widths to 100%, stack horizontal items vertically. Only override what needs to change — do not duplicate all styles.
Use CSS Grid auto-fit
For card grids: grid-template-columns:repeat(auto-fit, minmax(280px, 1fr)) eliminates the need for breakpoints entirely — automatically creates as many columns as fit.
Tip: Two CSS rules fix the most common mobile rendering issues: body { max-width:100% } and img { max-width:100%; height:auto }.
Tags: media-queries, responsive, css, fix, mobile
How-To
How to Fix Missing prefers-reduced-motion Media Query
Animations without a prefers-reduced-motion override can cause physical discomfort for users with vestibular disorders.
The global fix
Add: @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } }
Targeted approach
Disable specific animations: @media (prefers-reduced-motion: reduce) { .hero-parallax { transform: none; } .spinner { animation: none; } }.
Verify
On macOS: System Preferences > Accessibility > Display > Reduce motion. Enable it and reload — all CSS animations should stop.
Tip: Implementing prefers-reduced-motion is low effort and protects users with genuine medical conditions who experience nausea from on-screen motion.
Tags: animation, accessibility, css, fix, motion, wcag
How-To
How to Fix overflow:hidden on the body or html Element
overflow:hidden on body or html hides scrollbars and breaks scrolling and accessibility zoom.
Find the rule
Search your CSS for html { overflow or body { overflow. Our CSS Checker flags this pattern.
The fix
Remove overflow:hidden from html and body. If it was added to prevent horizontal scrolling, find the actual cause: run in DevTools console: document.querySelectorAll("*").forEach(el => { if(el.offsetWidth > document.documentElement.offsetWidth) console.log(el) }).
Target the right element
Apply overflow-x:hidden only to the specific element causing overflow: .hero-section { overflow-x:hidden; } rather than the entire page.
Tip: overflow:hidden on a modal backdrop is fine. The problem is only when applied to html or body — the root elements that control page scrolling.
Tags: css, overflow, mobile, accessibility, fix
How-To
How to Fix will-change Overuse in CSS
will-change consumes GPU memory. Overuse hurts performance. Apply only to elements you have profiled as needing it.
Remove unnecessary instances
Remove will-change from any element not specifically profiled. Check for global rules like * { will-change:transform } or .card { will-change:box-shadow } — these are almost always wrong.
Apply only when needed
Only use will-change on elements profiled as causing jank during animation using Chrome DevTools Performance panel.
Remove after animation
Apply just before and remove after: element.style.willChange = "transform"; // start animation element.addEventListener("transitionend", () => { element.style.willChange = "auto"; }).
Tip: transform: translateZ(0) and translate3d(0,0,0) are older hacks with the same effect. Remove these too unless specifically profiled as necessary.
Tags: will-change, css, performance, fix, gpu, animation
How-To
How to Fix Animating Layout Properties
Animating width, height, margin and padding triggers expensive layout recalculation. Animate transform and opacity instead.
Properties to avoid animating
Never animate: width, height, margin, padding, top, left, right, bottom, font-size. These trigger full layout recalculation. Also avoid: background-color, box-shadow — expensive paint operations.
Properties to animate instead
Animate only: transform (movement, scale, rotation) and opacity (fade). These run on the GPU compositor thread without triggering layout or paint.
The substitutions
Moving element: transform:translateX(-100px) instead of left:-100px. Scaling: transform:scaleX() instead of width animation. Fading: opacity:0 + visibility:hidden instead of display:none.
Tip: In Chrome DevTools Performance panel, purple paint and green layout bars indicate expensive animations. Correct animations using only transform/opacity show as minimal compositor-only frames.
Tags: animation, css, performance, fix, layout, jank, inp
How-To
How to Fix Missing :focus-visible Styles
:focus-visible shows keyboard focus rings only for keyboard navigation. Use it for accessible focus styles without affecting mouse users.
The modern solution
Use :focus-visible for all interactive elements: :focus-visible { outline: 2px solid #005fcc; outline-offset: 2px; }. Browsers only apply this for keyboard navigation, not mouse clicks.
Fallback for older browsers
:focus { outline: 2px solid #005fcc; } :focus:not(:focus-visible) { outline: none; }. Older browsers ignore :focus-visible and always show the outline.
Verify
Click a button with mouse — no focus ring should appear. Press Tab to reach the same button — focus ring should appear.
Tip: Focus ring colour needs 3:1 contrast ratio against the background (WCAG 1.4.11). Blue (#005fcc) on white passes this threshold.
Tags: focus, focus-visible, css, accessibility, fix, wcag, keyboard
Tutorial
JavaScript Security: XSS, eval(), innerHTML and localStorage
JavaScript security vulnerabilities expose users to data theft. Understanding the most common risks helps you write safer code.
Cross-Site Scripting (XSS)
XSS occurs when user-controlled data is inserted into HTML without sanitisation, allowing attackers to inject malicious JavaScript. XSS can steal cookies and session tokens, redirect users to phishing sites, and modify page content. The root cause is always trusting user input.
The eval() vulnerability
eval() executes any string as JavaScript code. If user input reaches eval(), attackers can execute arbitrary code. There is no legitimate use case for eval() in modern JavaScript.
innerHTML and XSS
Assigning user-controlled content to innerHTML executes scripts and event handlers in that content. Use textContent for plain text: element.textContent = userInput. Use DOMPurify for sanitised HTML: element.innerHTML = DOMPurify.sanitize(userHtml).
localStorage security
localStorage is accessible to any JavaScript on your page. Never store session tokens, JWTs, passwords or sensitive data here. Use httpOnly cookies for authentication tokens — these cannot be accessed by JavaScript.
CSP as defence-in-depth
A Content Security Policy provides a second layer of defence. Even if an XSS attack injects a script tag, CSP blocks it from executing if the source is not in your allowlist.
Tip: Switching from innerHTML to textContent for all user-controlled content prevents the majority of XSS vulnerabilities. Run our JS Checker to identify all innerHTML assignments with non-literal content.
Tags: javascript, security, xss, eval, innerhtml, localstorage, technical
How-To
How to Remove unsafe-eval from Your CSP
Find and replace every dynamic-code call, then drop the keyword and confirm nothing broke.
1. Find out whether you use eval at all
Search your JavaScript: grep -rn "eval(" ./public --include="*.js" and repeat for "new Function(". Most sites find zero results in their own code, which means the keyword can simply be deleted. Check bundled libraries too, since that is where it usually lives.
2. Check for the string forms of setTimeout
setTimeout("doThing()", 100) with a string first argument is eval by another name. Search for setTimeout(" and setInterval(" specifically. These are easy to miss because the call looks ordinary until you notice the quotes.
3. Replace eval used for JSON
The most common legitimate-looking use is parsing. Replace eval('(' + text + ')') with JSON.parse(text). It is faster and cannot execute code. If the text is not valid JSON, fix whatever produces it rather than reaching back for eval.
4. Replace eval used to call a function by name
Where code builds a function name as a string, define a lookup table instead: const handlers = { save: doSave, load: doLoad }; then call handlers[name] && handlers[name](). An unknown name now misses the table instead of executing arbitrary text.
5. Deal with libraries that require it
Older template engines compiled templates by generating source and calling Function on it. Check whether a current version has removed this — many have, precisely because of CSP. Otherwise look for the library's runtime-only build, where templates are compiled during your build step and shipped as plain functions.
6. Remove the keyword from the header
Delete 'unsafe-eval' from script-src in whichever layer sets your CSP — nginx add_header, Apache Header set, Plesk additional directives, or Cloudflare transform rule. Reload the server and confirm with curl -sI that the keyword is gone from the live header.
7. Exercise the site with the console open
Load more than the homepage: forms, logged-in pages, checkout, admin, anything with a date picker or chart library. CSP eval violations appear in the console naming the exact file and line, so anything you missed announces itself immediately rather than silently failing.
8. Handle WebAssembly separately if you use it
Browsers gate WebAssembly compilation under a separate keyword. If your site uses wasm and it stops working, add 'wasm-unsafe-eval' rather than reinstating 'unsafe-eval' — the narrow keyword permits wasm compilation without re-opening JavaScript eval.
Tip: This is usually the cheapest CSP improvement available. Most sites never call eval, so the keyword can be deleted with no code change at all — but confirm with the console rather than assuming, because a bundled library may be using it without your knowledge.
Tags: eval, security, howto, fix
How-To
How to Fix innerHTML XSS Vulnerabilities
Assigning user-controlled content to innerHTML without sanitisation enables XSS. Use textContent or DOMPurify.
Use textContent for plain text
If just displaying text: element.textContent = userInput. textContent treats the value as plain text — HTML tags display as literal characters, not executed.
Use DOMPurify for HTML
If formatted HTML from user input is needed: element.innerHTML = DOMPurify.sanitize(userHtml). DOMPurify strips dangerous tags and attributes while preserving safe formatting.
insertAdjacentText
Also use insertAdjacentText instead of insertAdjacentHTML for user content: element.insertAdjacentText("beforeend", userText).
Tip: Never assign to innerHTML inside a function receiving parameters from outside your code. If the value comes from an API, URL parameter or form field, use textContent or DOMPurify.
Tags: innerhtml, xss, javascript, security, fix
How-To
How to Fix Sensitive Data Stored in localStorage
localStorage is accessible to any JavaScript. Never store tokens, passwords or sensitive data there.
What should never be in localStorage
Never store: authentication tokens, JWTs, session IDs, passwords, credit card numbers, personal identifiable information (PII), API keys or secrets.
Use httpOnly cookies instead
Store auth tokens in httpOnly cookies set by your server: Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict. HttpOnly prevents JavaScript from accessing the cookie at all.
What localStorage is appropriate for
UI preferences (dark mode, font size), non-sensitive user preferences, cached non-sensitive API responses, draft content. Test: would it matter if an attacker read this? If yes, do not use localStorage.
Tip: sessionStorage has the same security characteristics as localStorage — also accessible to JavaScript. Do not use it for sensitive data either.
Tags: localstorage, security, javascript, fix, cookies, tokens
Fix
Fix: Recovering from a Google Algorithm Update
Traffic dropped after an update. Confirm it was the algorithm, identify which quality signal moved, and fix the underlying cause rather than chasing tactics.
Confirm the drop aligns with a known update
Before changing anything, plot your organic traffic against confirmed update dates in Search Console. A drop starting on the exact day of a core update is very different from a gradual slide, which usually means a technical problem, lost backlinks or a seasonal pattern. Fixing the wrong cause wastes weeks.
Rule out the boring explanations first
Check for a tracking tag removed during a deploy, a robots.txt change blocking crawlers, a noindex shipped accidentally, a site migration, or a server outage during the window. A surprising proportion of apparent algorithm hits are self-inflicted technical accidents that coincided with an update.
Identify which pages lost, not just how much
In Search Console, compare the three months before and after by page and by query. A site-wide loss points to a quality or trust assessment. Losses concentrated in one section point to a problem with that content type. Losses on one query cluster often mean a competitor simply improved.
Match the loss pattern to the update's focus
Core updates reassess overall content quality and helpfulness. Spam updates target manipulative links and content. Reviews updates target thin review content. Helpful content assessments target pages written primarily for search engines. Read what Google published about the specific update rather than guessing from forum speculation.
Fix the underlying quality problem
If the assessment is about helpfulness, the remedy is genuinely better content — first-hand experience, original data, real expertise, and answers that satisfy the query fully. Removing or consolidating thin pages often helps more than editing them, because site-wide quality signals are affected by your weakest content.
Strengthen demonstrable expertise and trust
Add real author identities with credentials and a track record, cite sources, publish contact and company details, and make it obvious who stands behind the content. These signals matter most on topics where poor information causes harm — health, finance, safety, legal.
Be patient and avoid thrashing
Core update recoveries typically arrive with a subsequent update, not immediately, so expect weeks to months. Making many simultaneous changes destroys your ability to tell what worked. Change deliberately, document what you changed and when, and wait for the next assessment.
Track the recovery against a baseline
Record impressions, clicks and average position per section before you start, so improvement is measurable rather than a feeling. If the next update brings no movement, revisit the diagnosis — an unrecovered site usually means the real cause was misidentified.
Tip: Resist the urge to make dozens of changes at once. A recovery attempt with twenty simultaneous edits teaches you nothing about cause and effect, and if traffic drops further you cannot tell which change did it.
Tags: algo-impact, algorithm impact, fix
How-To
How to Diagnose an Algorithm Update Impact
Step-by-step process for establishing whether an update hit you, which pages moved, and what changed.
1. Pull the before and after data
Search Console, Performance, set the date range to compare the four weeks before and after the suspected update. Export by page and by query. Four weeks each side smooths out weekday and seasonal noise that shorter windows exaggerate.
2. Plot the drop against confirmed update dates
Overlay your traffic against the published dates of core, spam and reviews updates. A drop beginning within a day or two of an announced rollout is a strong signal. A drop that started a fortnight earlier is something else, and treating it as an update will send you down the wrong path.
3. Eliminate technical causes
Check for a removed analytics tag, a robots.txt change, an accidental noindex, a certificate expiry, a hosting outage, a redirect gone wrong, or a CDN misconfiguration during the window. Compare crawl stats and index coverage before and after — a technical cause usually shows there rather than in rankings alone.
4. Segment the loss by section
Group affected URLs by directory or content type — blog, product, category, location pages. Site-wide losses indicate a quality or trust assessment. Section-specific losses point at that content type. This single step directs everything you do next.
5. Segment by query intent
Separate informational queries from commercial and navigational ones. Losing informational queries while holding commercial ones has a very different meaning from the reverse, and tells you whether the assessment concerned content quality or something closer to trust and conversion intent.
6. Check whether competitors actually moved
Losing position often means someone else improved rather than you declining. Look at who now occupies the positions you held and compare their pages honestly against yours — depth, freshness, first-hand experience, evidence. Sometimes the answer is simply that their page is better.
7. Audit your weakest content
Site-wide quality assessments weigh your whole index. List pages with negligible traffic, thin content, duplicated boilerplate or outdated information. These drag on everything else. Decide per page: improve substantially, consolidate into a stronger page, or remove.
8. Document a baseline and a change log
Record current metrics per section, then log every change with its date. When the next update lands you will be able to attribute movement to specific work rather than guessing — which is the difference between a repeatable process and superstition.
Tip: Compare like with like. Year-on-year comparison catches seasonality that a month-on-month view mistakes for an algorithmic hit, and many suspected penalties turn out to be an annual demand cycle.
Tags: algo-impact, algorithm impact, howto
Tutorial
Understanding Google Algorithm Updates
What core, spam and reviews updates actually assess, why recovery takes time, and how to build a site that survives them.
What an update actually is
Google changes ranking systems constantly, but named updates are broad reassessments rolled out over days or weeks. A core update does not penalise you in the sense of applying a sanction — it re-evaluates how well pages meet queries relative to everything else available. Losing position means something else now looks like a better answer.
Core updates versus targeted updates
Core updates reassess overall quality and relevance across the whole index. Targeted updates address a specific problem: spam updates go after manipulative links and generated content, reviews updates after thin affiliate reviews, and helpful content assessments after material written primarily to rank rather than to inform.
Why there is no penalty to remove
A manual action is a human sanction, appears in Search Console, and can be appealed. An algorithmic reassessment has no notification, no message and nothing to appeal. This is why sites sometimes wait for a fix that will never come — there is nothing to lift, only an assessment to change through better content.
What quality means in practice
The published guidance is consistent: content should demonstrate genuine experience, expertise, authority and trust; it should be written for people rather than search engines; it should say something a reader cannot get from the top ten results already. Original data, first-hand testing and real credentials are what these assessments look for.
Why site-wide signals matter
Quality assessments consider your site as a whole, not just the page ranking for a query. A large volume of thin, outdated or auto-generated pages weighs on everything you publish. This is why pruning weak content sometimes improves pages you never touched — you have raised the average.
Why recovery is slow
Assessments are typically refreshed when the next relevant update rolls out, not continuously. That means a genuine improvement made today may not be reflected for weeks or months. The waiting period is the hardest part, and it is where most sites make things worse by thrashing.
The common misdiagnosis
Many suspected algorithm hits are not algorithmic. Tracking tags removed in a deploy, accidental noindex directives, robots.txt changes, migrations, expired certificates and seasonal demand all produce traffic drops that look identical in a chart. Ruling these out first is not a formality — it is where the answer often lies.
Building for durability
Sites that survive updates well tend to share traits: a clear subject focus, genuine expertise behind the content, original material rather than rewritten consensus, honest commercial disclosure, sound technical health, and no dead weight of thin pages. None of it is a trick, which is precisely why it keeps working.
Tip: Read Google's own published guidance on core updates rather than forum speculation. The self-assessment questions they publish are the closest thing to a specification for what these systems are trying to measure.
Tags: algo-impact, algorithm impact, tutorial
Fix
Fix: Catching Ecommerce Regressions Before Customers Do
Set up monitoring so price, stock, schema and availability breakages are caught by you rather than reported by a customer or dropped by Google.
Decide what actually matters to monitor
Not everything is worth an alert. The changes that cost money are: price mismatches between the page and the schema, availability flipping to out of stock unintentionally, product pages returning errors or redirects, schema disappearing after a deploy, and canonical or noindex tags changing. Start with those five.
Fix price and availability mismatches first
If your visible price and your Product schema price disagree, Google may show the wrong figure in results — which produces angry customers and can trigger merchant policy problems. Ensure both are generated from the same source of truth rather than one being hard-coded in a template.
Make sure schema survives deploys
Structured data frequently vanishes when a template is changed or a plugin updated, and nobody notices until rich results disappear weeks later. Add a build-time check that fails if a product page renders without valid Product schema, so a broken deploy is caught before it ships.
Catch discontinued products properly
A removed product should not simply 404 silently. Decide the policy: redirect to the closest alternative or the parent category if the product is genuinely gone, or keep the page live with clear out-of-stock status if it will return. Silent 404s waste the ranking and links that page earned.
Watch for accidental noindex and canonical changes
A canonical pointing at the wrong variant, or a noindex applied site-wide by a misconfigured plugin, can remove your whole catalogue from search. These are single-line changes with catastrophic scope, which makes them exactly what automated monitoring is for.
Monitor competitor pricing separately from your own health
Competitor price tracking is a commercial exercise and should not be mixed into technical alerting, or the noise will bury the alerts that mean something is broken. Keep the two streams distinct with different thresholds and different recipients.
Set alert thresholds that people will not ignore
An alert that fires daily gets filtered into a folder within a fortnight. Alert on material changes only — price moving more than a set percentage, availability flipping, schema disappearing, status codes changing — and review the thresholds after the first month.
Verify the monitoring itself works
Deliberately break something in a staging environment — remove the schema, change a price — and confirm the alert fires and reaches a human. Unverified monitoring is worse than none, because it produces false confidence that someone would be told.
Tip: The most expensive ecommerce failures are silent: schema removed in a deploy, a canonical pointing at the wrong variant, a category quietly noindexed. None of them break the page visibly, so nothing surfaces until traffic has already gone.
Tags: ecommerce-watch, ecommerce watch, fix
How-To
How to Set Up Ecommerce Change Monitoring
Choose the pages, the fields and the thresholds, then prove the alerting actually reaches someone.
1. Pick the pages that carry the revenue
You cannot watch everything usefully. Start with your top revenue products, your main category pages, and anything running a promotion. A short high-value list checked properly beats a full catalogue producing noise nobody reads.
2. Define the fields to track per page
For a product page: visible price, schema price, availability status, HTTP status code, canonical URL, robots directives, presence of valid Product schema, and title. For a category page: status code, canonical, robots directives, and product count.
3. Establish a clean baseline
Capture the current state of every tracked field while you know the site is healthy. Every future alert compares against this. Taking the baseline during an active promotion or a partial deploy bakes an abnormal state in as normal.
4. Set thresholds that mean something
Price: alert on any change above a set percentage, so rounding and currency updates do not fire. Availability: alert on any transition. Status codes: alert on anything that is not 200. Schema: alert on disappearance or validation failure, not on cosmetic reordering.
5. Set a sensible check frequency
Hourly for pages in an active promotion, daily for the main catalogue, weekly for the long tail. Checking everything hourly generates noise and load without improving how quickly anyone actually responds.
6. Route alerts where they will be seen
Email alone gets filtered. Send them to a team chat channel with a clear subject naming the page and the field that changed. Include the previous and new value in the alert body so triage does not require opening the site.
7. Separate commercial from technical alerts
Competitor price movements are business intelligence for whoever sets pricing. Schema disappearing is an incident for whoever owns the site. Different audiences, different urgency, different channels — mixing them guarantees both get ignored.
8. Test the alerts before relying on them
In staging, remove the schema from a product page and change a price. Confirm both alerts fire, reach the channel, and contain enough detail to act on. Repeat this test after any change to the monitoring setup.
Tip: Review your alert history monthly and delete the rules that never fired usefully. Monitoring decays into noise, and noise gets muted — the rules you keep should each have earned their place.
Tags: ecommerce-watch, ecommerce watch, howto
Tutorial
Understanding Ecommerce Monitoring
Why ecommerce sites break silently, which changes actually cost money, and what continuous monitoring is genuinely for.
Why ecommerce breaks differently
A brochure site changes when someone edits it. An ecommerce site changes constantly without anyone touching the template — stock levels move, prices update from a feed, products are discontinued, variants come and go. Most of these changes are invisible on the page but very visible to search engines.
The silent failure problem
The failures that cost the most money do not look like failures. Schema removed during a plugin update. A canonical pointing at the wrong variant. A category noindexed by a misconfigured setting. The page still loads and looks perfect, so nothing surfaces until rankings and traffic have already gone.
Why price and schema must agree
If your Product schema declares a different price from the visible page, search engines may display the wrong figure. Customers arrive expecting one price and find another, which produces complaints, refunds and potential merchant policy consequences. Both values must come from a single source of truth.
Availability signals matter more than they look
Marking items out of stock incorrectly can remove them from shopping surfaces and depress rankings. Equally, leaving discontinued products marked as available produces failed orders and disappointed customers. Availability is a signal to both search engines and buyers, and accuracy matters to both.
The discontinued product decision
When a product goes away permanently, a silent 404 discards whatever rankings and links it accumulated. Redirecting to the closest alternative or the parent category preserves that value and gives the visitor somewhere useful to land. If it is returning, keeping the page with clear out-of-stock status is better still.
Deploys as the main risk window
Most silent breakages happen during a deploy — a template change, a plugin update, a theme switch. This is why build-time checks matter alongside monitoring: catching a missing schema before it ships is far cheaper than discovering it three weeks later from a traffic chart.
Competitor tracking is a different activity
Watching competitor prices is commercial research. Watching your own pages for breakage is operational monitoring. They use similar tooling and are frequently conflated, but they have different audiences, different urgency and different consequences — keeping them separate keeps both useful.
What good monitoring looks like
A short, high-value page list. A clean baseline. Thresholds tuned so alerts are rare and meaningful. Alerts routed to a channel someone actually watches, containing enough detail to triage without opening the site. And periodic testing to prove the whole chain still works.
Tip: The measure of good monitoring is not how much it watches but how often an alert leads to action. If nobody has acted on an alert in three months, either the site is unusually stable or the alerts are being ignored.
Tags: ecommerce-watch, ecommerce watch, tutorial
Fix
Fix: Poor Local Rankings Across Your Service Area
Your Geo-Grid shows strong rankings at your pin and weak ones a few miles out. Fix the proximity, relevance and prominence signals that drive local results.
Read the grid before changing anything
A geo-grid ranks your business from many points across an area. Strong at the centre and fading outward is normal and expected — proximity is a real ranking factor. What matters is how fast it fades, and whether competitors hold positions across a wider radius than you do.
Fix the fundamentals on your Business Profile
Category is the single biggest lever: your primary category must match what you actually do and what people search for. Add relevant secondary categories, complete every field, add services with descriptions, and keep hours accurate including holidays. An incomplete profile underperforms everywhere on the grid.
Make your name, address and phone identical everywhere
Inconsistent details across directories, your site and your profile weaken the confidence signal. Pick one exact format — including abbreviations and suite numbers — and make every citation match it character for character. Fix the largest directories first, since they carry the most weight.
Build genuine location relevance on your site
Create substantive pages for the areas you serve — not doorway pages with the town name swapped, which are treated as spam. Each should carry real content: local projects, area-specific information, directions, testimonials from that area. Thin location pages actively harm you.
Earn reviews steadily rather than in bursts
Review volume, recency and rating all feed prominence. A steady flow from genuinely local customers is worth more than a batch. Ask consistently, respond to all of them, and never buy them — review manipulation is detected and the penalties reach the whole profile.
Strengthen local prominence off-site
Local links and mentions from community organisations, suppliers, local press and trade bodies build the prominence signal that extends your radius. This is what allows a business to rank several miles from its pin while a competitor with an identical profile does not.
Add LocalBusiness schema with accurate geo data
Mark up name, address, phone, opening hours, geo coordinates and service area. It will not override proximity, but it removes ambiguity about what and where you are, and it feeds the same details to other surfaces consistently.
Re-scan the grid and compare against the baseline
Local changes take weeks to surface. Re-run the grid on the same points at the same zoom and compare against your original. Look for the fade radius extending, not just the centre point improving — a wider radius is what actually produces more enquiries.
Tip: You cannot beat proximity. A business three miles away will usually outrank you for someone standing next to it, and no amount of optimisation changes that. The realistic goal is extending your radius, not winning every point on the grid.
Tags: geo-grid, geo-grid, fix
How-To
How to Run and Read a Geo-Grid Scan
Set the grid up so results are comparable over time, then interpret the pattern rather than the average.
1. Choose the keyword that matters commercially
Scan the term customers actually use to find a business like yours — usually the service plus intent, such as emergency plumber. Avoid your business name, which will rank first everywhere and tell you nothing about discoverability.
2. Centre the grid on your real location
The centre point must be your actual business address, not your town centre. Everything on the grid is measured relative to that pin, and a misplaced centre makes the whole pattern misleading.
3. Set the radius to match your service area
A local shop might use two miles; a regional contractor twenty. Too small and everything looks green while you learn nothing. Too large and you measure areas you could never serve profitably. Match it to where you would genuinely take a job.
4. Pick a grid density you can repeat
A seven by seven grid gives forty-nine data points, which is enough resolution to see a fade pattern without an unreadable wall of numbers. Whatever you choose, use it every time — changing density between scans makes comparison meaningless.
5. Record the exact parameters
Note the keyword, centre coordinates, radius, grid size, date and time. Local results vary by time of day and device. Without recorded parameters your next scan is not a comparison, it is a different measurement that happens to look similar.
6. Read the pattern, not the average
An average position across the grid hides everything useful. What matters is shape: how far out you hold position, whether there is a direction you are weak in, and whether the fade is gradual or falls off a cliff. A sharp edge often marks a competitor's territory.
7. Scan your main competitors on the same grid
Run the identical grid for the businesses beating you. Comparing their fade radius against yours shows whether they hold a wider area from a comparable location — which points at prominence signals rather than proximity.
8. Re-scan on a fixed schedule
Monthly, same parameters, same time of day. Local rankings move constantly, so a single scan is a snapshot with no trend. The value comes from watching the radius change after you have done work, not from the first picture.
Tip: Scan at the same time of day each month. Local results shift with the working day, and comparing a Tuesday morning scan against a Saturday evening one will show movement that has nothing to do with anything you did.
Tags: geo-grid, geo-grid, howto
Tutorial
Understanding Geo-Grid Local Rank Tracking
Why a single local ranking number is misleading, what drives results at each point on a grid, and how to interpret the pattern.
Why one ranking number is meaningless locally
Local results depend heavily on where the searcher is standing. A business can be first for someone outside the door and invisible two miles away. Reporting a single position for a local term hides that entirely, which is what geo-grid scanning exists to fix.
What a geo-grid actually measures
It searches the same term from many simulated points across an area and records your position at each. The output is a map of visibility rather than a number, showing exactly where you appear and where you do not.
The three signals behind every point
Local ranking rests on relevance (how well your business matches the query), distance (how close you are to the searcher), and prominence (how well known and well regarded you are). Each grid point weighs all three, with distance shifting most across the grid.
Why proximity cannot be defeated
Distance is a genuine and heavily weighted factor. A closer competitor will usually win for searchers near them, regardless of how good your profile is. Understanding this prevents wasted effort chasing points you were never going to hold.
What prominence buys you
Prominence is what extends your radius. Two businesses with identical profiles and identical distances will diverge based on reviews, links, citations and general renown. This is the lever that widens the green area, and it is the slowest to move.
Reading the shape of the fade
A gradual, even fade in all directions is healthy and normal. A sharp cliff on one side usually means a strong competitor holds that area. Weakness in one direction with strength in another can point at a service-area or citation gap for that locality.
Why the pattern beats the average
Averaging grid positions produces a single number that moves for reasons you cannot interpret. The useful questions are directional: how far out do I hold the top three, has that radius grown since last month, and where exactly does it break down.
Using it as a measurement tool, not a target
The grid tells you where you are visible; it does not tell you what to do. Its value is establishing a baseline, then measuring whether real work — profile completeness, reviews, local links, area content — actually widened your radius over months.
Tip: Track the radius at which you hold the top three, as a single number, month over month. It is the most honest summary of local visibility and it correlates with enquiries far better than an average position does.
Tags: geo-grid, geo-grid, tutorial
Fix
Fix: Losing Ground to Competitors You Are Not Watching
Set up competitor monitoring that surfaces meaningful moves — content, technical and ranking — rather than a stream of noise nobody reads.
Choose real competitors, not aspirational ones
Track the sites that actually outrank you for the terms you care about, which is often not the brands you consider rivals commercially. Pull the top results for your ten most valuable queries and count who appears repeatedly. That list is your competitive set.
Keep the list short enough to act on
Three to five competitors monitored properly beats twenty producing alerts nobody reads. You are looking for patterns and meaningful moves, and that requires attention per competitor rather than breadth.
Track the changes that actually predict movement
New pages published, substantial rewrites of existing pages, title and meta changes on pages that rank against you, new schema types, and site structure changes. These precede ranking movement. Cosmetic redesigns and blog cadence usually do not.
Watch ranking changes on your shared terms
Monitor position for the queries where you overlap, not their whole visibility footprint. A competitor gaining three places on a term that drives your revenue matters; their new ranking for something unrelated to you does not.
Compare technical health honestly
Run the same audits against their site as your own — speed, Core Web Vitals, mobile usability, schema coverage, crawlability. Where they are meaningfully better, that is a gap with a known remedy. Where you are better, stop worrying about it.
Look for content gaps rather than copying pages
The useful output is topics they cover well that you do not cover at all. Copying their page produces a worse version of something that already ranks. Covering a gap they have left, with genuine depth, is where the opportunity actually is.
Set alert thresholds so alerts stay meaningful
Alert on new pages in your topic areas, on ranking changes beyond a set number of positions, and on structural changes. Do not alert on every minor edit, or the channel will be muted within a fortnight and you will lose the signal entirely.
Review on a schedule and write down conclusions
Monthly, look across the alerts for patterns rather than reacting individually. Write down what you concluded and what you will do. Competitor monitoring without a review cadence becomes a feed people skim and forget.
Tip: The point is not to copy competitors. It is to notice what is working in your market early enough to respond deliberately, and to spot gaps nobody is covering — which is where the cheapest wins usually are.
Tags: competitor-watch, competitor watch, fix
How-To
How to Set Up Competitor Monitoring
Identify who you actually compete with, pick the signals worth watching, and build a review habit that produces decisions.
1. Build the competitor list from search results
Take your ten highest-value queries and record who ranks in the top five for each. Count appearances. The sites appearing repeatedly are your search competitors, which is frequently not the same list as your commercial rivals.
2. Narrow to three to five
Rank the list by overlap with your important terms and by how close they are to your size and market. Monitoring twenty sites produces a feed; monitoring four produces understanding. You can rotate the list quarterly as the market changes.
3. Define which pages to watch per competitor
Their top landing pages for your shared queries, their main category or service pages, and their blog or resources index so new publications surface. Watching every URL they have generates noise without insight.
4. Choose the fields to track
Title, meta description, H1, word count, schema types present, canonical, and publication or modified dates. These are the fields that change when someone is actively working on a page, which is the signal you want.
5. Add ranking tracking on shared terms only
Track position for the queries where you overlap. This keeps the data focused on competitive reality rather than producing a general visibility chart that is interesting but not actionable.
6. Set thresholds that filter noise
New page in a tracked section: alert. Substantial content change on a page that ranks against you: alert. Ranking movement beyond three positions on a shared term: alert. Minor copy edits and image swaps: ignore.
7. Baseline their technical health
Run your speed, Core Web Vitals, schema and crawlability checks against their key pages once, and record the results. This gives you a comparison point and highlights gaps that are within your control to close.
8. Book a monthly review
Half an hour, looking across the month's alerts for patterns. Which topics are they investing in, what have they stopped doing, where are they gaining. Write down two or three decisions. Without this step the monitoring produces data and no action.
Tip: Track when competitors stop doing something as well as when they start. An abandoned content programme or a removed section is a signal about what did not work for them, which is information you would otherwise pay to learn.
Tags: competitor-watch, competitor watch, howto
Tutorial
Understanding Competitive SEO Analysis
Who your search competitors actually are, which of their signals predict movement, and how to use the information without simply copying.
Search competitors are not commercial competitors
The sites outranking you for your money terms are frequently publishers, marketplaces, directories and aggregators rather than businesses selling what you sell. Building your competitive set from search results rather than from your commercial assumptions is the first correction most people need to make.
Why monitoring beats a one-off audit
A snapshot tells you where everyone stands today. Monitoring tells you who is investing, in what, and how the market is moving. Direction matters more than position, because it tells you what is about to happen rather than what already has.
The signals that precede ranking movement
New content in a topic area, substantial rewrites of existing pages, title and meta changes, new schema types, and structural reorganisation. These are deliberate acts that usually show up in rankings weeks later, which is the window monitoring buys you.
Content gaps are worth more than content copies
If a competitor ranks well for a topic, publishing a similar page means competing with an established result on its own ground. Finding a topic in your area that nobody covers properly is a far cheaper win, and monitoring several competitors together is how those gaps become visible.
Technical comparison sets a realistic bar
Comparing speed, Core Web Vitals, schema coverage and crawlability against the sites beating you turns vague improvement into a specific target. It also stops you over-investing in areas where you are already ahead, which is a common and expensive mistake.
Noise is the failure mode
The predictable way competitor monitoring fails is generating so many alerts that people stop reading them. Every threshold should be set on the question: would I change a decision because of this. If not, do not alert on it.
Interpretation needs a cadence
Individual alerts rarely mean much. Patterns across a month do — a sustained content push into one topic, a technical overhaul, a section quietly retired. This is why a scheduled review matters more than the alerting itself.
Knowing when to ignore them
Not every competitor move deserves a response. A larger competitor can afford strategies that would be wasteful for you, and chasing everything they do surrenders your own plan. The value is in informed choices, including the choice to do nothing.
Tip: Re-derive your competitor list every quarter from current search results. Search competitors change as the market moves, and monitoring last year's list carefully while a new entrant takes your rankings is a common and avoidable failure.
Tags: competitor-watch, competitor watch, tutorial
Fix
Fix: Making Your Site Usable by AI Agents
AI agents cannot complete tasks on your site. Fix the rendering, structure and blocking issues that stop them reading and acting.
Serve meaningful content without JavaScript
Many agents fetch HTML and do not execute JavaScript, or execute it inconsistently. If your product details, prices, or article text only exist after a client-side render, an agent sees an empty shell. Server-render or pre-render the content that matters.
Check what you are actually blocking in robots.txt
Agent crawlers use their own user-agent strings. Blocking them wholesale removes you from AI answers and agent-driven purchases. Decide deliberately which agents to allow rather than inheriting a blanket rule someone added years ago.
Make key facts machine-readable with schema
Price, availability, opening hours, address, author, publication date. An agent reading structured data gets an unambiguous answer; an agent parsing prose has to infer, and inference produces wrong answers about your business.
Use real semantic HTML for structure
Headings in order, lists as lists, tables as tables, buttons as buttons, links as links. Agents rely on structure to understand a page. A layout built entirely from divs with styling conveys nothing about what anything is.
Give forms and controls accessible names
An agent trying to complete a task needs to identify fields. Every input needs a label, buttons need descriptive text rather than an icon alone, and required fields need marking. Anything that helps a screen reader helps an agent, for the same reasons.
Do not gate essential content behind interaction
Content that only appears after a click, a hover, an infinite scroll or a modal dismissal is frequently invisible to agents. If it matters for understanding what you offer, it should be present in the initial markup.
Keep response times and status codes clean
Agents time out faster than people and generally do not retry. Slow responses, soft 404s that return 200, and redirect chains all produce failures the agent reports as your site not working, with no diagnostic to you.
Test with JavaScript disabled and re-check
Turn off JavaScript in DevTools and load your key pages. What remains is approximately what many agents see. If the page is blank or the price is missing, that is your fix list, in priority order.
Tip: Agent compatibility overlaps almost entirely with accessibility and clean semantic markup. Work done for screen readers pays off directly here, which makes it some of the best-value technical work available.
Tags: agent-compat, ai agent compatibility, fix
How-To
How to Test Your Site for AI Agent Compatibility
Fetch your pages the way an agent does, check what survives, and fix what is missing.
1. Fetch a page with no JavaScript at all
curl -s https://yoursite.com/key-page | wc -c and then read the output. This is the raw HTML an agent receives before any rendering. If your main content is absent, everything else on this list is secondary to fixing that.
2. Compare rendered against raw
Load the page in a browser, then view source rather than inspecting the DOM. View source shows what was delivered; the inspector shows what JavaScript built. A large gap between them is the compatibility problem in a single view.
3. Check your robots.txt against agent user-agents
curl -s https://yoursite.com/robots.txt and read every Disallow and every named user-agent. Identify which AI and agent crawlers you are blocking, and decide whether that is a deliberate policy or an inherited default nobody reviewed.
4. Validate your structured data
Run your key pages through a schema validator and confirm the facts an agent would need — price, availability, address, hours, author — are present and correct. Missing or invalid schema forces inference, and inference about your prices is not something you want.
5. Audit the semantic structure
Check headings run in order without skipping levels, that lists are list elements, that buttons are buttons and links are links. A quick way to see this is a browser accessibility inspector, which shows the structure an assistive technology — or an agent — perceives.
6. Test a task, not just a page
Pick something a customer would want done — find a price, check availability, locate contact details, start a booking. Follow that path yourself using only the raw HTML. Wherever you cannot proceed without executing JavaScript or clicking something, an agent stops too.
7. Check status codes and timing
curl -sI on key pages. Confirm 200 for real pages, genuine 404 for missing ones rather than a soft 404 returning 200, and no long redirect chains. Measure response time — agents abandon far sooner than humans and rarely retry.
8. Re-test after fixes with JavaScript disabled
Disable JavaScript in DevTools settings and walk your key journeys again. This is the closest quick approximation to the agent experience, and it will show immediately whether your fixes actually landed.
Tip: View source rather than the inspector when checking what agents receive. The inspector shows the DOM after JavaScript has run, which is exactly the difference you are trying to measure.
Tags: agent-compat, ai agent compatibility, howto
Tutorial
Understanding AI Agent Compatibility
How AI agents read websites, why they fail differently from search crawlers, and what makes a site usable by them.
What an AI agent is doing on your site
Unlike a search crawler indexing for later, an agent is usually completing a task now — answering a question, comparing prices, checking availability, sometimes making a booking or purchase on someone's behalf. It needs to extract specific facts and often to act, both within one visit.
Why JavaScript is the main obstacle
Search engines invested years in rendering JavaScript. Many agents fetch HTML and parse it, without a full browser engine or with limited patience for one. A site whose content exists only after client-side rendering is effectively blank to them, however good it looks to a person.
The inference problem
Where facts are not marked up, an agent must infer them from prose — and it can infer wrongly. It may report a struck-through old price, an out-of-date opening time, or a shipping figure from an unrelated sentence. Structured data eliminates the guessing.
Why semantic HTML matters more than it used to
Agents use document structure to understand meaning. Headings mark sections, lists mark enumerations, tables mark relationships, buttons mark actions. A page built from styled divs is visually identical and structurally meaningless, which is fine for humans and useless for machines.
Robots.txt as a business decision
AI crawlers respect robots.txt, so blocking them removes you from AI answers and from agent-driven transactions. Some publishers block deliberately to protect content; a retailer usually should not. The important thing is that it is a decision rather than an inherited default nobody has read.
Interaction-gated content is invisible
Content behind a click, a hover, a tab, an infinite scroll or a dismissed modal is frequently never seen. If a specification, a price or a delivery term only appears after interaction, agents will report it as missing — and answer the user accordingly.
Timing and error handling
Agents time out faster than people and rarely retry. Slow pages simply fail. Soft 404s returning 200 are worse than honest errors, because the agent believes it has real content and reports nonsense. Clean status codes matter more here than for human traffic.
The overlap with accessibility
Almost everything that makes a site agent-friendly also makes it accessible: semantic structure, labelled controls, content available without interaction, meaningful text alternatives. The two agendas point the same way, which makes this unusually good value work.
Tip: Load your site with JavaScript disabled once a quarter. It takes two minutes, approximates what a large share of agents see, and reveals rendering regressions that no visual check would ever catch.
Tags: agent-compat, ai agent compatibility, tutorial
Fix
Fix: Preparing Your Site for Agent-Driven Traffic
Publish the files and signals agents look for, state your terms clearly, and make your key facts retrievable.
Publish an llms.txt at your site root
A markdown file at /llms.txt summarising what your site offers, with links to your most important pages and a short description of each. It gives agents a curated entry point rather than leaving them to infer your structure from navigation.
Serve it with the right content type
location = /llms.txt { default_type "text/markdown; charset=utf-8"; } in nginx, or the ForceType equivalent in Apache. Served as the wrong type, some clients will not parse it, and the file quietly achieves nothing.
Make a deliberate decision in robots.txt
Name the AI and agent user-agents explicitly and decide allow or disallow for each. A blanket block excludes you from AI answers; a blanket allow may not suit a publisher. Either can be right — what matters is that it is chosen rather than inherited.
State your usage terms where they can be found
If you have conditions on how your content may be used, put them somewhere discoverable and machine-readable rather than buried in terms of service prose. Ambiguity helps nobody, and clarity is increasingly what distinguishes cooperative sites.
Ensure your core facts are in structured data
Organisation details, contact information, opening hours, products with prices and availability, articles with authors and dates. This is the difference between an agent answering confidently about you and answering approximately.
Keep the critical content server-rendered
Whatever an agent most needs — prices, availability, contact details, key specifications — should be present in the initial HTML. Client-side rendering for enhancement is fine; client-side rendering for essential facts is not.
Provide clean, stable URLs
Agents cite and revisit URLs. Session identifiers, tracking parameters and unstable paths break that. Canonical, parameter-free URLs for your important pages make you reliably referenceable rather than a moving target.
Verify the files are actually reachable
curl -s https://yoursite.com/llms.txt and curl -sI to check the content type, then the same for robots.txt. Confirm neither is blocked by a rule elsewhere in your configuration and that both return 200 with the expected body.
Tip: Agent readiness is largely about removing ambiguity: what you offer, on what terms, and where the facts are. Sites that are explicit get represented accurately; sites that leave it to inference get represented approximately.
Tags: agent-readiness, agent readiness, fix
How-To
How to Make Your Site Agent-Ready
Write and publish llms.txt, configure crawler access deliberately, and verify agents can retrieve what they need.
1. Inventory the pages that matter
Before writing anything, list the twenty or so pages an agent would need to answer real questions about you — main services or categories, pricing, contact, about, key guides. This list becomes the body of your llms.txt.
2. Write llms.txt in plain markdown
Start with an H1 naming your site and a short paragraph on what it is. Then group links under headings by topic, each link followed by a brief description of what that page contains. Keep it factual and concise — it is a map, not marketing copy.
3. Place it at the root and set the content type
It must be at https://yoursite.com/llms.txt, not in a subdirectory. Configure the server to serve it as text/markdown or text/plain with a UTF-8 charset, then confirm with curl -sI that the header is right.
4. Consider a fuller llms-full.txt
Some sites also publish a larger file containing the actual content of key pages in markdown, so an agent can read the substance without fetching each URL. Generate it in your build from the same source as the pages, so it never drifts out of date.
5. Review robots.txt line by line
curl -s https://yoursite.com/robots.txt and read every rule. Identify each named user-agent and what it is allowed. Decide explicitly for AI and agent crawlers, and add a comment noting the decision and date so the next person knows it was deliberate.
6. Point to your files from robots.txt
Add comment lines noting the llms.txt and llms-full.txt locations alongside your sitemap declaration. It costs nothing and makes discovery easier for anything that reads robots.txt first, which most crawlers do.
7. Validate the structured data on key pages
Run your main pages through a schema validator. Confirm Organization, LocalBusiness, Product or Article types are present as appropriate, with the specific properties an agent would need — price, availability, hours, address, author, dates.
8. Automate regeneration and verify
Add llms.txt generation to your build so it updates when pages change; a stale map is worse than none because it sends agents to pages that have moved. Then verify the live file with curl and check the links in it all resolve.
Tip: Generate llms.txt from the same source that builds your pages. Hand-maintained files drift within a couple of months, and a map pointing at dead URLs actively damages the impression an agent forms of your site.
Tags: agent-readiness, agent readiness, howto
Tutorial
Understanding Agent Readiness and llms.txt
What llms.txt is for, how it differs from a sitemap, and what else determines whether agents can work with your site.
The problem llms.txt addresses
An agent arriving at your site has to work out what you offer and which pages matter by crawling navigation and inferring structure. That is slow and error-prone. llms.txt is a curated summary at a known location — a map you write yourself rather than one inferred from your menus.
How it differs from a sitemap
A sitemap is a machine-readable list of every URL, aimed at crawlers deciding what to index. llms.txt is a short human-readable markdown document highlighting what matters and explaining what each thing is. One is exhaustive, the other is editorial, and they serve different consumers.
What goes in it
A heading naming the site, a paragraph explaining what it does, then grouped links with a sentence describing each. Twenty well-chosen links with good descriptions beat two hundred bare URLs, because the point is orientation rather than coverage.
The fuller variant
Some sites also publish llms-full.txt containing the actual content of key pages converted to markdown. This lets an agent read substance in one fetch. It is more work to maintain and only worth it if generated automatically from the same source as the pages.
It is a convention, not a standard
llms.txt is an emerging convention rather than a ratified specification, and support varies. It costs very little to publish, which is why adoption is growing, but it should sit alongside good structured data and clean HTML rather than replacing them.
Robots.txt is the bigger lever
Whatever you publish, robots.txt determines whether agents may fetch it at all. Many sites block AI crawlers by inheritance rather than decision, then wonder why they are absent from AI answers. Reviewing those rules deliberately matters more than any file you add.
Readiness is more than files
Publishing llms.txt on a site whose prices only appear after JavaScript runs achieves little. Agent readiness rests on server-rendered essential content, valid structured data, semantic HTML, clean status codes and stable URLs. The file is the signpost, not the substance.
Why this is worth doing now
Agent-driven traffic is growing, and the sites that are easy to read and act upon get represented accurately in AI answers while others get approximated or omitted. The work involved is mostly things that also improve accessibility and search performance, so the downside is minimal.
Tip: Check whether you are blocking AI crawlers before investing in agent-readiness files. Publishing a careful llms.txt while robots.txt forbids the agents from fetching anything is a common and entirely self-defeating combination.
Tags: agent-readiness, agent readiness, tutorial
Fix
Fix: Crawl Waste and Duplication in an Ecommerce Catalogue
Faceted navigation, variants and pagination generate enormous numbers of near-identical URLs. Control what gets crawled and indexed.
Measure the scale of the problem first
Compare your real product count against the URLs discovered by a crawl and against indexed pages in Search Console. A catalogue of 500 products producing 80,000 crawlable URLs tells you facets are multiplying, and the ratio tells you how urgent this is.
Identify which parameters create duplicates
Sort order, view mode, items per page, session identifiers and tracking parameters usually produce identical content at different URLs. Filters that genuinely narrow the product set — a colour, a size — sometimes deserve indexing; ordering parameters never do.
Canonical every variant to one URL
Sorted, paginated and filtered views should carry a canonical pointing at the clean category URL, unless the filtered view is a genuine landing page you want ranking. This consolidates signals instead of splitting them across dozens of near-identical pages.
Block the pure noise in robots.txt
Disallow patterns that can never be useful: Disallow: /*?sort=, Disallow: /*&sort=, Disallow: /*?sessionid=, Disallow: /*?utm_. Blocking stops the crawl waste at source rather than relying on canonicals the crawler must first fetch to see.
Handle product variants deliberately
Decide whether each colour or size gets its own indexable URL or whether one product page covers all variants. Both work; what fails is having twenty near-identical variant pages competing with each other and splitting your ranking signals.
Fix pagination properly
Each paginated page should be self-canonical, not canonicalised to page one — that hides deeper products from crawling entirely. Ensure every product is reachable within a reasonable click depth, and consider a view-all page where the catalogue size permits.
Deal with out-of-stock and discontinued products
Set a policy: temporarily unavailable products keep their page with clear availability status; permanently discontinued ones redirect to the closest alternative or parent category. Silent 404s throw away the rankings and links those pages earned.
Re-crawl and check the ratio improved
After the changes, crawl again and compare discovered URLs against your product count. The number should fall substantially. Then watch Search Console crawl stats — budget redirected from junk URLs to real products is the outcome you are looking for.
Tip: Crawl budget is finite and real on large catalogues. Every request spent on ?sort=price-desc is a request not spent discovering a new product, which is why this shows up as slow indexing of new stock.
Tags: ecommerce-crawl, ecommerce crawl, fix
How-To
How to Crawl a Large Ecommerce Site
Configure the crawl so it completes, finds the real problems, and does not knock the site over.
1. Set a crawl rate the site can absorb
Start conservatively — a few requests per second — and watch server response times as it runs. A crawl that degrades the site for real customers is worse than no crawl. Increase only if response times stay flat.
2. Crawl in the right mode for your stack
If your product data renders client-side, a plain HTML crawl will report empty pages. Enable JavaScript rendering for a representative sample first to establish whether it is needed, since rendering crawls are far slower and more resource-intensive.
3. Include parameters on the first pass
Crawl with URL parameters included initially, even though it produces a huge URL set. That first crawl is how you discover the scale of facet multiplication. You can exclude them on later crawls once you know the pattern.
4. Segment results by URL pattern
Group discovered URLs by directory and by parameter — products, categories, filtered views, paginated views, search results. The distribution immediately shows where the waste is, which a flat list of 80,000 URLs never will.
5. Check status codes and redirect chains
Look for 404s on linked products, soft 404s returning 200 with a not-found message, redirect chains longer than one hop, and redirect loops. On large catalogues these accumulate quietly through years of product churn.
6. Audit canonical and robots directives at scale
Export canonical and robots values for every URL. Look for pages canonicalising to the wrong target, paginated pages canonicalised to page one, and any unintended noindex — a single misapplied rule can remove a whole category.
7. Verify structured data across the catalogue
Check every product page renders valid Product schema with price and availability. Sample-checking a handful hides the pattern where one template — perhaps a sale variant — omits it entirely across thousands of pages.
8. Compare against your actual product count
The final number to look at is discovered indexable URLs against real products. A ratio far above one tells you how much of your crawl budget is being spent on duplicates, and gives you a measurable target for the fixes.
Tip: Run the crawl outside peak trading hours and watch server response times while it runs. A crawler is indistinguishable from a traffic spike as far as your infrastructure is concerned.
Tags: ecommerce-crawl, ecommerce crawl, howto
Tutorial
Understanding Ecommerce Crawling and Crawl Budget
Why catalogues generate millions of URLs, how crawl budget is spent, and which controls actually work.
Why ecommerce sites explode in URL count
Combine ten filters with three sort orders and pagination and a single category can produce thousands of distinct URLs, each with near-identical content. Nobody designs this deliberately — it emerges from faceted navigation working exactly as intended for humans.
What crawl budget actually means
Search engines allocate a finite amount of crawling to each site, based on capacity and demand. On a small site this never binds. On a large catalogue it very much does, and every request spent on a sorted duplicate is one not spent finding new stock.
How you notice the problem
New products taking weeks to appear in search. Search Console showing far more discovered than indexed pages. Crawl stats dominated by parameter URLs. None of these look like a crawl budget problem at first glance, which is why it goes undiagnosed.
Canonicals consolidate but do not prevent
A canonical tells search engines which version to index and consolidates signals. It does not stop the duplicate being crawled — the crawler must fetch the page to see the tag. So canonicals fix indexing and duplication, but not crawl waste.
Robots.txt prevents crawling but not indexing
Blocking a pattern stops the fetch, which saves budget. But a blocked URL with external links can still appear in results without a description, because the crawler cannot see the noindex it would otherwise obey. The two tools solve different halves of the problem.
Which filters deserve to be indexed
Some faceted pages are genuine landing pages with real search demand — a brand within a category, for instance. Those should be indexable and canonical to themselves. Ordering, view mode and pagination parameters never have independent demand and should always consolidate.
The variant question
Whether each colour or size gets its own URL is a real trade-off. Separate URLs can capture specific demand but split signals and multiply pages. One page covering all variants concentrates authority but may miss variant-specific searches. Either is defensible; drifting between them is not.
Product lifecycle is a crawl issue too
Catalogues churn. Products are added, discontinued, renamed and reinstated constantly. Without a policy for discontinued products, a large site accumulates thousands of 404s and orphaned URLs over the years, each still being crawled and each having discarded whatever value it once held.
Tip: Track discovered URLs against real product count as a single ratio over time. It is the clearest measure of whether your crawl controls are working, and it moves visibly when facets are brought under control.
Tags: ecommerce-crawl, ecommerce crawl, tutorial
Fix
Fix: Structured Data That Silently Broke
Schema disappears or breaks during deploys and nobody notices until rich results vanish. Find the drift and prevent recurrence.
Establish what schema should be on each template
Before you can detect drift you need the intended state: Product schema on product pages, Article on posts, LocalBusiness on contact, BreadcrumbList throughout. Write it down per template — this is the specification everything is checked against.
Find pages where it is missing entirely
Crawl and record which pages have JSON-LD and which do not. Missing schema usually clusters by template rather than scattering randomly, which points you straight at the template or plugin responsible.
Fix invalid JSON before anything else
A JSON-LD block with a syntax error is worth nothing — search engines discard it silently. The usual causes are unescaped double quotes in a title, a trailing comma, a newline inside a string value, or a literal closing script tag inside a description.
Escape closing script tags in content
If a description or headline contains the characters of a closing script tag, it terminates the JSON-LD block early and breaks the page. Serialise properly and replace those sequences with an escaped form. This one bites hard and is easy to miss.
Correct required and recommended properties
Each type has required properties for rich result eligibility and recommended ones that improve it. Missing required properties disqualify the page entirely; missing recommended ones quietly reduce what you get. Check both against current documentation.
Make sure schema matches visible content
Structured data must reflect what is actually on the page. A schema price differing from the displayed price, or ratings not shown to users, is a policy violation that can cost you rich results across the site rather than just that page.
Generate schema from data, not from templates
The durable fix is building the object in code from your real data and serialising it once, rather than interpolating values into a hand-written JSON string in a template. String interpolation is what breaks when a product title contains a quotation mark.
Add a build-time validation gate
Parse every JSON-LD block during the build and fail the build if any is invalid or missing from a template that requires it. This is the step that stops drift returning — monitoring tells you it broke, a build gate stops it shipping.
Tip: Schema breaks silently. The page looks perfect, nothing errors, and rich results simply stop appearing weeks later. That delay between cause and symptom is exactly why automated validation matters more here than almost anywhere else.
Tags: schema-drift, schema drift detector, fix
How-To
How to Detect and Prevent Schema Drift
Baseline your structured data, monitor for changes, and gate deploys so regressions cannot ship.
1. Inventory schema by template
Crawl the site and record, for each URL, which schema types are present. Group by template. You are looking for the intended pattern and for pages that deviate from it — those deviations are your current drift.
2. Capture a clean baseline
Store the schema types and key properties per template while everything is correct. Every future comparison runs against this. Taking a baseline while something is already broken bakes the fault in as normal, which is a common and frustrating mistake.
3. Validate every block, not a sample
Parse all JSON-LD on every crawled page and record parse failures with their URL. Sampling hides the common case where one template variant — a sale price layout, a particular product type — is broken across thousands of pages.
4. Check required properties per type
For each schema type, verify the properties required for rich result eligibility are present and non-empty. An Article without a headline, or a Product without an offers block, is present but ineligible, which looks fine in a presence check.
5. Compare schema values against page content
Extract the visible price, title and rating and compare against the schema values. Mismatches are both a rich-result risk and a policy problem, and they are invisible to any check that only validates syntax.
6. Add validation to your build pipeline
A script that loads every built HTML file, extracts JSON-LD, parses it, and exits non-zero on any failure. Wire it into CI so a broken schema fails the build. This is the single highest-value step on this list.
7. Monitor Search Console enhancement reports
The Enhancements section reports valid, warning and invalid items per schema type across your indexed pages. Watch the trend rather than the absolute number — a sudden drop in valid items after a deploy is drift you can date precisely.
8. Re-baseline after intentional changes
When you deliberately change schema, update the baseline in the same commit. Otherwise every subsequent comparison flags the intended change as drift, and people start ignoring the alerts — which is how monitoring dies.
Tip: Wire the validation into CI rather than running it on a schedule. Catching a broken schema before it ships costs nothing; catching it three weeks later means three weeks of lost rich results you cannot get back.
Tags: schema-drift, schema drift detector, howto
Tutorial
Understanding Schema Drift
Why structured data degrades over time, how the failures stay hidden, and what keeps it correct.
What drift means here
Schema drift is structured data silently diverging from what it should be — disappearing from a template, losing properties, developing syntax errors, or contradicting the visible page. It is not a single bug but a gradual decay that accumulates through ordinary site changes.
Why it happens
Templates get edited, plugins update, themes change, a developer refactors output, a CMS migration rewrites markup. Any of these can drop a JSON-LD block or alter its structure. Since nothing visible changes, the alteration passes review and ships.
The invisibility problem
A broken schema block produces no visual defect, no console error and no failed test. Search engines discard invalid JSON-LD silently. The only symptom is rich results gradually disappearing, which surfaces in traffic data weeks after the cause.
The most common breakages
Unescaped double quotes inside a title or description. Trailing commas. Newlines inside string values. A literal closing script tag appearing in content, which terminates the block early. All of these come from interpolating raw content into a hand-written JSON string in a template.
Why generating from data is the fix
Building the schema object in code and serialising it once handles escaping automatically. A product title containing a quotation mark is escaped properly rather than breaking the block. Almost every drift failure traces back to string interpolation in a template.
Content mismatch as a policy risk
Schema must reflect what users see. A price in the markup that differs from the displayed price, or aggregate ratings not visible on the page, breaches structured data policy. Consequences can extend beyond the page to rich result eligibility across the site.
Monitoring versus gating
Monitoring tells you drift occurred, after it shipped. A build-time gate stops it shipping at all. Both have a place — the gate prevents regressions from your own code, monitoring catches changes from plugins, CMS updates and content editors.
Search Console as the outside view
The Enhancements reports show what Google actually parsed across your indexed pages, which is the authoritative view. Watching those trends catches drift your own tooling missed, particularly where the problem only appears on pages your crawler did not reach.
Tip: Search Console's enhancement reports lag by days and only cover indexed pages, so they are a safety net rather than a first line of defence. Build-time validation is what actually prevents the problem.
Tags: schema-drift, schema drift detector, tutorial
Fix
Fix: Missing or Inadequate GDPR Documentation
Produce the policies, records and processes GDPR requires, tailored to what your site actually does rather than a generic template.
Map what personal data you actually handle
Before writing any policy, list every place personal data enters: contact forms, accounts, newsletter signups, analytics, payment processing, support tickets, server logs. For each, record what is collected, why, where it goes and how long it is kept. Everything else builds on this.
Establish a lawful basis for each purpose
Every processing purpose needs one of the six lawful bases, chosen before you collect rather than justified afterwards. Most sites use consent for marketing, contract for order fulfilment, and legitimate interests for security and fraud prevention. Write down which applies where.
Write a privacy policy that matches reality
Generic templates fail because they describe processing you do not do and omit what you actually do. Yours must name the real categories of data, the real purposes, the real third parties receiving it, retention periods, and the rights people have. Accuracy is the requirement.
Fix your cookie banner so consent is genuine
Non-essential cookies must not fire before consent. Rejecting must be as easy as accepting — a prominent accept button with a buried reject link does not meet the standard. Pre-ticked boxes are not consent, and consent must be withdrawable as easily as it was given.
Document your processors and transfers
Keep a record of every third party processing data on your behalf — hosting, analytics, email, payments, CRM — with a data processing agreement in place for each. Note any transfers outside the UK or EEA and the safeguard relied on.
Build a working process for data subject requests
People can request access, correction, deletion, portability, or object to processing, and you must respond within one calendar month. Decide now who handles these, how identity is verified, and where the data lives. A process invented during a live request goes badly.
Prepare for breach notification in advance
Serious breaches must be reported to the regulator within 72 hours of becoming aware. Write the procedure while calm: who assesses severity, who notifies, what gets recorded. Three days is very little time to invent a process from scratch.
Keep records that demonstrate compliance
The accountability principle means being able to show what you decided and why. Keep your data map, lawful basis decisions, processor agreements, consent records and request log. This documentation is what a regulator asks for first.
Tip: Generic policy templates are the most common failure. A policy describing processing you do not do, and omitting what you actually do, is worse than none — it demonstrates that nobody assessed the real situation.
Tags: gdpr-kit, gdpr kit, fix
How-To
How to Assemble Your GDPR Documentation
Work through the data map, the policies, the agreements and the processes in the order that makes each one possible.
1. Build the data inventory first
Walk your own site as a user and note every point personal data is collected. Then check server-side: logs, backups, CRM syncs, email lists, analytics. For each, record data types, purpose, storage location, retention period and who has access. Nothing else can be written accurately without this.
2. Assign a lawful basis to each purpose
Go through the inventory row by row and record which of the six bases applies. If you cannot identify one, that is a processing activity to stop rather than a documentation gap to paper over. Record the reasoning, particularly for legitimate interests.
3. List your processors and get agreements
Every third party handling data for you needs a data processing agreement. Most major providers publish a standard one you can accept. Record the provider, what they process, where they are located, and which safeguard covers any international transfer.
4. Write the privacy policy from the inventory
Work directly from your data map so the policy describes reality. Cover: what you collect, why, the lawful basis, who it is shared with, how long it is kept, what rights people have, how to exercise them, and how to complain to the regulator.
5. Fix the cookie banner and consent mechanics
Audit what actually fires before consent using the browser network tab. Non-essential tags must not load until the user agrees. Make reject as prominent as accept, remove pre-ticked boxes, and provide a way to change the choice later.
6. Write the data subject request procedure
Document who receives requests, how identity is verified, where each data type lives so it can be found, the one-month deadline, and how the response is recorded. Test it once with an internal dry run — that is when you discover the data is in five places nobody listed.
7. Write the breach response procedure
Cover how a suspected breach is reported internally, who assesses whether it is notifiable, the 72-hour regulator deadline, when individuals must be told, and what gets logged. Keep it short enough that someone can follow it under pressure.
8. Set a review schedule
Record the date each document was written and review annually, or whenever you add a new tool, processor or data collection point. Documentation that describes last year's setup fails the accountability test just as surely as having none.
Tip: Open your own site in a private window with the network tab recording and see exactly what fires before you touch the banner. It is the fastest way to find out whether your consent mechanism does anything at all.
Tags: gdpr-kit, gdpr kit, howto
Tutorial
Understanding GDPR Documentation Requirements
What GDPR actually requires you to write down, why the accountability principle drives most of it, and how the pieces fit together.
Why documentation is a requirement, not admin
GDPR's accountability principle requires you to demonstrate compliance, not merely achieve it. Doing the right thing without records fails the test, because there is no way to show it. This is why so much of GDPR work is writing things down.
The data inventory underpins everything
You cannot state a lawful basis, write an accurate policy, answer an access request or assess a breach without knowing what data you hold, where and why. Every other document depends on this one, which is why it comes first and why skipping it produces generic policies that fail.
Lawful basis must be chosen in advance
Six bases exist; you pick one per purpose before collecting. Consent for marketing, contract for fulfilling an order, legitimate interests for security. What you cannot do is collect first and select a justification later — the choice is meant to constrain the collection.
What the privacy policy is for
It is the transparency obligation made concrete: telling people what happens to their data in language they can understand. Its failure mode is being a template describing a generic business, which satisfies nobody and demonstrates that nobody assessed the actual processing.
Consent has a specific meaning
Freely given, specific, informed and unambiguous, by clear affirmative action. Pre-ticked boxes fail. Implied consent from continued browsing fails. Making reject harder than accept fails. And it must be as easy to withdraw as it was to give, which requires a mechanism you have to build.
Processors extend your responsibility
Anyone processing data on your behalf — host, analytics provider, email platform, payment processor — is your processor, and you need a written agreement with each. Their breach is your notification obligation, which is why the register of processors matters more than it first appears.
Individual rights need a working process
Access, rectification, erasure, portability, objection, and rights around automated decisions. One calendar month to respond. The obligation is not the theory but the operational reality: knowing where every copy of someone's data lives so you can actually retrieve or delete it.
The 72-hour breach clock
Notifiable breaches must reach the regulator within 72 hours of awareness. That is not enough time to invent a process, decide who assesses severity, and work out what data was involved. The procedure has to exist beforehand, which is the entire point of writing it down early.
Tip: The ICO publishes free, plain-English guidance and checklists aimed at small organisations. It is the authoritative source for UK GDPR and considerably more useful than most commercial compliance material.
Tags: gdpr-kit, gdpr kit, tutorial
Fix
Fix: Acting on a Full Site Audit Report
A large audit returns hundreds of issues. Triage by impact, fix causes rather than instances, and verify each change.
Sort by impact before touching anything
A long issue list is demoralising and most of it does not matter much. Order by what affects indexing first, then rankings, then user experience, then cosmetic. A noindex on a key page outranks two hundred missing alt attributes every time.
Fix anything blocking indexing first
Accidental noindex directives, robots.txt rules blocking important sections, canonical tags pointing at the wrong URL, and pages returning errors. These remove pages from search entirely, so nothing else you fix matters until they are resolved.
Then fix what breaks crawling
Redirect chains and loops, orphaned pages with no internal links, broken internal links, and soft 404s returning a 200 status. These waste crawl budget and prevent discovery, which shows up as new content taking too long to appear.
Group issues by cause, not by instance
Two hundred pages missing a meta description is usually one template missing a field. Fixing the template resolves all two hundred. Working through them individually is slow, error-prone and leaves the cause in place to reappear on the next new page.
Handle duplicate content at the source
Duplicate titles and descriptions usually mean a template producing identical output, or parameter URLs generating multiple copies of a page. Fix the generation logic or add canonicals — editing symptoms one by one does not scale on a real catalogue.
Address Core Web Vitals as a distinct project
Performance issues rarely resolve with quick fixes. Treat images, render-blocking resources, layout shift and server response time as their own piece of work with its own measurement, rather than mixing them into a general issue list.
Deal with the content quality findings last but do not skip them
Thin pages, missing headings, low word counts and unclear structure are slower to fix and slower to show results, but they affect site-wide quality assessment. Decide per page: improve substantially, consolidate, or remove.
Re-run the audit and compare against the baseline
Record the issue counts by category before you start, then re-run after the work. Categories should fall, and any that rose need investigating — fixes sometimes create new problems, particularly around redirects and canonicals.
Tip: Do not try to reach zero issues. Some findings are informational, some do not apply to your situation, and some cost more to fix than they are worth. A deliberately accepted issue with a documented reason is a fine outcome.
Tags: site-audit-pro, site audit pro, fix
How-To
How to Work Through a Full Site Audit
Set the crawl up correctly, triage the findings, fix by cause, and measure the difference.
1. Configure the crawl to match your site
Enable JavaScript rendering if your content renders client-side, include or exclude parameters deliberately, and set a crawl rate the server can absorb. A misconfigured crawl produces a report describing a site that does not exist.
2. Record a baseline of issue counts
Before fixing anything, export the counts per category. This is what you will compare against, and without it you will have no way to demonstrate the work achieved anything beyond a general feeling of improvement.
3. Separate blocking issues from the rest
Filter for noindex, robots.txt blocks, wrong canonicals, and error status codes on pages that should be live. This is usually a short list and it is always the first work. Everything else can wait until pages are actually eligible to rank.
4. Cluster the remaining issues by template
Add a column for the template or section each affected URL belongs to and pivot on it. This turns hundreds of rows into a handful of underlying causes, which is the difference between a week of work and an afternoon.
5. Fix one cause and verify before moving on
Change the template, re-crawl that section only, confirm the issue count for that cause dropped to zero. Batching many changes before verifying makes it impossible to tell which fix worked or which one introduced something new.
6. Watch for issues you create while fixing
Adding redirects can create chains. Adding canonicals can point at the wrong target. Changing templates can drop schema. Re-crawl after each significant change and compare against the baseline in both directions, not just the one you expect.
7. Handle performance separately with its own measurement
Core Web Vitals need field data over time, not a single crawl result. Track them in Search Console alongside your lab measurements, and treat performance work as a project with its own before-and-after rather than a checklist item.
8. Re-run the full audit and document what you accepted
Compare every category against the baseline. For issues you deliberately chose not to fix, write down the reason. That record stops the same findings being re-litigated at every future audit.
Tip: Re-crawl only the affected section after each fix rather than the whole site. Full crawls of a large site are slow, and the feedback loop matters more than completeness while you are still working.
Tags: site-audit-pro, site audit pro, howto
Tutorial
Understanding Comprehensive Site Audits
What a full audit actually measures, why the issue count is a poor target, and how to turn a long report into useful work.
What a full audit covers
A comprehensive crawl checks technical health (status codes, redirects, canonicals, robots directives), on-page elements (titles, descriptions, headings, content), structured data, internal linking, performance signals and mobile usability — across every page rather than a sample.
Why crawling beats sampling
Problems in generated sites cluster by template rather than scattering randomly. Checking ten pages might miss a fault affecting a thousand, or find one affecting only those ten. Crawling everything is what reveals which category you are in.
Why the issue count is a bad target
Audit tools flag everything they can detect, weighted by detectability rather than importance. Two hundred missing alt attributes produce a bigger number than one noindex on your highest-value page, while mattering far less. Chasing the count optimises for the wrong thing.
Triage by consequence
The useful ordering is: does this stop pages being indexed, does it waste crawl budget, does it weaken ranking signals, does it hurt users, is it cosmetic. Work down that list. Most long audit reports are dominated by the bottom two categories.
Causes versus instances
An audit reports instances — this page, this URL. The fix almost always lives at the cause — this template, this plugin, this generation rule. Learning to collapse instances into causes is the main skill in using these reports efficiently.
Some findings should be accepted
Not every flagged item is worth fixing. Some do not apply to your architecture, some cost more than the benefit, and some are informational. Deliberately accepting an issue with a written reason is a legitimate outcome and stops it resurfacing every quarter.
Audits measure, they do not prioritise
The tool cannot know which pages drive your revenue. A missing title on a page nobody visits and a missing title on your main service page appear identically in the report. Overlaying your own traffic and revenue data is what turns findings into priorities.
Auditing is a cycle, not an event
Sites change constantly and issues reappear as new content is published and templates evolve. A quarterly audit with baseline comparison catches regressions early. A one-off audit produces a burst of work and then slow decay back toward where you started.
Tip: Overlay your analytics onto the audit export before prioritising. An issue on a page with real traffic is worth ten on pages nobody visits, and no audit tool can make that judgement for you.
Tags: site-audit-pro, site audit pro, tutorial
Fix
Fix: Rankings You Cannot Trust or Explain
Your tracked positions jump around, disagree with what you see in Google, or do not match Search Console. Fix the tracking setup before drawing conclusions.
Stop comparing tracked rank to what you see in your own browser
Your personal results are personalised by location, search history, account and device, so they will never match a neutral tracked position. Checking your own rankings by searching is the single most common source of false alarms. Trust the tracker's neutral result and Search Console, not your address bar.
Set the location to where your customers actually are
Rankings differ enormously by location, especially for anything local or service-based. Tracking a national position for a term that returns a local pack tells you nothing useful. Set the location to your real market — city level for a local business, country for a national one.
Track the right device
Mobile and desktop results differ, sometimes substantially. Check your analytics for the actual split and track the device that carries your traffic. Tracking desktop for a term where 80% of your visits are mobile means you are watching the wrong number.
Reconcile against Search Console rather than assuming one is wrong
Search Console average position is an average across every impression, location and device, so it will not match a single tracked position and is not supposed to. Use the tracker for a specific, repeatable measurement and Search Console for the aggregate reality. Disagreement between them is normal.
Ignore movement inside the noise band
Positions fluctuate daily without anything changing on your site — personalisation, testing, index churn. A move of one or two places is usually noise. Set your own threshold, typically three or more places sustained over several days, before treating a change as real.
Track terms that carry commercial value
A long list of easy terms produces a nice chart and no revenue. Track the terms customers actually use when ready to act, plus a small number of important informational ones. Twenty meaningful terms beat two hundred vanity ones.
Record what you changed and when
Ranking movement is meaningless without a change log. Note content updates, technical fixes, migrations and link acquisitions with dates, so a movement three weeks later can be attributed rather than guessed at.
Check the SERP itself, not just the number
Position five on a page dominated by ads, an AI overview and a local pack can deliver less traffic than position eight on a clean results page. Look at what the results page actually contains before concluding a position is good or bad.
Tip: If tracked rank and Search Console disagree, both are probably right — they measure different things. The tracker gives a repeatable point measurement, Search Console gives the weighted average of real impressions.
Tags: rank-tracker, rank tracker, fix
How-To
How to Set Up Rank Tracking Properly
Choose keywords with commercial value, set location and device to match your market, and build a change log so movement means something.
1. Pull your real queries from Search Console first
Do not start from a keyword tool. Export your queries from Search Console, sorted by impressions and by clicks. These are the terms Google already associates with you, which is the honest starting point for what is worth tracking.
2. Pick terms by commercial intent, not volume
Sort your candidate list by how close each term is to someone wanting to buy or enquire. A term with 200 searches a month and clear intent is worth more than one with 20,000 and none. Aim for twenty to fifty tracked terms you could actually name a business reason for.
3. Set the location to your market
National business: set the country. Local or service-area business: set the city or town where your customers are. If you serve several areas, track your main terms separately per area rather than averaging them into one meaningless national number.
4. Set the device to match your traffic
Check the mobile/desktop split in your analytics and track the dominant one. If the split is close, track both for your most important terms — the results genuinely differ and one can move while the other does not.
5. Take a baseline before making changes
Record starting positions for everything you track before you begin any optimisation work. Without a baseline you will be comparing against memory, and memory is generous about where things used to be.
6. Set a check frequency you will act on
Daily tracking produces noise and daily anxiety. Weekly is enough for most sites to spot real movement while filtering fluctuation. Only track daily around a migration or a known algorithm update, where you need the resolution.
7. Start a change log in the same place
One line per change with a date: content published, page rewritten, technical fix, redirect, link earned, migration. This is what turns a ranking chart from a mood ring into an analysis tool, and it costs almost nothing to maintain.
8. Review monthly against the baseline and the log
Look at direction over weeks, not day-to-day positions. For anything that moved meaningfully, check the log for what preceded it, and check the SERP to see whether the results page itself changed shape.
Tip: Track a handful of competitor domains on the same terms. Knowing whether you fell or they rose is the difference between fixing your own page and understanding that someone simply published something better.
Tags: rank-tracker, rank tracker, howto
Tutorial
Understanding Rank Tracking
Why a ranking position is not a single fact, how tracked rank differs from Search Console, and what the number can and cannot tell you.
There is no single ranking any more
Results are personalised by location, device, search history, language and account. Two people searching the same term at the same moment see different pages. A tracked rank is therefore not the truth but a consistent, neutral measurement — useful precisely because it holds the variables still.
Why your own search is the worst check
Searching for your own term in your own browser gives you the most distorted result available: your location, your history, your account, and Google's knowledge that you visit your own site constantly. It reliably shows you ranking better than you do, which is why people believe trackers are broken.
Tracked rank versus Search Console
Search Console reports average position across every impression — all locations, devices and query variations, weighted by how often each occurred. A tracker reports one position under fixed conditions. They will never match, and expecting them to is a misunderstanding rather than a fault.
Which to use for what
Use the tracker to answer 'did this specific term move for this market' — repeatable and comparable over time. Use Search Console to answer 'what is actually happening to our visibility' — real impressions, real clicks, real queries including ones you never thought to track.
Noise is the normal state
Positions move daily without cause: index churn, testing, personalisation, competitor activity. A one or two place shift is almost always noise. Only sustained movement of several places over several days is worth investigating, and treating every wobble as a signal wastes a great deal of time.
Position matters less than the page it sits on
A results page can contain ads, an AI overview, a local pack, video carousels and people-also-ask boxes before the first blue link. Position three on such a page may sit below the fold. The number alone no longer tells you what traffic to expect.
Why the term list matters more than the tool
Tracking hundreds of easy terms produces a flattering chart and no insight. A short list chosen for commercial intent, tracked in the right location on the right device, tells you whether the business is becoming more findable by the people who matter.
Rankings are a diagnostic, not a goal
Position is a means to traffic, and traffic is a means to enquiries and sales. A site can gain positions and lose revenue if it gains them on the wrong terms. Read rankings alongside Search Console clicks and your own conversion data, never alone.
Tip: Check tracked positions in an incognito window with location set manually if you want to sanity-check the tracker. Your normal browser is personalised beyond usefulness for this purpose.
Tags: rank-tracker, rank tracker, tutorial
Fix
Fix: Missing Trust Signals on Your Site
Visitors and search engines both assess whether a site is legitimate. Fix the identity, contact, policy and evidence signals that are missing.
Publish real, verifiable business identity
Company name, registration number where applicable, registered address and VAT number if you have one. Anonymous commercial sites are treated with suspicion by users and quality raters alike, and this is one of the cheapest gaps to close.
Give people more than one way to reach you
A contact page with a physical address, a telephone number and an email — not solely a web form. A form-only contact page on a site taking money is a recognised low-trust pattern, because it costs nothing to set up and offers no accountability.
Publish the policies your site actually needs
Privacy policy, terms of service, and where you sell: delivery, returns and refunds. These must describe what you really do rather than being a downloaded template, because a policy contradicting your actual process is worse than none in a dispute.
Put real people behind the content
Named authors with genuine credentials and a short biography, and an about page describing who runs the business and why they are qualified. On topics affecting health, money or safety this matters more than almost anything else you can change.
Show evidence rather than claims
Case studies with figures, named testimonials, verifiable reviews, accreditations and memberships with registration numbers. Unattributed praise reads as invented; a testimonial with a real name, company and detail reads as real because it is checkable.
Make security visible where it matters
Valid HTTPS across the whole site, no mixed content, and clear indication of how payments are handled at the point where a customer is asked to pay. A padlock warning at checkout costs conversions immediately.
Keep the site demonstrably current
Recent publication or update dates, working links, accurate prices and current copyright year. An abandoned-looking site suggests an abandoned business, and stale content undermines every other trust signal you have built.
Handle complaints and corrections openly
A visible route to raise a problem, and corrections noted rather than quietly edited. Being straightforward about mistakes is itself a trust signal, and its absence is noticeable on sites that publish anything factual.
Tip: Trust signals compound. A named author on a site with no address, no phone number and a template privacy policy achieves little; the same author on a site with full company details reads as credible.
Tags: trust-audit, trust audit, fix
How-To
How to Run a Trust Audit on Your Site
Check identity, contact, policy, authorship, evidence and currency signals the way a cautious first-time visitor would.
1. Look at your own site as a stranger
Open it in a private window with no account signed in. Ask the question a first-time visitor asks: who are these people, are they real, and what happens if something goes wrong. Note every point where you cannot answer from the page in front of you.
2. Check business identity is present and findable
Look for company name, registration number, registered address and VAT number. Note whether they appear in the footer, on the about page and on the contact page, or whether you had to hunt. Findability matters as much as presence.
3. Test the contact routes
Confirm there is a physical address, a phone number and an email, not just a form. Then actually use one — send the email or call the number. A contact page listing a dead address is worse than a sparse one, and you will not know without testing.
4. Read your own policies against reality
Open the privacy policy, terms, and any delivery or returns policy, and check each statement against what your business actually does. Template text describing processing you do not do, or a returns window you do not honour, is a liability rather than a trust signal.
5. Check authorship and expertise signals
Does content carry a named author, and does that name link to a real biography with credentials. Is there an about page explaining who runs this and why they know the subject. For health, finance, legal or safety topics, treat missing authorship as a priority fix.
6. Assess the evidence you present
Go through testimonials, case studies, reviews and accreditations and ask of each: could a sceptical person verify this. Unattributed quotes and logos without registration numbers fail that test and can reduce trust rather than build it.
7. Check security and payment signals
Confirm HTTPS across every page including forms, look for mixed content warnings in the console, and check what the checkout or enquiry flow tells a customer about how their details are handled.
8. Check for staleness
Look for publication and update dates, test a sample of outbound links, verify prices are current, and check the copyright year. Then fix whatever a stranger would read as neglect.
Tip: Ask someone who does not know the business to find your address, phone number and returns policy while you watch. Where they hesitate is where your trust signals are failing, and it is rarely where you expected.
Tags: trust-audit, trust audit, howto
Tutorial
Understanding Website Trust Signals
What trust signals are, why search engines and users assess them, and which ones carry the most weight.
What a trust signal is
Anything on your site that helps someone decide you are legitimate and accountable: verifiable identity, real contact routes, honest policies, named expertise, checkable evidence, and visible security. Individually small, collectively decisive.
Why search engines care
Search quality guidelines direct human raters to assess expertise, experience, authoritativeness and trustworthiness — with trust as the central element. Raters are told to look for who is responsible for a site, how to contact them, and whether the content is backed by real credentials.
Why it matters most on some topics
Pages that could affect someone's health, finances, safety or legal position are held to a far higher standard, because bad information there causes real harm. A site in those areas without named expertise and verifiable identity is at a structural disadvantage, however good the writing.
Users apply the same test faster
Visitors decide within seconds whether a site looks legitimate, largely on signals they cannot articulate: is there an address, does the contact page have a phone number, does anyone put their name to this, does the design look maintained. Conversion follows that judgement.
Identity and contact are the foundation
Everything else rests on someone being accountable. A named company at a real address with a working phone number can be checked, complained to and held responsible. Anonymity is the single strongest negative signal a commercial site can carry.
Evidence beats assertion
Claiming twenty years of experience is worth little; a case study with figures, a named client testimonial, or an accreditation number that can be looked up is worth a great deal. The test is whether a sceptic could verify it without asking you.
Policies are both legal and reputational
Privacy, terms, delivery and returns policies are frequently legally required, and they also tell a customer what happens when something goes wrong. Template policies that contradict your actual practice fail on both counts simultaneously.
Currency signals ongoing life
Recent dates, working links, accurate prices and a maintained appearance all say the business is still operating. Staleness reads as abandonment, and an abandoned-looking site undermines every other trust signal you have carefully built.
Tip: Read the search quality rater guidelines section on trust. It sets out explicitly what human evaluators are told to look for, which is the closest thing available to a specification for these signals.
Tags: trust-audit, trust audit, tutorial
Fix
Fix: Pages That Convert Badly to Markdown for AI
AI systems read a markdown version of your page. If conversion produces a mess, fix the underlying HTML structure that causes it.
Use real headings in the right order
Markdown conversion maps h1 to h6 directly. A page that styles a div to look like a heading produces markdown with no structure at all. Use one h1, then h2 and h3 in order without skipping levels, so the converted document has a real outline.
Make lists actual list elements
A series of divs with bullet characters looks identical to a person and converts to an undifferentiated block of text. Use ul, ol and li so each item survives as a list item, which is how an AI system identifies a set of related points.
Use proper table markup
A grid built from divs and CSS loses every relationship between cell and column when converted. Real table, thead, tbody, th and td markup converts to a markdown table where the relationships survive, which matters enormously for specifications and pricing.
Put the content before the furniture
Conversion follows document order. If your markup places navigation, sidebars and promotional blocks before the article, the markdown opens with clutter. Order the source so the main content comes first, using CSS for visual placement.
Give every meaningful image alt text
Images convert to markdown as alt text and nothing else. A decorative image should have an empty alt attribute so it disappears cleanly; an informative one — a chart, a diagram, a screenshot conveying a fact — needs alt text carrying that information.
Keep essential content out of JavaScript
If conversion runs on the delivered HTML, anything rendered client-side is simply absent. Prices, specifications and body text must be in the server-rendered markup or they will not appear in the markdown at all.
Remove content that only exists after interaction
Tabs, accordions and modals frequently hide content from conversion. If a specification or a delivery term matters, it should be in the initial markup even if visually collapsed, rather than fetched when clicked.
Convert and read the output
Run your key pages through conversion and actually read the result. If you cannot understand the page from the markdown, neither can a language model, and whatever is missing or garbled points straight at the markup causing it.
Tip: Everything that improves markdown conversion also improves accessibility and search parsing, because all three depend on the same thing: semantic markup that expresses what content is rather than only how it looks.
Tags: markdown-generator, markdown generator, fix
How-To
How to Generate and Use a Markdown Version of Your Pages
Produce clean markdown for AI consumption, check the output, and publish it where agents will find it.
1. Pick the pages worth converting
Start with the pages carrying the facts people actually ask about: your main service or product pages, pricing, the guides that answer real questions, contact and about. These are the pages an AI system needs in order to say anything accurate about you. Converting your entire site produces a large pile of files of which most are navigation-heavy and low-value, and volume is not the objective — being correctly represented on the things that matter is.
2. Generate the markdown
Run each selected page through the converter and save the output alongside a note of which page it came from. At this stage you are not assessing polish or formatting — the only question is whether the essential facts survived the conversion at all. Prices, specifications, contact details, the core explanation. If they are absent from the markdown, they are absent for anything consuming it, and no amount of tidying the output will put them back.
3. Read the output as if you knew nothing
Read each markdown file cold, as though you had never seen the site. Can you tell what is being offered, what it costs, who it is for and how to get in touch. Anything you cannot answer from the markdown is something a language model cannot answer either, for exactly the same reason. Note each gap — every one traces back to a specific piece of markup, and that trace is what turns this from an impression into a fix list.
4. Trace problems back to the HTML
Each symptom has a specific cause. Missing headings mean styled divs are being used where h2 and h3 elements belong. A flattened wall of text where a list should be means divs instead of ul and li. A garbled table means a CSS grid instead of real table markup. Content absent entirely means it is rendered client-side and was never in the delivered HTML. Fix the source markup in every case — editing the markdown by hand produces a file that is correct once and wrong after the next deploy.
5. Strip the navigation and boilerplate
Menus, cookie banners, footers and promotional blocks convert into noise that buries the actual content. Configure the conversion to target the main content region, or mark it up with a main element so it can be targeted reliably.
6. Publish it where agents look
Save alongside the page — /page-name.md next to /page-name.html — and serve it as text/markdown. Reference the markdown versions from your llms.txt so there is a discoverable route to them.
7. Automate generation in your build
Hand-generated markdown drifts out of date within weeks, and stale content is worse than none because it misinforms. Generate from the same source that builds the pages, so the two cannot diverge.
8. Re-check after template changes
Any change to page templates can alter conversion output. Add a spot check to your release routine: convert one page of each type and confirm the structure still holds.
Tip: Serve markdown with the correct content type — text/markdown or text/plain with a UTF-8 charset. Served as HTML or as a download, some clients will not parse it and the file quietly achieves nothing.
Tags: markdown-generator, markdown generator, howto
Tutorial
Understanding Markdown for AI Consumption
Why AI systems prefer markdown, what conversion actually does to your HTML, and how markup quality determines the result.
Why markdown rather than HTML
HTML delivered to a browser is mostly presentation — nested divs, classes, inline styles, scripts, tracking. Markdown strips that to structure and text: headings, lists, tables, links, emphasis. For a language model this is a far higher ratio of meaning to noise, and it consumes fewer tokens.
What conversion actually does
A converter walks the HTML and maps semantic elements to markdown equivalents: h2 becomes a heading, ul becomes a bullet list, table becomes a markdown table, a becomes a link, img becomes alt text. Anything without semantic meaning — a styled div — has nothing to map to and becomes plain text.
Why your markup quality decides the output
This mapping is why a visually identical page can convert beautifully or terribly. A heading that is really an h2 survives; a heading that is a div with large bold text becomes an ordinary paragraph. The page looks the same to a person and completely different to a machine.
Document order matters more than you expect
Conversion follows source order, not visual order. A layout that places a sidebar before the article in the markup produces markdown opening with the sidebar. CSS can position things anywhere; the converter only sees the sequence in the file.
What disappears entirely
Client-side rendered content, if conversion runs on delivered HTML. Content behind tabs, accordions or modals in many cases. Images without alt text. Anything conveyed purely by colour or position. Each of these is invisible to conversion in exactly the way it is invisible to a screen reader.
Where the markdown is used
Some AI systems fetch a markdown version if one is offered. Some convert on the fly. Some read llms.txt, which is itself markdown, and follow its links. Publishing clean markdown means you control the conversion rather than accepting whatever a third party's converter produces.
Keeping it current is the hard part
A markdown file generated by hand is accurate the day it is made and misleading within a month. Because it is a derived artefact, it must be regenerated whenever the source changes, which means generating it in the build rather than as an occasional task.
The overlap with everything else
Markup that converts well is markup that is accessible, that search engines parse reliably, and that agents can act on. The work is the same work, which makes this a useful lens for finding structural problems rather than a separate project.
Tip: Convert your most important page and read the markdown alongside the live page. The gap between what a visitor sees and what survives conversion is usually larger than expected, and every gap is a structural problem worth fixing.
Tags: markdown-generator, markdown generator, tutorial
Fix
Fix: Content That Does Not Answer Real Questions
Your pages cover topics but miss the actual questions people ask. Find those questions and answer them directly.
Start from questions you already receive
Before any tool, collect the questions customers actually ask: support tickets, sales calls, live chat logs, email enquiries. These are real, specific and already known to matter, and they are almost always underused as a content source.
Pull question queries from Search Console
Filter your queries for those containing how, what, why, when, where, can, does and is. These are questions Google already associates with your site — often ones you never deliberately targeted and answer only partially.
Answer the question in the first paragraph
A page that spends four hundred words building up before answering fails both readers and AI systems. State the answer plainly at the top, then expand. This is the single change that most improves the chance of being quoted in an AI answer or a featured snippet.
Use the question as the heading
Make the h2 the question in the words people actually use, then answer immediately beneath. This creates an unambiguous question-answer pair that both search engines and language models can extract cleanly.
Cover the follow-up questions too
Every question implies the next one. If someone asks what something costs, they will next ask what affects the price and what is included. Answering the chain on one page serves the reader properly and covers far more query variations.
Add FAQ schema where it genuinely fits
Mark up real question and answer pairs with FAQPage structured data. It must reflect questions and answers visible on the page — inventing FAQ markup for content that is not there is a policy violation and can cost you rich results.
Do not spread thin answers across many pages
One page answering a question properly with its follow-ups beats ten pages each answering one question in eighty words. Thin question pages are a recognised low-quality pattern and drag on your whole site.
Review against reality periodically
Answers go stale — prices change, processes change, regulations change. A confidently wrong answer is worse than no answer, and question pages tend to be written once and forgotten.
Tip: The best question sources are the ones you already own: your support inbox and your sales calls. They contain the precise wording customers use, which is usually more specific and more useful than anything a keyword tool suggests.
Tags: question-finder, question finder, fix
How-To
How to Find the Questions Your Audience Asks
Gather questions from your own data and from search, prioritise by intent, and turn them into content that answers directly.
1. Mine your own support and sales records
Export the last few months of support tickets, chat transcripts and sales enquiries. Extract every question, in the customer's own words. This is the highest-quality source available and it costs nothing but the time to read it.
2. Filter Search Console for question queries
In the Performance report, filter queries containing how, what, why, when, where, can, does, is. Sort by impressions. High impressions with low click-through usually means you rank for the question but do not answer it well.
3. Collect the questions search engines surface
Note the people-also-ask boxes and related searches on results pages for your main terms. These are questions the engine has determined are related, which makes them a direct signal about what else the same person wants to know.
4. Group questions into clusters
Many questions are the same question asked differently. Group them so one thorough page can serve the whole cluster, rather than producing near-duplicate pages competing with each other for variations of the same query.
5. Prioritise by intent, not volume
A question asked by someone about to buy is worth more than a popular one asked out of idle curiosity. Rank your clusters by how close the asker is to acting, and start there.
6. Check what currently answers them
Search each priority question and read the top results. If the existing answers are thorough, you need to add something genuinely better — first-hand experience, data, a clearer explanation. If they are thin, that is your opening.
7. Write the answer first, then the context
Question as the heading, direct answer immediately below in two or three sentences, then the detail, caveats and follow-ups. This structure serves an impatient reader, a featured snippet and a language model equally well.
8. Track whether the answers get found
Watch impressions and clicks for the question queries in Search Console after publishing. If impressions rise but clicks do not, the answer is ranking but not compelling — usually a title and opening paragraph problem rather than a content one.
Tip: Write down the exact wording customers use rather than tidying it into formal phrasing. People search the way they speak, and the untidied version is usually closer to the real query.
Tags: question-finder, question finder, howto
Tutorial
Understanding Question-Based Content
Why questions drive search and AI answers, how answer structure determines whether you get quoted, and where to find the questions worth answering.
Why questions matter more than they did
Search has shifted from keywords to natural language, driven by voice input and AI assistants. People type and speak full questions, and systems increasingly return a synthesised answer rather than a list of links. Content organised around questions matches how people now ask.
How AI systems use your answers
A language model answering a question assembles it from sources it can parse. Content with a clear question heading and a direct answer beneath is easy to extract and attribute. Content burying the answer in the middle of a long narrative is much less likely to be used.
The answer-first principle
Stating the answer immediately serves everyone: the reader in a hurry, the featured snippet algorithm, and the model assembling a response. The traditional structure of building context before the conclusion is a poor fit for how these queries are answered.
Question clusters, not single questions
People rarely ask one question. What does it cost leads to what affects the price, what is included, and are there cheaper options. A page covering the cluster serves the actual information need and captures far more query variations than one narrow answer.
Where the real questions live
Your support inbox, sales calls and live chat contain the exact questions your customers ask in their own words. Search Console shows which question queries already reach you. People-also-ask boxes show what search engines consider related. All three beat guessing.
Why thin question pages fail
Publishing hundreds of pages each answering one question in a paragraph is a recognisable low-quality pattern. It creates near-duplicates, spreads authority thinly, and drags on site-wide quality assessment. Depth on fewer pages beats breadth across many.
FAQ schema and its limits
FAQPage structured data helps machines identify question and answer pairs, but it must mark up content genuinely visible on the page. Rich result treatment for FAQ markup has been narrowed over time, so treat it as machine-readability rather than a guaranteed display win.
Keeping answers true
Question content dates badly because it makes specific claims — prices, timescales, processes, rules. An out-of-date answer that reads confidently is worse than none, so anything factual needs a review cycle rather than being published and forgotten.
Tip: Read your support inbox before any keyword tool. The questions people ask when they are stuck are more specific, more urgent and better worded than anything a volume database will suggest.
Tags: question-finder, question finder, tutorial
Fix
Fix: Tracked Questions That Never Turn Into Content
You are collecting questions but nothing is being published. Build a working pipeline from saved question to live answer.
Give every saved question a status
A list where every row looks identical becomes a graveyard within a month. Give each saved question a status — new, prioritised, in progress, published, rejected — so the list can be filtered down to the handful that need attention now. This is the difference between opening a list of two hundred undifferentiated questions, which is paralysing, and opening a queue of six prioritised ones, which is a morning's work. Status is what keeps the thing usable.
Reject questions deliberately rather than ignoring them
Some questions genuinely should not be answered on your site: irrelevant to what you do, far too broad to answer usefully, or already covered thoroughly by an existing page. Mark those rejected with a one-line reason rather than leaving them in limbo. The reason matters because the same question will arrive again and you want to remember why you passed. A list that only ever grows is a list people stop opening, and at that point the whole process has quietly died.
Group duplicates before writing anything
The same question arrives phrased a dozen different ways over a few months. Merge them into a single entry that records every variation seen and how often. This gives you one thorough answer to write instead of several thin near-duplicates that compete with each other in search, split whatever authority they earn, and collectively read as low-quality filler. The recorded variations also tell you which phrasings to use as headings.
Prioritise by commercial intent
Rank saved questions by how close the asker is to buying or enquiring. A question from someone comparing options is worth more than a general one, and working in that order means the effort produces revenue rather than volume.
Decide the destination before writing
Each prioritised question should be assigned to a home: a new page, a section added to an existing page, or an FAQ entry. Answering questions on scattered new pages is how thin content accumulates.
Set a realistic publishing cadence
One well-answered question a week beats twenty planned and none written. A steady rate that actually happens is what turns the list into content; an ambitious plan that stalls leaves you with a longer list and nothing published.
Close the loop when it is published
Mark the question published and record the URL. This prevents rewriting the same answer months later and gives you a record of what has been covered when the same question arrives again.
Review the backlog on a schedule
Monthly, look at what is still sitting there. Some will have become urgent, some irrelevant. Without a review the list only accumulates, and an unreviewed backlog is indistinguishable from no backlog.
Tip: The failure mode is always collection without publication. If the list has grown for three months and nothing has shipped, the problem is the pipeline rather than the questions.
Tags: my-questions, my questions, fix
How-To
How to Manage a Saved Question List
Capture questions properly, deduplicate, prioritise by intent, and run a cadence that produces published answers.
1. Capture the question in the asker's words
Save the exact phrasing the person used, not a tidied-up version. Raw wording is closer to how the question will actually be typed or spoken into a search box, and it preserves the specific detail — the platform they are on, the constraint they mentioned, the thing they had already tried — that makes the eventual answer genuinely useful rather than generic. Tidying is the fastest way to lose the value of the question.
2. Record where it came from
Record where each question came from: support ticket, sales call, live chat, search query, or a people-also-ask box. The source is your best proxy for intent. A question asked on a sales call comes from somebody with a budget who is actively comparing options. The same question pulled from a related-searches box comes from somebody browsing. Both are worth answering eventually; only one is worth answering this week.
3. Merge the duplicates
Work through the list grouping questions that are really the same underlying need asked differently. Keep every variation attached to the merged entry rather than deleting them — when you come to write, that list of phrasings shows you the full range of ways people express this need, which shapes both the headings you use and the sub-questions you need to cover. Merging first is also what stops you publishing five near-identical pages that compete with each other.
4. Score each cluster for intent and effort
Give each cluster two scores: how close the asker is to taking action, and how much work a genuinely good answer would take. High intent with low effort is where you start — those are the quick wins that pay for the whole process. High intent with high effort is worth scheduling properly rather than squeezing in. Low intent with high effort almost never justifies itself, and being able to see that on a grid is what makes the decision easy rather than a matter of enthusiasm.
5. Assign a destination to each
New page, section on an existing page, or FAQ entry. Deciding this before writing prevents the default of creating a new thin page for every question, which is how sites accumulate hundreds of near-duplicates.
6. Schedule realistically and stick to it
Pick a cadence you will actually meet — one a week is plenty for most businesses. Put the specific question in the calendar rather than a general intention to write, because a scheduled item gets done and an intention does not.
7. Write answer-first
Question as the heading, direct answer in the first two or three sentences, then detail and follow-ups. Cover the obvious next questions in the same piece so the cluster is genuinely served.
8. Mark published and record the URL
Close the entry with its destination URL. Then check Search Console a few weeks later for whether that question query now brings impressions, which tells you whether the answer was found.
Tip: Track questions that arrive repeatedly after you have published an answer. If people keep asking, the answer is not being found or not answering properly — both are fixable and neither is visible from the list alone.
Tags: my-questions, my questions, howto
Tutorial
Understanding Question Tracking as a Content Process
Why saved questions are the best content input available, and why most question lists produce nothing.
Why questions beat keyword lists
A question from a real person carries intent, context and exact wording. A keyword from a database carries a volume estimate and nothing else. Working from questions means writing for a specific person with a specific need, which is what produces content people find useful.
The collection trap
Collecting questions is easy and satisfying; answering them is work. Most question lists grow steadily and produce nothing, because capture has no friction and publication has plenty. Recognising this is the difference between a process and a hobby.
Status is what makes a list usable
An undifferentiated list of two hundred questions is paralysing. The same list with statuses — new, prioritised, in progress, published, rejected — becomes a short queue of the things that matter now, and the rest is out of the way.
Rejection is part of the process
Not every question deserves an answer on your site. Marking questions rejected with a reason keeps the list honest and short. A list that only grows becomes something people stop opening, at which point the whole exercise is dead.
Deduplication before writing
The same underlying question arrives in many forms. Writing separately for each produces near-duplicate pages that compete with each other, split authority and read as thin. Merging first means one thorough answer serving every variation.
Intent is the right priority axis
Volume tells you how many people ask; intent tells you whether they are about to spend money. For a business, a question asked by someone comparing suppliers is worth more than a popular one asked out of curiosity.
Destination decides quality
Choosing where an answer lives — new page, existing page, FAQ — before writing prevents the default behaviour of creating a new page each time. That default is the main mechanism by which sites accumulate thin content.
Cadence beats ambition
A modest rate you actually sustain produces more published answers in a year than an ambitious plan that stalls in week three. The list is only valuable at the rate you empty it, and a steady trickle compounds.
Tip: Measure the list by how many questions you have published answers to, not by how many you have collected. The collection number always goes up and tells you nothing about whether the process works.
Tags: my-questions, my questions, tutorial
Fix
Fix: Missing the Questions Trending in Your Market
Trending questions have a short window. Fix the process that stops you noticing and responding in time.
Decide which topics you actually want to be known for
Trending question feeds contain far more than you should chase. Define the two or three subject areas where being an authority helps your business, and filter everything against that. Chasing unrelated trends produces traffic that never converts and dilutes your topical focus.
Check the feed on a fixed schedule
Trends have a window measured in days or weeks. A monthly review misses most of them. A short check twice a week is enough to spot what is rising while there is still time to publish something useful.
Separate genuine trends from noise
A question spiking for one day is usually a news artefact. One rising steadily over a fortnight indicates real, sustained demand. Responding to every spike wastes effort on content that is irrelevant within days.
Check whether you can answer credibly
Trending is not sufficient reason to publish. If you have no genuine expertise or experience in the topic, a rushed page adds nothing, competes with better sources and weakens your site's quality profile. Being able to say something real is the test.
Publish quickly but not thinly
The advantage of a trend is timing, which pushes toward speed. But a thin page published fast ranks briefly and leaves permanent low-quality content behind. Publish a genuinely useful answer quickly, rather than a placeholder quickly.
Update rather than republish
When a topic recurs — an annual regulation change, a repeating seasonal question — update the existing page and its date rather than publishing a new one each time. This accumulates authority instead of splitting it across near-duplicates.
Prune what stops being relevant
Trend-driven content dates fast. Review it after six months: update what still matters, consolidate what has been superseded, remove what has become irrelevant. Leaving it all in place accumulates dead weight.
Track whether it actually delivered
Check impressions and clicks a month after publishing. Trend content that never gained traction tells you the trend was not real for your audience, which is worth knowing before you chase the next one.
Tip: Trending questions are only worth answering where you have something genuine to say. A rushed page on a topic you know nothing about ranks briefly, converts nobody, and leaves permanent low-quality content on your site.
Tags: questions-and-answers, trending q&a, fix
How-To
How to Use Trending Questions
Filter the feed to your topics, distinguish real trends from spikes, and publish while the window is open.
1. Define your topic filter first
Write down the two or three subject areas where you have both genuine expertise and a commercial reason to be visible. Everything in the feed gets checked against that list before anything else. Without an explicit filter written down in advance, you will chase whatever looks interesting on the day — and interesting is not the same as useful. The filter is also what lets you say no quickly, which is most of the value.
2. Check the feed twice a week
Put it in the calendar as a short recurring task, fifteen minutes twice a week. Trends move considerably faster than a monthly content meeting, and the entire value of trend data is noticing while the window is still open. A rising question spotted on day three is an opportunity; the same question spotted six weeks later is a crowded field where you are competing with established pages for a shrinking audience.
3. Distinguish a spike from a trend
Look at the shape of the curve over time rather than today's number. A vertical one-day spike is almost always a news event and will be gone within the week — publishing for it means arriving after it has passed. Steady growth over a week or two indicates real, sustained change in what people want to know, usually driven by a regulation, a product change or a shift in working practice. Only the second shape justifies the effort of writing something properly.
4. Apply the credibility test
Ask one honest question: can you say something a reader could not already get from the current top results. First-hand experience, your own data, a clearer explanation, a practitioner's caveat. If the answer is no, skip it regardless of how well it is trending. This single test prevents most of the low-quality content that trend-chasing produces, and it protects the site-wide quality assessment that every one of your pages is judged against.
5. Check what already ranks
Search the question and actually read the top three results rather than skimming the titles. If they answer it thoroughly and recently, you need a genuine differentiator to justify competing. If they are thin, outdated, or answering a slightly different question than the one being asked, the opening is real and worth taking. This takes ten minutes and saves you from publishing into a space that is already well served.
6. Decide new page or existing page
If you have a page covering the area, adding a well-structured section is usually stronger than a new page: it builds on existing authority rather than starting from nothing. Reserve new pages for genuinely distinct topics.
7. Publish answer-first
Question as the heading, direct answer in the first few sentences, then context and follow-ups. Speed matters for a trend, but structure is what gets the answer quoted and found once it is live.
8. Review after a month and after six
At a month, check impressions and clicks to see whether it landed. At six months, decide whether to update, consolidate or remove. Trend content needs an exit plan or it becomes permanent clutter.
Tip: Adding a strong section to an existing relevant page usually outperforms a new page for a trending question, because it inherits authority instead of starting from zero — and it leaves you with one strong page rather than two weak ones.
Tags: questions-and-answers, trending q&a, howto
Tutorial
Understanding Trending Questions and Timing
How question trends behave, why timing dominates, and how to benefit without accumulating disposable content.
Why trends have a window
Interest in a question rises, peaks and falls. Publishing during the rise means you are indexed and ranking as demand grows. Publishing after the peak means competing with established pages for an audience that is already leaving. Timing matters more here than in any other content work.
Spikes versus sustained interest
A one-day spike usually reflects a news event and evaporates. A steady rise over a fortnight reflects genuine changing demand — a new regulation, a product change, a shift in how people work. Only the second justifies real effort, and telling them apart requires looking at shape rather than volume.
Why relevance filtering comes first
A trending question outside your expertise is a trap. You can rank briefly on freshness, but you will not answer well, the traffic will not convert, and the page becomes permanent low-quality content on a site whose overall quality is assessed as a whole.
The speed and depth tension
Trends reward speed; quality assessment rewards depth. The resolution is not to compromise both but to be quick about topics where you already have the knowledge, so a thorough answer can be written fast because you are not researching from scratch.
Updating beats republishing
Recurring questions — annual changes, seasonal concerns — should update one page rather than spawning a new one each cycle. The updated page accumulates authority, links and history; the yearly new pages compete with each other and split all three.
The half-life problem
Trend content dates faster than evergreen content because it addresses a specific moment. Without a review cycle it accumulates as a layer of outdated pages that drag on site-wide quality, which is the hidden cost of a trend-led strategy.
Where trending questions come from
Real-world events, product launches, regulation changes, seasonal patterns and media coverage all drive question volume. Recognising which of these is behind a trend tells you how long the window will stay open and whether it will recur.
Using trends as signal, not strategy
Trending questions are best used to inform and prioritise content you would have wanted anyway, rather than as a content strategy in themselves. A site built entirely on trends has no durable core and needs constant replenishment to stand still.
Tip: Before writing for a trend, ask what the page will be worth in a year. If the answer is nothing, consider whether a section on an existing page serves you better than a new page that will need deleting.
Tags: questions-and-answers, trending q&a, tutorial
Tutorial
Hand-writing JSON-LD is where most structured data goes wrong — a missing required property, the wrong type, two conflicting blocks on one page. The Schema Builder generates valid markup for the type you pick and validates what you already have. Here is what it does and when to reach for it.
Why hand-written JSON-LD fails so often
Structured data looks simple until you ship it. The Builder exists because the same six faults come up again and again, and every one of them is silent — the page renders perfectly and the markup is simply ignored. Missing required fields, the wrong type chosen for the page, broken @id references, over-nested objects, and two plugins each emitting their own block so the page carries conflicting schemas. None of these throw an error in a browser. What makes it worse is the feedback loop. A code error breaks a page and you find out immediately; a schema error changes nothing visible, so the only symptom is a rich result that never appears — and you have no way of knowing whether it never appeared because the markup is wrong or because Google simply chose not to display it. Weeks pass before anyone investigates, and by then the markup has usually been copied onto another fifty pages.
What the Builder actually does
Two modes. The Generator takes a schema type and the fields for it and emits a complete, correctly structured JSON-LD block ready to paste into your head. The Validator takes JSON-LD you already have and checks it against schema.org rules, reporting missing required properties and recommended ones you have skipped. The Types Reference lists the common types with their key properties and whether each is rich-result eligible. The distinction between the two modes matters in practice. Use the Generator when you are adding markup to a page that has none, or replacing markup you know is wrong. Use the Validator first when a page already has structured data, because on most real sites it does — added by a theme, an SEO plugin, or a previous developer — and generating a fresh block without checking is how pages end up with three competing descriptions of themselves.
Choosing the right type is most of the job
The single most consequential decision is which type to use, and it is the one people get wrong most often. A product page needs Product with an offers block, not WebPage. A step-by-step guide needs HowTo, not Article. A recipe needs Recipe with recipeIngredient and recipeInstructions. Choosing wrongly does not produce an error — it produces markup that is valid, ignored, and delivers nothing. A useful test: describe the page in one sentence to someone who cannot see it. If the sentence is 'this is a thing you can buy', it is Product. If it is 'this explains how to do something in order', it is HowTo. If it is 'this is a place with an address and opening hours', it is LocalBusiness. Where the sentence needs an 'and' — this is a product page and a buying guide — pick the primary purpose and mark up the secondary aspect separately if at all.
Required versus recommended properties
Required properties are the ones without which the markup is discarded for rich result purposes. Recommended properties are the ones that improve what you get. A Product with name and offers is eligible; the same Product with aggregateRating, brand and image is eligible for considerably more. The Builder distinguishes the two so you know which gaps actually cost you something. There is a third category worth knowing about: properties that are technically optional but which Google effectively expects for certain result types. Review markup without a visible reviewer, or an Offer without availability, will validate cleanly and still fail to produce the enhancement you were aiming for. This is why validating against schema.org and testing with Google's own tool are two separate steps rather than one.
@id references and nesting
Larger pages describe several connected things — an Organization that publishes an Article written by a Person. Rather than repeating the organisation inside every block, you give it an @id and reference that identifier elsewhere. Get the references wrong and the graph falls apart into disconnected fragments. Over-nest instead of referencing and it becomes unreadable to parsers. The practical rule is to define each entity once, give it a stable @id built from your canonical URL plus a fragment, and reference that identifier everywhere else it is needed. Something like https://example.com/#organization for your business, reused on every page. This keeps the graph consistent site-wide, and it means updating your organisation details is one change rather than a change on every page that mentions them.
The conflicting-schema problem
A very common state on WordPress: your theme emits schema, an SEO plugin emits schema, and a review plugin emits a third block. The page now declares itself as several conflicting things and search engines pick one or ignore all. The Builder's validator surfaces this, and the fix is to disable the duplicates rather than add a fourth. Diagnosing this is straightforward once you know to look. View source, search for application/ld+json, and count the blocks. More than one is not automatically wrong — a page can legitimately carry Organization, BreadcrumbList and Article — but two blocks both declaring the page as a different primary type is. The fix is almost always configuration rather than code: find which plugin emits which block and switch off the ones you do not want.
Where the Builder fits with the other schema tools
Use the Builder to create markup and check it is structurally sound. Use the Schema Debugger for deep validation of what is live on a page, property by property. Use Rich Snippets to see which result types your markup makes you eligible for. They are three stages of one workflow rather than three alternatives. A sensible order of work: Builder to create or correct, Debugger to validate what is actually live including anything the CMS adds, Rich Snippets to check eligibility and preview the result. Skipping the middle stage is the common mistake, because what you paste into a template and what the browser finally receives are often not the same thing once plugins and caching layers have had their turn.
Tip: Generate the markup here, then always confirm with Google's Rich Results Test before considering it done. schema.org validity and Google rich-result eligibility are different bars, and a block can clear the first while failing the second.
Tags: schema-builder, schema, json-ld, structured-data, rich-results
How-To
Step-by-step walkthrough of generating JSON-LD in the Schema Builder — picking the right type, filling required properties, validating existing markup, and pasting it into your page without creating conflicts.
1. Work out what the page actually is
Before touching the tool, decide what the page fundamentally describes. One product for sale is Product. A step-by-step guide is HowTo. A news or blog piece is Article. A location is LocalBusiness. A list of questions and answers is FAQPage. Getting this wrong is the most expensive mistake available, because the resulting markup is valid, ignored, and gives you nothing. Write the answer down before opening the tool. This sounds like an unnecessary step and it prevents the single most expensive error in structured data — marking up a category page as a Product because it has products on it, or a blog post as a HowTo because it happens to contain a numbered list. The markup describes what the page IS, not what it mentions.
2. Check what schema the page already has
Open the Validator tab first and paste in whatever JSON-LD the page currently emits — view source and search for application/ld+json. On a WordPress site you will often find more than one block, added by the theme and by plugins. Knowing what is already there prevents you adding a conflicting fourth block. On a WordPress site expect to find something. Yoast, RankMath and most themes emit Organization, WebSite, BreadcrumbList and often Article by default. That is usually fine and worth keeping — what you are looking for is whether anything already declares the specific type you are about to add, because that is the conflict that will cost you.
3. Open the Generator and pick your type
Go to /schema-tools.html#schema-builder and select the schema type from the dropdown. The Types Reference tab lists each common type with its key properties and whether it is rich-result eligible, which is worth reading before you commit to a choice. If your page could reasonably be two types, the Types Reference tab is the deciding factor: it shows which types are rich-result eligible and which are not. Person and Organization, for instance, offer limited rich result treatment, so where a page could be either Article or Person, Article is usually the more productive choice.
4. Fill every required property
Required properties are non-negotiable — without them the markup is discarded for rich results. Product needs name and offers. Article needs headline, author, datePublished and image. Recipe needs name, image, recipeIngredient and recipeInstructions. LocalBusiness needs name, address and telephone. Leave none of them blank. Values must come from the page itself, not from your intentions for it. If the page does not display a price, do not put one in the offers block. If there is no author byline visible, do not invent one. Markup that describes content the visitor cannot see is a policy breach, and the consequences reach beyond the single page to your site's rich result eligibility generally.
5. Add the recommended ones that apply
Recommended properties are where eligibility turns into a better result. aggregateRating and brand on a Product. totalTime on a HowTo. openingHours on a LocalBusiness. Only add what is genuinely true of the page — inventing a rating you do not display is a policy breach that can cost you rich results site-wide. A practical order: add the ones that unlock a visible enhancement first — aggregateRating for stars, offers availability for in-stock labels, image for thumbnails — then the ones that improve entity understanding, such as brand, sku and @id references. Anything you cannot populate from what is genuinely on the page gets left out rather than guessed.
6. Generate and read the output
Click Generate JSON-LD and read the block rather than copying it blind. Check the values are the real ones from your page, the URLs are absolute rather than relative, and the dates are in ISO 8601 format. This thirty-second read catches most of what would otherwise fail later. Four specific things to check in that read: dates in ISO 8601 format with a timezone, URLs absolute rather than relative, currency codes correct and matching the displayed price, and no placeholder text left in from the form. These four account for most of the faults that survive generation and only surface later in a rich results test.
7. Paste it into the head and remove the conflicts
Copy the script block into your page head. Then go back to whatever you found in step 2 and disable the duplicates — in the theme settings or the plugin options — so the page emits one authoritative block rather than several competing ones. If a plugin manages your schema, put the values into the plugin rather than the template. Pasting into a theme file that a plugin also writes to means one of them wins unpredictably, and it will usually be the plugin — so your change appears to work locally and vanishes in production or after the next update.
8. Validate live, then test with Google
Come back to the Validator tab, run the live URL, and confirm the block parses with no missing required properties. Then run the same URL through Google's Rich Results Test, because schema.org validity and Google eligibility are separate bars and only the second one produces a rich result. Validate the live URL rather than the code you pasted, because caching layers, minifiers and optimisation plugins can all alter markup between your template and the delivered page. It is common for JSON-LD to be valid in the source file and mangled by an HTML minifier before it reaches the browser.
Tip: If the page is on WordPress, check your SEO plugin's schema settings before adding anything. Most emit Article, Organization and BreadcrumbList already, and the fix is usually to configure what is there rather than to paste in a competing block.
Tags: schema-builder, howto, schema, json-ld, setup
Tutorial
Instead of choosing a type and filling fields by hand, the AI Schema Generator reads your page and produces complete JSON-LD from what is actually there. Here is what it does, where it helps most, and the specific things to check before you ship its output.
The problem it solves
Manual schema is accurate but slow, and it does not scale past a handful of pages. You have to identify the type, look up required properties, extract each value from the page, and format dates and URLs correctly. Do that across a hundred product pages and the errors creep in — a missing offers block here, a relative URL there. The generator reads the page and does the extraction for you. The scaling point is the real one. Marking up ten pages by hand is a manageable afternoon. Marking up a thousand product pages by hand is not something anyone does correctly, which is why in practice those sites either have no structured data at all or have it generated by a template that was written once and never revisited. Automated generation is the only realistic route to coverage at that scale.
How it works in practice
You paste a page URL and either let it auto-detect the type or choose one. It fetches the page, reads the content, identifies the entities on it, and emits a complete JSON-LD block ready for the head. There is also a manual builder tab for cases where you would rather specify everything yourself, which is the right choice when the page is unusual. Auto-detect is the right default for conventional pages and the wrong one for anything unusual. If the page is a straightforward product, article or business listing, let it detect. If the page is doing two jobs — a service page that is also a location page, a landing page that is also a guide — choose the type yourself, because an inferred primary type on a hybrid page is a coin toss.
Where auto-detection earns its keep
On conventional pages it is reliable: a product page with a price and a title, an article with an author and a date, a local business page with an address. It is at its weakest on hybrid pages — a category page that is also a guide, a landing page that is part product and part FAQ — where a human decision about the primary type is better than an inferred one. It also depends heavily on how well the page expresses itself in markup. A product page with a clear h1, a visible price in a recognisable format and a proper description gives the generator everything it needs. A page where the price is assembled by JavaScript and the title is a styled div gives it very little, and the output will reflect that.
The properties most worth checking
Four things account for most of the faults worth catching. Dates must be ISO 8601 with the right timezone. URLs must be absolute and resolvable, not relative paths. @id references must actually point at something. And required properties for your chosen type must all be present — a Product without offers is valid markup and ineligible for anything. Dates are the most frequently wrong and the easiest to miss, because a human-readable date looks correct while being invalid to a parser. datePublished must be a full ISO 8601 value with a timezone offset, not '12 March 2026'. The same applies to durations on Recipe and HowTo, which use ISO 8601 duration format rather than plain minutes.
Duplicates are the usual live problem
On WordPress especially, generated schema frequently lands on a page that already has schema from Yoast or a similar plugin. The page then declares itself twice, sometimes inconsistently. Either put the generated block into the plugin's own schema settings, or disable the plugin's output for that page type — do not simply paste a second block into the head. The cleanest resolution on WordPress is usually to let the SEO plugin own the schema and to feed it the generated values through its own fields, rather than adding a parallel block. Where the plugin cannot express what you need, disable its output for that specific post type instead of trying to have both — two blocks describing the same entity differently is worse than either alone.
Accuracy is the hard limit
The generator describes what it can read on the page. If the page does not state the brand, the generator cannot invent one, and it should not. Any property that is not visible on the page has no business in the markup either — structured data that contradicts or exceeds the page content is a policy breach, not a shortcut. This is worth stating plainly because it is where automated generation gets people into trouble. The tool can only describe what it can read. If you want richer markup, the answer is to put the information on the page — add the brand, show the rating, display the author — and regenerate. It is never to add properties to the markup that the page does not support.
Where it fits alongside the other tools
Use the AI generator for speed across many pages. Use the Schema Builder when you want deliberate control over a specific important page. Use the Schema Debugger to validate whatever is live, property by property. Generating is the fast part; validating is the part that determines whether it works. In a bulk workflow, generate across the set, then validate a sample of each template rather than every page. Faults in generated markup are systematic rather than random — if the product template produces a broken offers block, it produces it on every product — so checking one page per template gives you full coverage of the fault modes.
Tip: Always run generated markup through Google's Rich Results Test before shipping it at scale. Generating a hundred blocks quickly is only a saving if they are eligible — a hundred ineligible blocks is a hundred pages to redo.
Tags: ai-schema, schema, json-ld, ai, structured-data
Tutorial
Validators tend to tell you whether markup parses. The Schema Debugger goes further — property by property against schema.org rules, rich result eligibility, and AEO signals. Here is what it checks and how to read what it reports.
Parsing is not the same as working
A JSON-LD block can parse perfectly and still deliver nothing. It may be the wrong type for the page, missing a property that rich results require, referencing an @id that does not exist, or describing something not visible on the page. Each of those is invisible to a basic syntax check and fatal to the outcome you wanted. The gap between the two is where most wasted effort in structured data lives. Teams validate with a syntax checker, see a green result, and conclude the job is done — then spend months wondering why no rich results appear. Syntax validity is the floor, not the standard, and a tool that only reports parse errors will always tell you what you want to hear.
What the Debugger examines
It fetches every JSON-LD block on the page and validates each against schema.org: required properties, recommended properties, correct value types, resolvable references, and whether the declared type suits the page. It separates errors, which disqualify you, from warnings, which merely reduce what you get — a distinction that determines what you fix first. It reads every block on the page rather than the first one, which matters more than it sounds. Pages frequently carry several blocks from different sources, and the broken one is rarely the one you were thinking about. A tool that validates only the first block you paste will miss the plugin-generated block that is actually causing the problem.
Errors versus warnings
Errors mean the markup will not produce the rich result: a missing required property, an invalid value type, a malformed date. Warnings mean it works but underperforms: a missing image on an Article, no aggregateRating on a Product. Fix every error before you touch a single warning, because until then the warnings are irrelevant. Triage this way: if the report shows any errors, ignore the warnings entirely until they are cleared. An error means nothing downstream works, so improving a recommended property while a required one is missing is pure waste. Once errors are at zero, warnings become a genuine list of improvements ordered by what they unlock.
The type-specific traps
Certain types fail in predictable ways. Product needs a properly formed offers block with price and priceCurrency. FAQPage needs mainEntity with Question objects each containing an acceptedAnswer. HowTo needs step objects rather than a plain list. Recipe needs both recipeIngredient and recipeInstructions. aggregateRating needs ratingValue and reviewCount, and the ratings must be visible on the page. A pattern runs through all of these: the types that produce the most valuable rich results are also the ones with the strictest requirements. Product, Recipe and Review carry real commercial upside and correspondingly detailed rules, including requirements about what must be visible on the page. Simpler types like Article are easier to satisfy and deliver less.
Reference and nesting problems
Complex pages link entities by @id — an Article referencing its author and publisher. If a reference points at an identifier that does not exist on the page, the graph fragments and the connected entities are lost. The Debugger resolves these references and reports the ones that lead nowhere, which is a fault almost nothing else surfaces. These faults are particularly common after a migration or a template refactor, because @id values are frequently built from the site URL. Change the domain or the URL structure and every reference breaks at once, silently, across the whole site. It is worth re-running the Debugger deliberately after any change that affects canonical URLs.
The AEO angle
Structured data is not only about rich results any more. AI answer engines use it to identify entities, attribute content and decide what a page is about. Clean Organization, Person and FAQPage markup makes you easier to cite correctly. The Debugger flags the properties that matter for that as well as the ones Google requires. Concretely, this means Organization markup with a consistent @id, sameAs links to your real profiles, Person markup for named authors with genuine credentials, and FAQPage markup on pages that genuinely answer questions. These are the properties that let an AI system identify who you are and attribute a claim to you rather than paraphrasing you anonymously.
Using it after every deploy
Schema breaks silently during template changes and plugin updates, and the symptom — rich results quietly disappearing — shows up weeks later in traffic data. Running the Debugger on a page of each type after a deploy turns a delayed mystery into an immediate, fixable finding. Build it into the release routine rather than treating it as an occasional audit. One page per template, checked after each deploy, takes a few minutes and catches the regression on the day it ships. The alternative is discovering it from a Search Console enhancement report weeks later, by which time the recrawl has already removed your rich results.
Tip: Debug a page from each template rather than a sample of URLs. Schema faults cluster by template, so one product page tells you about every product page, while ten random URLs may miss an entire broken type.
Tags: schema-debugger, schema, validation, rich-results, aeo
How-To
Walkthrough of running the Schema Debugger — fetching every block on a URL, reading errors against warnings, resolving reference failures, and confirming the fix.
1. Pick one page per template
Do not start with a list of random URLs. Choose one product page, one article, one category, one location page — whatever templates your site has. Schema faults cluster by template, so this gives you full coverage in a fraction of the work. Write the list down and reuse it. The same set of representative URLs, checked after every significant deploy, gives you comparable results over time and turns debugging from an investigation into a routine check. Ad hoc URL selection each time makes it impossible to tell whether something has changed or you simply looked somewhere different.
2. Run the URL through the Debugger
Go to /schema-tools.html#schema-debugger, choose the From URL tab, and enter the page. It fetches and validates every JSON-LD block on that page, which matters because sites frequently have more than one and the problem is often in the one you forgot about. Use the live URL rather than pasting source you copied earlier. What the server delivers today, after caching, minification and any plugin updates since, is the only version that matters — and it is frequently not identical to what is in your template files.
3. Count the blocks before reading anything
Look first at how many blocks came back. More than you expected means something else is emitting schema — a theme, an SEO plugin, a reviews plugin. That is a finding in itself and often explains contradictory results in Search Console. If the count surprises you, resolve that first. Two blocks declaring different primary types for the same page is the most common cause of rich results that appear and disappear unpredictably, and no amount of fixing properties within one block will settle it while the other is still there.
4. Fix every error before any warning
Work through the errors first: missing required properties, wrong value types, malformed dates, unresolvable references. These disqualify the markup entirely, so time spent on warnings while an error is present is time wasted. Work through them in the order the tool reports them, fixing one and re-running rather than fixing several at once. Schema errors interact — a malformed offers block can produce three separate error lines — so batching changes makes it hard to tell which fix actually resolved what.
5. Chase the reference failures
Any @id that does not resolve breaks the connection between entities — an Article whose author reference points nowhere loses its author. Either define the referenced entity on the page or remove the reference. A dangling reference is worse than no reference at all. To check an @id quickly, search the page source for the exact identifier string. If it appears only once, in the reference, then nothing defines it and the reference is dangling. If it appears twice, once as a definition and once as a reference, that link is sound.
6. Check the markup against what the page shows
Structured data must reflect visible content. If the schema declares an aggregateRating, the ratings must be on the page. If it declares a price, that price must be the one displayed. Mismatches are a policy breach that can affect rich results well beyond the single page. Open the page alongside the report and compare values literally. Price in markup against price displayed. Rating in markup against rating displayed. Author in markup against byline. This is a two-minute check that catches the class of problem that costs sites their rich result eligibility site-wide.
7. Work through the warnings that pay
Now the recommended properties. Add the ones that genuinely apply and that you can populate truthfully from the page — image, brand, datePublished, totalTime. Skip anything you would have to invent to satisfy. Prioritise by what each one unlocks rather than by how many there are. An Article missing its image warning is worth fixing because it affects display; an Organization missing an optional contact point usually is not. The tool lists them; the judgement about which matter is yours.
8. Re-run, then confirm with Google
Re-run the Debugger on the same URL and confirm errors are at zero. Then run it through Google's Rich Results Test, because schema.org validity and Google eligibility are separate standards and only the second one puts a rich result on the page. Then wait for recrawl before drawing conclusions. Nothing you fix appears in search results until the page is fetched again, which is days to weeks. Checking Search Console's Enhancements report a fortnight later is what confirms Google actually accepted the change.
Tip: If the Debugger reports more blocks than you expected, resolve that before anything else. Two conflicting blocks describing the same page is a common cause of rich results appearing, disappearing and reappearing for no apparent reason.
Tags: schema-debugger, howto, schema, validation, debugging
Tutorial
Rich results turn a plain blue link into stars, prices, FAQs or images — and eligibility depends on markup you control. The Rich Snippets Suite previews how a result could look, recommends which types suit your page, and tests a live URL for eligibility.
What a rich result actually is
An ordinary result is a title, a URL and a description. A rich result adds structured elements drawn from your markup: star ratings, prices and availability, FAQ dropdowns, recipe times and images, event dates, job details, breadcrumbs, video thumbnails. The same position on the page occupies more space and reads as more credible, which is where the click-through gain comes from. The commercial case is simply space and attention. A result with star ratings, a price and an availability label occupies several times the vertical space of a plain link and carries information the user wanted before clicking. That combination is why the same ranking position can deliver substantially different traffic depending on whether the enhancement is present.
Eligibility is not a guarantee
This is the point most people miss. Valid markup makes you eligible; it does not oblige Google to show anything. Display depends on the query, the device, the competition and Google's own judgement about whether the enhancement helps. A page can be perfectly eligible and show a plain blue link, and that is normal rather than a fault. This distinction matters for how you spend effort. Eligibility is entirely within your control — markup, visible content, policy compliance. Display is not. So the correct goal is to be eligible everywhere it is appropriate, then measure what actually appears, rather than treating a missing enhancement as a fault to be chased.
What the suite provides
Three parts. The SERP Preview Builder shows how a result would look with a given title, description and rich result type. The Type Recommender takes a description of your page and suggests which rich result types are worth pursuing. The Rich Results Tester takes a live URL, or raw JSON-LD, and reports what could be generated from it. The three parts map onto three different moments. Recommender when you are deciding whether a page type is worth marking up at all. Preview Builder when you are writing or revising the title and description. Tester when markup exists and you want to know what it currently makes you eligible for.
Choosing which types to pursue
Not every type suits every page, and chasing the wrong one wastes effort. Product with ratings suits ecommerce. FAQ suits support and service pages. HowTo suits guides. Recipe, Event and JobPosting suit exactly what they describe. Article helps news and blog content. The recommender exists to stop you marking up a page as something it is not. Weigh commercial value as well as fit. FAQ markup is easy to add and its rich result treatment has been narrowed considerably, so the return is smaller than it once was. Product with ratings and availability remains one of the highest-value enhancements available for ecommerce. Same effort, very different outcomes.
Why eligible results sometimes never appear
Common causes: the markup is valid but the page does not visibly contain what it claims; the rich result type has been narrowed by Google since you implemented it; the query does not warrant an enhancement; or a competing page has stronger signals. Checking eligibility separates your problem from Google's decision. Before assuming a fault, check the obvious: is the page indexed at all, is it ranking anywhere for the query you are testing, and do the results that do show enhancements come from sites with substantially stronger authority. A page that is eligible but ranking on page three will not show a rich result because it is not being shown at all.
Coverage drops and what they mean
Rich results disappearing across many pages at once almost always means a markup regression — a template change, a plugin update, a theme switch. A gradual decline usually means Google has changed what it displays for that type. The response differs completely, so establishing which one you are looking at comes first. Date the drop precisely using Search Console's Enhancements report, then check your deploy history against that date. A drop that aligns with a release is yours to fix. A drop with no corresponding change on your side, affecting one result type across many sites, is Google narrowing what it displays — and no amount of markup work will reverse it.
How this fits with the other schema tools
Build the markup in the Schema Builder or AI Generator, validate it in the Debugger, then use Rich Snippets to see what it makes you eligible for and how it would look. This is the stage where structured data stops being a technical exercise and becomes a click-through decision. The order matters because each stage assumes the previous one succeeded. Testing eligibility on markup that has not been validated wastes time on faults the Debugger would have named precisely. Previewing a result for a page whose markup is not eligible tells you nothing about what will actually appear.
Tip: Track click-through rate in Search Console before and after a rich result appears, not just whether it appears. The whole point is more clicks at the same position, and occasionally an enhancement makes no difference at all.
Tags: rich-snippets, schema, serp, rich-results, ctr
How-To
Walkthrough of picking the right rich result type, previewing the SERP appearance, testing eligibility on a live URL, and diagnosing why an eligible page shows nothing.
1. Decide what the page genuinely is
Rich result types map to page types: Product for something for sale, Recipe for a recipe, FAQPage for questions and answers, HowTo for a procedure, Event for a dated occasion, JobPosting for a vacancy. Marking a page as something it is not will either be ignored or treated as a policy breach — never as a shortcut. If two types both seem to fit, ask which one a searcher would be looking for when they arrive. Someone searching for a recipe wants ingredients and cooking time; someone searching for a product wants price and availability. The rich result should answer the question that brought them, and that tells you which type to mark up.
2. Use the Type Recommender if unsure
At /schema-tools.html#rich-snippets, tell the recommender your page type and it will suggest which rich result types are realistically available. This is quicker than reading documentation for a dozen types and it prevents pursuing one your page cannot support. Treat its output as a shortlist rather than an instruction. It suggests what is available for your page type; the decision about which to implement should also weigh how much work each requires and what each actually displays, which is where the Types Reference and the preview help.
3. Preview the result before building anything
Use the SERP Preview Builder to enter your title, description and chosen rich result type. Seeing the appearance first tells you whether the enhancement is worth the implementation work for this particular page, and whether your title survives truncation. This step often changes the plan. Seeing that a FAQ enhancement adds two collapsed lines while a Product enhancement adds stars, a price and availability makes the prioritisation obvious in a way that reading documentation does not.
4. Test what the live URL currently produces
Switch to the Rich Results Tester, enter the live URL, and see what can be generated from the markup already there. Frequently a page is closer than expected — one missing property away from eligibility rather than needing markup from scratch. Pages frequently have partial markup already, added by a plugin or a previous effort. Knowing you need one property rather than a full implementation changes a half-day task into a ten-minute one, and it avoids adding a second competing block.
5. Add the properties eligibility requires
Work from the tester output. Product needs offers with price and priceCurrency, and aggregateRating if you want stars. FAQPage needs mainEntity with Question and acceptedAnswer objects. Recipe needs recipeIngredient and recipeInstructions. Add exactly what is required and true. Add them to the existing markup rather than creating a new block alongside it. A page with two Product blocks, one complete and one partial, is a worse position than the partial block alone, because now something has to choose between them.
6. Make sure the page displays what the markup claims
Every marked-up value must be visible to a visitor. Ratings in the markup mean ratings on the page. Prices in the markup mean that price displayed. FAQ answers in the markup mean those answers rendered. Invisible claims are a policy breach and can cost rich results across the whole site. This applies particularly to ratings and prices, which are the properties most often marked up without being displayed. If you want stars, the ratings must be visible to a visitor on that page — not on a linked reviews page, not in a tab that loads on click, but present in the rendered content.
7. Validate, then wait
Re-test the URL and confirm eligibility, then run Google's own Rich Results Test to confirm. Then wait — the page has to be recrawled before anything can change, which takes days to weeks. Re-implementing during that window is how people break working markup. Use the wait productively by implementing the same markup across the rest of that template, so the whole set is recrawled together. Fixing one page, waiting, then fixing the rest means two full recrawl cycles instead of one.
8. Diagnose if nothing appears
Still nothing after several weeks with confirmed eligibility. Check Search Console Enhancements for errors on that type, check the page is indexed at all, and check whether competitors showing rich results have signals you do not. Eligibility is yours to control; display is not. Work through it in order: indexed, ranking, eligible, enhancement type still supported. Most 'my rich results are not showing' cases resolve at one of the first two, and no amount of markup refinement addresses a page that is not ranking for the query in the first place.
Tip: Search Console's Enhancements reports are the authoritative view of what Google actually parsed across your indexed pages. A tester checks one URL in isolation; Enhancements shows the pattern across the site, which is what tells you whether a fault is systemic.
Tags: rich-snippets, howto, schema, serp, rich-results
Tutorial
When someone shares your link, the preview is built from Open Graph and Twitter Card tags. Get them wrong and you get a blank card, the wrong image, or a stale title from months ago. Here is what the tags do and which ones actually matter.
What the tags control
Open Graph tags tell Facebook, LinkedIn, WhatsApp, Slack, Discord and iMessage what title, description and image to show when your URL is shared. Twitter Cards do the same for Twitter and X. Without them the platform guesses from the page — usually badly, sometimes picking a navigation image and the first sentence of a cookie banner. The practical consequence is that your link's first impression on social is decided by three tags, not by your page design. A well-designed page shared with no og:image looks like a broken link, while a plain page with correct tags looks professional. The tags are doing work entirely disproportionate to their size.
The tags that matter most
og:title, og:description, og:image and og:url do most of the work. og:type distinguishes an article from a website and unlocks article-specific fields. twitter:card sets the layout, with summary_large_image being the one most sites want. Everything else is refinement — these five determine whether the preview looks right. Everything beyond those five is refinement — og:site_name, og:locale, article:published_time and the rest improve specific cases but do not decide whether the card renders. If you are triaging a broken preview, check the five first and ignore the rest until they are right.
The image is the whole preview
In practice the image is what makes a shared link look credible or broken. It needs to be large enough — 1200 by 630 is the common target — served over HTTPS, and at an absolute URL. Too small and platforms fall back to a small thumbnail or nothing. Relative URLs simply fail, which is one of the most common faults there is. Design the image for the crop rather than the full frame. Platforms crop from the edges at different ratios, so text near the border is routinely cut. Keep anything that must be readable within the central area, and check it at small sizes — many previews render considerably smaller than the source image.
Why previews go stale
Platforms cache aggressively. Change your title or image and the old preview can persist for days or longer, because the platform is serving what it fetched the first time your link was shared. This is not a fault in your tags and it cannot be fixed by editing them again — it needs the platform's own cache to be refreshed. Because of this, the correct sequence when fixing a preview is always: correct the tags, verify they are live, then force the platform to re-scrape. Doing it in the other order simply re-caches the broken version, and you then have to wait again before you can even test whether your fix worked.
Title mismatches and why they happen
og:title and the page title tag are separate, and it is normal for them to differ deliberately — a shorter, punchier title for social. What is not intended is them drifting apart because one was updated and the other forgotten, so the shared preview advertises something the page no longer says. The most reliable way to prevent drift is to have one source of truth. Either let your SEO plugin derive og:title from the page title automatically, or set both deliberately and treat them as a pair when editing. What causes problems is a template that sets one and a plugin that sets the other.
Platform differences worth knowing
Each platform reads the tags slightly differently. LinkedIn is strict about image dimensions. WhatsApp and iMessage use smaller previews and truncate more aggressively. Slack and Discord unfurl differently again. Testing one platform and assuming the rest is how a broken LinkedIn preview goes unnoticed for months. If your audience is concentrated on one platform, test that one properly rather than testing all of them superficially. A B2B site whose links are shared mainly on LinkedIn should be optimising for LinkedIn's stricter image handling, not for Facebook's more forgiving one.
How this fits with Social Preview
The generator produces the tags. The Social Preview tool shows what the result looks like on each platform. Use them together — generate, paste, then preview across platforms rather than trusting that correct tags always produce a correct-looking card. Correct tags are necessary and not sufficient — the tags can be perfectly valid while the resulting card looks wrong because of cropping or truncation. That is precisely the gap the preview tool covers, which is why the two are used together rather than one instead of the other.
Tip: Set og:image to an absolute HTTPS URL every time. A relative path is the single most common reason a preview renders with no image at all, and it is invisible until someone actually shares the link.
Tags: og-generator, open-graph, twitter-card, social, sharing
How-To
Walkthrough of generating complete OG and Twitter Card tags from a URL, checking the audit output, pasting them into the head, and clearing stale platform caches.
1. Run your page through the generator
Go to /schema-tools.html#og-generator and enter the page URL. It reads the page and produces a complete set of Open Graph, Twitter Card, Facebook and LinkedIn tags based on what is actually there, which is faster and more accurate than writing them by hand. Run a page that represents a template rather than your homepage. Homepages usually have hand-crafted tags; the pages that matter for sharing are articles and products, and those are generated from templates where a fault affects everything at once.
2. Read the tag audit before the output
The audit section tells you what the page currently has and what is missing. This matters because you may only need to add two tags rather than replace the lot — and replacing tags a plugin manages will simply be overwritten on the next update. The audit distinguishes missing tags from present-but-wrong ones, and the remedies differ. Missing means adding; wrong usually means finding what generates it and correcting there, because adding a second tag with the right value leaves two and platforms will pick one unpredictably.
3. Check the image tag first
og:image is where most previews fail. Confirm it is an absolute HTTPS URL, that the file is at least 1200 by 630, and that it actually loads when you paste the URL into a browser. A relative path or a small image produces a preview with no image, which is the most common complaint there is. Also confirm the image is not blocked to crawlers. An image behind a hotlink protection rule or in a directory disallowed in robots.txt will load fine for you in a browser and fail for the platform scraper, which produces a preview with no image and no obvious cause.
4. Check og:title against the page title
They can differ deliberately, and often should — a shorter, sharper title works better on social. What you are looking for is unintended drift, where one was updated and the other left behind so the preview advertises something outdated. Where they differ, make sure it is a decision rather than an accident. A deliberate difference — a longer descriptive page title, a punchier social title — is good practice. An accidental difference means someone updated one field and not the other, and the preview is now advertising last month's version.
5. Set og:url to the canonical
og:url should be the canonical URL, not the URL with tracking parameters attached. This is what consolidates share counts and what stops the same page being treated as several different links across platforms. This also matters for engagement metrics. Platforms aggregate reactions and shares per URL, so a page shared with three different parameter combinations accumulates three separate counts rather than one. Setting og:url to the canonical consolidates them.
6. Paste the tags into the head
Copy the generated tags into your page head, above any existing conflicting ones, and remove the duplicates. If an SEO plugin manages these tags, put the values into the plugin's social settings instead — pasting into the template will be overwritten. Put them in the head, not the body. Some platforms only parse the head, and a tag placed after the opening body tag will be ignored by them while appearing correct in your source — a fault that produces inconsistent results across platforms and is very hard to spot.
7. Preview across the platforms
Use the platform preview tabs to check Facebook, Twitter, LinkedIn and iMessage rendering. Each truncates differently and LinkedIn is stricter about images, so a card that looks right on one can be broken on another. Check the ones your audience actually uses first, then the rest. Whatsapp and iMessage previews matter far more than most people expect, because a large share of link sharing happens privately in messaging apps rather than publicly on social platforms.
8. Clear the stale caches
Platforms cache the first version they fetched. Use Facebook's Sharing Debugger and Twitter's Card Validator to force a refresh, then reshare a test link. Without this step your corrected tags will not be visible to anyone for days. Note that the debuggers show you what the platform sees now, which is a useful diagnostic in itself. If the debugger reports different tag values from your page source, something between your server and the scraper is altering the response — a cache, a CDN rule, or a plugin serving different output to bots.
Tip: Fix the tags first, then clear the cache — in that order. Clearing the cache before the tags are corrected simply re-caches the broken preview and you have to wait all over again.
Tags: og-generator, howto, open-graph, twitter-card, setup
Tutorial
A link that looks right on Facebook can be broken on LinkedIn and blank in WhatsApp. The Social Preview Tester shows how your URL renders across every major platform before you post it, so a broken card is caught by you rather than your audience.
Why one preview check is not enough
Each platform reads your tags with its own rules, its own image requirements and its own truncation. A card that looks correct on Facebook can show no image on LinkedIn, a truncated title on WhatsApp, and a bare URL in Slack. Checking one platform and assuming the others match is how a broken preview survives for months. The variation is not cosmetic. LinkedIn will refuse an image below its minimum and show a text-only card. WhatsApp shows a small square crop. Slack shows a compact unfurl with the description truncated hard. The same tags produce four materially different first impressions, and only looking at each one tells you which are acceptable.
What the tester shows
It fetches your URL and renders how the link would appear on Google, Facebook, Twitter or X, LinkedIn, WhatsApp, iMessage, Slack and Discord. You see the image cropping, the title truncation point and the description handling for each, side by side, before you post anything. Seeing them side by side is the point. Judged individually each card looks plausible; compared together it becomes obvious that the title survives on two platforms and loses its key word on the other two, or that the image crop works in landscape and fails in square.
The image is what fails most
Most broken previews are image problems: a relative URL that does not resolve, an image below the minimum size, an image served over HTTP on an HTTPS page, or an aspect ratio that crops badly at 1200 by 630. Seeing the actual crop per platform is how you catch the ones that are technically valid and visually wrong. A quick diagnostic: copy the og:image URL from your page source and open it directly in a browser. If it does not load, nothing else matters. If it loads but is small, expect fallbacks. If it loads and is large, the remaining risk is cropping, which is what the per-platform previews show you.
Truncation differs everywhere
Titles and descriptions are cut at different points on every platform, and WhatsApp and iMessage are considerably more aggressive than Facebook. A title that reads well in full can lose its most important word on the platform your audience actually uses, which you only see by looking. Because of this, front-loading is not a style preference but a functional requirement. Write the title so that the first forty characters carry the meaning, and treat everything after that as expendable — because on at least one platform your audience uses, it will be expended.
Cache staleness is the other half
Even correct tags produce a wrong preview if the platform cached an older version. This is why a card can look fine in the tester and wrong when actually shared. The tester shows what the tags would produce; the platform shows what it fetched previously, and the two disagree until the cache is refreshed. This is also why a preview can look correct in testing and wrong when a colleague shares it — they are seeing a cached version from an earlier fetch. Testing proves the tags are right; only re-scraping and a real share prove what people will actually see.
Check before campaigns, not after
The cost of a broken preview is highest exactly when it matters most — a launch post, a paid campaign, a newsletter send. Once a link is shared with a broken card, the poor first impression is already made and refreshing the cache does not undo it for anyone who has seen it. It is worth building this into the publishing routine for anything with a launch attached. Thirty seconds of preview checking before a campaign link goes out costs nothing; a broken card on a post that reaches thousands of people cannot be retrieved.
How this fits with the OG generator
The OG generator produces and fixes the tags. This tool shows the consequence across platforms. Generate, paste, preview, then refresh caches — that sequence catches both the tag faults and the rendering surprises that correct tags can still produce. Together they cover both failure modes: tags that are wrong, and tags that are right but render badly. Neither tool alone catches both, and the second failure mode is the one that survives longest because everything looks correct in the source.
Tip: Test the exact URL you are going to share, including any tracking parameters. Platforms treat a URL with parameters as a distinct link with its own cache, so the version you tested may not be the version people see.
Tags: social-preview, open-graph, social, sharing, preview
How-To
Walkthrough of previewing a URL across platforms, identifying which tag causes each fault, fixing it, and forcing the platform caches to refresh.
1. Preview the exact URL you will share
Go to /schema-tools.html#social-preview and enter the precise URL, including tracking parameters if you will be sharing it that way. Platforms cache per URL, so a version without parameters is a different link with a different cached preview. If you use different tracking parameters for different channels, test each variant you will actually post. It is common for one channel's link to have been shared and cached long ago while another is fresh, so the same page can preview correctly in one place and badly in another.
2. Compare every platform tab
Work through Facebook, Twitter, LinkedIn, WhatsApp and iMessage rather than checking one. You are looking for three faults specifically: a missing or badly cropped image, a title cut at an unfortunate point, and a description that does not survive truncation. Note which faults are universal and which are platform-specific, because they point at different causes. A missing image everywhere is a tag or file problem. A missing image on LinkedIn only is almost always a dimensions problem, since LinkedIn enforces a minimum the others do not.
3. Diagnose a missing image
No image on any platform means og:image is missing, relative, or not resolvable. Copy the og:image value from your page source and paste it directly into a browser — if it does not load there, it will not load for a platform either. It must be absolute and HTTPS. Also check the image is served over HTTPS. An http:// image referenced from an https:// page is mixed content, and several platforms will refuse it outright even though the file itself is perfectly reachable.
4. Diagnose an image that appears but crops badly
This is a dimensions problem rather than a tag problem. Aim for 1200 by 630 with the important content near the centre, since platforms crop from the edges differently. A tall or square image will be cut unpredictably across platforms. Test by viewing your image at 1.91:1 and at 1:1 and checking both are acceptable. Those two ratios cover most platform behaviour, and an image that survives both will not surprise you anywhere.
5. Fix titles that truncate badly
Front-load the meaningful words. If the platform your audience uses cuts at sixty characters, the brand name at the end is the part that disappears, not the part that matters. og:title can differ from your page title, so optimise it for the preview specifically. A useful discipline is writing the title, then deleting the last third and reading what remains. If it still communicates the page, the title survives truncation. If it becomes meaningless, the important words are in the wrong place.
6. Correct the tags at source
Edit the tags in your page head, or in your SEO plugin's social settings if one manages them. Editing the template while a plugin controls the output means your change is discarded on the next page render. After editing, view the live page source and confirm your values are actually there. Caching plugins and CDNs frequently serve an older version of the head for some time after a change, and testing before the cache clears produces a false negative that sends you chasing the wrong problem.
7. Force the caches to refresh
Use Facebook's Sharing Debugger and Twitter's Card Validator to re-scrape the URL. LinkedIn has its own post inspector. Until you do this, the old preview persists regardless of what your tags now say. Do this for each platform separately — refreshing Facebook's cache does nothing for LinkedIn or Twitter. Each maintains its own, and each needs its own re-scrape before your corrected preview is what people see.
8. Re-preview and share a test link
Run the tester again to confirm the corrected rendering, then share the link privately — a direct message to yourself works — and check the real card. The tester predicts; the actual share confirms. Share it somewhere private first — a direct message to yourself, a test channel — rather than posting publicly to check. A public post with a broken card cannot be quietly retried, and deleting and reposting loses whatever engagement it had.
Tip: Do this before a launch or a paid campaign, never after. A broken card on a campaign post has already made its impression by the time you notice, and refreshing the cache does nothing for the people who have already scrolled past.
Tags: social-preview, howto, open-graph, social, debugging
Tutorial
Your title and description are the advert for your page, and Google truncates or rewrites both. The SERP Preview shows how a result will actually appear before you publish, and what to do when Google rewrites what you wrote.
Why previewing matters
A title and description look fine in a CMS field and quite different in a search result. Google truncates by pixel width rather than character count, so a title with wide characters is cut earlier than one with narrow ones. Previewing shows the actual cut point rather than an estimate from a character counter. Character counters are the usual substitute and they are unreliable, because Google measures pixels. A title full of capitals, wide letters or em dashes truncates considerably earlier than the same character count in narrow lowercase text. Seeing the rendered cut point is the only way to know where yours falls.
What the tool shows
It renders your title, description and URL as they would appear in a Google result, including the truncation point and how the URL displays as a breadcrumb. It also indicates which rich result type could apply, so you can see the whole result rather than just the text. It also shows the URL as a breadcrumb path rather than a raw address, which is how results have displayed for some years now. Deep or parameter-heavy URLs read badly in that format, and seeing it rendered makes the case for a cleaner structure more effectively than any argument about best practice.
Titles get rewritten and that is normal
Google rewrites a substantial proportion of titles, usually when yours is too long, keyword-stuffed, identical across many pages, or does not match the query intent. A rewrite is not a penalty. It is a signal that your title was not serving the query as well as something else on the page — often your own H1. The rewrite rate is high enough that treating every instance as a problem will waste a great deal of time. The cases worth investigating are pages where the rewrite is clearly worse than what you wrote, or where a rewrite coincides with a click-through decline — not simply the fact that a rewrite occurred.
How to reduce rewrites
Keep titles within the visible width, make them unique per page, describe the page honestly rather than stuffing terms, and make sure the title matches what the page actually delivers. Titles that mirror the H1 and describe real content are rewritten far less often than ones written for a keyword tool. One specific and common cause: templated titles that append the same suffix to every page. When a hundred pages all end with the same brand and category string, Google frequently discards the whole title in favour of the H1. Shortening or removing the suffix on important pages often stops the substitution.
Descriptions are rewritten more often still
Google frequently ignores your meta description and generates one from page content matching the query. This is usually reasonable behaviour — a query-specific snippet often serves the user better than a generic description. Write a good one anyway, because it is used when it fits and it is what shows on social and in some other surfaces. Because generated snippets are query-specific, a page ranking for many queries will show many different descriptions. This is worth understanding before you spend a day rewriting descriptions: for pages with diverse query profiles, your description may rarely be shown at all, whatever its quality.
The rest of the result is not text
Breadcrumbs, star ratings, thumbnails and sitelinks all come from other signals — URL structure, structured data, images and site authority. The preview shows how these change the visual footprint, which matters because a result with breadcrumbs and stars occupies far more attention than a plain one at the same position. Breadcrumbs come from BreadcrumbList markup or a clear URL hierarchy, stars from valid Review or aggregateRating markup on visible ratings, thumbnails from images the crawler can associate with the page, and sitelinks from site structure and internal linking. Each is earned separately and none is guaranteed.
Where this fits
Use the preview when writing or revising titles and descriptions, particularly for pages that already rank but under-perform on click-through. A page at position four with a poor title is a cheaper win than trying to move it to position three. The economics are usually favourable. Moving a page from position four to three is difficult and slow; improving its click-through at position four is a title rewrite and a fortnight's wait. For pages with impressions already, the second is nearly always the cheaper route to more traffic.
Tip: Use Search Console to find pages with high impressions and low click-through, then preview and rewrite those titles first. They are ranking already — the traffic is being lost at the result, not at the ranking.
Tags: serp-preview, serp, title-tag, meta-description, ctr
How-To
Walkthrough of previewing a result, fixing truncation, reducing the chance of a Google rewrite, and measuring whether the change improved click-through.
1. Find the pages worth fixing
Open Search Console, sort queries by impressions, and look for pages with many impressions and a low click-through rate. Those already rank — the loss is happening at the result itself, which makes them the cheapest wins available. Filter to a consistent date range and exclude branded queries, which naturally have very high click-through and will distort the comparison. What you want are informational and commercial queries where you rank in the top ten and click-through sits below what that position normally earns.
2. Preview the current result
Go to /schema-tools.html#serp-preview and enter the existing title, description and URL. See where the truncation actually falls. Pixel width rather than character count decides this, so a title that passed a character counter can still be cut mid-word. Look at where the cut lands in relation to meaning, not just character count. A title cut after a complete useful phrase is acceptable; one cut mid-word or before the differentiating term is not, even if both are technically the same length.
3. Front-load the meaningful words
Whatever matters most goes first. If the title is cut, the end disappears — so a brand name or a category suffix should never carry the meaning. Write so that the first forty characters can stand alone if the rest is lost. A practical test is to read only the first half of your title aloud. If someone hearing that half alone would know what the page offers, it survives truncation. If they would need the second half, the title needs restructuring rather than shortening.
4. Make the title unique and honest
Duplicate titles across pages and titles stuffed with terms are the two strongest predictors of a Google rewrite. Describe what this specific page delivers, in words a person would use, and keep it close to the H1. Check for duplicates across the site with a crawl before rewriting individual pages. Duplicate titles usually come from a template, so the fix is a template change affecting hundreds of pages rather than an edit to the one you happened to notice.
5. Write the description as an advert, not a summary
It has no direct ranking effect, so its only job is earning the click. State what the reader gets and why this page answers their question. Include the terms people search, naturally, because matched terms are bolded in the result and draw the eye. Where the page answers a specific question, putting the answer or the key differentiator in the first line works better than describing the page. 'Free, no signup, results in 30 seconds' earns more clicks than 'This page explains our audit tool and its features.'
6. Check the URL display
The result shows a breadcrumb-style path rather than the raw URL. Deep, parameter-heavy URLs display badly and read as untrustworthy. A clean, shallow path is worth more here than it appears in the address bar. If the URL is genuinely poor — long, parameterised, or with a meaningless path — note it but do not rewrite URLs purely for appearance. Changing URLs requires redirects and carries real risk, so it is a decision to make during a restructure rather than a title optimisation pass.
7. Publish and wait for recrawl
Nothing changes until Google recrawls the page, which takes days to weeks. Requesting indexing in Search Console can speed it up for an important page. Rewriting again during the wait makes it impossible to attribute any change. Record the date you changed it. Without that, the before-and-after comparison four weeks later is guesswork, and you will not be able to distinguish your change from ordinary fluctuation or a competitor's activity in the same period.
8. Measure the click-through, not the position
Four weeks later, compare click-through rate for that page against the four weeks before. Position should be roughly unchanged — that is the point. If click-through rose at the same position, the rewrite worked. Compare like periods — four weeks against the equivalent four weeks, ideally year on year if the page is seasonal. And accept that some rewrites will not help; the value is in the pages that improve, not in every attempt succeeding.
Tip: If Google keeps rewriting a title after you have fixed length and uniqueness, check whether your H1 and title disagree. Google frequently substitutes the H1 when it judges it a better description of the page than the title you supplied.
Tags: serp-preview, howto, title-tag, meta-description, ctr
Fix
Fix: Content-Security-Policy
A CSP header tells the browser which scripts and resources may run. A strict, nonce- or hash-based CSP is the strongest defence against XSS.
Decide nonces or hashes first
This one decision shapes everything else. Use a NONCE if your pages are generated by a server on each request (PHP, Node, Rails, Django, WordPress) — the server can emit a fresh random value every time. Use HASHES if you serve static HTML files from disk with no render step, because there is nothing to generate a per-request value. Do not try to mix approaches on the same page; pick one and apply it consistently.
Deploy in report-only mode first
Never switch a CSP straight on — you will break the site. Send Content-Security-Policy-Report-Only with exactly the policy you intend to enforce. The browser reports what WOULD have been blocked without blocking anything. Leave it running long enough to cover real traffic, including logged-in journeys, checkout, and admin pages, which often load resources the homepage never touches.
Collect the violation reports
Add both report-to and report-uri directives. report-to is the modern Reporting API directive and is preferred; report-uri is deprecated but still needed because report-to does not yet have full cross-browser support. Sending both covers every browser. With report-to you also need a Reporting-Endpoints response header naming the endpoint that receives the JSON reports.
Write the strict policy
A strict starting policy looks like: default-src 'none'; script-src 'nonce-{RANDOM}'; style-src 'self'; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'. object-src 'none' disables plugin embeds. base-uri 'none' stops an injected <base> element rewriting every relative URL on your page to an attacker's server — an easy directive to forget.
Stamp the nonce on every script
The server generates a fresh cryptographically random value per response, puts it in the header as 'nonce-VALUE' and in each script tag as nonce="VALUE". Two rules matter: it must be genuinely random, and it must change on every single response. A fixed or reused nonce is worthless — an attacker simply reads it from the page source and reuses it in the injected script.
Add strict-dynamic if a script loads other scripts
A common breakage: an allowed script (a tag manager, a payment SDK) creates further script elements at runtime, and those have no nonce, so they are blocked. The strict-dynamic keyword propagates trust — scripts loaded by an already-trusted script inherit that trust without needing their own nonce or hash. This is the intended fix, not a workaround.
Refactor what the policy forbids
A strict CSP blocks inline event handler attributes like onclick="…" and eval(). Replace onclick attributes with addEventListener() inside a proper script file. Replace eval() with direct calls or JSON.parse(). If you truly cannot remove event handler attributes, the unsafe-hashes keyword allows them specifically — noticeably safer than opening up all inline JavaScript with unsafe-inline.
Enforce and verify
Once report-only produces no legitimate violations, rename the header to Content-Security-Policy. Verify with Google's CSP Evaluator (csp-evaluator.withgoogle.com), which grades the policy and flags weaknesses like overly broad allowlists. Re-check after any deploy that adds a third-party script.
Tip: If you cannot reach a strict CSP, an allowlist policy is still far better than none — even default-src https: disables inline script and eval and forces all resources over HTTPS. But avoid unsafe-inline, data: URIs in script-src, and wildcard sources: each of these quietly undoes most of the protection you are trying to add.
Tags: csp, security, fix
Tutorial
Understanding Content-Security-Policy
Content Security Policy lets you tell the browser exactly which code is allowed to run on your pages. If an attacker injects a script, a well-built CSP means the browser simply refuses to execute it. This guide explains how it works, why strict policies beat allowlists, and what each directive actually does.
The problem CSP solves
Cross-site scripting (XSS) is the attack CSP exists to stop. It happens when an attacker gets their own JavaScript to run on your page — through a comment field, a URL parameter reflected into the HTML, or a compromised third-party library. Once their script runs, it has exactly the same privileges as your own code: it can read cookies, make requests as the logged-in user, rewrite the page, and harvest anything typed into a form. The underlying cause is that a browser, by default, has no way to tell your script apart from an injected one. It runs whatever appears in the page.
What CSP actually changes
CSP flips the browser's default from 'run everything' to 'run only what the site owner explicitly allowed'. You send a Content-Security-Policy header listing approved sources for each type of resource. Anything not covered is refused. It is worth being clear about the limit: CSP does not fix the injection bug. Attacker text still reaches your page. CSP is the second line of defence that stops that text being executed — output encoding and sanitisation remain essential.
The directives you will actually use
script-src controls JavaScript. style-src controls CSS. img-src controls images. connect-src controls where fetch, XHR and WebSocket may talk to. frame-src controls what may be embedded in your page, and frame-ancestors controls who may embed you. object-src controls plugin embeds. base-uri controls the <base> element. default-src is the fallback for fetch directives you have not named — but note it does NOT cover frame-ancestors, base-uri, or form-action, so those must be set explicitly.
Why allowlists disappoint
The intuitive approach is listing trusted domains: script-src 'self' https://cdn.example.com. In practice these policies age badly. They grow long as third parties are added, and they often end up including a domain that hosts something exploitable, at which point an attacker can load their payload from an approved source and the policy waves it through. Large allowlists also publish a map of your dependencies to anyone reading the header.
Nonces: the strict approach for dynamic sites
A nonce is a random token generated fresh for every single response. The server puts it in the CSP header (script-src 'nonce-abc123') and on each legitimate script tag (<script nonce="abc123">). The browser runs only scripts carrying the matching token. An injected script cannot guess it, because it changes every time the page is served. The critical requirement is per-request randomness — a hard-coded nonce provides no protection at all.
Hashes: the strict approach for static sites
If your HTML is static files on disk, there is no server-side render step to inject a fresh nonce. Instead you take a SHA-256 hash of each inline script's exact contents and list those hashes in the policy. The browser hashes what it finds and only runs exact matches. The trade-off is maintenance: change one character inside an inline script and its hash changes, so the header must be regenerated or that script silently stops working.
strict-dynamic and the loader problem
Real sites often have one script that loads others — a tag manager, an analytics loader, a payment SDK. Those child scripts are created at runtime and carry no nonce, so a strict policy blocks them and something breaks. strict-dynamic solves this properly: it says any script loaded by an already-trusted script is itself trusted. This keeps the strict model intact rather than forcing you to fall back to a permissive allowlist.
Rolling out without breaking the site
The standard path is report-only first. Send Content-Security-Policy-Report-Only with a reporting endpoint and watch what real traffic would have broken — checkout flows, admin screens and logged-in pages routinely load things the homepage does not. Widen the policy only for legitimate resources, then switch to the enforcing header. Rolling out enforcement first is how sites end up with broken payment buttons discovered by customers.
Where CSP fits with everything else
CSP does more than XSS. frame-ancestors blocks clickjacking by controlling who may iframe you, replacing the older X-Frame-Options header. upgrade-insecure-requests tells browsers to retry http:// resources over https://, useful for large legacy sites with insecure URLs scattered through old content. Together these make one header cover three distinct classes of attack.
Tip: Test any policy with Google's CSP Evaluator at csp-evaluator.withgoogle.com before and after you deploy it. It will tell you plainly whether your policy actually resists XSS or merely looks strict — a very common gap is a policy that includes unsafe-inline and therefore provides almost none of the protection its author assumed.
Tags: csp, security, tutorial, learn
Fix
Fix: CSP blocks eval()
script-src should not include 'unsafe-eval'. That keyword re-enables dynamic code execution, which is one of the main things a strict CSP is meant to shut off.
Find every dynamic-code call
Search your JavaScript for eval(, new Function(, setTimeout with a string first argument, and setInterval with a string first argument. Each of these takes text and executes it as live code. Also check any older library you bundle — template engines and expression evaluators from the pre-2015 era frequently compile via eval or Function and are the usual reason a site cannot drop the keyword.
Replace eval used for parsing
By far the most common legitimate-looking use is parsing JSON. Replace eval('(' + text + ')') with JSON.parse(text) — it is faster, and it cannot execute code, only build data. If the text is not valid JSON, fix the producer rather than reaching back for eval.
Replace eval used for dispatch
The second common pattern is building a function name from a string and calling it. Replace this with a lookup table: define const handlers = { save: doSave, load: doLoad } and call handlers[name](). This is faster, debuggable, and an attacker-supplied name simply misses the table instead of running arbitrary code.
Deal with libraries that need it
If a dependency genuinely requires eval, check whether a current version has removed it — many have, precisely because of CSP. Failing that, look for a precompiled build: template libraries typically ship a runtime-only bundle that uses templates compiled at build time and needs no eval at runtime.
Remove the keyword and watch the console
Delete 'unsafe-eval' from script-src, reload with DevTools open, and exercise the site properly — not just the homepage. CSP eval violations appear in the console naming the file and line, so anything you missed announces itself clearly.
Know the WebAssembly exception
If your site uses WebAssembly, be aware that browsers gate wasm compilation separately. There is a 'wasm-unsafe-eval' keyword for exactly this purpose. Use that narrowly-scoped keyword rather than reinstating full 'unsafe-eval', which would re-open JavaScript execution as well.
Tip: Removing 'unsafe-eval' is one of the cheapest CSP wins available: most sites do not use eval at all, so the keyword can simply be deleted with no code changes and no user-visible effect. Check the console after removing it rather than assuming.
Tags: eval, security, fix
Tutorial
Understanding CSP blocks eval()
eval() and its relatives take a string of text and run it as live JavaScript. This guide explains why that is dangerous, what the CSP 'unsafe-eval' keyword actually controls, and what to use instead.
What eval actually does
eval('alert(1)') does not treat that text as data — it compiles and runs it as real JavaScript, with the full privileges of your page. The same applies to new Function('…'), and to setTimeout('…', 100) when the first argument is a string rather than a function. All of them are routes from text to execution.
Why that is dangerous
The danger appears the moment any part of that string comes from somewhere you do not fully control: a URL parameter, a form field, a value from an API, a stored record. An attacker who can influence the text can run their own code — read document.cookie, issue authenticated requests, rewrite the page. A single eval on user-influenced input is a complete compromise of that page.
What unsafe-eval controls
By default, a CSP with a script-src directive blocks these dynamic-code APIs entirely. Adding 'unsafe-eval' switches that protection back off. So the keyword is not adding a feature so much as removing a defence, which is why security scanners flag it. Because most sites never call eval, the keyword is very often present for no reason at all — copied from an example policy.
The safe replacements
Parsing JSON: use JSON.parse, which builds data and cannot execute. Choosing a function by name: use an object as a lookup table and call table[name](), so an unknown name misses instead of executing. Deferring work: pass a real function to setTimeout(fn, 100), never a string. Doing arithmetic on user input: use a dedicated expression parser library rather than handing the text to the JavaScript engine.
Libraries as the usual blocker
When a site cannot remove the keyword, the cause is usually a dependency rather than its own code. Older client-side template engines compiled templates by generating source and calling Function on it. The fix is either upgrading to a version that precompiles, or switching to the library's runtime-only build where templates are compiled during the build step and shipped as plain functions.
WebAssembly is handled separately
Browsers treat compiling WebAssembly as a form of dynamic code generation, so a strict CSP can block it. Rather than reinstating 'unsafe-eval' and re-opening JavaScript eval as a side effect, use 'wasm-unsafe-eval', which permits WebAssembly compilation only. Scoping the exception to what you actually need is the whole point.
Tip: A useful rule: if you are about to build a string and execute it, there is nearly always a data structure that does the same job safely. Lookup tables replace dynamic dispatch, JSON.parse replaces eval for data, and real function references replace string callbacks.
Tags: eval, security, tutorial, learn
Fix
Fix: Inline JavaScript blocked
script-src should not include 'unsafe-inline'. That keyword lets any inline script in the page run, including one an attacker injected.
Understand what you are removing
'unsafe-inline' permits two things: <script> blocks written directly in the HTML, and inline event handler attributes like onclick="doThing()". Both run without any check on origin. While the keyword is present, an attacker who injects <script>…</script> anywhere in your markup gets their code executed with full page privileges.
Inventory the inline scripts
Search your templates for <script> tags with no src attribute, and for on* attributes in markup: onclick, onload, onchange, onsubmit, onerror. Count them before planning. A handful means an afternoon; several hundred spread across pages means this is a project, and knowing which you face prevents a half-finished migration.
Move script blocks into files
For each inline <script> block, cut the code into a .js file and reference it with <script src="/js/page-name.js"></script>. External files are trusted by origin under script-src 'self', so no unsafe-inline is needed. Where a block depends on server-rendered values, output those as data- attributes or a JSON blob and read them from the external file rather than interpolating code.
Replace event handler attributes
onclick="save()" must become an addEventListener('click', save) call inside a script file, with the element identified by id or a data attribute. This is the single most tedious part of the migration and the part most often left half-done — an attribute missed is a page that breaks the moment the policy is enforced.
Add nonces for what genuinely must stay inline
If your server renders pages, keep the remaining inline scripts and give each a nonce attribute matching a per-request random value in the header. If your HTML is static files, use SHA-256 hashes of each script's exact content instead. Choose based on whether a server generates the page, not on preference.
Handle scripts created at runtime
Code that does document.createElement('script') produces scripts a server-side nonce injector never sees, so they get blocked. Set el.nonce = <the current nonce> in that JavaScript, or cover the case with strict-dynamic so scripts loaded by an already-trusted script inherit trust automatically.
Test in report-only before enforcing
Run the policy without unsafe-inline as Content-Security-Policy-Report-Only and exercise every journey — logged-in pages, forms, checkout, admin. Each missed handler shows up as a violation report naming the file and line. Only switch to the enforcing header when the reports are clean.
Tip: If you cannot complete the migration, do not leave it half-done and enforced — a page with one missed onclick is a broken page. Stay in report-only until the reports are clean, then enforce. A CSP with unsafe-inline still delivers real value from its other directives in the meantime.
Tags: inline-js, security, fix
Tutorial
Understanding Inline JavaScript blocked
Inline JavaScript is code written directly inside your HTML, either in a script block or in an onclick-style attribute. Allowing it with 'unsafe-inline' is convenient but removes most of what CSP is for. This guide explains why, and what the realistic paths off it are.
What counts as inline
Two things. First, a <script> element with the code written between the tags rather than loaded via src. Second, event handler attributes in markup — onclick, onmouseover, onload, onerror and the rest. Both are extremely common in older sites and in anything hand-built, and both are governed by the same CSP keyword.
Why the browser cannot help you
When 'unsafe-inline' is in your policy, the browser executes every inline script it finds in the document. It has no means of distinguishing the script you authored from one that arrived through a comment box, a search parameter reflected into the page, or a compromised third-party widget. They are identical to the parser. This is precisely the gap CSP was designed to close.
What removing it buys you
Without the keyword, injected inline script simply does not run. The attacker may still get their text into your HTML, but it sits there inert. This turns a full page compromise into a cosmetic defect. It is the single largest security improvement available in a CSP, which is why scanners weight it heavily.
External files: the durable answer
Scripts loaded from files are trusted by origin. script-src 'self' permits everything served from your own domain and blocks inline injection outright. Beyond security, this is better engineering — the code becomes cacheable, minifiable, lintable and testable, none of which is true of a script buried in a template.
Nonces for server-rendered pages
A nonce is a random token regenerated for every response. It goes in the header and on each legitimate script tag; the browser runs only matching scripts. An injected script cannot carry the right token because it changes each time the page is served. This requires something that renders the page per request — a PHP, Node, Rails or Django application, or a CMS.
Hashes for static sites
With static HTML files there is no per-request render step, so nonces are impossible. Instead you list a SHA-256 hash of each inline script's exact bytes in the policy. It works, but the maintenance is real: every edit to an inline script changes its hash, and a stale hash means that script silently stops running until the header is regenerated.
Why some sites legitimately stay put
A large static site can easily have a hundred-plus unique inline scripts, none shared between pages. Hash-listing them all produces an enormous header that must be rebuilt on every content change. Where that is the situation, the honest engineering answer is often to keep unsafe-inline, invest in output escaping as the real defence, and revisit if the site gains a render layer.
What to do if you keep it
Do not treat unsafe-inline as the end of the conversation. Ensure every value written into HTML is escaped on output, since that is what actually prevents injection. Keep the rest of the policy strict — default-src 'none', object-src 'none', base-uri 'none', frame-ancestors 'none' — so the remaining directives still do their work.
Tip: Adding a nonce to your policy has a useful side effect: browsers that support nonces ignore 'unsafe-inline' when one is present. This means you can list both, and modern browsers get the strict behaviour while very old ones fall back to the permissive one.
Tags: inline-js, security, tutorial, learn
Fix
Fix: Inline styles blocked
style-src should not include 'unsafe-inline'. Injected CSS cannot run code, but it can hide, move or overlay content to deceive users.
Know what a style nonce does and does not cover
A nonce or hash can authorise a <style> block. Neither can authorise a style="…" attribute on an element. This is the crux of the whole job: if your markup carries inline style attributes, removing style-src 'unsafe-inline' requires converting them to classes. There is no header-only shortcut.
Count the inline style attributes
Search your HTML and templates for style=" and count. This number determines whether the task is an hour or a month. Sites built with hand-written HTML or older page builders routinely carry thousands, and knowing that before starting prevents an abandoned half-migration.
Convert attributes into classes
Replace style="color:red;font-weight:bold" with class="alert-text" and define .alert-text once in your stylesheet. Most inline styles repeat across a site, so the number of distinct classes needed is far smaller than the number of attributes being replaced — group them as you go rather than creating one class per element.
Fix JavaScript that sets styles
Code doing element.style.display = 'none' creates inline styles at runtime and will be blocked. Replace with element.classList.add('hidden') and a .hidden { display:none } rule. Toggling classes is also faster and keeps presentation in the stylesheet where it belongs.
Handle genuinely dynamic values
Some values cannot be predicted — a progress bar width, a user-chosen colour. Use CSS custom properties: set element.style.setProperty('--width', pct + '%') and have the stylesheet consume var(--width). Note that CSP still governs this, so where it is blocked, drive it via a class list of predefined steps instead.
Use unsafe-hashes only if cornered
If a small number of style attributes cannot be removed, the unsafe-hashes keyword permits specific hashed inline attributes. It is weaker than a clean policy but substantially better than opening up all inline CSS. Treat it as a bridge with an end date, not a destination.
Deploy in report-only and check rendering
Style violations do not throw errors the way script ones do — they simply make the page look wrong. Run report-only, then click through the site and actually look at it: layout, modals, hidden elements, print views. Automated tests rarely catch a purely visual regression.
Tip: Weigh this one honestly. Inline styles cannot execute JavaScript, so the risk is lower than inline scripts, while the refactoring cost is usually far higher. If you have limited time, remove script-src 'unsafe-inline' first — that is where nearly all the security value sits.
Tags: inline-css, security, fix
Tutorial
Understanding Inline styles blocked
Inline styles are CSS written directly on elements via the style attribute, or in a style block in the page. This guide explains what an attacker can actually do with injected CSS, and why removing style-src 'unsafe-inline' is usually a refactoring job rather than a header change.
What inline styles are
Two forms: a style="color:red" attribute on an element, and a <style> block inside the document. They are common in hand-built sites, in email-derived markup, in page-builder output, and in anything generated by JavaScript that assigns to element.style.
The risk is real but different
Injected CSS cannot execute JavaScript, so it cannot steal a session cookie directly. What it can do is change what the user sees: overlay a fake login prompt on your real page, hide a security warning or a transaction amount, reposition a button so the click lands somewhere unintended, or make attacker text look like official site content. These are deception attacks rather than code execution.
Historical data exfiltration
CSS has been used to leak data as well. Attribute selectors combined with background-image requests can reveal the contents of form fields character by character, because each match triggers a network request to an attacker-controlled URL. Browsers have limited some of these tricks, but the general category is why style-src is treated as a security directive at all.
Why nonces do not finish the job
A nonce or hash authorises a <style> element, and that part works cleanly. But style attributes on individual elements cannot carry a nonce. Any site whose markup uses style="…" therefore has to convert those attributes into classes before the keyword can come out of the policy. This asymmetry surprises people who expect style-src to work exactly like script-src.
The refactoring pattern
Move each inline declaration into a named class in your stylesheet, then apply the class in markup. In JavaScript, toggle classes instead of assigning to element.style. For values that genuinely vary at runtime, CSS custom properties let the stylesheet own the rule while JavaScript supplies only the number.
Scale is what makes this hard
On a small site this is an afternoon. On a large legacy site with thousands of style attributes accumulated over years, it is a sustained project touching nearly every template, with a visual regression risk on every page. Many teams reasonably conclude the security gain does not justify that cost and keep the keyword.
A sensible order of work
Given limited effort, remove script-src 'unsafe-inline' first — that blocks code execution and is where the overwhelming majority of the risk lies. Tighten style-src afterwards, if and when the markup is being refactored for other reasons. Locking down styles while leaving scripts wide open is effort spent in the wrong place.
Tip: A practical halfway house: keep style-src 'unsafe-inline' but make sure default-src is 'none' and script-src is strict. You keep almost all of CSP's protective value while avoiding a refactor that can touch thousands of lines of markup.
Tags: inline-css, security, tutorial, learn
Fix
Fix: Strict-Transport-Security (HSTS)
HSTS tells browsers to reach your site only over HTTPS, closing the window where a first plain-HTTP request can be intercepted and downgraded.
Confirm HTTPS works everywhere first
HSTS is sticky and hard to undo, so verify before enabling. Every page, asset, API endpoint and — if you plan to use includeSubDomains — every subdomain must serve valid HTTPS. Any host that cannot will become unreachable in browsers that have seen the header, and you cannot fix that remotely once it is cached.
Fix the redirect so it stays on one host
Port 80 should redirect to the same resource on HTTPS on the same host. A redirect that jumps host at the same time — http://example.com straight to https://example.org — prevents Strict-Transport-Security ever being set for the first host. Where you need both, chain it: http://example.com to https://example.com first, then to https://example.org.
Start with a short max-age while testing
Set something like Strict-Transport-Security: max-age=300 initially. If something breaks, the effect expires in minutes rather than years. Confirm with curl -sI that the header is present on every response, including redirects and error pages, before raising the value.
Raise max-age to at least six months
The recommended minimum is six months (15768000 seconds); two years (63072000) is the preferred value. Once set, you are committed to supporting HTTPS on that host for the full duration, because browsers will refuse to fall back to HTTP no matter what you serve.
Add includeSubDomains carefully
This extends the policy to every subdomain. Test thoroughly first: legacy hosts, staging environments, mail interfaces and internal tools living on subdomains will all be forced to HTTPS. Discovering an HTTP-only internal subdomain after a two-year policy is cached is a genuinely painful outage.
Add upgrade-insecure-requests alongside
Set the CSP directive upgrade-insecure-requests so that any http:// resource URL still lurking in your pages is retried over HTTPS automatically. This is aimed at sites with large volumes of legacy content where rewriting every old URL is impractical.
Consider preloading last
Submitting at hstspreload.org bakes your domain into browsers, so HTTPS is enforced even on a user's very first visit, before any header is seen. Requirements: includeSubDomains set, preload in the header, and max-age of at least one year (31536000). Removal takes months to propagate, so treat this as permanent.
Tip: The order that avoids outages is: fix HTTPS everywhere, then short max-age, then long max-age, then includeSubDomains, then preload. Skipping to preload on day one is the classic way to discover an HTTP-only subdomain the hard way.
Tags: hsts, security, fix
Tutorial
Understanding Strict-Transport-Security (HSTS)
HSTS is a header telling browsers to connect to your site only over HTTPS for a set period. This guide covers the attack it prevents, what each directive does, and why preloading deserves caution.
The gap HSTS closes
When someone types yoursite.com, the browser's first request commonly goes out over plain HTTP before your redirect sends it to HTTPS. That first request is unencrypted and interceptable. An attacker on the same network — public Wi-Fi is the classic setting — can capture it and keep the victim on an insecure connection, proxying your site while reading everything. This is SSL stripping.
How the header defends against it
Strict-Transport-Security instructs the browser to remember that this host is HTTPS-only for max-age seconds. From then on the browser rewrites http:// to https:// internally before any request leaves the machine. There is no plain-HTTP request to intercept. As a bonus, HSTS also stops users clicking through certificate warnings, closing another attack path.
The directives
max-age is the lifetime in seconds and is the only required one. includeSubDomains extends the policy to every subdomain of the host. preload signals that you want the domain included in the lists browsers ship with. Each adds protection and each adds commitment.
The first-visit problem
HSTS only works after the browser has seen the header at least once. A brand-new visitor's very first request is still unprotected, which is exactly the request an attacker wants. Preloading exists to close this: browser vendors ship a built-in list of HSTS domains, so enforcement applies before any header is received.
Why preloading needs care
Preload entries are compiled into browser releases. Removing your domain means requesting removal and then waiting for browsers to update — months, not minutes. Until then every subdomain must serve valid HTTPS. If that is not certain for the foreseeable future, the header alone is the wiser choice.
Choosing max-age
Short values weaken the protection, because the browser forgets and the next first-request is exposed again. Six months is the usual floor, two years the recommendation. The commitment cuts the other way too: once browsers have cached a two-year policy, you cannot serve that host over HTTP for two years, whatever happens to your certificate.
What it does not do
HSTS assumes HTTPS is available and correctly configured; it does not provide encryption itself. It also cannot help with mixed content — an https:// page pulling in an http:// script is a separate problem, addressed by fixing those URLs or by the upgrade-insecure-requests CSP directive.
Tip: Check your header with curl -sI https://yoursite.com | grep -i strict. Confirm it appears on every response, not just the homepage — a header set on one virtual host but missing on another is a common and easily-missed misconfiguration.
Tags: hsts, security, tutorial, learn
Fix
Fix: X-Frame-Options / frame-ancestors
Framing controls stop your pages being embedded in someone else's site, which is what makes clickjacking possible.
Set frame-ancestors in your CSP
Add frame-ancestors 'none' to Content-Security-Policy to block all framing, or frame-ancestors 'self' to allow only your own pages to frame you. This is the modern directive and the one to configure first. Note it does not inherit from default-src, so it must be stated explicitly even in a strict policy.
Add X-Frame-Options for old browsers
Also send X-Frame-Options: DENY, or SAMEORIGIN if you frame your own pages. Browsers that understand frame-ancestors ignore this header when both are present, so there is no conflict — it is purely a fallback for older clients.
Do not use ALLOW-FROM
The ALLOW-FROM directive of X-Frame-Options was never widely supported and is obsolete. If you need to permit specific external origins to embed you, that is only expressible through CSP frame-ancestors with the origins listed.
Name specific partners rather than opening up
If a partner legitimately embeds your widget, write frame-ancestors https://partner.example.com. Listing exact origins keeps everyone else out. Never resort to a wildcard because one integration needs framing — that removes the protection entirely.
Apply it to the pages that matter most
The pages worth protecting are the ones where a click does something: payment confirmation, settings changes, permission grants, account deletion, admin actions. Static marketing pages carry far less risk. If a blanket policy is impractical, cover the state-changing pages first.
Verify with a real test page
Create a local HTML file containing an iframe pointing at your site, open it, and confirm the frame stays empty and the console reports the refusal. Checking the header exists with curl proves it is sent; loading it in an iframe proves it works.
Tip: Set both headers. frame-ancestors is authoritative in modern browsers and X-Frame-Options covers the rest, and because modern browsers ignore the older header when both are present, there is no downside to belt and braces here.
Tags: xframe, security, fix
Tutorial
Understanding X-Frame-Options / frame-ancestors
Clickjacking loads your real site invisibly over a malicious page so users click things they cannot see. This guide explains the attack and how framing controls defeat it.
How clickjacking works
The attacker builds a page with an appealing decoy — a game, a prize claim, a video play button. On top, they load your real site in an iframe set to near-zero opacity, positioned so that your critical button sits exactly under their decoy. The victim, already logged into your site, clicks what looks like the decoy and actually clicks your button. Their session makes the action authentic.
What it is used for
Anything a single click can accomplish: approving a payment, granting an OAuth permission, changing a privacy setting, deleting an account, following or liking. Because the request carries the user's real session, your server sees a legitimate authenticated action and has no way to tell it was not intended.
frame-ancestors, the modern control
The CSP directive frame-ancestors specifies who may embed your page. 'none' forbids all framing; 'self' permits only same-origin framing; a list of origins permits exactly those. The browser enforces this before rendering the frame, so the attacker's iframe simply stays blank.
X-Frame-Options, the predecessor
The older header offers DENY and SAMEORIGIN, roughly matching 'none' and 'self'. It has no way to express an allowlist of external origins — the ALLOW-FROM value was poorly supported and is obsolete. Where both headers are present, modern browsers follow the CSP directive and ignore this one.
Why you should still send both
frame-ancestors covers current browsers properly. X-Frame-Options covers older clients that predate CSP support. Since the newer directive takes precedence where both exist, sending both costs nothing and closes the gap for older user agents.
SameSite cookies as a second layer
A cookie set with SameSite=Strict or Lax is not sent on most cross-site requests, which blunts clickjacking too: framed inside an attacker's page, the request may arrive without the session cookie and therefore unauthenticated. This is defence in depth, not a substitute for framing headers.
Handling legitimate embedding
Some sites are meant to be embedded — payment widgets, booking forms, media players. Enumerate exactly which origins need it and list them in frame-ancestors. If the embedding partner set is open-ended, protect the sensitive actions instead by requiring an explicit confirmation step that a single hidden click cannot complete.
Tip: Test it directly: save a local HTML file with <iframe src="https://yoursite.com"></iframe>, open it in a browser, and watch the console. A properly protected site shows an empty frame and a refusal message naming the directive that blocked it.
Tags: xframe, security, tutorial, learn
Fix
Fix: No mixed content
An HTTPS page must not load http:// resources. Browsers block the dangerous kinds outright and warn on the rest, and the padlock is lost either way.
Let the console find them for you
Open DevTools, load the page, and read the console. Browsers report every mixed-content resource by exact URL, saying whether it was blocked or merely flagged. This is faster and more complete than grepping, because it catches resources injected by scripts and by third-party embeds that never appear in your source.
Grep the codebase for http:// as well
Search templates, CSS, JavaScript and database content for http:// in src, href, url() and similar positions. Ignore matches in xmlns declarations and schema.org identifiers — those are namespace names, not resource requests, and rewriting them breaks things for no benefit.
Rewrite the URLs to https://
Nearly every reputable host now serves HTTPS, so the fix is usually just the scheme. Verify each rewritten URL actually loads over HTTPS before shipping — a handful of ancient third-party endpoints still do not support it, and those need replacing rather than rewriting.
Clean stored content too
On CMS-driven sites the offenders are frequently inside post bodies and widget settings rather than templates. For WordPress, wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guid handles it; run with --dry-run first. Leave the guid column alone, since changing it can make feed readers treat every post as new.
Check form actions
A form on an HTTPS page posting to an http:// action sends everything the user typed in clear text. This is easy to miss because the page itself shows a padlock. Search for action="http:// specifically and fix every instance.
Add upgrade-insecure-requests as a safety net
The CSP directive upgrade-insecure-requests tells browsers to retry any http:// resource over https:// automatically. It is designed for sites with large volumes of legacy URLs. Treat it as a backstop while you clean the source, not as the fix itself — it masks the problem rather than removing it.
Re-test on real pages
Check pages that carry user-generated or legacy content, not just the homepage: old blog posts, product descriptions, embedded videos, email-derived pages. Confirm the console is clean and the padlock shows without warnings.
Tip: Fix the source URLs even after adding upgrade-insecure-requests. The directive relies on the destination supporting HTTPS — where it does not, the resource simply fails to load, and you get a broken page instead of an insecure one.
Tags: mixed-content, security, fix
Tutorial
Understanding No mixed content
Mixed content is an HTTPS page loading some of its resources over plain HTTP. This guide explains why it undermines the whole page, how browsers treat the different kinds, and how to clear it.
Why one insecure resource matters
An encrypted page is only as trustworthy as everything it loads. A script fetched over HTTP can be intercepted and replaced in transit by anyone on the network path. That substituted script then runs inside your HTTPS page with full privileges — reading cookies, altering the DOM, capturing form input. The padlock becomes a promise the page cannot keep.
Active versus passive
Active mixed content — scripts, stylesheets, iframes, XHR/fetch — can change page behaviour, so browsers block it outright. Passive mixed content — images, audio, video — cannot execute, so historically it was allowed with a warning. Browsers have progressively tightened this, auto-upgrading passive requests to HTTPS and blocking them when that fails.
What passive content can still do
Even images matter. An attacker able to swap an image can deface the page, replace a security notice with a misleading one, or substitute a fake support phone number. It is a deception risk rather than a code-execution one, but on a banking or checkout page it is far from harmless.
Where it comes from
Four usual sources: hard-coded http:// URLs in templates from before the HTTPS migration; user-generated or CMS-stored content containing old absolute URLs; third-party embeds and widgets that build their own URLs; and CSS files referencing images or fonts via url(http://…), which is easy to overlook because it is not in the HTML.
The upgrade-insecure-requests directive
This CSP directive instructs the browser to rewrite http:// to https:// for every resource before requesting it. It is genuinely useful for large legacy sites where thousands of old URLs sit in archived content. Its limitation is that it only helps when the target actually supports HTTPS — otherwise the request fails and the resource is missing.
Protocol-relative URLs are legacy advice
Older guidance suggested //example.com/file.js so the scheme is inherited from the page. This dates from when sites served both HTTP and HTTPS. Today, with sites HTTPS-only, explicit https:// is clearer and avoids surprises when a page is opened from the filesystem or by a tool that does not set a scheme.
Keeping it fixed
Mixed content tends to return through content editors pasting old URLs and through new third-party embeds. A CSP report endpoint gives you ongoing visibility, and a build-time or CI check that greps for http:// in resource positions catches regressions before they ship.
Tip: The browser console is the authoritative source here. It sees resources added by scripts at runtime, which no static search of your files will ever find — always confirm there rather than trusting a grep to have caught everything.
Tags: mixed-content, security, tutorial, learn
Fix
Fix: Subresource Integrity (SRI)
SRI pins a third-party script or stylesheet to exact contents via a hash, so a compromised CDN cannot silently serve you different code.
List your cross-origin scripts and styles
Find every <script src> and <link rel="stylesheet"> pointing at a domain you do not control — CDNs, font services, analytics, widget providers. Each one is a standing trust decision: that code runs with full access to your pages, and you have no control over what the host serves tomorrow.
Generate a hash for each file
Use srihash.org, or on the command line: curl -s <url> | openssl dgst -sha384 -binary | openssl base64 -A. SHA-384 is the usual choice; SHA-256 and SHA-512 are also supported. The result goes in the tag as integrity="sha384-<base64>".
Always add crossorigin="anonymous"
This is the step people miss and it matters more than it looks. Without a crossorigin attribute the browser fails open — it loads the resource as though integrity had never been specified, so you get the appearance of protection and none of the substance. Never ship an integrity attribute without it.
Check the CDN sends CORS headers
Integrity verification of a cross-origin file requires the host to send Access-Control-Allow-Origin. Major CDNs do. If one does not, the resource will fail to load once you add the attributes, and the correct response is to self-host the file rather than to drop SRI.
Pin to an immutable versioned URL
SRI works properly only against a URL whose contents never change. Point at .../jquery-3.7.1.min.js, never at a 'latest' alias. A moving URL guarantees the hash will eventually stop matching and the resource will be blocked without warning.
Automate hashing in your build
Regenerating hashes by hand is where this decays — someone bumps a version, forgets the hash, and the page breaks or the attribute is quietly removed. Have your build pipeline compute the hash and inject it, so upgrading a dependency updates the integrity value automatically.
Verify a failure deliberately
Alter one character of a hash locally and reload. The browser should refuse the resource and log an integrity error. If the file still loads, your protection is not active — almost always a missing crossorigin attribute.
Tip: An integrity attribute without crossorigin="anonymous" is worse than none, because it looks protected in code review while the browser is quietly failing open and loading whatever the CDN serves.
Tags: sri, security, fix
Tutorial
Understanding Subresource Integrity (SRI)
Subresource Integrity lets you pin the exact contents of a file you load from a third party. If the delivered bytes do not match your hash, the browser refuses to use it. This guide covers the supply-chain risk it addresses and how to apply it correctly.
The supply chain problem
Loading jQuery from a CDN means trusting that host to serve the same code forever. That script runs with complete access to your page — it can read cookies, alter links, capture form input, exfiltrate data. If the CDN is breached or an account there is compromised, every site loading that file is compromised simultaneously. This is a supply chain attack, and it has happened repeatedly to real, popular libraries.
How SRI answers it
You publish a cryptographic hash of the file you expect. The browser downloads the resource, hashes what it received, and compares. A match means execution proceeds; any difference at all — a single byte — means the resource is discarded and a network error is reported. Trust moves from 'this host' to 'these exact contents'.
Generating the hash
srihash.org will do it from a URL. On the command line, curl the file and pipe it through openssl dgst -sha384 -binary then openssl base64 -A. SHA-384 is the common default; SHA-256 and SHA-512 are equally valid. You may list several hashes on one element, and the browser accepts the resource if it matches any of them — useful during a planned version change.
The crossorigin requirement
For cross-origin resources the browser must fetch in CORS mode to be permitted to inspect the bytes for hashing. That means crossorigin="anonymous" on the tag and Access-Control-Allow-Origin from the host. Omitting the attribute causes fail-open behaviour: the browser loads the file without verifying, and the integrity attribute achieves nothing at all.
Immutability is the whole premise
SRI assumes the file at a URL never changes. Versioned CDN paths satisfy this; 'latest' aliases do not. Pointing SRI at a mutable URL guarantees eventual breakage, because the day the CDN updates the file, the hash stops matching and the browser blocks it — usually noticed by users first.
The maintenance trade-off
Deliberate upgrades require regenerating the hash. That is the cost of the guarantee, and it is why automation matters: a build step that hashes each dependency keeps the attributes correct without human discipline. Teams that update hashes manually tend to end up deleting the attribute when it gets in the way.
Where SRI stops
SRI verifies the file you named. It does not constrain what that file does once running, and it does not follow along if the script loads further scripts at runtime — those are unverified. Combine SRI with a strict CSP, so origin control and content pinning cover each other's gaps.
Tip: Self-hosting a library removes the CDN from your trust chain entirely and is often the simpler answer for a handful of small dependencies. The CDN's caching advantage is far smaller than it used to be, since browsers now partition cache per-site.
Tags: sri, security, tutorial, learn
Fix
Fix: Cross-Origin-Resource-Policy
CORP declares which origins may embed a given resource, limiting cross-origin leaks of the kind speculative-execution attacks exploit.
Set the header to same-origin
Send Cross-Origin-Resource-Policy: same-origin so only your own site can embed your resources. For most sites this is correct and is what scanners expect. In nginx: add_header Cross-Origin-Resource-Policy "same-origin" always; in Apache: Header always set Cross-Origin-Resource-Policy "same-origin".
Check whether anything legitimately hotlinks you
Before applying it site-wide, consider whether other domains embed your images, fonts or scripts — partner sites, documentation, a separate marketing domain, or your own CDN on a different hostname. Those resources will stop loading for them under same-origin.
Use same-site where subdomains are involved
If assets are served from static.example.com and consumed by www.example.com, same-origin is too tight because the origins differ. same-site permits both while still excluding unrelated domains. Note this compares registrable domains, not full origins.
Mark genuinely public assets cross-origin
For files intended for public embedding — an open-source logo, a shared widget script, a public API response — set cross-origin explicitly on those paths only. Keep the restrictive default and carve out exceptions rather than the reverse.
Apply it to every response
The header must be present on the resources themselves, not merely on HTML pages. Configure it at the server or CDN so images, fonts, scripts and stylesheets all carry it — a policy set only on documents leaves precisely the resources it is meant to protect uncovered.
Verify with curl
Run curl -sI https://yoursite.com/some-asset.png and confirm the header appears. Check a page, an image and a script, since server configuration frequently applies headers to some location blocks and not others.
Tip: Start with same-origin and relax only where something visibly breaks. Being too tight produces an obvious, quickly-noticed failure; being too loose produces no symptom at all, which is why the permissive setting tends to survive unexamined.
Tags: corp, security, fix
Tutorial
Understanding Cross-Origin-Resource-Policy
Cross-Origin-Resource-Policy lets a resource declare who is allowed to load it. This guide explains the class of attack it mitigates and how it relates to CORS, CORB and the cross-origin isolation headers.
The threat it addresses
Spectre-class speculative execution attacks let malicious code potentially read memory in its own process, including data from other origins that the browser has pulled in. Because a hostile page can cause your resources to be loaded — with an img or script tag — those bytes can end up in the attacker's process. CORP allows a resource to refuse that embedding in the first place.
What the values mean
same-origin restricts loading to pages from the identical scheme, host and port. same-site widens it to the registrable domain, so subdomains qualify. cross-origin places no restriction and is appropriate only for resources genuinely meant to be embedded anywhere, such as a public CDN asset.
How it differs from CORS
CORS governs whether JavaScript may read a response it requested. CORP governs whether a document may embed the resource at all, regardless of any intent to read it. They address different moments: CORS is about programmatic access to data, CORP is about preventing the bytes reaching a hostile process.
Its relationship to CORB
Cross-Origin Read Blocking is a browser heuristic that blocks obviously-wrong cross-origin loads, such as an HTML or JSON response arriving as an image. CORB is automatic and best-effort. CORP is explicit and authoritative — you state the policy rather than relying on the browser's inference.
Where cross-origin isolation fits
Powerful features such as SharedArrayBuffer and high-resolution timers require a cross-origin isolated context, which needs COOP (Cross-Origin-Opener-Policy) and COEP (Cross-Origin-Embedder-Policy). Under COEP, every subresource must opt in — via CORP or CORS — so setting CORP is a prerequisite if your resources are consumed by isolated pages.
Why it is easy to overlook
CORP has no visible effect when set correctly and no visible effect when omitted, so nothing prompts you to configure it. Security scanners flag its absence because it is a cheap, low-risk header that closes a real, if subtle, gap.
Practical guidance
Default to same-origin across the site. Move to same-site where you serve assets from a sibling subdomain. Reserve cross-origin for the specific paths that are deliberately public. Confirm the header reaches images, fonts and scripts, not only HTML documents.
Tip: If setting same-origin breaks something, the breakage tells you exactly which resource is embedded elsewhere and by whom — often surfacing hotlinking or a forgotten integration you did not know existed.
Tags: corp, security, tutorial, learn
Fix
Fix: Cross-Origin Resource Sharing (CORS)
An over-permissive CORS policy lets other sites' JavaScript read your responses. Wildcards and reflected origins are the usual mistakes.
Remove the wildcard from anything non-public
Access-Control-Allow-Origin: * permits any site on the internet to read the response. That is fine for genuinely public, non-sensitive data and wrong for anything user-specific, authenticated, or internal. Audit which endpoints send it and why.
Never pair a wildcard with credentials
Access-Control-Allow-Origin: * combined with Access-Control-Allow-Credentials: true would expose authenticated responses to every site. Browsers reject that exact combination, but the attempted configuration signals a misunderstanding worth fixing — if you need credentials cross-origin, you must name a specific origin.
Use an explicit allow-list
Keep a list of trusted origins in configuration. On each request, compare the incoming Origin header against that list and echo it back only on an exact match. Never reflect the Origin header unconditionally — that is functionally identical to a wildcard, while also working with credentials, which makes it worse.
Match origins exactly
Compare full origins, not substrings. A check like origin.endsWith('example.com') accepts evil-example.com and notexample.com.attacker.net. Compare against complete strings including scheme and port, or parse the URL properly before comparing.
Add Vary: Origin when echoing
If the response varies by Origin, send Vary: Origin. Without it, a shared cache can store the response generated for one origin and serve it to another, leaking data across sites or breaking legitimate access. This is routinely forgotten and causes intermittent, hard-to-diagnose bugs.
Restrict methods and headers too
Access-Control-Allow-Methods and Access-Control-Allow-Headers should list only what the endpoint genuinely needs. Allowing every method and arbitrary headers widens the surface for no benefit.
Send nothing where nothing is needed
If no other origin needs programmatic access, send no CORS headers at all. The browser's same-origin policy then applies in full, and that is the most secure configuration available.
Tip: CORS relaxes a browser protection; it never adds one. Absence of CORS headers is the secure default, so every header you add should exist because a specific, identified consumer needs it.
Tags: cors, security, fix
Tutorial
Understanding Cross-Origin Resource Sharing (CORS)
CORS decides whether JavaScript on one site may read responses from another. Configured loosely, it can expose your data to any site on the internet. This guide explains the model and the common misconfigurations.
The same-origin baseline
By default a browser lets JavaScript on site A send a request to site B but not read the response. This is what stops a malicious page silently reading your webmail while you are logged in. CORS is the mechanism by which site B can choose to permit specific other origins to read its responses.
The headers involved
Access-Control-Allow-Origin names who may read the response. Access-Control-Allow-Credentials indicates whether cookies and auth headers may be sent. Access-Control-Allow-Methods and -Headers govern preflight approval for non-simple requests. Preflight is an OPTIONS request the browser sends first to ask permission before the real one.
The wildcard question
Access-Control-Allow-Origin: * says any origin may read this. Perfectly reasonable for a public font, an open dataset or a documentation asset. Wrong for anything reflecting a signed-in user, anything internal, and anything you would not publish openly — because that is effectively what the header does.
The credentials rule
Browsers forbid combining a wildcard origin with credentials, because it would hand authenticated responses to every site. To use credentials cross-origin you must echo back one specific origin. This is deliberately awkward: it forces an explicit decision about exactly who is trusted.
Reflection is the dangerous anti-pattern
A server that takes whatever Origin arrives and echoes it back appears to work perfectly and is equivalent to a wildcard — except it also functions with credentials, so it is strictly more dangerous. Any attacker's page becomes an allowed origin. Always validate against an allow-list before echoing.
Substring matching bites people
Checking whether the origin contains or ends with your domain is a frequent shortcut and a frequent vulnerability. attacker-example.com ends with example.com under a naive check. Compare complete origin strings, scheme and port included.
Why Vary: Origin matters
When the response depends on the request's Origin, caches must be told. Without Vary: Origin, a CDN or proxy may serve a cached response containing one origin's permission to a different origin entirely. The symptom is intermittent and confusing, and the security consequence can be a genuine cross-site leak.
Tip: A quick self-check: curl your endpoint with an invented Origin header, such as -H 'Origin: https://evil.example'. If that value comes back in Access-Control-Allow-Origin, you are reflecting rather than validating, and any site can read the response.
Tags: cors, security, tutorial, learn
Fix
Fix: TLS Certificate Valid
The certificate must be in date, match the hostname, chain to a trusted CA, and be served with its intermediates.
Identify which of the four is failing
A certificate can be invalid for distinct reasons: expired, wrong hostname, untrusted issuer, or incomplete chain. Run it through SSL Labs (ssllabs.com/ssltest) which names the specific fault. Guessing wastes time, because the remedies are completely different.
Issue a trusted certificate
Use Let's Encrypt via certbot, or your hosting panel's built-in SSL. These are free and trusted by every mainstream browser. Self-signed certificates should never appear on a public site — every visitor gets a full-page warning that trains them to click through security errors.
Cover every hostname you serve
The certificate must include each name users actually type. www.example.com and example.com are different names and both need covering, via Subject Alternative Names or a wildcard. A certificate valid for one but not the other produces a warning on half your traffic.
Serve the full chain
Browsers need the intermediate certificates linking yours to a trusted root. Omitting them causes the classic symptom of a site that works in one browser and fails in another, because some clients cache intermediates from previous sites and others do not. With certbot use fullchain.pem, never cert.pem.
Restart and verify externally
Reload the web server, then test from outside with SSL Labs rather than your own browser, which may have cached a chain. Aim for a clean report with no chain warnings.
Automate renewal at the same time
Most invalid-certificate incidents are simply expiries. Set up certbot renew on a timer and prove it works with certbot renew --dry-run, so today's fix does not become next quarter's outage.
Tip: Mozilla's SSL Configuration Generator at ssl-config.mozilla.org produces a correct, current server configuration for nginx, Apache and others. Use it rather than copying a config from a blog post, which may carry cipher advice years out of date.
Tags: tls-valid, security, fix
Tutorial
Understanding TLS Certificate Valid
HTTPS is only as trustworthy as the certificate behind it. This guide explains what makes a certificate valid, why chains matter, and how the trust model actually works.
What a certificate asserts
It binds a public key to a hostname, signed by a Certificate Authority the browser already trusts. That signature is the CA vouching that whoever controls this key also controls this domain. Encryption without that binding would be useless — you would have a private conversation with an unknown party, which is precisely what a MiTM attacker wants.
The four validity conditions
Within its date range; issued for the hostname being visited; signed by a CA in the browser's trust store; and served with the intermediates that connect it to a trusted root. Failing any one produces a browser warning, and users cannot meaningfully distinguish which failed.
The chain of trust
Root CAs keep their keys offline and sign intermediate certificates; intermediates sign yours. Your server must send both your certificate and the intermediates, because the browser only holds the roots. A missing intermediate is the usual cause of 'works for me' reports where the site fails for other people.
Domain validation and free certificates
Let's Encrypt automated domain validation — prove control of the domain by answering a challenge, receive a certificate. This made HTTPS free and scriptable and is why plain HTTP has essentially vanished from the public web. For a normal website, a domain-validated certificate is exactly what is needed.
What EV and OV add, and do not
Organisation- and extended-validation certificates involve checks on the business, at greater cost. Browsers used to show a green company name for EV; they no longer do, having found it did not affect user behaviour. Cryptographically, all three provide identical protection.
Certificate Transparency
CAs must log every certificate they issue to public, append-only logs, and browsers check for that evidence. This means a certificate mis-issued for your domain becomes publicly visible. Monitoring CT logs for your own domains is a cheap way to detect mis-issuance you would otherwise never learn about.
Why validity is operational, not one-off
Certificates expire by design, and the maximum permitted lifetime has been shortening over time as the industry moves towards fully automated issuance. That makes automated renewal a requirement rather than a convenience, and makes an expiry monitor worth having as a backstop.
Tip: Test with SSL Labs rather than your own browser. Browsers cache intermediate certificates from other sites they have visited, so a site with a broken chain can look perfectly fine to you while failing for a first-time visitor.
Tags: tls-valid, security, tutorial, learn
Fix
Fix: TLS Certificate Not Expiring Soon
A certificate close to expiry risks a total outage. Renewal should be automated and independently monitored.
Check the current expiry date
Run: echo | openssl s_client -connect yoursite.com:443 2>/dev/null | openssl x509 -noout -dates. This reports notBefore and notAfter. Anything under 30 days remaining needs attention now rather than at the weekend.
Confirm automated renewal actually exists
Having certbot installed is not the same as renewal being scheduled. Check with systemctl list-timers | grep certbot, or look for a cron entry. Many outages come from a certificate that was once renewed by hand and never automated.
Prove renewal works before you need it
Run certbot renew --dry-run. This performs the entire renewal against the staging environment without consuming rate limits or replacing your certificate. It surfaces the real blockers — a changed DNS record, a firewall rule, a webroot that moved — while you still have weeks of validity left.
Make sure the server reloads after renewal
A renewed certificate on disk is not a renewed certificate in memory. The web server must reload to pick it up. Use certbot's --deploy-hook to run a reload automatically, otherwise you will serve an expired certificate from a directory containing a perfectly valid one.
Renew early, not at the deadline
Standard practice is renewing at roughly a third of the lifetime remaining — around 30 days for a 90-day certificate. That buffer means a failed renewal is an alert to investigate rather than an imminent outage, and it absorbs holidays and weekends.
Add an independent monitor
Automation breaks silently: an expired account, a moved webroot, a DNS change. An external SSL expiry monitor that emails you two weeks out is a cheap backstop that does not share a failure mode with the automation it is watching.
Tip: An expired certificate is a full outage, not a degradation — every visitor hits a full-page interstitial and most will leave. Of all the checks in this audit, this is the one whose failure costs the most business per hour.
Tags: tls-expiry, security, fix
Tutorial
Understanding TLS Certificate Not Expiring Soon
Certificates are deliberately short-lived, which is fine when renewal is automated and catastrophic when it is not. This guide explains why lifetimes keep shrinking and how to stay ahead of them.
Why certificates expire at all
Expiry limits the damage window if a private key is ever compromised without anyone noticing. Certificate revocation has historically worked poorly in browsers, so a short lifetime acts as the reliable backstop: a stolen key stops being useful when the certificate lapses regardless of whether revocation reached anyone.
The trend towards shorter lifetimes
Maximum permitted certificate lifetimes have fallen repeatedly over the past decade, and the industry direction is towards further reductions. The reasoning is that if issuance and renewal are fully automated, duration stops mattering operationally — and short-lived certificates make the whole ecosystem more resilient. The practical consequence is that manual renewal is no longer viable.
Let's Encrypt and the 90-day norm
Let's Encrypt issues 90-day certificates, chosen deliberately to force automation. That length is comfortable for a machine and awkward for a human, which is the point. Their tooling renews at around 30 days remaining, leaving a third of the lifetime as buffer.
Where automation actually fails
Rarely in the renewal command itself. It fails because a DNS record changed and the challenge no longer validates; because a firewall now blocks the HTTP challenge path; because the webroot moved during a redesign; because an account email bounced; or because the renewal succeeded and nothing reloaded the server. Each of these is silent until the certificate lapses.
Testing without consequences
certbot renew --dry-run runs the whole process against staging: no rate limits consumed, no certificate replaced, but every real blocker exposed. Running it periodically — and especially after any infrastructure change — turns a future outage into a present, fixable warning.
Monitoring as a separate layer
The monitor must not share a failure mode with the automation. An external service checking your live endpoint and alerting at two weeks will catch cases where renewal ran, reported success, and the server never picked up the new file — a scenario internal checks routinely miss.
What an expiry actually costs
Every browser shows a full-page security warning. Search engines, uptime monitors and API clients all fail. Payment integrations and webhooks break. Unlike most misconfigurations, which degrade something, this stops everything, and the fix cannot be rushed if the underlying cause is a DNS or firewall problem.
Tip: Set your own alert threshold well before your automation's renewal window, so you learn that renewal failed rather than that the certificate expired — the difference is a quiet afternoon versus an emergency.
Tags: tls-expiry, security, tutorial, learn
Fix
Fix: TLS Protocol Modern
Serve only TLS 1.2 and 1.3. TLS 1.0 and 1.1 are formally deprecated and carry known weaknesses.
Set the protocol floor
nginx: ssl_protocols TLSv1.2 TLSv1.3; — Apache: SSLProtocol -all +TLSv1.2 +TLSv1.3. One line in each case. Every current browser and device supports TLS 1.2 at minimum, so compatibility loss in practice is negligible.
Generate the rest of the config properly
Do not hand-pick cipher suites from memory or from an old blog post. Use ssl-config.mozilla.org, choose the Intermediate profile for a public site, and paste the generated block. It covers protocols, ciphers, curves and session settings coherently and is kept current.
Enable TLS 1.3 explicitly
TLS 1.3 needs a reasonably current OpenSSL and server build. Confirm yours supports it — nginx -V will show the OpenSSL version. It reduces handshake round trips and removes obsolete cipher constructions, so it is both faster and safer.
Check every virtual host and endpoint
Configuration frequently lives per-server-block. A hardened main site sitting beside an API subdomain or a legacy vhost still offering TLS 1.0 leaves the weakness in place. Enumerate every hostname and test each one.
Do not forget the CDN or load balancer
If traffic terminates TLS at Cloudflare, a load balancer or a reverse proxy, that is where the protocol setting matters — your origin configuration may be invisible to clients. Set the minimum version at whichever layer actually faces the internet.
Verify externally
Test with SSL Labs and confirm only 1.2 and 1.3 are listed as accepted. The report also grades cipher configuration and flags anything else outdated, which is worth reading rather than just checking the letter grade.
Tip: Check your analytics for genuinely ancient clients before tightening if you serve an unusual audience, but for almost every site the honest answer is that nothing is left on TLS 1.0 — the browsers that required it stopped receiving updates years ago.
Tags: tls-protocol, security, fix
Tutorial
Understanding TLS Protocol Modern
TLS is the protocol encrypting HTTPS, and it has versions. This guide explains why 1.0 and 1.1 were retired, what 1.3 improves, and how to configure the floor correctly.
Versions and negotiation
Client and server agree the highest protocol version both support. If a server still offers TLS 1.0, a client can end up there — and an attacker able to interfere with the handshake may try to force it. Removing old versions from the server side is what eliminates that possibility.
Why 1.0 and 1.1 were deprecated
Both depend on cryptographic primitives now considered unsafe, including MD5 and SHA-1 in the handshake, and both were subject to attacks such as BEAST against TLS 1.0's CBC construction. RFC 8996 formally deprecates them, and the major browsers disabled them in 2020.
A common confusion about POODLE
POODLE was an attack against SSL 3.0, TLS's predecessor, not against TLS 1.0 itself — though a variant affected some flawed TLS implementations. It is frequently misattributed. The accurate reason to drop TLS 1.0 is its weak primitives and BEAST, not POODLE.
What TLS 1.2 provides
TLS 1.2 introduced modern hash and cipher negotiation, including AEAD ciphers such as AES-GCM which combine encryption and authentication. It remains perfectly secure when configured with sensible cipher suites and is the appropriate minimum for compatibility today.
What TLS 1.3 improves
The handshake is cut to a single round trip, so connections establish faster. Obsolete options were removed outright rather than merely discouraged — no RSA key exchange, no CBC mode ciphers, no compression, no renegotiation. Forward secrecy is mandatory. Less to configure means fewer ways to configure it badly.
Ciphers matter as much as versions
Setting the version floor is only half the job. A server offering TLS 1.2 with weak cipher suites is still weak. This is why using a generated configuration matters: it sets protocols and ciphers as a coherent set rather than leaving one modernised and the other stale.
Where the setting actually applies
If a CDN or load balancer terminates TLS, the client never negotiates with your origin, so the origin's ssl_protocols line has no bearing on what users get. Configure the minimum version at the edge that faces the internet, and confirm by testing the public hostname.
Tip: Mozilla publishes three configuration profiles — Modern, Intermediate and Old. Intermediate is the right default for a public website: TLS 1.2 and 1.3, strong ciphers, and no compatibility surprises.
Tags: tls-protocol, security, tutorial, learn
Fix
Fix: Cookies use Secure flag
Without Secure, a cookie can be sent over plain HTTP and read by anyone on the network path.
Add Secure to every cookie
The attribute belongs on all cookies, not only session ones. Set-Cookie: name=value; Path=/; Secure; HttpOnly. There is no cost on an HTTPS-only site, and it removes any possibility of the value leaking over an unencrypted request.
Set it in your framework's config, not per cookie
Express: app.use(session({ cookie: { secure: true, httpOnly: true, sameSite: 'lax' } })). PHP: session.cookie_secure = 1 in php.ini. Django: SESSION_COOKIE_SECURE = True. Configuring centrally means new cookies inherit the setting rather than depending on whoever adds them remembering.
Use the __Secure- or __Host- name prefix
Prefixing a cookie name with __Secure- means browsers reject it unless it was set over HTTPS with the Secure attribute. __Host- is stricter still: Secure, Path=/, and no Domain attribute, so it cannot be set or overwritten by a subdomain. Use __Host- for cookies needed on one exact host, __Secure- otherwise.
Understand why the prefix matters
Secure controls sending, but historically an attacker with control of a plain-HTTP subdomain could still overwrite a secure cookie. The name prefixes close that gap by making the browser enforce the conditions at the point the cookie is set. This is the piece most guidance omits.
Do not forget non-session cookies
Preference, consent, cart, A/B test and analytics cookies all travel with every request. Even where the contents look harmless, they can expose user behaviour and sometimes identifiers. Apply Secure across the board.
Verify in DevTools
Open Application then Cookies and inspect the Secure, HttpOnly and SameSite columns for each cookie. This is faster and more reliable than reading server code, and it shows cookies set by third-party scripts that your code never touches.
Tip: Also tighten Domain and Path while you are there. Set Domain only if the cookie genuinely needs to cross subdomains, and set the narrowest Path that works — every unnecessary widening is another place the cookie can be exposed.
Tags: cookie-secure, security, fix
Tutorial
Understanding Cookies use Secure flag
The Secure attribute confines a cookie to HTTPS connections. This guide explains what it protects, what it does not, and why the __Host- and __Secure- name prefixes complete the picture.
What the attribute does
Secure tells the browser to include this cookie only on HTTPS requests. Without it, any plain-HTTP request to your domain — a typed URL, an old bookmark, an http:// image reference — carries the cookie in clear text across the network, where anyone on the path can read it.
Why session cookies are the priority
A session cookie is a bearer token: possession is authentication. An attacker who captures one does not need the password, does not trigger a login notification, and often bypasses two-factor entirely because the session is already established. Public Wi-Fi is the textbook capture scenario.
The gap Secure alone leaves
Secure governs sending, not setting. An attacker who can serve content on a plain-HTTP subdomain of your domain could historically set or overwrite a cookie that your HTTPS site then reads — a cookie-forcing or session-fixation route. The Secure flag on your cookie does not prevent someone else writing a different one.
How __Secure- closes it
A cookie whose name begins with __Secure- is rejected by the browser unless it was set from a secure origin with the Secure attribute. The guarantee is now enforced at write time as well as read time, so a plain-HTTP context cannot create or replace it.
How __Host- goes further
__Host- requires Secure, requires Path=/, and forbids the Domain attribute. Forbidding Domain is the important part: it means the cookie is locked to the exact host that set it and no subdomain can touch it. For session identifiers on a single host, this is the strongest option available.
The three attributes together
Secure keeps the cookie on HTTPS. HttpOnly keeps it away from JavaScript, so XSS cannot read it. SameSite keeps it off cross-site requests, blunting CSRF. Each addresses a different attack, and a session cookie should carry all three — omitting one leaves that specific route open.
Expiry is part of security too
Cookies should live no longer than needed. Max-Age is preferred over Expires because it is relative and therefore immune to client clock skew, and it takes precedence when both are present. If neither is set, the cookie lasts until the browser closes, which is often the right choice for a session identifier.
Tip: DevTools, Application, Cookies gives you the truth in one screen: every cookie with its Secure, HttpOnly, SameSite, Domain, Path and expiry. Check it after login, since the cookies that matter most often only appear once authenticated.
Tags: cookie-secure, security, tutorial, learn
Fix
Fix: Cookies use HttpOnly flag
HttpOnly hides a cookie from JavaScript, so an XSS bug cannot read your session token via document.cookie.
Add HttpOnly to every authentication cookie
Set-Cookie: session=abc; Path=/; Secure; HttpOnly; SameSite=Lax. Any cookie identifying a logged-in user should carry it. There is no legitimate reason for front-end JavaScript to read a session identifier.
Configure it at framework level
Express sessions default httpOnly to true — verify it has not been overridden. PHP: session.cookie_httponly = 1. Django sets SESSION_COOKIE_HTTPONLY True by default. Centralising the setting means it applies to cookies added later by someone else.
Identify cookies that genuinely need JavaScript access
Some do: a UI theme preference, a cookie-consent flag, an analytics client ID. Those can omit HttpOnly. The test is whether the value would be damaging in an attacker's hands — a theme name is not, a session token is.
Stop storing tokens where scripts can read them
If your application keeps a JWT or API token in localStorage or a readable cookie, any XSS retrieves it instantly. Move authentication to an HttpOnly cookie set by the server. localStorage has no protection equivalent to HttpOnly and never will.
Check third-party scripts are not copying values
Tag managers and analytics snippets sometimes read document.cookie wholesale. Cookies with HttpOnly are invisible to them, which is a privacy benefit as well as a security one — worth confirming what your third parties actually collect.
Verify in DevTools
Application, Cookies, HttpOnly column. Then run document.cookie in the console: properly protected cookies should not appear in the output at all.
Tip: HttpOnly does not prevent XSS; it limits what an XSS bug can achieve. Since no site can promise zero XSS forever, it is exactly the kind of cheap, always-on mitigation worth having in place before you need it.
Tags: cookie-httponly, security, fix
Tutorial
Understanding Cookies use HttpOnly flag
HttpOnly stops JavaScript reading a cookie. This guide explains the attack it blunts, why it is not a fix for XSS, and where it should and should not be applied.
The attack it interrupts
In a typical XSS exploitation, injected JavaScript reads document.cookie and sends the session token to a server the attacker controls. They then replay it and are logged in as the victim — no password, no second factor, no alert. HttpOnly cookies never appear in document.cookie, so this particular step fails.
What it does not do
It does not prevent injection, and it does not stop the injected script acting. An attacker who cannot read the cookie can still make authenticated requests from within the page, because the browser attaches the cookie automatically. They can transfer funds or change an email address; they simply cannot take the session away and use it elsewhere, later, from their own machine.
Why that distinction still matters
Confining the attacker to the victim's live session is a substantial reduction. They must act while the user is on the page, under your CSRF protections and rate limits, and their activity is visible in your logs against that session. A stolen token, by contrast, is usable at leisure and looks like a normal login.
Which cookies should have it
Every session, authentication and CSRF-token cookie. The rule of thumb: if your front-end code never legitimately reads it, set HttpOnly. Most applications find that this covers nearly every cookie they set server-side.
The localStorage comparison
Tokens in localStorage are readable by any script on the page, including a compromised dependency. There is no HttpOnly equivalent for localStorage. Architectures that hold JWTs there for convenience are choosing a design where any XSS is a complete account takeover — an HttpOnly cookie set by the server is the safer default.
Interaction with the other flags
HttpOnly blocks script access. Secure blocks transmission over HTTP. SameSite blocks sending on cross-site requests. They are independent and complementary; a session cookie should carry all three, since each closes a route the others do not.
Verifying it is actually on
Set the cookie, then type document.cookie in the console. If your session cookie shows up there, HttpOnly is not applied, whatever the configuration file says — frameworks and proxies can and do override each other.
Tip: Check this after logging in, not on the public homepage. The cookies that matter are usually issued at authentication, and a session cookie missing HttpOnly is invisible if you only inspect the logged-out state.
Tags: cookie-httponly, security, tutorial, learn
Fix
Fix: Cookies use SameSite attribute
SameSite controls whether a cookie rides along on cross-site requests, which is what makes CSRF possible.
Set SameSite explicitly on every cookie
Set-Cookie: session=abc; Path=/; Secure; HttpOnly; SameSite=Lax. Modern browsers default to Lax when the attribute is absent, but stating it is clearer, portable across older clients, and is what scanners check for.
Choose Lax as the sensible default
Lax sends the cookie on top-level navigations — a user clicking a link to your site from elsewhere — but withholds it on cross-site sub-requests such as form posts, iframes and image loads. That blocks the main CSRF vectors while keeping inbound links working normally.
Use Strict for the highest-value cookies
Strict withholds the cookie on every cross-site request including plain navigation, so a user arriving from an external link appears logged out until they navigate within the site. Appropriate for banking or admin interfaces; usually too abrupt for a consumer site's main session cookie.
Use None only for genuine cross-site needs
SameSite=None permits the cookie everywhere and must be paired with Secure or browsers reject it. Only correct where a cookie is legitimately needed in a third-party context, such as an embedded widget. Reaching for None to make something work is usually a sign the design should change.
Keep your CSRF tokens
SameSite is a good layer but not a complete defence: browser support has varied, some request types behave differently under Lax, and same-site-but-different-subdomain scenarios are not covered. Anti-CSRF tokens on state-changing requests remain the primary control.
Test the journeys that break
Check arriving from an external link, submitting embedded forms, returning from a payment provider redirect, and any OAuth callback. Payment and SSO round trips are where an over-strict SameSite reveals itself, and it fails in a confusing way — the user simply appears logged out.
Tip: If a payment or SSO return trip logs users out, Strict is the likely culprit: the browser withheld the session cookie on the inbound navigation from the external provider. Lax is normally the correct setting for a session cookie.
Tags: cookie-samesite, security, fix
Tutorial
Understanding Cookies use SameSite attribute
SameSite decides whether a cookie is sent when a request originates from another site. This guide covers CSRF, what each value does, and why SameSite alone is not enough.
The CSRF attack
A user is logged into your site. They visit a malicious page, which silently issues a request to yours — a form post, an image URL, a fetch. Because the browser attaches cookies by origin rather than by intent, the request arrives fully authenticated. Your server cannot distinguish it from a deliberate action by the user.
What SameSite changes
The attribute tells the browser to consider where the request came from. If it originated on another site, the cookie can be withheld. The forged request then arrives unauthenticated and fails. This moves a defence that previously required application-level tokens into the browser itself.
The three values
Strict: never sent on any cross-site request, including a user clicking a link to your site. Lax: sent on top-level navigations but not on cross-site sub-requests such as posts, iframes and image loads. None: always sent, and only permitted alongside Secure.
Why Lax is usually right
Strict is more secure but produces a jarring effect — a user clicking a link from an email or a search result lands on your site apparently logged out, then finds themselves logged in after one more click. Lax avoids that while still blocking cross-site form posts and sub-resource requests, which is where CSRF actually lives.
Modern defaults
Browsers now treat a cookie with no SameSite attribute as Lax rather than the historical None. This raised the floor considerably across the web. It is still worth setting explicitly: defaults differ between browser versions, and an explicit value documents the intent for whoever reads the code next.
Why tokens are still required
SameSite is a strong layer, not a complete answer. Requests from a different subdomain of the same registrable domain are same-site, so a compromised subdomain is not blocked. Older clients may not enforce it. Some request shapes behave unexpectedly under Lax. Anti-CSRF tokens on every state-changing request remain the primary defence, with SameSite as depth.
Third-party contexts
Embedded widgets, cross-domain SSO and payment iframes may genuinely need cookies in a third-party context, requiring None plus Secure. Note that browsers are increasingly restricting third-party cookies regardless of SameSite, so designs depending on them are on shifting ground and worth revisiting.
Tip: SameSite also blunts clickjacking and some cross-site leak attacks, because a framed or cross-site request may arrive without the session cookie and therefore unauthenticated — one attribute contributing to three separate defences.
Tags: cookie-samesite, security, tutorial, learn
Fix
Fix: Referrer-Policy header
Referrer-Policy limits how much of your URL is disclosed to other sites when users click away or your pages load third-party resources.
Set the header
Referrer-Policy: strict-origin-when-cross-origin. Full URL is sent for same-origin requests, only the bare origin cross-origin, and nothing at all when downgrading from HTTPS to HTTP. This is the balanced default and matches what modern browsers now do without a header.
Tighten it if URLs carry secrets
If any of your URLs contain reset tokens, invitation codes, search terms or record identifiers, use same-origin (referrer only to your own site) or no-referrer (never send one). Password reset and magic-link URLs are the classic case for the strictest setting.
Audit what is actually in your URLs
Grep your routes for tokens, email addresses and identifiers in paths or query strings. The best fix is often to stop putting them there — move sensitive values into POST bodies or short-lived server-side sessions — with the header as a second line of defence.
Override per element where needed
Individual links and resources accept a referrerpolicy attribute, and rel="noreferrer" on a link strips it entirely. Use these for specific outbound links rather than loosening the site-wide policy for one integration.
Check your analytics still work
Referrer data feeds attribution reporting. strict-origin-when-cross-origin preserves the origin, which is what most analytics needs. no-referrer removes it entirely, so confirm nothing you depend on breaks before choosing the strictest option.
Verify with curl
curl -sI https://yoursite.com | grep -i referrer. Confirm the header is present on every response type, since header configuration is frequently applied to one location block and missed on others.
Tip: Browsers now default to strict-origin-when-cross-origin when no header is present, so the risk here is lower than it once was. Setting it explicitly still matters for older clients and to document the intent.
Tags: referrer-policy, security, fix
Tutorial
Understanding Referrer-Policy header
When a user clicks from your site to another, the browser tells the destination where they came from. This guide explains what leaks, what each policy value does, and when to tighten.
What the Referer header carries
On every navigation and sub-resource request, browsers may include a Referer header naming the page that initiated it — historically the full URL including path and query string. The misspelling dates from the original HTTP specification and has stuck.
What can leak
If a URL contains a password reset token, a search query, an order number, an email address or a document identifier, that entire URL can be handed to any external site the user clicks through to, and to every third-party resource the page loads. Analytics scripts, embedded fonts and ad tags all receive it.
Why this is a real exposure
Referrer data ends up in the destination's server logs and analytics dashboards, often retained for a long time and visible to people who have no relationship with your users. A reset token appearing in a third party's logs is a genuine credential exposure, not a theoretical one.
The policy values
no-referrer sends nothing ever. same-origin sends the full URL to your own site and nothing to others. origin sends just the scheme and host to everyone. strict-origin sends the origin but never on an HTTPS-to-HTTP downgrade. strict-origin-when-cross-origin sends the full URL same-origin and the origin cross-origin, and nothing on downgrade.
Why strict-origin-when-cross-origin became the default
It balances two things people want: internal analytics that can see full paths, and external parties that learn only which site the visitor came from. Browsers adopted it as the no-header default, which raised the baseline for every site that never configured anything.
When to go stricter
If your application puts secrets in URLs, the header is a mitigation and not a solution — but no-referrer or same-origin is the right mitigation while you fix the underlying design. Anything reachable by URL alone should be treated as a credential.
Per-element control
You do not have to choose one policy for everything. The referrerpolicy attribute works on links, images, iframes and scripts, and rel="noreferrer" strips the referrer for a specific link. This lets you keep a sensible site default and tighten the specific cases that warrant it.
Tip: The stronger fix is architectural: keep tokens and identifiers out of URLs entirely. A header prevents disclosure by referrer, but URLs also land in browser history, bookmarks, server logs and shared screenshots.
Tags: referrer-policy, security, tutorial, learn
Fix
Fix: Permissions-Policy header
Permissions-Policy switches off browser features your site does not use, limiting what a compromised script or embed can request.
Set a baseline disabling what you do not use
Permissions-Policy: geolocation=(), microphone=(), camera=(). Empty parentheses disable the feature entirely, for your page and any embedded frame. These three are what scanners look for and what most sites never use.
Allow only what you actually need
If a feature is genuinely used, scope it: geolocation=(self) permits your own origin only. To permit a named third party, geolocation=(self "https://maps.example.com"). Origins go in double quotes inside the parentheses; self is a bare keyword without quotes.
Extend the list beyond the obvious three
Consider payment=(), usb=(), midi=(), magnetometer=(), accelerometer=(), gyroscope=(), autoplay=(), fullscreen=(self), display-capture=(). Unlisted features keep browser defaults, so anything you want restricted must be named explicitly.
Remember it also constrains iframes
This is where most of the value sits. An embedded third-party frame inherits your policy, so a compromised widget cannot prompt for the user's camera or location. If a frame legitimately needs a feature, grant it in the policy and via the iframe's allow attribute.
Do not confuse it with the old header
Permissions-Policy replaced Feature-Policy, and the syntax changed — Feature-Policy used space-separated origins without parentheses. Copying old examples produces a header browsers silently ignore, which looks configured and does nothing.
Verify it is parsed
Check the header with curl, then open DevTools and look for console warnings about malformed policy directives. A syntax error means the whole header may be discarded, so seeing it in the response is not proof it is in effect.
Tip: Disabling geolocation, microphone and camera is safe for the vast majority of sites, costs nothing, and is exactly what a scanner checks. If you later need one, granting it is a one-line change.
Tags: permissions-policy, security, fix
Tutorial
Understanding Permissions-Policy header
Permissions-Policy declares which browser features your page and its embedded frames may use. This guide explains the syntax, the iframe angle, and a sensible baseline.
What it governs
Access to capabilities that require user permission or hardware: camera, microphone, geolocation, payment request, USB, MIDI, motion sensors, screen capture, autoplay, fullscreen. By default a page and its frames may attempt to use these; the header lets you switch them off.
Why disabling unused features helps
It is blast-radius reduction. If a script on your page is ever compromised — your own code, a dependency, a tag manager payload — the policy prevents it prompting the user for their camera or location. A permission prompt that appears to come from your trusted site is a persuasive phishing surface.
The iframe case is the important one
Embedded third-party content inherits your policy. Ad frames, chat widgets, video players and payment iframes are all code you did not write, running on your page. Permissions-Policy is one of the few controls that meaningfully constrains what they can ask the user for.
The syntax in detail
feature=() disables it everywhere. feature=(self) allows your origin only. feature=(self "https://trusted.example") allows your origin plus a named one. feature=* allows everywhere. Note that self is unquoted while origins are quoted — the reverse of CSP, and a frequent source of malformed headers.
Feature-Policy was the predecessor
The header was renamed and its grammar changed when it moved from Feature-Policy to Permissions-Policy. Old syntax copied from pre-2021 articles will not parse. Browsers tend to ignore a malformed header rather than warn loudly, so the misconfiguration can persist unnoticed.
Interaction with the iframe allow attribute
An iframe's allow attribute can only narrow what the parent policy already permits, never widen it. To grant a frame a feature you must permit it in the header and delegate it via allow. Getting one and not the other is the usual reason a legitimate embed cannot access what it needs.
A sensible baseline
Disable geolocation, microphone and camera unless used. Disable payment, usb and midi, which almost no site needs. Restrict fullscreen and autoplay to self. Then add back precisely what a specific feature or partner requires, rather than starting permissive and hoping to tighten later.
Tip: Enumerate your third-party embeds before writing the policy. The features to think hardest about are the ones your own code never touches but a widget might — that is exactly the gap this header is for.
Tags: permissions-policy, security, tutorial, learn
Fix
Fix: X-Powered-By header absent
X-Powered-By announces your language or framework and version. It serves no purpose for visitors and helps attackers narrow their toolkit.
Disable it in the application
Express: app.disable('x-powered-by') — ideally the first line after creating the app. PHP: expose_php = Off in php.ini, then restart PHP-FPM. ASP.NET: remove the header via web.config. Fixing it at source is better than stripping it downstream.
Strip it at the proxy as a backstop
Where the header comes from something you cannot change, remove it at the edge. nginx: proxy_hide_header X-Powered-By; inside the location block. Apache: Header always unset X-Powered-By. This also catches anything added by a component you have forgotten about.
Look for related framework headers
Applications emit others: X-AspNet-Version, X-Generator, X-Drupal-Cache, X-Runtime, X-Rack-Cache. Each names a technology or a version. Strip them the same way; a single leftover header undoes the fingerprinting benefit of removing the rest.
Check every response type
Headers can differ between HTML pages, API endpoints, static assets and error responses, because they are often served by different components. Test a page, an API call, an image and a 404 rather than assuming the homepage is representative.
Restart properly
PHP settings need PHP-FPM restarted, not just a web server reload. Confirm the change took effect rather than trusting the config file — an edited ini that was never loaded is a common false success.
Verify
curl -sI https://yoursite.com | grep -i powered — no output means it is gone. Repeat against an API endpoint, where the framework often responds directly and behaves differently.
Tip: This costs one line and breaks nothing. It is the cheapest item in most audits, which is exactly why leaving it in place is hard to justify to anyone reviewing your configuration.
Tags: powered-by, security, fix
Tutorial
Understanding X-Powered-By header absent
X-Powered-By is a header many frameworks add by default, naming the technology and often its version. This guide explains what it gives away and how to remove it everywhere.
Where it comes from
Frameworks and language runtimes add it automatically as a form of branding. PHP sends X-Powered-By: PHP/8.1.2. Express sends X-Powered-By: Express. ASP.NET adds its own variants. Nobody chose to enable these; they are on unless switched off, so most site owners never know they are there.
What it tells an attacker
It narrows the search. Knowing you run PHP 8.1.2 means an attacker can skip everything irrelevant and go straight to exploits for that runtime and version. Combined with a verbose Server header they have your full stack before sending a single unusual request.
Why it exists at all
Historically it was promotional — frameworks advertising their adoption. It has no functional role: browsers ignore it, caches ignore it, and no client behaviour depends on it. It is pure information disclosure with no compensating benefit.
Removing it properly
Best at the source, in the application or runtime configuration, so it is never generated. A proxy-level strip is a good safety net, especially in front of components you do not control, but relying solely on the proxy means a route that bypasses it will leak.
The wider family of headers
X-AspNet-Version, X-AspNetMvc-Version, X-Generator, X-Drupal-Cache, X-Runtime, X-Rack-Cache and similar all disclose technology or timing details. A thorough pass removes the whole set rather than the single header a scanner happened to name.
Fingerprinting continues regardless
Removing headers raises the effort required; it does not make you unidentifiable. Cookie names, URL structures, error page wording and response header ordering all hint at the stack. The goal is reducing what is free to obtain, not achieving anonymity.
Where it fits overall
Grouped with Server version and CMS generator tags, this is the fingerprinting family of checks. Individually each is minor; together they determine whether an automated scan can classify your site in one request or has to work for it.
Tip: Check API endpoints separately from web pages. APIs are frequently served by the application framework directly rather than through the same proxy path, and are a common place where a header removed everywhere else survives.
Tags: powered-by, security, tutorial, learn
Fix
Fix: No known CVEs
A detected version matching a published CVE means a documented weakness in exactly what you run. Patch to the fixed release.
Read the specific advisory
Take the CVE identifier and look it up. Check the affected version range, the fixed version, and whether exploitation has been observed. Not every CVE applies to every configuration — some need a module or feature you do not use — so read before scheduling emergency work.
Check CISA KEV first
The CISA Known Exploited Vulnerabilities catalogue lists flaws confirmed as actively exploited in the wild. If your CVE is on it, treat it as an emergency regardless of its severity score. KEV membership is a far better urgency signal than CVSS, because it reflects observed attacker behaviour rather than theoretical impact.
Do not rely on a CVSS score being present
Since April 2026, NIST no longer enriches every CVE. Unless a flaw is in CISA KEV, in US federal software, or designated critical under Executive Order 14028, it is marked lowest priority in the NVD with no CVSS score, no affected-product mapping and no weakness classification. A missing score does not mean low risk — it frequently means nobody has assessed it yet.
Cross-check other vulnerability sources
Because NVD coverage is now partial, check the vendor's own advisory, CISA's Vulnrichment project (which adds early scoring), and OSV for open-source dependencies. The EU's EUVD is a further source. Relying on a single database now leaves genuine gaps.
Upgrade to the fixed version
This is nearly always the whole remedy. OS packages: apt update && apt upgrade or the yum/dnf equivalent. Node: npm audit fix, or npm install package@version. PHP: composer update. CMS: apply core, theme and plugin updates.
Handle breaking upgrades deliberately
If the fix needs a major version bump that breaks compatibility, check whether maintainers backported the patch to your branch, look for a vendor LTS release, or apply the upstream patch yourself. Deferring a known-exploited CVE because upgrading is awkward is the decision that precedes most compromises.
Cover the whole stack and re-scan
CVEs affect the OS, web server, runtime, dependencies, and CMS plugins — plugins being the most common real-world entry point. After upgrading, restart the service and re-run the audit to confirm the reported version actually changed; an upgraded package with the old process still running is a common false fix.
Tip: Enable unattended-upgrades for OS security patches and Dependabot or similar for application dependencies. Most compromises exploit vulnerabilities patched months earlier, so shrinking the gap between release and application matters more than any amount of scanning.
Tags: cve, security, fix
Tutorial
Understanding No known CVEs
A CVE is a publicly catalogued security flaw with a unique identifier. This guide explains the system, why known vulnerabilities are the most dangerous kind, and how the vulnerability data landscape changed in 2026.
What a CVE is
Common Vulnerabilities and Exposures assigns each publicly disclosed flaw a unique identifier such as CVE-2024-12345. The purpose is unambiguous reference: your scanner, the vendor advisory, the patch notes and the exploit database all mean the same flaw by the same name. CVE identifiers are issued by CVE Numbering Authorities, which include major vendors as well as central bodies.
How scanners detect them
A passive scan fingerprints software versions from headers, generator tags and asset paths, then matches those versions against vulnerability databases. This is inference from a version string rather than proof of exploitability — but it is precisely the inference an attacker's automated tooling makes, which is why it is worth acting on.
Why public disclosure raises the risk
Publication brings a detailed description and often proof-of-concept code. Within days that is incorporated into scanning tools and fired at the whole internet indiscriminately. An undisclosed flaw requires someone to find it; a published one requires only that you have not yet patched.
The NVD change of April 2026
The NIST National Vulnerability Database was historically the primary enrichment source, adding CVSS scores, affected-product data and weakness classifications to raw CVE records. Facing a 263% rise in submissions between 2020 and 2025, NIST formally abandoned the goal of analysing every CVE. It now enriches only those in CISA's KEV catalogue, in US federal software, or designated critical under Executive Order 14028.
What that means in practice
CVEs outside those categories are still listed but marked lowest priority, with no CVSS score, no CPE product mapping and no CWE classification. The entire backlog published before March 2026 was moved to a Not Scheduled category. Estimates suggest the prioritised categories cover roughly 15 to 20 percent of volume, so most new CVEs now arrive without the metadata automated tooling expects.
Where to get vulnerability data now
No single database suffices. Vendor advisories remain authoritative for their own products. CISA's Vulnrichment adds early scoring for newly published CVEs. OSV covers open-source ecosystems and works from package versions rather than CPE strings. The EU's European Vulnerability Database has operated since May 2025. Cross-referencing is now normal practice rather than diligence.
Severity scores and their limits
CVSS gives a 0-10 rating, but it is a poor priority signal alone — and increasingly may be absent. A medium-severity flaw with a working public exploit in an internet-facing component outranks a critical one requiring local access to something you do not expose. KEV membership, exploit availability and your own exposure matter more than the number.
Dependencies are where the volume lives
Your own code is a small fraction of what you run. A typical application pulls in hundreds of transitive dependencies, each with its own CVE history. Tooling that inventories dependencies and alerts on advisories is the only workable approach at that scale, and OSV-based scanners handle this better than CPE-based matching.
Tip: Patch first, then hide versions. Version obfuscation removes you from opportunistic scanning, but if the flaw is still present a targeted attacker will find it — the two measures complement each other and neither substitutes for the other.
Tags: cve, security, tutorial, learn
Fix
Fix: CMS not exposing version
A CMS that advertises its exact version tells attackers precisely which version-specific exploits to try.
Remove the generator meta tag
WordPress: add remove_action('wp_head', 'wp_generator'); to your theme's functions.php or a small site plugin. Other systems have equivalent settings. This removes the most obvious disclosure, the <meta name="generator"> element in your page source.
Strip version query strings from assets
WordPress appends ?ver=6.2 to script and style URLs, which discloses the version even with the meta tag gone. Filter script_loader_src and style_loader_src to remove the ver parameter, or use a security plugin that does it. Note this affects cache-busting, so pair it with a build-time versioning scheme.
Remove or block readme and version files
readme.html, license.txt and similar files in the web root state the version plainly. Delete them after upgrades, or block them at the server level, since some are recreated by the update process.
Check the REST API and feeds
WordPress exposes version information through /wp-json/ and in RSS feed generator elements. Filters exist for both. Anything reachable without authentication is fair game for a scanner, so enumerate what your CMS publishes by default.
Keep everything updated — this is the real fix
Core, themes and plugins. Enable automatic updates for security releases at minimum. Plugins are the most common compromise route on CMS sites by a wide margin, and an abandoned plugin with no maintainer should be removed rather than left in place.
Audit the plugin inventory
Remove anything deactivated but still installed — deactivated code sitting on disk is still reachable in some exploit scenarios. Check each remaining plugin has been updated in the last year; unmaintained code accumulates unpatched flaws indefinitely.
Tip: Hiding the version helps against untargeted scanning only. A CMS site that hides its version but runs a plugin with a known unpatched flaw is thoroughly exploitable — the update routine matters far more than the concealment.
Tags: cms-version, security, fix
Tutorial
Understanding CMS not exposing version
Content management systems advertise themselves and their versions in several places. This guide covers where the disclosures are, why they matter, and what actually protects a CMS site.
Where the version leaks
The generator meta tag in the page head is the obvious one. Beyond it: ?ver= query strings on script and stylesheet URLs, readme and license files in the web root, RSS feed generator elements, REST API responses, login page markup, and default file paths that vary between releases.
Why the version is valuable to an attacker
Every CMS release has its own catalogue of known vulnerabilities. Knowing you run a specific version lets an attacker select exactly the exploits that apply and skip everything else. Automated tools do this at scale — fingerprint, match, exploit — across millions of sites.
Plugins are the real attack surface
Core CMS software is scrutinised by many people and patched quickly. Plugins and themes are often written by one person, may be abandoned, and receive far less review. The large majority of CMS compromises come through a plugin, not core, which is why plugin discipline matters more than core version concealment.
What hiding achieves
It removes you from the results of scans filtering on a specific version string. That is a genuine reduction in opportunistic attack volume, and it is worth doing. It does not stop anyone determined, because behavioural fingerprinting identifies both the CMS and roughly its version regardless.
The cache-busting trade-off
Version query strings exist to force browsers to fetch updated assets. Removing them without an alternative means users can be served stale CSS and JavaScript after an update. Replace the version parameter with a content hash or a build identifier so cache-busting keeps working.
Reducing what is reachable
Beyond version hiding: restrict access to admin and login paths where practical, disable file editing from the admin interface, remove unused themes and plugins entirely rather than deactivating them, and ensure uploads directories cannot execute code.
What actually keeps a CMS site safe
Prompt updates across core, themes and plugins; a minimal plugin inventory from maintained sources; strong authentication with multi-factor on admin accounts; and working backups you have restored from at least once. Version concealment is a thin layer on top of those.
Tip: Search your own page source for the CMS name and version number before assuming they are hidden. They typically appear in more places than expected, and removing one while leaving four others achieves nothing.
Tags: cms-version, security, tutorial, learn
Fix
Fix: SPF record present
SPF publishes which mail servers may send as your domain, so receivers can identify forgeries. RFC 7208 imposes limits that silently break records.
Inventory everything that sends as you
Before writing anything, list every source: your mail provider, transactional email services, marketing platforms, CRM, helpdesk, invoicing software, monitoring alerts, and any application server sending directly. Omitting one means its mail starts failing authentication.
Publish a single TXT record
Add one TXT record at the domain root: v=spf1 include:_spf.google.com ~all — substituting the include mechanisms your providers document. Each provider publishes the exact value; do not guess at hostnames.
Have exactly one SPF record
RFC 7208 section 3.2 permits only one SPF TXT record per domain. Two do not merge — the result is a PermError that breaks authentication for every message, even when each record is individually valid. When adding a provider, merge its mechanism into the existing record.
Stay within the ten-lookup limit
Section 4.6.4 caps evaluation at ten DNS-querying terms. include, a, mx, ptr, exists and redirect all count, including lookups nested inside included records; ip4, ip6 and all do not. Exceed it and receivers return PermError, which DMARC treats as a fail. Organisations using three or four providers routinely hit this without knowing.
Watch the void lookup limit too
RFC 7208 also limits void lookups — queries returning NXDOMAIN or an empty answer — to two. A stale include: pointing at a retired vendor domain can trigger PermError on its own, even while you are comfortably under ten lookups. Remove includes for services you no longer use.
Get back under the limits if you are over
Remove vendors you no longer send through. Replace lookup-heavy mechanisms with direct ip4/ip6 entries where the provider publishes stable addresses. Drop mx if it is not needed, since a domain with several MX hosts can consume multiple lookups. Some providers offer a single consolidated include in place of several.
Choose the qualifier and validate
~all is soft fail, -all is hard fail. Start with ~all while confirming every legitimate source passes, then move to -all. Validate with an SPF checker that reports the actual lookup count, then use DMARC reports to reveal senders you overlooked.
Tip: SPF alone is insufficient: it breaks on forwarding, because the forwarding server is not in your record. That is exactly why DKIM and DMARC exist alongside it, and why all three should be configured together.
Tags: spf, security, fix
Tutorial
Understanding SPF record present
SPF is a DNS record listing which servers may send email using your domain. This guide covers how it works, the RFC limits that break records silently, and why it cannot stand alone.
The problem it addresses
The From address on an email is trivially forgeable — it is simply a text field. Without a checkable mechanism, anyone can send mail appearing to come from your domain, phishing your customers using your brand's credibility. SPF was the first widely adopted attempt to make that verifiable.
How it works
You publish a TXT record listing authorised sending sources. A receiving server takes the envelope sender domain, looks up its SPF record, and checks whether the connecting server's IP is authorised. Authorised means pass; unauthorised means fail, handled according to your qualifier.
The mechanisms
include: pulls in another domain's SPF record and is the usual way to authorise a provider. ip4: and ip6: authorise addresses or ranges directly. a and mx authorise hosts named in those DNS records. The record ends with a qualifier — ~all soft fail, -all hard fail, ?all neutral.
The ten-lookup limit
RFC 7208 section 4.6.4 caps evaluation at ten DNS-querying terms per check, counting nested lookups inside included records. This is a resource-protection measure against DNS amplification rather than a security feature, but the practical effect is severe: exceeding it produces PermError and SPF stops working. It does not degrade gracefully or keep counting.
The void lookup limit
A second, widely overlooked limit: at most two void lookups, meaning queries that return NXDOMAIN or an empty answer. A single include pointing at a decommissioned vendor domain can push you over and produce PermError independently of the ten-lookup count. This is why retiring a service means removing its include, not just cancelling the account.
One record only
A domain must publish exactly one SPF TXT record. Two do not combine — the result is PermError and SPF fails entirely. This happens routinely when a new service is onboarded and its suggested record is added alongside the existing one rather than merged into it.
Why forwarding breaks it
When mail is forwarded, the forwarding server relays it while keeping the original sender address, but that server is not in your record, so the check fails. This is inherent to checking the connecting IP and cannot be configured away. It is the main structural reason SPF cannot be the only mechanism.
How PermError interacts with DMARC
DMARC treats an SPF PermError as a fail. So a record that exceeds either limit does not merely lose SPF — it actively harms DMARC alignment, and your legitimate mail can be quarantined or rejected under your own policy. The failure is silent unless you are reading DMARC reports.
Tip: Check your record with a tool that reports the exact DNS lookup count, not just syntax validity. A record can be perfectly well-formed and still fail every check because it needs eleven lookups.
Tags: spf, security, tutorial, learn
Fix
Fix: DMARC record present
DMARC tells receivers what to do when SPF and DKIM fail, reports who is sending as your domain, and is now a delivery requirement for bulk senders.
Publish SPF and DKIM first
DMARC evaluates those two checks, so it has nothing to work with until both are in place and passing for your legitimate mail. Publishing a strict policy before they work reliably will block your own email.
Start in monitoring mode
Add a TXT record at _dmarc.yourdomain with v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain. p=none changes nothing about delivery — it only requests reports. This is the safe starting point for every domain.
Read the aggregate reports
Reports arrive as XML, daily, from each receiving provider. Use an analyser rather than reading raw XML. They show every source sending as your domain, whether each passed SPF and DKIM, and at what volume — including legitimate senders you had forgotten and any actual spoofing.
Fix alignment for your real senders
A source can pass SPF yet fail DMARC because of alignment: the authenticated domain must match the visible From domain. Marketing platforms sending as your brand from their own envelope domain are the classic case; the fix is a custom return-path plus DKIM signing on your domain.
Know the bulk sender thresholds
Google, Yahoo and Microsoft require SPF, DKIM and DMARC with alignment for domains sending roughly 5,000 or more messages a day to their consumer addresses. With Google the classification is permanent once crossed — reducing volume later does not reverse it. Non-compliant mail now receives permanent rejections rather than spam placement.
Watch the spam complaint rate
The bulk sender rules set a hard ceiling of 0.3% spam complaints, with under 0.1% as the target. Yahoo calculates against inbox-delivered mail rather than total delivered, making its effective threshold stricter. One-click unsubscribe conforming to RFC 8058 is also required for marketing mail.
Move to quarantine, then reject
Once reports show all legitimate mail passing and aligned, move to p=quarantine, monitor, then p=reject. The pct= tag lets you apply the policy to a percentage of mail while you watch. Keep the rua address in place permanently, since new senders appear over time.
Tip: p=none gives you reporting and no protection. A domain sitting at p=none for years has the appearance of DMARC without the benefit — and for bulk senders, an unaligned or monitoring-only policy is increasingly treated as non-compliant.
Tags: dmarc, security, fix
Tutorial
Understanding DMARC record present
DMARC ties SPF and DKIM to the visible From address, sets policy for failures, and reports back. This guide covers alignment, the policy ladder, and why it became a delivery requirement.
What DMARC adds
SPF and DKIM can detect a forgery but say nothing about what a receiver should do with one, and give you no visibility. DMARC supplies both: a published policy — none, quarantine or reject — and a reporting channel showing what is being sent as your domain and whether it authenticates.
Alignment is the key concept
SPF checks the envelope sender; DKIM checks the signing domain. Neither necessarily relates to the From address the user sees. DMARC requires the authenticated domain to align with that visible From domain. This closes the loophole where a message passes SPF for the attacker's own domain while displaying yours.
The policy ladder
p=none means report only. p=quarantine means treat failures as suspicious, typically routing to spam. p=reject means refuse failing messages outright. The intended path is none to quarantine to reject, using reports to confirm nothing legitimate is caught before each step.
Why monitoring mode comes first
Almost every organisation sends from more sources than it realises — invoicing, support, monitoring, marketing, a departmental tool someone signed up for. Reports surface all of them. Jumping to p=reject without this step reliably blocks something important, and you learn from an angry customer.
The bulk sender requirements
From February 2024, Google and Yahoo began requiring bulk senders to publish DMARC and authenticate their mail. Microsoft aligned with the same 5,000-per-day threshold for its consumer domains in 2025. Enforcement is now active, with non-compliant mail receiving permanent rejection codes rather than being filed as spam.
Thresholds that matter operationally
The 5,000 messages per day threshold counts all mail from your sending domain to those consumer addresses, transactional included — which is how many B2B companies cross it without running marketing campaigns. Spam complaints must stay below 0.3%, with 0.1% as the operating target, and Yahoo's stricter denominator means the same behaviour can be compliant at Google and not at Yahoo.
What enforcement buys you
At p=reject, mail forged to appear from your domain is refused before it reaches anyone. That protects your customers from phishing wearing your brand, and protects your domain's sending reputation. Everything before enforcement is preparation for this.
Reporting is the ongoing value
Aggregate reports keep working after enforcement, and they are the only view you have of who is attempting to send as your domain. Leave the rua address in place permanently; a new CRM or invoicing tool added by another team shows up there before it shows up as a support complaint.
Tip: Set up a DMARC report analyser before publishing the record. Raw aggregate XML from dozens of providers is genuinely unreadable at volume, and the reports are the entire point of monitoring mode.
Tags: dmarc, security, tutorial, learn
Fix
Fix: DKIM signature present
DKIM cryptographically signs your outgoing mail so receivers can verify it came from your domain and was not altered in transit.
Generate a key pair at your provider
Mail providers handle this in their admin console — Google Workspace under Apps then Gmail then Authenticate email, Microsoft 365 under the Defender portal, and similar for transactional services. They generate the key pair and give you the DNS record to publish.
Publish the public key in DNS
Add the TXT record at selector._domainkey.yourdomain, where the selector is the name your provider specifies. The value is long and must be copied exactly — a truncated or line-wrapped key produces a record that looks present and never validates.
Use a 2048-bit key
2048-bit RSA is the current expectation; 1024-bit is considered weak and some receivers treat it accordingly. If your DNS provider struggles with the record length, they may need it split into strings — a documented technique, and one your provider's help pages will cover.
Turn on signing after DNS propagates
Publish the record first, wait for propagation, then enable signing. Enabling signing before the key is resolvable means mail goes out with signatures that cannot be verified, which is worse than sending unsigned mail.
Set up DKIM for every sending source
Each service sending as your domain needs its own selector and key: your mail provider, your transactional service, your marketing platform. One signed source does not cover the others, and DMARC alignment depends on each of them signing with your domain.
Verify with a real message
Send a message to a mailbox you control and inspect the headers for an Authentication-Results line showing dkim=pass. Provider dashboards report configuration status; the received headers report reality, and the two occasionally disagree.
Tip: Rotate keys periodically, using selectors to do it without downtime: publish a new selector, switch signing to it, then remove the old record once no mail in flight relies on it. This is what selectors are for.
Tags: dkim, security, fix
Tutorial
Understanding DKIM signature present
DKIM attaches a cryptographic signature to every message you send, letting receivers verify origin and integrity. This guide covers how it works, why it complements SPF, and what selectors are for.
How signing works
Your mail server holds a private key and uses it to sign selected headers and the body of each outgoing message, adding a DKIM-Signature header. You publish the matching public key in DNS. The receiver retrieves that key, verifies the signature, and learns two things: which domain took responsibility, and that the content was not altered in transit.
Why it complements SPF
SPF checks the connecting server's IP address, which changes when mail is forwarded — so SPF breaks on forwarding. DKIM travels inside the message itself, so a forwarded message still carries a valid signature. Between them, a receiver has two independent ways to trust your mail, and at least one survives most delivery paths.
What a selector is
The selector is a label in the DNS name — selector._domainkey.yourdomain — that identifies which key to use. It exists so a domain can have several keys at once: one per sending service, and old and new keys simultaneously during rotation. Without selectors, rotating a key would mean an outage.
Key length
2048-bit RSA is the current standard. 1024-bit keys were common historically and are now considered weak. The practical obstacle is that a 2048-bit key produces a long DNS record, which some older DNS interfaces handle awkwardly — this is a known problem with a documented solution, not a reason to use a shorter key.
What DKIM does not prove
A valid signature proves the message came from a server holding the key for that domain and was not modified. It does not mean the message is trustworthy — spammers sign their own mail correctly all the time. Nor does it, by itself, connect the signing domain to the From address the user sees. That connection is what DMARC alignment adds.
Every sender needs its own key
Marketing platforms, transactional email services and helpdesk tools all send as your domain and each needs its own selector and key, configured in that service. A domain where only the primary mail provider signs will see everything else fail DMARC alignment once you move to enforcement.
Rotation practice
Keys should be rotated periodically, and selectors make it painless: publish the new selector's record, switch signing across, verify, then retire the old record after enough time has passed for in-flight mail. Some providers automate the whole cycle.
Tip: Check the Authentication-Results header of a message you have actually received. It reports spf, dkim and dmarc results in one line and is the fastest way to see what receiving servers genuinely conclude about your mail.
Tags: dkim, security, tutorial, learn
How-To
How to Remove unsafe-inline from script-src
Move inline scripts to files, convert event handler attributes, then add nonces or hashes for whatever must remain.
1. Count what you are dealing with
grep -c "<script>" across your templates, and grep for on-event attributes: grep -rnoE 'on(click|load|change|submit|error|mouseover)=' ./public --include="*.html" | wc -l. A handful is an afternoon. Several hundred is a project. Knowing which before you start prevents an abandoned half-migration that leaves the site broken.
2. Move each script block into a file
Cut the code from between the <script> tags into /js/page-name.js and reference it with <script src="/js/page-name.js"></script>. External files are trusted by origin under script-src 'self', so they need no nonce or hash at all. This is the durable fix and it also makes the code cacheable and lintable.
3. Pass server values as data attributes
Where an inline script existed only because it needed a server-rendered value, output that value as a data- attribute or a JSON blob and read it from the external file: <div id="cfg" data-user-id="123"> then document.getElementById('cfg').dataset.userId. This removes the last reason many inline blocks exist.
4. Convert every event handler attribute
onclick="save()" becomes an addEventListener('click', save) call inside a script file, with the element found by id or a data attribute. This is the most tedious part and the part most often left incomplete — one missed attribute is one broken page once the policy is enforced.
5. Add a nonce if your pages are server-rendered
Generate a fresh random value per request, put it in the header as script-src 'nonce-VALUE' and on each remaining inline script as nonce="VALUE". It must be genuinely random and must change on every response — a fixed nonce is worthless because an attacker simply reads it from the page source.
6. Use hashes if your HTML is static
With static files there is no per-request render step, so compute a SHA-256 of each inline script's exact contents and list them in the policy. Generate with: openssl dgst -sha256 -binary <<< 'script contents' | openssl base64 -A. Remember every edit to that script changes its hash.
7. Cover scripts created at runtime
Code doing document.createElement('script') produces scripts a server-side nonce injector never sees, so they get blocked. Either set el.nonce in that JavaScript, or add 'strict-dynamic' so scripts loaded by an already-trusted script inherit trust automatically. strict-dynamic is usually the cleaner answer.
8. Run report-only until the reports are clean
Deploy the policy without unsafe-inline as Content-Security-Policy-Report-Only and exercise every journey — logged in, forms, checkout, admin. Each missed handler appears as a violation naming the file and line. Only switch to enforcing when nothing legitimate is being reported.
Tip: If you cannot finish the migration, stay in report-only rather than enforcing a partial one. A page with one missed onclick is a broken page, and the rest of your policy still delivers value in the meantime.
Tags: inline-js, security, howto, fix
How-To
How to Remove unsafe-inline from style-src
Convert style attributes to classes, switch JavaScript to class toggling, then tighten the policy.
1. Count the inline style attributes
grep -roE 'style="[^"]*"' ./public --include="*.html" | wc -l gives you the scale. This number decides whether the job is an hour or a month — sites built with hand-written HTML or older page builders routinely carry thousands, and starting without knowing is how migrations get abandoned half-done.
2. Group the repeated ones
Sort the extracted styles and count duplicates: the same handful of declarations usually repeats across hundreds of elements. grep -roE 'style="[^"]*"' ./public | sort | uniq -c | sort -rn shows the top offenders. Converting the top twenty patterns often covers most of the file.
3. Create classes for each pattern
Replace style="color:red;font-weight:bold" with class="alert-text" and define .alert-text{color:red;font-weight:bold} once in your stylesheet. Name classes by purpose rather than appearance — .alert-text ages better than .red-bold when the design changes.
4. Convert JavaScript that sets styles
element.style.display='none' creates an inline style at runtime and will be blocked. Replace with element.classList.add('hidden') and a .hidden{display:none} rule. Search for .style. in your JavaScript to find them all. Class toggling is also faster than mutating individual style properties.
5. Handle genuinely dynamic values with custom properties
For values that cannot be predicted — a progress bar width, a user-chosen colour — use element.style.setProperty('--bar-width', pct + '%') with the stylesheet consuming var(--bar-width). Where the policy still blocks this, fall back to a set of predefined step classes.
6. Deal with <style> blocks
Unlike style attributes, a <style> element can carry a nonce or be covered by a hash. Move page-specific CSS into your stylesheet where practical; for what remains, add the nonce alongside your script nonce or list the hash in style-src.
7. Consider unsafe-hashes for the stragglers
If a small number of style attributes genuinely cannot be removed, the 'unsafe-hashes' keyword permits specific hashed inline attributes. It is weaker than a clean policy but much better than opening all inline CSS. Treat it as a bridge with an end date.
8. Deploy report-only and look at the pages
Style violations do not throw errors — they just make the page look wrong. Run report-only, then click through the site and actually look: layout, modals, hidden elements, print views, email templates rendered in-page. Automated tests rarely catch a purely visual regression.
Tip: Do script-src before style-src. Inline styles cannot execute code, so the security gain here is smaller while the refactoring cost is usually far higher — spend limited effort where the risk actually is.
Tags: inline-css, security, howto, fix
How-To
How to Fix an Invalid TLS Certificate
Identify which of the four validity conditions is failing, issue a trusted certificate covering every hostname, and serve the full chain.
1. Find out exactly what is wrong
Run the domain through SSL Labs at ssllabs.com/ssltest. A certificate can be invalid for four distinct reasons — expired, wrong hostname, untrusted issuer, incomplete chain — and the remedies are completely different. Guessing wastes time.
2. Check dates and names from the command line
echo | openssl s_client -connect yoursite.com:443 -servername yoursite.com 2>/dev/null | openssl x509 -noout -dates -subject -issuer. This gives you validity dates, the subject name and who issued it in one command.
3. Issue a trusted certificate
Use certbot: certbot --nginx -d example.com -d www.example.com, or your hosting panel's SSL section. Let's Encrypt certificates are free and trusted by every mainstream browser. Self-signed certificates should never appear on a public site — every visitor gets a full-page warning.
4. Cover every hostname users actually type
example.com and www.example.com are different names and both need covering. Include each with a -d flag, or use a wildcard. A certificate valid for one but not the other produces a warning for half your traffic, which is easy to miss if you always type the same form.
5. Serve the full chain, not just your certificate
Browsers need the intermediate certificates linking yours to a trusted root. With certbot, point your config at fullchain.pem, never cert.pem. A missing intermediate is the classic cause of a site that works in your browser and fails in someone else's.
6. Check the config points at the right files
nginx: ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;. Apache: SSLCertificateFile pointing at fullchain.pem. Test with nginx -t or apachectl configtest before reloading.
7. Verify from outside, not from your own browser
Re-run SSL Labs. Your browser caches intermediate certificates from other sites it has visited, so a broken chain can look perfectly fine to you while failing for a first-time visitor. Aim for a report with no chain warnings.
8. Automate renewal while you are here
Most invalid-certificate incidents are simply expiries. Confirm the renewal timer exists with systemctl list-timers | grep certbot, and prove it works with certbot renew --dry-run, so today's fix does not become next quarter's outage.
Tip: Use Mozilla's SSL Configuration Generator at ssl-config.mozilla.org for the surrounding server config rather than copying from a blog post. Cipher and protocol advice ages badly, and generated config covers protocols, ciphers and curves as a coherent set.
Tags: tls-valid, security, howto, fix
How-To
How to Automate TLS Certificate Renewal
Check the expiry, confirm the renewal timer exists, prove it works with a dry run, and make sure the server reloads afterwards.
1. Check how long you actually have
echo | openssl s_client -connect yoursite.com:443 2>/dev/null | openssl x509 -noout -dates. Read notAfter. Under 30 days remaining needs attention now, not at the weekend — a failed renewal often has a cause that takes time to fix.
2. Confirm renewal is actually scheduled
Having certbot installed is not the same as renewal running. systemctl list-timers | grep certbot, or check for a cron entry with crontab -l and ls /etc/cron.d/. Many outages come from a certificate that was renewed by hand once and never automated.
3. Prove renewal works before you need it
certbot renew --dry-run performs the whole process against the staging environment without consuming rate limits or replacing your certificate. It surfaces the real blockers — a changed DNS record, a firewall rule, a moved webroot — while you still have weeks of validity.
4. Make sure the server reloads after renewal
A renewed certificate on disk is not a renewed certificate in memory. Add a deploy hook: certbot renew --deploy-hook "systemctl reload nginx". Without it you will serve an expired certificate from a directory containing a perfectly valid one, which is a baffling failure to debug.
5. Fix the common blockers
HTTP-01 challenges need /.well-known/acme-challenge/ reachable over port 80 — check no redirect or firewall rule blocks it. DNS-01 challenges need working API credentials for your DNS provider. If the webroot moved during a redesign, update the certbot configuration to match.
6. Renew early rather than at the deadline
Certbot attempts renewal at around a third of the lifetime remaining, roughly 30 days for a 90-day certificate. Keep that buffer. It means a failed renewal is an alert to investigate rather than an imminent outage, and it absorbs holidays and weekends.
7. Add an independent expiry monitor
Automation breaks silently — an expired account email, a moved webroot, a DNS change. An external monitor that checks your live endpoint and alerts at two weeks does not share a failure mode with the automation it is watching, which is the entire point.
8. Check every hostname, not just the main one
Subdomains, API endpoints and any host with its own certificate all need their own renewal working. Loop through them: for h in example.com api.example.com; do echo | openssl s_client -connect $h:443 2>/dev/null | openssl x509 -noout -enddate; done
Tip: An expired certificate is a full outage, not a degradation — every visitor hits a full-page interstitial and most leave immediately. Of everything in this audit, it is the failure that costs the most business per hour.
Tags: tls-expiry, security, howto, fix
How-To
How to Disable Old TLS Versions
Set the protocol floor to TLS 1.2, generate the cipher config properly, and check every host including the CDN.
1. See what you currently offer
Run SSL Labs, or use nmap --script ssl-enum-ciphers -p 443 yoursite.com. Either lists every protocol version your server accepts. You are looking for TLS 1.0 or 1.1 appearing in that list.
2. Set the floor in nginx
ssl_protocols TLSv1.2 TLSv1.3; in the http block or server block. One line. Every current browser and device supports TLS 1.2 at minimum, so real-world compatibility loss is negligible. Test with nginx -t before reloading.
3. Or set it in Apache
SSLProtocol -all +TLSv1.2 +TLSv1.3 in the SSL vhost. The -all first is important — it disables everything, then re-enables only the two you want, rather than leaving older versions on by omission.
4. Generate the cipher configuration properly
Do not hand-pick cipher suites. Go to ssl-config.mozilla.org, select your server and version, choose the Intermediate profile for a public site, and paste the generated block. It sets protocols, ciphers, curves and session parameters as a coherent whole.
5. Confirm your build supports TLS 1.3
nginx -V shows the OpenSSL version it was built against; TLS 1.3 needs OpenSSL 1.1.1 or later. If your build is older, enabling TLSv1.3 in the config silently does nothing. Upgrading the package is usually the fix.
6. Set it at the layer that faces the internet
If a CDN, load balancer or reverse proxy terminates TLS, that is where the setting matters — clients never negotiate with your origin, so the origin's ssl_protocols line has no bearing on what users get. Cloudflare exposes a minimum TLS version setting in its dashboard.
7. Check every virtual host
Configuration often lives per server block. A hardened main site beside an API subdomain or a legacy vhost still offering TLS 1.0 leaves the weakness in place. Enumerate every hostname on the box and test each one separately.
8. Verify externally and read the whole report
Re-run SSL Labs and confirm only 1.2 and 1.3 are accepted. Read past the letter grade — the report also flags weak ciphers, missing forward secrecy and certificate chain problems that the protocol setting does not address.
Tip: Setting the version floor is only half the job. A server offering TLS 1.2 with weak cipher suites is still weak, which is why using generated configuration matters more than changing the one protocol line.
Tags: tls-protocol, security, howto, fix
How-To
How to Add the Secure Flag to Cookies
Set it in framework config rather than per cookie, add the __Host- or __Secure- prefix, and verify in DevTools.
1. See which cookies currently lack it
Open DevTools, Application tab, Cookies. The Secure column shows the state of each cookie. Do this after logging in — the cookies that matter most are usually issued at authentication and are invisible in the logged-out state.
2. Set it in your framework's session config
Express: app.use(session({ cookie: { secure: true, httpOnly: true, sameSite: 'lax' } })). PHP: session.cookie_secure = 1 in php.ini. Django: SESSION_COOKIE_SECURE = True. Configuring centrally means cookies added later inherit the setting.
3. Set it on cookies you write directly
Any Set-Cookie you construct by hand needs the attribute explicitly: Set-Cookie: pref=dark; Path=/; Secure; HttpOnly; SameSite=Lax. Apply it to preference, consent, cart and analytics cookies too, not only session ones.
4. Add the __Secure- name prefix
Renaming a cookie to __Secure-session means browsers reject it unless it was set over HTTPS with the Secure attribute. This closes a gap the flag alone leaves: Secure controls sending, but the prefix enforces the conditions at the point the cookie is written.
5. Use __Host- for single-host cookies
__Host- is stricter: it requires Secure, requires Path=/, and forbids the Domain attribute. Forbidding Domain is the important part — it locks the cookie to the exact host that set it so no subdomain can create or overwrite it. Best choice for a session identifier.
6. Tighten Domain and Path while you are there
Set Domain only if the cookie genuinely needs to cross subdomains, and set the narrowest Path that works. Every unnecessary widening is another context in which the cookie is transmitted and another place it can be exposed.
7. Set sensible expiry
Prefer Max-Age over Expires — it is relative and therefore immune to client clock skew, and it takes precedence when both are present. Session identifiers should expire quickly; if neither attribute is set the cookie lasts until the browser closes, which is often right for a session.
8. Verify in DevTools, not in your code
Application, Cookies, and read the Secure, HttpOnly, SameSite, Domain, Path and expiry columns for every cookie. This shows cookies set by third-party scripts your code never touches, and catches framework settings overridden by a proxy.
Tip: Check after login and after adding items to a cart or similar flows. Cookies appear at different points in a user journey, and auditing only the landing page misses the ones carrying anything worth stealing.
Tags: cookie-secure, security, howto, fix
How-To
How to Add the HttpOnly Flag to Cookies
Apply it to every cookie your front-end does not read, move tokens out of localStorage, and prove it with document.cookie.
1. List which cookies JavaScript genuinely needs
Search your front-end for document.cookie and note which names it reads. Typically a theme preference, a consent flag or an analytics client ID. Everything else — session identifiers, auth tokens, CSRF tokens — should be invisible to scripts.
2. Set it in framework configuration
Express sessions default httpOnly to true, but verify it has not been overridden. PHP: session.cookie_httponly = 1 in php.ini, then restart PHP-FPM. Django sets SESSION_COOKIE_HTTPONLY True by default. Central configuration covers cookies added later by someone else.
3. Add it to hand-written Set-Cookie headers
Set-Cookie: session=abc; Path=/; Secure; HttpOnly; SameSite=Lax. Every authentication and session cookie should carry it. There is no legitimate reason for page JavaScript to read a session identifier.
4. Move tokens out of localStorage
If your application keeps a JWT or API token in localStorage, any XSS retrieves it instantly and there is no HttpOnly equivalent for localStorage. Move authentication to an HttpOnly cookie set by the server — this is an architectural change but it removes an entire class of total-compromise scenario.
5. Check what third-party scripts collect
Tag managers and analytics snippets sometimes read document.cookie wholesale. Cookies with HttpOnly are invisible to them, which is a privacy improvement as well as a security one. Worth confirming what your third parties actually gather.
6. Keep CSRF protection in place
HttpOnly stops a token being stolen and replayed elsewhere; it does not stop injected script making authenticated requests from within the page. Anti-CSRF tokens on state-changing requests remain necessary, as does output escaping to prevent the injection in the first place.
7. Prove it with the console
Log in, open the console, and type document.cookie. Properly protected cookies should not appear in the output at all. If your session cookie shows up there, HttpOnly is not applied whatever your configuration file says — frameworks and proxies can override each other.
8. Re-check after any proxy or CDN change
A reverse proxy or CDN that rewrites Set-Cookie headers can drop attributes. After changing anything in front of your application, repeat the DevTools and document.cookie checks rather than assuming the setting survived.
Tip: HttpOnly does not prevent XSS; it limits what an XSS bug achieves. Since no site can promise zero XSS forever, it is exactly the kind of cheap always-on mitigation to have in place before you need it.
Tags: cookie-httponly, security, howto, fix
How-To
How to Set SameSite on Cookies
Choose Lax for most cookies, reserve Strict for high-value ones, and test the redirect journeys that break.
1. Check what is currently set
DevTools, Application, Cookies, SameSite column. Modern browsers default to Lax when the attribute is absent, but the default varies by browser version and stating it explicitly is what scanners check for and what documents your intent.
2. Choose Lax as the default
Lax sends the cookie on top-level navigations — someone clicking a link to your site — but withholds it on cross-site sub-requests such as form posts, iframes and image loads. This blocks the main CSRF vectors while keeping inbound links working normally.
3. Set it in framework configuration
Express: cookie: { sameSite: 'lax', secure: true, httpOnly: true }. PHP: session.cookie_samesite = "Lax" in php.ini. Django: SESSION_COOKIE_SAMESITE = 'Lax'. Setting it centrally means new cookies inherit it.
4. Reserve Strict for the highest-value cookies
Strict withholds the cookie on every cross-site request including plain navigation, so a user arriving from an external link appears logged out until they navigate within the site. Appropriate for banking or admin interfaces, usually too abrupt for a consumer site's main session.
5. Use None only where genuinely required
SameSite=None permits the cookie everywhere and must be paired with Secure or browsers reject it outright. Only correct where a cookie is legitimately needed in a third-party context, such as an embedded widget. Reaching for None to make something work usually signals a design that should change.
6. Keep your CSRF tokens
SameSite is a good layer but not complete: requests from a different subdomain of the same registrable domain count as same-site, so a compromised subdomain is not blocked, and older clients may not enforce it. Anti-CSRF tokens on state-changing requests remain the primary control.
7. Test the journeys that actually break
Arriving from an external link, submitting embedded forms, returning from a payment provider redirect, and any OAuth or SSO callback. Payment and SSO round trips are where an over-strict setting reveals itself, and it fails confusingly — the user simply appears logged out.
8. Verify each cookie individually
DevTools shows SameSite per cookie. Third-party scripts set their own cookies with their own attributes, so your session cookie being correct does not mean every cookie on the page is. Check the full list rather than the one you configured.
Tip: If a payment or SSO return trip logs users out, Strict is almost always the cause — the browser withheld the session cookie on the inbound navigation from the external provider. Lax is normally correct for a session cookie.
Tags: cookie-samesite, security, howto, fix
How-To
How to Set a Referrer-Policy Header
Choose a policy based on what your URLs contain, apply it at the server, and override per element where needed.
1. Audit what your URLs actually contain
Before choosing a policy, grep your routes and templates for tokens, email addresses, order numbers and identifiers appearing in paths or query strings. Password reset links, magic links and invitation URLs are the ones that matter — anything reachable by URL alone is effectively a credential.
2. Choose the policy value
strict-origin-when-cross-origin is the balanced default: full URL same-origin, bare origin cross-origin, nothing on an HTTPS-to-HTTP downgrade. Choose same-origin if you want external sites to receive nothing at all, or no-referrer if your URLs routinely carry secrets.
3. Add it in nginx
add_header Referrer-Policy "strict-origin-when-cross-origin" always; inside the server block. The always keyword ensures it appears on error responses too. Test with nginx -t, then systemctl reload nginx.
4. Or add it in Apache or Plesk
Apache: Header always set Referrer-Policy "strict-origin-when-cross-origin" — needs mod_headers. Plesk: Websites & Domains, Apache & nginx Settings, Additional nginx directives, paste the add_header line. Cloudflare users can set it as a Transform Rule at the edge.
5. Override per element where a specific case needs it
Individual elements accept a referrerpolicy attribute, and rel="noreferrer" on a link strips the referrer entirely. Use these for particular outbound links or third-party embeds rather than loosening the whole site's policy for one integration.
6. Check your analytics still report correctly
Referrer data feeds attribution. strict-origin-when-cross-origin preserves the origin, which is what most analytics platforms need. If you choose no-referrer, confirm nothing you depend on breaks before rolling it out widely.
7. Verify the header is present everywhere
curl -sI https://yoursite.com | grep -i referrer, then repeat against an asset URL and an error page. Header configuration is frequently applied to one location block and missed on others, so testing the homepage alone is not enough.
8. Fix the underlying URL design where you can
The header mitigates disclosure by referrer, but URLs also end up in browser history, bookmarks, proxy logs and shared screenshots. Moving tokens out of URLs into POST bodies or short-lived server-side sessions is the durable fix; the header is the safety net.
Tip: Modern browsers already default to strict-origin-when-cross-origin when no header is present, so the exposure is smaller than it once was. Setting it explicitly still matters for older clients and documents the intent for whoever reads the config next.
Tags: referrer-policy, security, howto, fix
How-To
How to Set a Permissions-Policy Header
Disable the browser features you do not use, scope the ones you do, and get the syntax right.
1. List the features your site genuinely uses
Most sites use none of the powerful ones. Check whether anything calls navigator.geolocation, getUserMedia, the Payment Request API, WebUSB or the motion sensors. Search your JavaScript for geolocation, getUserMedia, PaymentRequest and requestDevice.
2. Enumerate your third-party embeds
This is where the header earns its place. List every iframe you load — ads, chat widgets, video players, maps, payment frames. Those inherit your policy, so the header is one of the few controls that limits what someone else's code can ask your users for.
3. Write the baseline header
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=(), midi=(), magnetometer=(), accelerometer=(), gyroscope=() — empty parentheses disable the feature for your page and all embedded frames. Unlisted features keep browser defaults, so anything you want restricted must be named.
4. Get the syntax exactly right
feature=() disables. feature=(self) allows your own origin — self is a bare keyword, unquoted. feature=(self "https://trusted.example") allows a named origin — origins are double-quoted. This is the reverse of CSP's quoting convention and is the most common cause of a malformed header.
5. Add it at the server
nginx: add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; Apache: Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()". Keep it on a single line — wrapped values are a frequent source of malformed headers.
6. Do not copy Feature-Policy examples
The header was renamed from Feature-Policy and the grammar changed. Old examples used space-separated origins with no parentheses and will not parse. Browsers tend to ignore a malformed header silently, so the misconfiguration looks fine in your config and does nothing.
7. Delegate to iframes that legitimately need a feature
An iframe's allow attribute can only narrow what the parent policy permits, never widen it. To grant a frame a feature, permit it in the header and add allow="geolocation" to the iframe. Getting one without the other is the usual reason a legitimate embed cannot access what it needs.
8. Verify it parses, not just that it is sent
curl -sI to confirm the header is present, then open DevTools and check the console for warnings about unrecognised or malformed policy directives. A syntax error can cause the whole header to be discarded, so seeing it in the response is not proof it is in effect.
Tip: Disabling geolocation, microphone and camera is safe for the vast majority of sites, costs nothing, and is exactly what a scanner checks for. If you later need one, granting it is a one-line change.
Tags: permissions-policy, security, howto, fix
How-To
How to Add Subresource Integrity to CDN Scripts
Generate hashes, add integrity with crossorigin, pin to immutable URLs, and automate it in your build.
1. List every cross-origin script and stylesheet
grep -roE '<(script|link)[^>]*(src|href)="https?://[^"]*"' ./public --include="*.html" and filter for domains you do not control. Each result is code running with full access to your pages, served by someone else.
2. Generate a hash for each file
curl -s https://cdn.example.com/lib.js | openssl dgst -sha384 -binary | openssl base64 -A gives you the value. Or paste the URL into srihash.org. SHA-384 is the usual choice; SHA-256 and SHA-512 are equally valid.
3. Add both attributes to the tag
<script src="https://cdn.example.com/lib-1.2.3.js" integrity="sha384-BASE64VALUE" crossorigin="anonymous"></script>. Both attributes are required. The same applies to stylesheets via <link rel="stylesheet" ... integrity=... crossorigin="anonymous">.
4. Never omit crossorigin
This is the step people miss and it matters more than it looks. Without a crossorigin attribute the browser fails open — it loads the resource as though integrity had never been specified. You get the appearance of protection with none of the substance, and nothing warns you.
5. Check the CDN sends CORS headers
Integrity verification of a cross-origin file needs Access-Control-Allow-Origin from the host. Confirm with curl -sI https://cdn.example.com/lib.js | grep -i access-control. If it is absent the resource will fail to load once you add the attributes, and the right response is to self-host rather than drop SRI.
6. Pin to an immutable versioned URL
Point at .../jquery-3.7.1.min.js, never at a latest alias or an unversioned path. SRI only works against a URL whose contents never change; a moving URL guarantees the hash will eventually stop matching and the resource will be blocked without warning.
7. Automate hash generation in your build
Regenerating by hand is where this decays — someone bumps a version, forgets the hash, and either the page breaks or the attribute gets quietly deleted. Have the build pipeline compute and inject the hash so upgrading a dependency updates the integrity value automatically.
8. Prove the protection actually fires
Change one character of a hash locally and reload with the console open. The browser should refuse the resource and log an integrity error. If the file still loads, your protection is not active — almost always a missing crossorigin attribute.
Tip: For a handful of small dependencies, self-hosting removes the CDN from your trust chain entirely and is often simpler than maintaining hashes. The old caching argument for CDNs is much weaker now that browsers partition cache per-site.
Tags: sri, security, howto, fix
How-To
How to Remove the X-Powered-By Header
Disable it in the application, strip it at the proxy as a backstop, and check API routes separately.
1. Confirm it is being sent
curl -sI https://yoursite.com | grep -i powered. Then repeat against an API endpoint, because APIs are often served directly by the application framework rather than through the same proxy path and behave differently.
2. Disable it in Express
app.disable('x-powered-by') — put it immediately after creating the app, before any routes or middleware. Alternatively use the helmet middleware, which removes it along with setting several other security headers. Fixing it at source is better than stripping it downstream.
3. Disable it in PHP
Set expose_php = Off in php.ini. Find the right file with php --ini, since a server can have several. Then restart PHP-FPM — a reload of the web server alone will not pick it up, which is a common false success.
4. Disable it in ASP.NET
Remove the header via web.config: under system.webServer, httpProtocol, customHeaders, add <remove name="X-Powered-By" />. Also check for X-AspNet-Version and X-AspNetMvc-Version, which are separate headers needing separate removal.
5. Strip it at the proxy as a backstop
Where the header comes from something you cannot change, remove it at the edge. nginx: proxy_hide_header X-Powered-By; in the location block. Apache: Header always unset X-Powered-By. This also catches components you have forgotten about.
6. Look for the related framework headers
X-AspNet-Version, X-Generator, X-Drupal-Cache, X-Runtime, X-Rack-Cache and similar each name a technology or version. Strip them the same way — a single leftover header undoes the benefit of removing the rest.
7. Restart the right service
PHP settings need PHP-FPM restarted. Node changes need the process restarted, not just the file saved. Confirm the change actually took effect rather than trusting the edited config — an ini file that was never loaded is a frequent cause of an apparently failed fix.
8. Verify across every response type
curl -sI against an HTML page, an API endpoint, a static asset and a 404. Different components serve these and each can carry different headers. No output from grep -i powered on all four means the job is done.
Tip: This is one line of configuration and breaks nothing, which makes it the cheapest item in most audits. That is exactly why leaving it in place is difficult to justify to anyone reviewing your setup.
Tags: powered-by, security, howto, fix
How-To
How to Respond to a Detected CVE
Read the advisory, check KEV, patch to the fixed release, and verify the running version actually changed.
1. Read the specific advisory
Look up the CVE identifier and check the affected version range, the fixed version, and whether exploitation has been observed. Not every CVE applies to every configuration — some require a module or feature you do not run — so read before scheduling emergency work.
2. Check CISA KEV first
Search the CISA Known Exploited Vulnerabilities catalogue for the identifier. If it is listed, the flaw is confirmed as actively exploited in the wild and should be treated as an emergency whatever its severity score. KEV membership is a far better urgency signal than CVSS.
3. Do not assume a missing CVSS means low risk
Since April 2026 NIST only enriches CVEs that are in KEV, in US federal software, or critical under Executive Order 14028. Everything else appears with no CVSS, no product mapping and no weakness classification. Absence of a score now usually means nobody has assessed it yet.
4. Cross-check other sources
Because NVD coverage is partial, check the vendor's own advisory, CISA Vulnrichment for early scoring, and OSV for open-source dependencies — OSV works from package versions rather than CPE strings, which handles the gap better. The EU's EUVD is a further source.
5. Upgrade to the fixed version
OS packages: apt update && apt upgrade, or the yum/dnf equivalent. Node: npm audit fix, or npm install package@version for a specific one. PHP: composer update. CMS: apply core, theme and plugin updates. This is nearly always the entire remedy.
6. Handle breaking upgrades deliberately
If the fix needs a major version bump that breaks compatibility, check whether maintainers backported the patch to your branch, look for a vendor LTS release, or apply the upstream patch yourself. Deferring a known-exploited CVE because upgrading is awkward is the decision that precedes most compromises.
7. Restart and confirm the version changed
An upgraded package with the old binary still running is a common false fix. Restart the service, then check the actual running version — nginx -v, php -v, node -v, or the CMS admin dashboard — and re-run the audit to confirm the reported version has moved.
8. Automate so it does not recur
Enable unattended-upgrades for OS security patches. Add Dependabot or an equivalent so dependency updates arrive as pull requests. Most compromises exploit vulnerabilities patched months earlier, so shrinking the gap between release and application matters more than scanning frequency.
Tip: Patch first, hide versions second. Version obfuscation removes you from opportunistic scans, but if the underlying flaw is present a targeted attacker will still find it — the two measures complement each other and neither replaces the other.
Tags: cve, security, howto, fix
How-To
How to Stop Your CMS Advertising Its Version
Remove the generator tag, strip version query strings, block readme files, and check the API and feeds.
1. Find out what you are currently disclosing
curl -s https://yoursite.com | grep -i generator shows the meta tag. Then check for version query strings: curl -s https://yoursite.com | grep -oE '\?ver=[0-9.]+' | sort -u. Also try /readme.html and /license.txt directly.
2. Remove the generator meta tag
WordPress: add remove_action('wp_head', 'wp_generator'); to your theme's functions.php, or better, to a small site-specific plugin so it survives theme changes. Other CMSs have equivalent settings, usually in configuration rather than code.
3. Strip version query strings from assets
WordPress appends ?ver=6.2 to script and style URLs, disclosing the version even with the meta tag gone. Filter script_loader_src and style_loader_src to remove the ver parameter. Pair this with a build-time versioning scheme so cache-busting keeps working.
4. Remove or block readme and licence files
readme.html and license.txt in the web root state the version plainly. Delete them after upgrades, or block them at the server: location ~* ^/(readme|license)\.(html|txt)$ { deny all; } in nginx. Blocking is more durable, since updates recreate them.
5. Check the REST API and feeds
WordPress exposes version information through /wp-json/ and in RSS feed generator elements. Filters exist for both. Test with curl -s https://yoursite.com/wp-json/ | head and curl -s https://yoursite.com/feed/ | grep -i generator.
6. Audit and reduce the plugin inventory
Plugins are the most common CMS compromise route by a wide margin. Remove anything deactivated but still installed — deactivated code on disk is still reachable in some exploit scenarios. Check each remaining plugin has been updated within the last year and drop abandoned ones.
7. Turn on automatic security updates
This matters far more than hiding the version. Enable automatic updates for core security releases at minimum, and for plugins where you are confident in the vendor. A hidden version running unpatched code is thoroughly exploitable.
8. Re-check everything you cleaned
curl the homepage, an asset URL, the feed, the API root and the readme path. The version typically appears in more places than expected, and removing one disclosure while leaving four others achieves nothing at all.
Tip: Hiding the version only defeats untargeted scanning. A CMS site that conceals its version but runs a plugin with a known unpatched flaw is still fully exploitable — the update routine is what actually protects you.
Tags: cms-version, security, howto, fix
How-To
How to Publish an SPF Record
Inventory your senders, publish exactly one record, and stay inside both RFC 7208 limits.
1. Inventory everything that sends as your domain
List every source before writing anything: mail provider, transactional email service, marketing platform, CRM, helpdesk, invoicing software, monitoring alerts, and any application server sending directly. Omitting one means its mail starts failing authentication the day you tighten the policy.
2. Get the correct include from each provider
Every provider documents the exact mechanism to use — include:_spf.google.com for Google Workspace, and so on. Do not guess at hostnames. Where a provider offers a single consolidated include instead of several, take it: it saves DNS lookups.
3. Publish one TXT record at the domain root
v=spf1 include:_spf.google.com include:sendgrid.net ~all as a TXT record on the apex. Order the mechanisms with ip4/ip6 literals first, then includes, then the final qualifier.
4. Make absolutely sure there is only one
RFC 7208 section 3.2 permits exactly one SPF TXT record per domain. Two do not merge — the result is a PermError that breaks authentication for every message, even when each record is individually valid. Check with dig +short TXT example.com | grep spf1 and confirm one line comes back.
5. Count your DNS lookups
Section 4.6.4 caps evaluation at ten DNS-querying terms. include, a, mx, ptr, exists and redirect all count, including lookups nested inside included records; ip4, ip6 and all do not. Use an SPF checker that reports the actual count — three or four providers will often take you over.
6. Watch the void lookup limit as well
RFC 7208 also limits void lookups — queries returning NXDOMAIN or an empty answer — to two. A stale include pointing at a retired vendor domain triggers PermError on its own, independently of the ten-lookup count. Remove includes for services you no longer use.
7. Get back under the limits if you are over
Remove vendors you no longer send through. Replace lookup-heavy mechanisms with direct ip4/ip6 entries where the provider publishes stable addresses. Drop mx if it is not needed, since a domain with several MX hosts can consume several lookups on its own.
8. Choose the qualifier and validate
Start with ~all (soft fail) while confirming every legitimate source passes, then move to -all (hard fail). Validate with a checker that reports syntax, lookup count and evaluation result, then use DMARC reports to surface senders you overlooked.
Tip: SPF alone is not enough — it breaks on forwarding, because the forwarding server is not in your record. Configure DKIM and DMARC alongside it, and remember DMARC treats an SPF PermError as a fail, so breaching either limit actively harms your own delivery.
Tags: spf, security, howto, fix
How-To
How to Publish a DMARC Record
Start in monitoring mode, read the reports, fix alignment, then move to enforcement in stages.
1. Get SPF and DKIM working first
DMARC evaluates the results of those two checks, so it has nothing to work with until both are in place and passing for your legitimate mail. Publishing a strict policy before they work reliably will block your own email.
2. Publish the record in monitoring mode
Add a TXT record at _dmarc.yourdomain with: v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain; fo=1. p=none changes nothing about delivery — it only requests reports. This is the safe starting point for every domain without exception.
3. Set up a report analyser before you start
Aggregate reports arrive as XML from dozens of providers daily and are genuinely unreadable at volume. Point the rua address at a DMARC analysis service, or a dedicated mailbox feeding one. The reports are the entire point of monitoring mode.
4. Read the reports and inventory your senders
They show every source sending as your domain, whether each passed SPF and DKIM, and at what volume. Expect to find legitimate senders you had forgotten — invoicing, monitoring, a departmental tool someone signed up for — alongside any genuine spoofing.
5. Fix alignment, not just authentication
A source can pass SPF yet fail DMARC because of alignment: the authenticated domain must match the visible From domain. Marketing platforms sending as your brand from their own envelope domain are the classic case. The fix is a custom return-path plus DKIM signing on your domain.
6. Know the bulk sender thresholds
Google, Yahoo and Microsoft require SPF, DKIM and DMARC with alignment for domains sending roughly 5,000 or more messages a day to their consumer addresses. Google's classification is permanent once crossed, and it counts transactional mail — which is how many B2B companies qualify without running campaigns.
7. Move to quarantine, then reject
Once reports show all legitimate mail passing and aligned, change p=none to p=quarantine. Monitor for a few weeks, then p=reject. The pct= tag lets you apply the policy to a percentage of mail while you watch, easing the transition on high-volume domains.
8. Keep reporting on permanently
Leave the rua address in place after enforcement. New senders appear over time as teams adopt tools, and without reports the first sign of a misconfiguration is a colleague saying their mail is bouncing. Reports are also your only view of attempted spoofing.
Tip: p=none gives you reporting and no protection. A domain sitting at p=none for years has the appearance of DMARC without the benefit — and for bulk senders, a monitoring-only policy is increasingly treated as non-compliant.
Tags: dmarc, security, howto, fix
How-To
How to Set Up DKIM Signing
Generate a key at each provider, publish the public key at the right selector, then enable signing and verify from a real message.
1. Generate a key pair at your mail provider
Google Workspace: Apps, Gmail, Authenticate email. Microsoft 365: the Defender portal. Transactional services have equivalent screens. They generate the key pair and give you the DNS record to publish — you do not normally create keys by hand.
2. Use a 2048-bit key
2048-bit RSA is the current expectation; 1024-bit is considered weak and some receivers treat it accordingly. If your provider offers a choice, take 2048. If your DNS host struggles with the record length, it may need splitting into multiple strings — a documented technique, not a reason to use a shorter key.
3. Publish the TXT record at the selector
Add the record at selector._domainkey.yourdomain, where the selector is the name your provider specifies. The value is long and must be copied exactly. A truncated or line-wrapped key produces a record that looks present and never validates.
4. Wait for propagation and confirm it resolves
dig +short TXT selector._domainkey.yourdomain should return the key. Check this before enabling signing — sending signed mail whose key cannot be resolved is worse than sending unsigned mail, because the signature fails rather than being absent.
5. Enable signing at the provider
Only after the record resolves. The provider dashboard usually has a separate toggle for starting to sign, distinct from generating the key. This ordering is what prevents a window of failing signatures.
6. Repeat for every sending source
Each service sending as your domain needs its own selector and key: mail provider, transactional service, marketing platform, helpdesk. One signed source does not cover the others, and DMARC alignment depends on each of them signing with your domain.
7. Verify from a message you actually received
Send to a mailbox you control, open the full headers, and look for Authentication-Results showing dkim=pass. Provider dashboards report configuration status; received headers report reality, and the two occasionally disagree.
8. Plan key rotation using selectors
Selectors exist so you can rotate without downtime: publish a new selector's record, switch signing to it, verify, then remove the old record once no in-flight mail depends on it. Some providers automate the whole cycle — check before building your own process.
Tip: Check the Authentication-Results header of a received message rather than trusting a dashboard. It reports spf, dkim and dmarc results in one line and is the fastest way to see what receiving servers actually conclude about your mail.
Tags: dkim, security, howto, fix
How-To
How to Set Cross-Origin-Resource-Policy
Add CORP to every response, choose the right value for your asset layout, and confirm it reaches images and scripts too.
1. Work out whether anything embeds your resources
Before restricting, check whether other domains legitimately load your images, fonts or scripts — partner sites, documentation, a separate marketing domain, or your own CDN on a different hostname. Search your access logs for requests with a Referer from a domain you do not own.
2. Choose the value
same-origin allows only pages from the identical scheme, host and port. same-site widens it to your registrable domain so subdomains qualify. cross-origin places no restriction. For most sites same-origin is correct; use same-site if assets live on static.example.com and pages on www.example.com.
3. Add it in nginx
add_header Cross-Origin-Resource-Policy "same-origin" always; in the server block. The always keyword matters here more than usual, because you want the header on asset responses and error responses, not just successful HTML.
4. Or add it in Apache or Plesk
Apache: Header always set Cross-Origin-Resource-Policy "same-origin" — requires mod_headers. Plesk: Websites & Domains, Apache & nginx Settings, Additional nginx directives, then paste the add_header line and apply. Plesk writes it into the vhost config for you.
5. Make sure it covers assets, not just pages
This is the step most often missed. The header must be on the resources themselves — images, fonts, scripts, stylesheets — because those are what a hostile page would embed. A policy set only on HTML documents leaves precisely the things it is meant to protect uncovered.
6. Carve out genuinely public assets
If you publish a logo, an embeddable widget script or a public API response meant for anyone to use, set cross-origin on those specific paths with a location block. Keep the restrictive default and add exceptions, rather than the other way round.
7. Verify against several resource types
curl -sI https://yoursite.com | grep -i cross-origin, then repeat for an image URL, a CSS file and a JavaScript file. Server configuration frequently applies headers to some location blocks and not others, so testing one URL proves very little.
8. Watch for breakage from other domains
If something stops loading elsewhere after you apply same-origin, the breakage tells you exactly which resource is embedded off-site and by whom — often surfacing hotlinking or a forgotten integration nobody remembered. Move that specific path to cross-origin rather than loosening the whole site.
Tip: Start with same-origin and relax only where something visibly breaks. Being too tight fails loudly and gets fixed; being too loose produces no symptom at all, which is why permissive settings survive unexamined for years.
Tags: corp, security, howto, fix
How-To
How to Fix an Over-Permissive CORS Policy
Replace wildcards and origin reflection with a validated allow-list, and add the Vary header that stops caches leaking across sites.
1. Find every endpoint sending CORS headers
grep your application for Access-Control-Allow-Origin, and check your server config and CDN rules too — CORS headers are often added at more than one layer. For each one, establish which specific consumer needs it. Endpoints where nobody can name a consumer should have the headers removed.
2. Remove wildcards from anything non-public
Access-Control-Allow-Origin: * lets any site on the internet read the response. Acceptable for a public font or an open dataset; wrong for anything user-specific, authenticated or internal. Treat a wildcard as equivalent to publishing that endpoint's output.
3. Build an allow-list in configuration
Keep the trusted origins in config, not scattered through the code: const ALLOWED = ['https://app.example.com', 'https://partner.example.com']. On each request, compare the incoming Origin header against that list and echo it back only on an exact match.
4. Never reflect the Origin header unconditionally
Taking whatever Origin arrives and echoing it back appears to work perfectly and is equivalent to a wildcard — except it also functions with credentials, which makes it strictly more dangerous. Any attacker page becomes an allowed origin. Always validate before echoing.
5. Compare full origins, not substrings
A check like origin.endsWith('example.com') accepts evil-example.com and notexample.com.attacker.net. Compare complete strings including scheme and port, or parse the URL and compare the origin property. This shortcut is a recurring source of real vulnerabilities.
6. Add Vary: Origin whenever you echo
If the response differs by Origin, send Vary: Origin. Without it a CDN or proxy can cache the response generated for one origin and serve it to another, either leaking data or breaking legitimate access. The resulting bugs are intermittent and very hard to diagnose.
7. Restrict methods, headers and credentials
Access-Control-Allow-Methods and Access-Control-Allow-Headers should list only what the endpoint needs. Never combine a wildcard origin with Access-Control-Allow-Credentials: true — browsers reject it, and the attempt indicates a misunderstanding worth correcting.
8. Test with a forged Origin header
curl -sI -H 'Origin: https://evil.example' https://yoursite.com/api/endpoint and look at what comes back. If your invented origin appears in Access-Control-Allow-Origin, you are reflecting rather than validating and any site can read that response.
Tip: CORS only ever relaxes a browser protection; it never adds one. Sending no CORS headers at all is the most secure configuration, so every header should exist because a specific, named consumer needs it.
Tags: cors, security, howto, fix
How-To
How to Fix document.write() in JavaScript
document.write() blocks the HTML parser and destroys page content if called after load. Replace with DOM manipulation.
Replace with DOM insertion
Instead of document.write("<p>Hello</p>"): const p = document.createElement("p"); p.textContent = "Hello"; document.body.appendChild(p). Or: document.getElementById("target").insertAdjacentHTML("beforeend", "<p>Hello</p>").
For script loading
Replace document.write("<script src=...>") with: const script = document.createElement("script"); script.src = "..."; document.head.appendChild(script). Or use dynamic import(): import("./module.js").then(m => m.init()).
Tip: Chrome blocks document.write() for scripts on slow connections (2G). Users on slow mobile connections may see broken functionality if document.write() is used for script injection.
Tags: document-write, javascript, performance, fix, technical
How-To
How to Fix Synchronous XMLHttpRequest
Synchronous XHR blocks the browser main thread entirely. Replace with async fetch().
Find synchronous XHR
Search for xhr.open(. Synchronous XHR has false as the third argument: xhr.open("GET", url, false). Our JS Checker flags this pattern.
Replace with fetch()
Replace with modern fetch() using async/await: async function getData(url) { try { const response = await fetch(url); const data = await response.json(); return data; } catch(err) { console.error("Fetch failed:", err); } }.
Tip: Synchronous XHR in Web Workers is still allowed — workers run on a separate thread. If you need synchronous request-response behaviour, consider a Web Worker.
Tags: xhr, javascript, performance, fix, async, fetch
Tutorial
Modern JavaScript Best Practices: let, const, Strict Mode and ES Modules
Modern JavaScript features prevent common bugs. This covers the shift from var to let/const, strict mode, and why they matter for code quality.
var vs let and const
var is function-scoped and hoisted — it is accessible before its declaration line and leaks out of blocks. let and const are block-scoped and not accessible before declaration. Use const by default. Use let only when reassignment is needed. Avoid var entirely in new code.
The closure-in-loop bug
Classic var bug: for (var i=0; i<3; i++) { setTimeout(() => console.log(i), 0); } prints 3,3,3. With let it correctly prints 0,1,2 because each iteration creates a new block scope.
Strict mode
"use strict" catches common mistakes: undeclared variables throw ReferenceError, duplicate parameter names throw SyntaxError. ES modules are automatically in strict mode. Add "use strict" to all non-module scripts.
Strict equality
Loose equality (==) performs type coercion: 0 == false is true. Always use === and !== except for the specific null check: if (x == null) which intentionally catches both null and undefined.
Tip: ESLint with no-var, prefer-const and eqeqeq rules enforces these practices automatically. Add it to your project to catch issues in real time.
Tags: javascript, modern, let, const, var, strict, es-modules, quality
How-To
How to Fix var Declarations — Use let and const
var is function-scoped causing hard-to-find bugs. Replace with const and let.
Replace with const by default
For any variable assigned once and never reassigned: const name = "John". const prevents reassignment of the binding — objects and arrays can still have contents mutated.
Use let for mutable variables
For variables that are reassigned: let count = 0; let isLoading = true. let is block-scoped so it does not leak out of for loops or if blocks.
Bulk replacement
Use Find and Replace with regex: Find: \bvar\b, Replace: const. Then change to let where the variable is actually reassigned.
Tip: ESLint no-var rule prevents var from being added back. Add it to your config: "no-var": "error".
Tags: var, let, const, javascript, fix, quality
How-To
How to Fix Loose Equality (==) in JavaScript
Loose equality performs type coercion causing subtle bugs. Replace == with === everywhere.
The fix
Replace all == with === and all != with !==. Strict equality compares both value and type without coercion.
The null check exception
The one intentional use: if (x == null) catches both null and undefined. This is a widely accepted pattern — use "always" eqeqeq with null exception.
Tip: ESLint eqeqeq rule: "eqeqeq": ["error", "always", { "null": "ignore" }] enforces strict equality while allowing the x == null pattern.
Tags: equality, javascript, fix, quality, bugs
How-To
How to Add use strict to JavaScript
Strict mode catches common JavaScript mistakes at runtime.
Add to script files
Add "use strict"; as the very first statement in each JavaScript file before any other code: "use strict"; const app = {};
ES modules are automatic
Files using import/export or loaded with <script type="module"> are automatically in strict mode — no need to add "use strict".
Tip: If adding "use strict" breaks a file, strict mode is exposing real bugs. Fix each issue — the strict mode errors are pointing to genuine problems in production.
Tags: strict, javascript, fix, quality, security
How-To
How to Fix Large JavaScript Files
Large JS files block the main thread and hurt INP and LCP. Split with code splitting and remove unused code.
Remove unused code with tree-shaking
Use ES module imports: import { specific } from "library" rather than require() which prevents tree-shaking. Run your build with production settings and compare bundle sizes.
Code split non-critical code
Use dynamic import(): const { heavyFeature } = await import("./heavyFeature.js"). Load heavy features only on button click, route change or scroll.
Minify for production
Minification reduces JS size by 20-40%. All modern bundlers minify by default in production mode. Ensure your build runs with production settings.
Tip: Run our Page Speed tool and look at the Coverage section — files with 80%+ unused code are prime candidates for code splitting.
Tags: javascript, performance, fix, bundle, code-splitting, inp, lcp
How-To
How to Fix setInterval() Overuse
Accumulating setInterval calls waste CPU and cause memory leaks. Replace with event-driven patterns.
Always store and clear intervals
Store the ID and clear it when done: const id = setInterval(fn, 1000); // When done: clearInterval(id). In React: return () => clearInterval(id) from useEffect.
Replace polling with events
Replace DOM size polling with ResizeObserver, DOM mutation polling with MutationObserver, scroll position polling with IntersectionObserver.
Use requestAnimationFrame for visual updates
For animation/visual updates: function animate() { update(); requestAnimationFrame(animate); } requestAnimationFrame(animate). RAF pauses when the tab is hidden saving CPU.
Tip: Chrome DevTools Performance tab recording in the background shows continuous JS activity if intervals are accumulating.
Tags: setinterval, javascript, performance, fix, memory-leak, quality
How-To
How to Fix Repeated DOM Queries — Cache DOM References
Repeated getElementById() calls force DOM traversal each time. Cache the reference in a variable.
Cache element references
Instead of calling getElementById repeatedly: const counter = document.getElementById("counter"); // Then reuse counter throughout your functions.
Cache before loops
Never query inside a loop: const list = document.getElementById("list"); for (let i=0; i<items.length; i++) { list.appendChild(items[i]); }.
Use DocumentFragment for multiple insertions
Batch DOM insertions: const fragment = document.createDocumentFragment(); items.forEach(item => { fragment.appendChild(createEl(item)); }); list.appendChild(fragment). One DOM insertion instead of many.
Tip: This optimisation matters most in functions that run frequently — event handlers, RAF callbacks and scroll listeners.
Tags: dom, javascript, performance, fix, quality, caching
How-To
How to Fix setTimeout for Animations — Use requestAnimationFrame
setTimeout produces janky animations. requestAnimationFrame synchronises with the browser rendering pipeline.
Replace setTimeout with RAF
Instead of: function animate() { update(); setTimeout(animate, 16); } Use: function animate() { update(); requestAnimationFrame(animate); } requestAnimationFrame(animate).
Cancelling RAF
Store the ID to cancel: let rafId = requestAnimationFrame(animate); // To cancel: cancelAnimationFrame(rafId). Always cancel when components unmount.
Time-based animations
RAF passes a timestamp: function animate(timestamp) { const elapsed = timestamp - startTime; update(elapsed); if (elapsed < duration) requestAnimationFrame(animate); }. Makes animations run at correct speed regardless of frame rate.
Tip: RAF automatically pauses when the tab is hidden saving battery on mobile. setTimeout does not pause — another reason RAF is always preferable for animations.
Tags: requestanimationframe, animation, javascript, performance, fix, quality
Tutorial
JavaScript Error Handling: Promises, async/await and try/catch
Unhandled errors cause silent failures. Proper error handling ensures errors are caught and handled gracefully.
Handling promises with .catch()
Every promise chain must end with .catch(): fetch("/api").then(r => r.json()).then(data => render(data)).catch(err => { console.error(err); showError(); }). Without .catch() any error is silently swallowed.
async/await with try/catch
Wrap all await in try/catch: async function load() { try { const r = await fetch("/api"); const data = await r.json(); render(data); } catch(err) { console.error(err); showError(); } }.
Global unhandled rejection handler
Add a safety net: window.addEventListener("unhandledrejection", e => { console.error("Unhandled:", e.reason); }). Catches anything that slips through local error handling.
Check HTTP status
fetch() does not reject on HTTP errors. Check response.ok: const r = await fetch(url); if (!r.ok) throw new Error("HTTP " + r.status); Without this a 500 looks like a success.
Tip: Add window.onerror and window.addEventListener("unhandledrejection") early in your application. Even with thorough try/catch, unexpected errors occur — a global handler ensures they are logged.
Tags: javascript, error-handling, promises, async, await, try-catch, quality
How-To
How to Fix .then() Calls Without .catch()
Promise chains without .catch() silently swallow errors.
Add .catch() to each chain
Add .catch(err => handleError(err)) to the end of every promise chain: fetch(url).then(r => r.json()).then(render).catch(err => showError()).
Convert to async/await
Cleaner with async/await: async function load() { try { const r = await fetch(url); render(await r.json()); } catch(err) { showError(); } }.
Use Promise.allSettled
Promise.all() rejects as soon as any promise rejects. Use Promise.allSettled() if you want all promises to complete regardless of failures.
Tip: Our JS Checker flags .then() calls where no .catch() follows in the same chain.
Tags: promises, javascript, error-handling, fix, quality
How-To
How to Fix async Functions Without try/catch
async functions return rejected promises on error. Without try/catch errors are unhandled.
Wrap await in try/catch
async function save(data) { try { const r = await fetch("/api", { method:"POST", body:JSON.stringify(data) }); if (!r.ok) throw new Error(r.status); return await r.json(); } catch(err) { console.error(err); throw err; } }.
Async event handlers
Async event handlers are a common source of unhandled rejections: button.onclick = async () => { try { await doSomething(); } catch(err) { handleError(err); } }.
Tip: A utility wrapper for async event handlers: const safe = fn => async (...args) => { try { return await fn(...args); } catch(err) { reportError(err); } }.
Tags: async, await, try-catch, javascript, fix, error-handling
How-To
How to Fix addEventListener Without removeEventListener
Event listeners that are never removed accumulate causing memory leaks, especially in SPAs.
Store the listener reference
To remove a listener you need a reference: const handler = (e) => handleClick(e); element.addEventListener("click", handler); // Later: element.removeEventListener("click", handler). Anonymous functions cannot be removed.
React cleanup
Remove listeners in useEffect cleanup: useEffect(() => { window.addEventListener("resize", fn); return () => window.removeEventListener("resize", fn); }, []).
Use AbortController
Clean way to remove multiple listeners: const ctrl = new AbortController(); element.addEventListener("click", fn, { signal: ctrl.signal }); // Remove all: ctrl.abort().
Tip: Passive event listeners improve scroll performance: document.addEventListener("scroll", fn, { passive: true }). Use for scroll, touchstart and touchmove listeners that do not call preventDefault().
Tags: event-listeners, javascript, memory-leak, fix, quality, spa
How-To
How to Fix Infinite Loops in JavaScript
An infinite loop freezes the browser tab. Add break conditions and use async patterns for non-blocking iteration.
Add a break condition
Every loop needs a termination: let attempts = 0; while (attempts < 100) { if (conditionMet()) break; attempts++; doSomething(); }. Add a maximum iteration count as a safety net.
Use async for long-running tasks
For tasks needing to run indefinitely: async function poll() { while (true) { await checkForUpdate(); await new Promise(r => setTimeout(r, 1000)); } }. The await yields control to the browser between iterations.
Use RAF for visual loops
let running = true; function gameLoop() { if (!running) return; update(); render(); requestAnimationFrame(gameLoop); } requestAnimationFrame(gameLoop). RAF yields after each frame letting the browser handle events.
Tip: If your page becomes unresponsive during development, Chrome DevTools Performance tab shows a continuous solid main thread bar — this indicates an infinite loop or very long synchronous operation.
Tags: infinite-loop, javascript, fix, performance, quality
How-To
@context is required in every JSON-LD block. Without it Google ignores the schema entirely.
The fix
Add "@context": "https://schema.org" as the first property in every JSON-LD object: { "@context": "https://schema.org", "@type": "Article", "headline": "Your Title" }.
For @graph structures
{ "@context": "https://schema.org", "@graph": [ { "@type": "WebPage" }, { "@type": "Organization" } ] }. The single @context applies to all objects in the graph.
Validate
Use our Schema Debugger. Also use Google Rich Results Test (search.google.com/test/rich-results) to check Google can parse your schema.
Tip: Use "https://schema.org" without a trailing slash — more consistent with Google examples.
Tags: schema, json-ld, context, fix, structured-data, seo
How-To
@type declares what entity your schema describes. Without it Google ignores the block.
Choose the right type
Article or BlogPosting for blog posts, Product for products, FAQPage for FAQ sections, LocalBusiness for location pages, HowTo for step-by-step guides, Event for events, BreadcrumbList for breadcrumbs, WebSite for site-level markup.
The fix
Add "@type": "YourType" to every JSON-LD object. The type name is case-sensitive — Product not product, FAQPage not faqpage.
Multiple types
An entity can have multiple types: "@type": ["LocalBusiness", "Restaurant"]. Use when content genuinely fits multiple categories.
Tip: Visit schema.org to browse all available types. For each type the page lists required, recommended and optional properties.
Tags: schema, json-ld, type, fix, structured-data, seo
How-To
Article schema enables rich results. Missing headline, author, datePublished and image are the most common errors.
Required fields
Google requires for Article rich results: headline (max 110 characters), author (Person or Organization object), image (ImageObject with url, width, height).
Add headline and dates
"headline": "Your Article Title" (max 110 chars). "datePublished": "2024-01-15T08:00:00Z". "dateModified": "2024-03-01T12:00:00Z". Use ISO 8601 format.
Add author
"author": {"@type": "Person", "name": "Author Name", "url": "https://example.com/author"}. Add sameAs with LinkedIn/Twitter to strengthen E-E-A-T.
Add image and publisher
"image": {"@type": "ImageObject", "url": "https://example.com/image.jpg", "width": 1200, "height": 630}. "publisher": {"@type": "Organization", "name": "Site Name", "logo": {"@type": "ImageObject", "url": "https://example.com/logo.png"}}.
Tip: Use our AI Schema Generator to automatically generate complete Article schema from your page content.
Tags: schema, article, json-ld, fix, structured-data, rich-results, seo
How-To
Product schema enables price and rating in Google Search. Missing name and offers are the most common errors.
Required fields
Google requires: name, and either offers with price and priceCurrency, or aggregateRating.
Add offers with price
"offers": {"@type": "Offer", "price": "29.99", "priceCurrency": "GBP", "availability": "https://schema.org/InStock"}. priceCurrency must be ISO 4217 (GBP, USD, EUR).
Add aggregateRating
"aggregateRating": {"@type": "AggregateRating", "ratingValue": "4.5", "reviewCount": "89", "bestRating": "5"}. Fake ratings violate Google policies.
Tip: Keep product schema in sync with actual prices. Schema showing a different price from the page can result in a manual action from Google.
Tags: schema, product, json-ld, fix, structured-data, rich-results, ecommerce
How-To
FAQPage schema enables question-answer dropdowns in Google Search. mainEntity, question names and answer text are all required.
FAQPage structure
{ "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What is your return policy?", "acceptedAnswer": {"@type": "Answer", "text": "We offer 30-day returns."}}] }.
Common errors
mainEntity missing — add the array. mainEntity not an array — wrap in []. Questions missing name or acceptedAnswer.text — add both.
Match visible content
Every question in FAQPage schema must correspond to visible FAQ content on the page. Schema with questions not on the page violates Google guidelines.
Tip: FAQPage schema is one of the highest-impact AEO improvements — AI search engines frequently cite pages with well-structured FAQPage schema.
Tags: schema, faqpage, json-ld, fix, structured-data, rich-results, aeo
How-To
How to Fix LocalBusiness Schema — Required Fields for Local SEO
LocalBusiness schema helps Google understand your business location. Name, address and telephone are the key fields.
Add name and address
"name": "Your Business Name", "address": {"@type": "PostalAddress", "streetAddress": "123 Main St", "addressLocality": "Manchester", "postalCode": "M1 1AB", "addressCountry": "GB"}.
Add coordinates
"geo": {"@type": "GeoCoordinates", "latitude": 53.4808, "longitude": -2.2426}. Find coordinates by right-clicking your location in Google Maps.
Add opening hours
"openingHours": ["Mo-Fr 09:00-17:30", "Sa 10:00-16:00"]. Use day abbreviations: Mo, Tu, We, Th, Fr, Sa, Su.
Match your GBP
NAP (Name, Address, Phone) in schema must exactly match your Google Business Profile. Add sameAs link to your GBP.
Tip: Use our Local SEO Checker to audit LocalBusiness schema alongside your Google Business Profile data.
Tags: schema, localbusiness, json-ld, fix, local-seo, rich-results, structured-data
How-To
How to Fix BreadcrumbList Schema
BreadcrumbList schema enables breadcrumb navigation paths in Google Search results.
BreadcrumbList structure
{ "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://example.com"}, {"@type": "ListItem", "position": 2, "name": "Blog", "item": "https://example.com/blog"}, {"@type": "ListItem", "position": 3, "name": "Article"}] }.
Common errors
Missing itemListElement array. Missing position on ListItem items — add starting from 1. Missing name. Missing item URL on intermediate pages. The last item does not require an item URL.
Tip: Add BreadcrumbList schema to every page that is not the homepage. Even simple two-level breadcrumbs improve SERP appearance.
Tags: schema, breadcrumb, json-ld, fix, structured-data, rich-results, seo
How-To
How to Fix WebSite Schema and Sitelinks Search Box
WebSite schema enables the Sitelinks Search Box in Google results.
Basic WebSite schema
{ "@context": "https://schema.org", "@type": "WebSite", "url": "https://example.com", "name": "Your Site Name" }.
Add Sitelinks Search Box
"potentialAction": {"@type": "SearchAction", "target": {"@type": "EntryPoint", "urlTemplate": "https://example.com/?s={search_term_string}"}, "query-input": "required name=search_term_string"}. Replace urlTemplate with your actual search URL pattern.
Tip: WebSite schema should only be on your homepage. Place it alongside Organization schema in a @graph for clean implementation.
Tags: schema, website, json-ld, fix, structured-data, sitelinks, seo
How-To
How to Fix HowTo Schema
HowTo schema enables step-by-step rich results. Name and step array are required.
HowTo structure
{ "@context": "https://schema.org", "@type": "HowTo", "name": "How to Fix Your Title Tag", "step": [{"@type": "HowToStep", "name": "Step name", "text": "Step description", "position": 1}] }.
Required fields
name (guide title), step array. Each HowToStep: name (step title), text (instructions), position (step number).
When to use HowTo
Use on pages with numbered step-by-step instructions. Content must be genuinely instructional — not marketing copy.
Tip: HowTo rich results show step titles as an expandable list in search results. Concise, action-oriented step names work best: "Add the meta tag" rather than "You should consider adding...".
Tags: schema, howto, json-ld, fix, structured-data, rich-results, seo
How-To
How to Fix Event Schema
Event schema enables date, location and ticket information in Google Search. name, startDate and location are required.
Required fields
Google requires: name, startDate in ISO 8601 format (include timezone: "2025-06-15T18:00:00+01:00"), and location (Place or VirtualLocation).
Add location
Physical: "location": {"@type": "Place", "name": "Venue", "address": {...}}. Online: "location": {"@type": "VirtualLocation", "url": "https://example.com/stream"}.
Add eventAttendanceMode
"eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode" for in-person, OnlineEventAttendanceMode for virtual, MixedEventAttendanceMode for hybrid. Required for online events.
Tip: Event rich results appear in Google Events tab — significantly increasing visibility beyond standard blue links.
Tags: schema, event, json-ld, fix, structured-data, rich-results, seo
How-To
How to Fix Review, Person and Organization Schema
Review needs reviewRating and author. Person needs name and sameAs. Organization needs name, logo and url.
Review required fields
"reviewRating": {"@type": "Rating", "ratingValue": "4", "bestRating": "5"} and "author": {"@type": "Person", "name": "Reviewer Name"} are both required.
Person schema for authors
"@type": "Person", "name": "Full Name", "jobTitle": "Job Title", "sameAs": ["https://twitter.com/example", "https://linkedin.com/in/example"]. sameAs links to external profiles help Google verify the person and establish expertise.
Organization schema
"@type": "Organization", "name": "Company", "url": "https://example.com", "logo": {"@type": "ImageObject", "url": "https://example.com/logo.png", "width": 300, "height": 60}, "sameAs": ["https://facebook.com/example"].
Using @id for cross-referencing
Define Organization once with "@id": "https://example.com/#organisation", then reference it: "publisher": {"@id": "https://example.com/#organisation"}. Tells Google these are the same entity.
Tip: Person schema with strong sameAs links is one of the most impactful E-E-A-T improvements. Google cross-references sameAs URLs to verify author credentials.
Tags: schema, review, person, organization, json-ld, fix, structured-data, eeat
How-To
How to Fix Missing Preconnect Hints for Third-Party Domains
Preconnect hints eliminate DNS, TCP and TLS connection time for third-party resources, reducing load time for fonts, analytics and CDN assets.
Find your third-party domains
Open Chrome DevTools Network tab and look for requests to domains other than your own. Common candidates: fonts.googleapis.com, fonts.gstatic.com, analytics services, CDNs, payment processors.
Add preconnect tags
Add inside <head>: <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>. The crossorigin attribute is required for CORS-enabled resources like fonts.
Prioritise correctly
Only preconnect to domains used early in page load. Preconnecting to everything wastes resources. Prioritise: font providers (highest impact on FCP/LCP), critical analytics, CDN for hero images.
Tip: For Google Fonts: <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link rel="stylesheet" href="https://fonts.googleapis.com/css2?...&display=swap">. This combination minimises font-related render delays.
Tags: preconnect, performance, fix, lcp, fcp, fonts, technical
Tutorial
HTTP status codes tell browsers, search engines and users whether a request succeeded. The wrong status code can prevent indexing or waste crawl budget.
2xx Success codes
200 OK is the standard success response. Google needs 200 to crawl and index a page. 201 Created is returned after successful form submissions or API resource creation. For web pages always return 200 for pages you want indexed.
3xx Redirect codes
301 Moved Permanently — Google transfers almost all ranking signals to the new URL. Use for permanent URL changes and domain migrations. 302 Found (Temporary) — Google does not reliably transfer ranking signals. Only use for genuine temporary redirects.
4xx Client error codes
400 Bad Request — malformed request. 401 Unauthorized — authentication required. 403 Forbidden — no permission. 404 Not Found — page does not exist. 410 Gone — page permanently deleted (Google deindexes faster than 404). 429 Too Many Requests — rate limited.
5xx Server error codes
500 Internal Server Error — generic server failure. 502 Bad Gateway — upstream server failure. 503 Service Unavailable — temporary overload. 504 Gateway Timeout — upstream too slow. Google respects Retry-After headers on 503 and will retry. Persistent 5xx errors will cause deindexing.
Status codes and SEO
Google only indexes pages returning 200. Redirect chains (301 → 301 → 301) dilute PageRank — flatten to single-hop redirects. 410 is preferable to 404 for intentionally deleted pages. Monitor status codes in Google Search Console under Coverage.
Tip: Set up uptime monitoring to alert you immediately when your site returns 5xx errors. Google gives sites some grace period before deindexing but persistent errors over several days result in removal.
Tags: http, status-codes, seo, technical, crawling, indexing
How-To
Each 4xx error has a different cause and fix.
Fix 404 Not Found
For pages that have moved: 301 redirect to new URL. For pages permanently deleted: return 410 Gone instead — Google deindexes 410 faster. For pages that should exist but return 404: check URL routing, .htaccess rewrite rules, and database connectivity.
Fix 410 Gone
410 is correct for intentionally deleted pages. In Apache .htaccess: Redirect 410 /old-page. In Express: app.get("/old-page", (req,res) => res.status(410).send("Page removed")).
Fix 403 Forbidden
Check file permissions: files should be 644, directories 755. Check .htaccess for deny rules. Ensure the web server user has read access.
Create a helpful 404 page
Return the correct 404 status code (not 200), explain the page is not found, provide navigation to main sections, include search. In Apache: ErrorDocument 404 /404.html.
Tip: Check Google Search Console Coverage report for 404 errors Googlebot has encountered — these indicate broken internal or external links pointing to deleted pages.
Tags: http, 404, 410, 403, 401, fix, seo, crawling
How-To
Server errors prevent Google from crawling your pages and can lead to deindexing.
Fix 500 Internal Server Error
Check server error logs: Apache: /var/log/apache2/error.log, Nginx: /var/log/nginx/error.log. Common causes: PHP/Node exceptions, database connection failure, out of memory, file permission issues.
Fix 502 Bad Gateway
Check the upstream is running: pm2 list (Node.js), systemctl status php-fpm (PHP). Check it is listening on the correct port: netstat -tlnp | grep 3000.
Fix 503 Service Unavailable
Add Retry-After header: Retry-After: 3600. Identify the overload cause: too many concurrent requests, memory exhaustion, scheduled maintenance.
Fix 504 Gateway Timeout
Increase proxy timeout: Nginx: proxy_read_timeout 60s. Optimise slow database queries. Move long-running tasks to background jobs.
Tip: Set up uptime monitoring to alert immediately on 5xx errors. Google gives some grace period before deindexing but persistent errors over several days will cause removal from the index.
Tags: http, 500, 502, 503, 504, fix, server, seo
How-To
How to Fix 429 Too Many Requests
HTTP 429 means rate limiting. Implement proper limits and exponential backoff for third-party APIs.
If 429 from your own server
Review your rate limit thresholds. Provide a Retry-After header: Retry-After: 60. Set generous limits for legitimate crawlers or whitelist Googlebot IP ranges.
If 429 from third-party APIs
Implement exponential backoff: async function fetchWithBackoff(url, retries=3) { for (let i=0; i<retries; i++) { const res = await fetch(url); if (res.status !== 429) return res; await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i))); } }.
Cache API responses
Reduce API call frequency by caching: store results in localStorage, Redis or your database with a TTL appropriate to how frequently the data changes.
Tip: Googlebot respects 429 and automatically reduces its crawl rate. You can also set a preferred crawl rate in Google Search Console under Settings > Crawl rate.
Tags: http, 429, rate-limiting, fix, technical, api
Tutorial
llms.txt is a proposed standard for guiding AI systems to your most important content. Adoption is still early but growing. Here is what it is, how to implement it and what to realistically expect.
What llms.txt is
llms.txt is a plain text Markdown file at yoursite.com/llms.txt. Proposed by Jeremy Howard of Answer.AI in September 2024, it guides AI systems to your most important content — similar to how robots.txt guides search engine crawlers. Unlike robots.txt which restricts access, llms.txt is a curated guide.
The current reality
As of early 2026, major AI providers (OpenAI, Google, Anthropic) have not confirmed official support for llms.txt in their primary AI products. Log analysis found AI crawlers (GPTBot, ClaudeBot, PerplexityBot) do not consistently request the file. However Anthropic, Cloudflare, Vercel and many developer-focused companies have implemented it.
The format
# Your Site Name > Brief description ## Key Pages - [Homepage](https://yoursite.com): What your site does - [Documentation](https://yoursite.com/docs): Technical guides ## Expertise SEO, web performance, structured data. Keep it concise — focus on your most authoritative pages.
Should you implement it
Yes — with realistic expectations. Implementation takes under an hour and has no downside. It does not affect Google rankings. It may benefit you as AI search engines evolve. It is especially valuable for developer-focused sites and technical documentation.
Tip: llms.txt is one signal among many. Structured data (especially FAQPage schema), direct-answer content and strong E-E-A-T signals have far more proven impact on AI citation frequency today.
Tags: llms-txt, aeo, ai, crawlers, visibility, technical
How-To
Step-by-step guide to creating a spec-compliant llms.txt file that passes all checks and gives AI models the context they need.
Create the file
Open a plain text editor (not Word). Create a new file. The filename must be exactly: llms.txt — lowercase, no spaces.
Add the H1 title (line 1)
# Your Site Name — this must be the very first line. Nothing before it, not even a blank line. Replace "Your Site Name" with your actual brand name or domain.
Add the blockquote summary (line 2 or 3)
> A clear, factual one-sentence description of what your site provides and who it is for. Example: > Professional SEO audit tools for freelancers and digital agencies. This is what AI models read first.
Add ## sections with links
Create sections using ## H2 headings. Each link follows this format: - [Page Title](https://yoursite.com/page): Brief description. Recommended sections: ## Key Pages, ## Documentation, ## About, ## Optional (for lower-priority pages).
Add a permissions statement
Add a brief statement about how AI models may use your content: AI models may index and cite this content for informational purposes. This is optional but good practice.
Reference your sitemap
If you have a sitemap, add: - [Sitemap](https://yoursite.com/sitemap.xml) under ## Key Pages or a ## Sitemap section. This helps AI crawlers discover all your content.
Upload to your domain root
Upload llms.txt via FTP, SFTP, or your host's file manager to the same directory as your robots.txt. Test by visiting https://yoursite.com/llms.txt in a browser. You should see the file content.
Tip: Run our LLMs.txt Auditor after uploading to validate your file scores well against the full spec. The auditor checks all requirements and generates a corrected version if needed.
Tags: llms-txt, aeo, ai, crawlers, howto, technical, implementation
How-To
The most common llms.txt spec failures and exactly how to fix each one. Use alongside the LLMs.txt Auditor results.
Fix: First line is not # Title
Open your llms.txt. The very first character of the file must be #. Check for: BOM characters (some editors add these invisibly), blank lines before the title, UTF-8 encoding issues. Safest fix: copy the content, delete the file, create a fresh one and paste content back.
Fix: Missing blockquote summary
After your # Title line, add a blank line then: > Your one-sentence site description. The > character followed by a space then your text. Keep it factual and specific — not marketing language.
Fix: No ## sections
Group your links into ## H2 sections. Minimum recommended: ## Key Pages (your most important URLs), ## Optional (blog, archive pages). Never put links outside of a section.
Fix: Wrong link format
Change plain URLs to the spec format. WRONG: https://yoursite.com/about — RIGHT: - [About Us](https://yoursite.com/about): Our team and expertise. The dash, brackets, parentheses and colon with description are all required.
Fix: External domain links
Search your file for any links not pointing to your own domain. Remove them entirely. llms.txt is a guide to your content — not a link directory.
Fix: X-Robots-Tag conflict
In Nginx remove: add_header X-Robots-Tag "noindex" always. In Apache .htaccess remove: Header set X-Robots-Tag "noindex". Confirm the fix by checking response headers at https://securityheaders.com.
Tip: Use our LLMs.txt Auditor to get a real score before and after fixing. The generator uses Claude to produce a corrected version automatically using real data from your live site.
Tags: llms-txt, aeo, ai, fix, howto, technical, spec
Tutorial
llms-full.txt: The Extended Version Explained
The llmstxt.org spec defines an optional llms-full.txt for comprehensive site coverage. Here is what it contains, when to use it and how it differs from llms.txt.
What llms-full.txt is
llms-full.txt is the extended companion to llms.txt. While llms.txt is a curated summary of your most important pages (under 60 lines typically), llms-full.txt can list every significant URL on your site with descriptions. It lives at yoursite.com/llms-full.txt.
When to create it
Create llms-full.txt if: you have more than 20 pages worth linking to, you have documentation or content that does not fit in llms.txt, or you want to provide AI models with comprehensive coverage beyond your highlights.
Format
Same format as llms.txt: # Title, > summary, ## sections, - [links]. The difference is scope — include all significant pages not just highlights. Group by content type: ## Tools, ## Documentation, ## Blog, ## Case Studies.
Relationship between the two files
AI models may request llms.txt for a quick overview and llms-full.txt for comprehensive coverage. llms.txt should stand alone as a complete guide. llms-full.txt adds depth. Neither replaces robots.txt or sitemap.xml.
Tip: Our LLMs.txt Auditor checks for both files. The auditor awards bonus points for having llms-full.txt. The generator currently produces llms.txt — create llms-full.txt manually by expanding the generated file.
Tags: llms-txt, llms-full, aeo, ai, technical, spec
How-To
Step-by-step deployment instructions for WordPress, Apache, Nginx, Shopify, Vercel and Cloudflare. Upload once, accessible at yoursite.com/llms.txt.
Apache (cPanel / shared hosting)
Upload llms.txt via FTP or cPanel File Manager to your public_html directory — the same folder that contains your index.html or .htaccess. Test by visiting https://yoursite.com/llms.txt in a browser. If you get a 403 error, check file permissions are set to 644.
Nginx
Upload llms.txt to your document root (typically /var/www/html/ or /var/www/yoursite/public/). Nginx serves static files from the document root automatically. No config change needed. Test with: curl -I https://yoursite.com/llms.txt — you should see HTTP/1.1 200 OK.
WordPress
Upload llms.txt via FTP to the WordPress root directory (same level as wp-config.php, not inside wp-content). Alternatively use a plugin like "Yoast SEO" which has llms.txt support, or add via WP-CLI: wp eval "file_put_contents(ABSPATH . 'llms.txt', $content);"
Shopify
Shopify does not allow arbitrary files in the root. Use a workaround: create a page with handle "llms-txt" and add the content as the page body. Then add a URL rewrite in your theme.liquid: {% if request.path == "/llms.txt" %}. Alternatively serve via a Shopify App Proxy pointing to an external URL.
Vercel
Place llms.txt in your /public folder. Vercel serves all files in /public at the root path automatically. For Next.js projects place it in /public/llms.txt — it will be served at https://yoursite.com/llms.txt with no config needed.
Cloudflare Pages
Place llms.txt in your project root or /public folder. Cloudflare Pages serves static assets from the root. Add a _headers file entry if you want to set a specific Content-Type: /llms.txt Content-Type: text/plain; charset=utf-8
Verify deployment
After uploading, verify with three checks: (1) Visit https://yoursite.com/llms.txt in a browser and confirm raw text content appears. (2) Run curl -I https://yoursite.com/llms.txt and confirm 200 status. (3) Run our LLMs.txt Auditor to validate the full spec and score.
Tip: After deploying, submit the URL to llmstxt.site and directory.llmstxt.cloud — two public directories that list llms.txt files. This increases discoverability by AI tools that check these registries.
Tags: llms-txt, deploy, wordpress, nginx, apache, shopify, vercel, howto, technical
Tutorial
llms.txt for Different Site Types — Real Examples
What to put in your llms.txt depends entirely on what your site does. This guide covers SaaS tools, local businesses, e-commerce, documentation sites, personal sites and agencies — with real examples for each.
SaaS and web tools
Focus on what the tool does, who it is for, and link to documentation and key feature pages. Include a ## Docs section with your API reference and getting-started guides. Example:
# ToolName
> A [category] tool for [audience] that [core benefit].
## Key Pages
- [Home](https://tool.com): Platform overview
- [Docs](https://tool.com/docs): Full API reference
- [Pricing](https://tool.com/pricing): Plans and limits
## Optional
- [Blog](https://tool.com/blog): Tutorials and updates
Local businesses
Include your location, opening hours, services and menus or service lists as direct content (not just links). The spec allows free-form markdown between sections. Example:
# Joes Garage
> Family-run car repair garage in Sheffield, serving the area since 1987.
Services: MOTs, full servicing, tyres, exhausts, diagnostics.
Hours: Mon-Fri 8am-6pm, Sat 9am-1pm.
## Services
- [MOT Testing](https://joesgarage.co.uk/mot): Class 4 and 7 MOTs, same-day slots available
- [Tyres](https://joesgarage.co.uk/tyres): Budget to premium brands, fitted while you wait
E-commerce
Describe your product range, policies and key categories. Link to category pages not individual products. Include returns, delivery and contact in ## Optional.
# ShopName
> UK-based online retailer specialising in [product type] with free delivery over £50.
## Categories
- [Running Shoes](https://shop.com/running): 200+ styles, all brands
- [Trail Shoes](https://shop.com/trail): Waterproof and grip-focused options
## Optional
- [Returns Policy](https://shop.com/returns): Free 30-day returns
- [Delivery Info](https://shop.com/delivery): Next-day available
Documentation sites
This is the primary use case the spec was designed for. Link to all major doc sections. Use ## Optional for less critical reference material. Follow the FastHTML example from the spec — include .md versions of pages where available.
# ProjectName Docs
> Python library for [purpose]. Compatible with [ecosystem].
## Getting Started
- [Quick Start](https://docs.project.com/quickstart.md): Up and running in 5 minutes
- [Installation](https://docs.project.com/install.md): All install methods
## Reference
- [API Reference](https://docs.project.com/api.md): Full method documentation
## Optional
- [Changelog](https://docs.project.com/changelog.md): Version history
Personal sites and CVs
Describe your expertise, current role, and link to portfolio work. Great for consultants, freelancers and speakers.
# Jane Smith
> Senior UX Designer with 12 years experience in fintech and healthcare products. Based in London.
## About
- [About Me](https://example.com/about): Background, skills and approach
- [Portfolio](https://example.com/work): Selected case studies
## Optional
- [CV](https://example.com/cv): Full work history and qualifications
- [Blog](https://example.com/blog): Design process articles
Agencies
Describe services, sectors you work in, and case studies. Keep it factual — avoid vague marketing language.
# AgencyName
> Digital marketing agency specialising in SEO and paid search for B2B SaaS companies. Based in Manchester.
## Services
- [SEO](https://agency.com/seo): Technical SEO, content strategy, link building
- [PPC](https://agency.com/ppc): Google Ads, LinkedIn, programmatic
## Case Studies
- [Case Study: SaaS Client](https://agency.com/case-studies/saas): 340% organic growth in 18 months
## Optional
- [About the Team](https://agency.com/team): Credentials and experience
Tip: The spec author Jeremy Howard notes that llms.txt works best when it is concise and factual. Write it as if briefing an expert assistant who needs to answer questions about your site accurately — not as a marketing pitch.
Tags: llms-txt, aeo, ai, ecommerce, local-business, saas, docs, examples, howto
Tutorial
llms.txt and .md Companion Pages — The Full Spec
The llmstxt.org spec includes two features beyond the main file: .md companion pages at existing URLs, and context files like llms-ctx.txt. Here is what they are and whether you need them.
The .md companion page proposal
The spec proposes that any page with content useful to AI should provide a clean markdown version at the same URL with .md appended. So https://yoursite.com/about becomes https://yoursite.com/about.md. For URLs without filenames, append index.html.md. These pages strip navigation, ads and JavaScript — serving clean prose that fits in an LLM context window.
Do you need .md pages?
For most sites, no. The .md companion page proposal is optional and primarily useful for documentation-heavy sites like software libraries. General business sites, blogs and e-commerce stores get little benefit from .md pages. Focus on a well-formed llms.txt first — that is the mandatory part of the spec.
llms-ctx.txt and llms-ctx-full.txt
These are expanded context files generated from your llms.txt using the llms_txt2ctx command line tool. They replace each link in llms.txt with the actual content of that page, formatted as XML suitable for pasting into an LLM context window. llms-ctx.txt excludes Optional section links. llms-ctx-full.txt includes them. These are generated programmatically — not hand-written.
When llms-ctx files matter
llms-ctx files are most useful for developers who want to give an AI assistant full context about a codebase or documentation set in one paste. For example a developer could paste the entire FastHTML llms-ctx.txt into a Claude conversation to get accurate answers about the library. For standard business sites this is niche — most users will not generate these files.
The inference vs training distinction
The spec is explicit: llms.txt is designed for inference — at the moment a user asks an AI for help. It is not primarily intended for training data. This is why it coexists with robots.txt rather than replacing it. robots.txt governs crawl access for indexing bots. llms.txt provides curated guidance for real-time AI use.
Tip: If you build on nbdev (Answer.AI's documentation tool), .md pages are generated automatically for all documentation pages. For most other platforms, focus your effort on a high-quality llms.txt and llms-full.txt rather than implementing .md companion pages.
Tags: llms-txt, markdown, llms-ctx, aeo, ai, technical, spec, advanced
How-To
How to Test Your llms.txt With AI Models
After creating your llms.txt, test it by asking AI models questions about your site. Here is how to do it manually and with automated tools.
Manual test with Claude
Open Claude at claude.ai. Type: "Please read this file and then answer questions about the site: https://yoursite.com/llms.txt". Claude will fetch the file and use it as context. Then ask: "What does this site do?", "What are the main sections of this site?", "What pages cover [your topic]?". If Claude answers accurately, your llms.txt is working.
Manual test with ChatGPT
In ChatGPT (with browsing enabled), ask: "Visit https://yoursite.com/llms.txt and summarise what this site is about". If ChatGPT gives an accurate summary matching your site description and content areas, your file is correctly structured.
Check with our LLMs.txt Auditor
Run our LLMs.txt Auditor tool at aiwebpageseo.com/content-tools.html — it fetches your live file and scores it against all spec requirements. It checks structure, link format, domain consistency, word count and cross-references with your robots.txt. Fix all failing checks before testing with AI models.
Test the ## Optional section
The Optional section has a specific meaning in the spec — AI models with limited context windows should skip it. To test this, ask an AI to summarise your site in one sentence. The answer should reflect your H1 and blockquote, not content from the Optional section. If Optional content dominates the answer, move it out of Optional into a named section.
Verify links resolve
Every link in your llms.txt should return HTTP 200. Check them manually by clicking each one, or use a link checker tool. Broken links (404s) in llms.txt actively mislead AI crawlers — they follow the link and get no content, which reduces the usefulness of your file.
Register in directories
Submit your llms.txt URL to llmstxt.site and directory.llmstxt.cloud. These are public directories of llms.txt files. Some AI tools check these directories to discover which sites have llms.txt files. Submission is free and takes under a minute.
Tip: Re-test every time you make significant changes to your site structure, add new content areas, or change your primary service offering. llms.txt should be kept in sync with the actual state of your site — a stale file is worse than no file because it actively misleads AI models.
Tags: llms-txt, testing, aeo, ai, claude, chatgpt, howto, validation
How-To
AI search engines cite pages that answer questions directly. Restructure content to lead with answers.
Lead with the answer
Restructure key pages to answer the primary question in the first 1-2 sentences, then elaborate. Pattern: "[Topic] is [definition]. It works by [mechanism]. [Specific benefit or statistic]." AI systems extract for answers from opening statements.
Use question-format headings
Replace generic headings with H2s and H3s: "What does our SEO audit check?", "How long does an audit take?". AI engines specifically look for content matching question patterns.
Add a FAQ section
Add a visible FAQ section with FAQPage schema to key pages. Write questions matching how your audience actually asks about your topic. Keep answers concise (50-150 words) and direct.
Include specific facts
AI systems prefer citing specific, verifiable claims. Instead of "our tool is fast" write "our audit completes in under 60 seconds". Specific numbers are more citable than vague claims.
Tip: Run our AEO Checker on your top pages and compare with competitors who rank well in AI results. The Keyword Gap section shows what questions they answer that you do not.
Tags: aeo, ai, content, visibility, fix, chatgpt, perplexity, google
How-To
AI search engines use structured data to understand content type, authorship and credibility.
FAQPage schema — highest priority
FAQPage schema is the most directly impactful for AEO. AI engines frequently extract answers from FAQPage mainEntity Question/Answer pairs. Add to any page with a genuine FAQ section.
Article schema with author Person
Add Article schema with a full author Person block including sameAs links to LinkedIn and Twitter. AI engines use author credentials to evaluate E-E-A-T. Named, credentialed authors are cited more often than anonymous content.
Organization schema for trust
Organization schema on your homepage establishes your brand entity. Include knowsAbout (topics of expertise) and sameAs links to authoritative external profiles.
Speakable schema
Mark sections suitable for AI extraction: "speakable": {"@type": "SpeakableSpecification", "cssSelector": [".article-summary", ".direct-answer"]}. Add to Article schema. Used for Google AI Overview extraction.
Tip: AI visibility and traditional SEO are increasingly aligned — both reward direct answers, strong E-E-A-T, well-structured schema and genuinely helpful content.
Tags: schema, aeo, ai, visibility, fix, structured-data, faqpage, article, eeat
How-To
How to Fix Primary Keyword Missing from Title Tag or H1
Including your primary keyword in the title and H1 is one of the strongest on-page SEO signals.
Add keyword to title tag
Place primary keyword near the start: <title>Primary Keyword — Supporting Context | Brand</title>. Front-loading ensures it appears before truncation at ~60 characters.
Add keyword to H1
Your H1 should include the primary keyword naturally. It does not need to be identical to the title — it can be more conversational.
Natural language only
Include keywords naturally within readable titles. Do not force it: "SEO Audit SEO Tool Free SEO" is stuffing. Write for humans first — if the keyword fits naturally, include it.
Tip: Run our Site Audit on key pages to check title and H1. Then run Keyword Analysis with your target keyword to see density and placement across all page elements.
Tags: keyword, title, h1, seo, fix, on-page
How-To
How to Fix Keyword Over-Optimisation and Stuffing
Keyword stuffing triggers Google spam signals. Reduce density and write naturally.
Reduce keyword density
Replace some exact-match repetitions with: synonyms, pronouns, semantic variations, related terms. Aim for 1-3% density for your primary keyword.
Use semantic keywords
Natural language includes semantically related terms. An article about running shoes naturally includes trainers, sole, cushioning, marathon, gait. These strengthen topical relevance without stuffing.
Rewrite affected sections
For stuffed passages, rewrite as if explaining to a knowledgeable friend. Read aloud — if it sounds unnatural or robotic it reads that way to Google too.
Tip: Our Keyword Analysis tool flags keywords at or approaching stuffing threshold. Use it to check density before publishing any new content.
Tags: keyword, stuffing, seo, fix, content, on-page
Tutorial
Keyword gap analysis identifies keywords your competitors rank for that you do not. Closing gaps is one of the highest-ROI content strategies available.
What keyword gap analysis is
Gap analysis compares keywords in competitor pages versus yours. Gaps are keywords competitors use that are absent or weak in your content — topics your audience searches for that you are not yet addressing.
Running a gap analysis
Use our Keyword Analysis tool — enter your page URL and a competitor URL. The tool highlights keywords competitors use that you are missing. Focus on keywords in their title, H1 and headings — these are topics they specifically target.
Prioritising gaps
Prioritise gaps where: the keyword is relevant to your page topic, it appears in competitor headings (indicating a main section), you can add it without stretching the page focus. Start with 3-5 gaps per page.
How to close gaps
For each priority gap: add a new H2 section on the existing page, expand an existing section, or create a new page if the topic is large enough. Add the keyword naturally to the heading and first sentence of the new section.
Monitoring results
After adding gap content, use Google Search Console to track impressions and clicks for new keywords. Ranking improvements typically take 4-12 weeks. Re-run gap analysis every few months.
Tip: The fastest keyword gap wins are topics requiring only 100-200 words to address adequately. These can move a page from position 15 to position 8 for a related query without creating new content from scratch.
Tags: keyword, gap, competitor, analysis, content, strategy, seo
Tutorial
Regex in Redirects: Using Regular Expressions in Apache and Nginx
Regular expressions (regex) let you write one redirect rule that handles hundreds of URL patterns at once. This tutorial explains how regex works in redirect rules, with plain-English examples for Apache .htaccess and Nginx config files.
What is a regular expression and why use one in redirects?
A regular expression (regex or regexp) is a pattern that matches text. In redirect rules, instead of writing one line per URL, you write one pattern that matches many URLs at once. Example: you rename your /blog/ directory to /articles/. Without regex you would need a separate redirect for every blog post — potentially hundreds of lines. With regex you write one rule: redirect anything starting with /blog/ to /articles/ keeping the rest of the URL intact. Regex saves time, reduces errors, and keeps your config file manageable.
The basic building blocks of regex
You only need a handful of characters to write useful redirect regex. The dot ( . ) matches any single character. The asterisk ( * ) means "zero or more of the thing before it". The plus ( + ) means "one or more". The caret ( ^ ) means "starts with". The dollar ( $ ) means "ends with". Brackets ( [] ) match any character inside them — [0-9] matches any digit. Parentheses ( () ) capture a group you can reuse. The backslash ( \ ) escapes a character that would otherwise mean something — \. matches a literal dot, not "any character". Start with these seven and you can write 90% of real-world redirect rules.
Apache .htaccess redirect regex
In Apache .htaccess files, RewriteRule is the directive for regex redirects. The pattern is matched against the URL path (without the domain). Example: RewriteRule ^blog/(.+)$ /articles/$1 [R=301,L] — this matches any URL starting with blog/ followed by one or more characters, captures those characters as $1, and redirects to /articles/ followed by the same characters. The [R=301] makes it a permanent redirect. The [L] means stop processing further rules after this one matches. Always test new RewriteRules on a staging server first — a mistake can take your entire site down.
Nginx redirect regex
In Nginx config files, location blocks with ~ or ~* use regex. The ~ is case-sensitive, ~* is case-insensitive. Example: location ~* ^/blog/(.+)$ { return 301 /articles/$1; } — this does the same job as the Apache example above. Nginx uses $1, $2 etc. to reference captured groups just like Apache. The return directive is simpler and faster than rewrite in Nginx — use return for straightforward redirects and rewrite only when you need to manipulate the URL internally without changing what the browser sees.
Common regex redirect patterns you can copy
Redirect old directory to new: ^/old-section/(.*)$ → /new-section/$1. Redirect with file extension removed: ^/page\.html$ → /page. Redirect any URL with query string ignored: ^/landing.*$ → /new-landing. Redirect numbered pages: ^/category/page/([0-9]+)$ → /category/?paged=$1. Redirect uppercase to lowercase (Nginx): location ~* [A-Z] { return 301 $scheme://$host${uri,,}; }. These are production-ready patterns — copy, adjust the path names, and test.
Testing and debugging regex redirects
Use our Redirect Checker to verify that your redirect rules are working as expected — paste any URL and it traces the full redirect chain including status codes. For building and testing regex patterns before adding them to your server config, a regex testing tool (search for "regex tester" online) lets you paste a pattern and test it against sample URLs instantly without touching your server. When a redirect rule is not firing, check: is the RewriteEngine On line present (Apache)? Is the rule above the generic /api/ or catch-all block (Nginx)? Is the rule order correct — Nginx and Apache process rules top to bottom and stop at the first match.
Tip: Always add a comment above each regex redirect rule explaining in plain English what it does and why. Six months later you will not remember what ^/p/([0-9]+)/v/([0-9]+)/$ means. Your comment saves hours of debugging.
Tags: redirects, regex, regular expressions, pattern matching, apache, nginx, url
How-To
How to Write a Regex Redirect Rule (Step by Step)
A beginner-friendly step-by-step guide to writing your first regular expression redirect rule in Apache or Nginx — from identifying the URL pattern to testing it live.
Step 1 — Write out the URLs you want to redirect
Before touching any config file, list three to five example URLs you want to redirect. For example: /services/web-design, /services/seo, /services/branding. Writing them out first makes the pattern obvious — in this case they all start with /services/ followed by a slug.
Step 2 — Identify the fixed part and the variable part
Every regex redirect has two parts: the bit that is always the same (fixed) and the bit that changes (variable). In /services/web-design and /services/seo, the fixed part is /services/ and the variable part is the slug after it (web-design, seo, branding). The fixed part is written literally in the regex. The variable part is captured with (.+) — meaning "one or more of any character".
Step 3 — Write the pattern
Combine the fixed and variable parts into a regex pattern. For the services example: ^/services/(.+)$. Breaking it down: ^ means the URL must start here (no characters before /services/). /services/ is written literally. (.+) captures one or more characters and saves them for use in the destination. $ means the URL must end here. This pattern matches /services/web-design, /services/seo, /services/any-slug-at-all.
Step 4 — Write the destination
Decide where each matching URL should go. If redirecting /services/ to /our-services/ keeping the slug: the destination is /our-services/$1 — where $1 is replaced by whatever (.+) captured. For Apache: RewriteRule ^/services/(.+)$ /our-services/$1 [R=301,L]. For Nginx: location ~ ^/services/(.+)$ { return 301 /our-services/$1; }.
Step 5 — Test before deploying
Paste your pattern into a regex tester with the example URLs from Step 1. Confirm each one matches. Then deploy to your server and test each URL using our Redirect Checker — paste the old URL and confirm you get a 301 pointing to the correct new URL. Test at least three examples: a typical case, one with a longer slug, and one that should NOT match the pattern (to confirm it is not over-matching).
Step 6 — Place the rule in the right position
In Apache .htaccess, place regex redirect rules before any catch-all rules. In Nginx, exact location matches (location = /path) take priority over regex matches — place your regex blocks after exact matches but before the generic location / block. Wrong position is the most common reason a redirect rule appears to do nothing.
Tip: If your redirect is not working, add a very simple test rule first — something that redirects /test-regex to /test-regex-worked. If that fires, your RewriteEngine is on and the issue is in your pattern. If it does not fire, your config file is not being read at all.
Tags: redirects, regex, regular expressions, pattern matching, apache, nginx, howto
Tutorial
Robots.txt Pattern Matching: What Google Actually Supports
Robots.txt uses a simplified pattern matching system — not full regular expressions. This tutorial explains exactly what wildcards and patterns are supported, what is ignored, and how to write rules that do what you intend.
Robots.txt is NOT full regex — this matters
Many site owners assume robots.txt supports full regular expressions like Apache or Nginx. It does not. Google's robots.txt parser supports only two special characters: the asterisk (*) as a wildcard meaning "zero or more of any character", and the dollar sign ($) meaning "end of URL". Everything else — dots, plus signs, brackets, parentheses — is treated as a literal character, not a regex operator. Writing ^/admin/ in robots.txt does not mean "starts with /admin/" — it means the literal string "^/admin/" which will never match anything. Understanding this prevents a very common and hard-to-spot misconfiguration.
The asterisk wildcard — how it works
The asterisk * in robots.txt means "match any sequence of characters here, including none". Disallow: /category/*/page/ blocks /category/shoes/page/, /category/shirts/page/, and any other URL following that pattern. Disallow: /*.pdf blocks any URL anywhere on your site ending in .pdf. Disallow: /search* blocks /search, /search/, /search?q=anything, /search-results — anything starting with /search. Disallow: /*?* blocks any URL containing a query string (the ? character). The asterisk is simple but powerful — most robots.txt requirements can be met with it alone.
The dollar sign anchor — end of URL matching
The dollar sign $ in robots.txt means "the URL must end here". Without it, Disallow: /private would match /private, /private/, /private-notes, /privately — anything containing /private. With a dollar sign: Disallow: /private$ matches only the exact URL /private with nothing after it. Combining asterisk and dollar: Disallow: /*.pdf$ matches any URL ending in .pdf. This is more precise than Disallow: /*.pdf which also matches /downloads/pdf-viewer (because pdf appears before the end). Use $ whenever you want to match a specific file extension or exact path ending.
Allow rules override Disallow rules
When an Allow and a Disallow rule both match a URL, the more specific rule wins. Disallow: /admin and Allow: /admin/public-report.html — the Allow rule wins for /admin/public-report.html because it is more specific (longer match). This lets you block a directory while keeping specific URLs within it accessible. If two rules are equally specific, the Allow wins. Use this pattern to block most of a directory while whitelisting specific pages: Disallow: /wp-admin/ then Allow: /wp-admin/admin-ajax.php (this is the standard WordPress robots.txt pattern that prevents blocking Ajax-dependent page builders).
What robots.txt blocks and what it does not
Robots.txt controls crawling only — it tells Googlebot whether to visit a URL. It does not control indexing. A URL blocked in robots.txt can still appear in Google's index if other sites link to it — Google knows the URL exists from the link, it just cannot read the content. To prevent indexing you must use a noindex meta tag or X-Robots-Tag header on the page itself. You cannot add noindex to a page that is blocked in robots.txt because Googlebot cannot read the tag. For pages you want both uncrawlable and unindexable: use noindex alone (without blocking in robots.txt), or accept that a disallowed URL may appear in results without a description.
Testing your robots.txt rules
Google Search Console includes a robots.txt tester — go to Settings > robots.txt > Test. Paste any URL and it shows whether Googlebot is allowed or blocked, and which specific rule triggered the result. Our Robots & Sitemap tool checks your robots.txt for common errors: blocking CSS and JavaScript (which prevents Google from rendering your pages properly), blocking your sitemap URL, using unsupported syntax like full regex patterns, and missing a sitemap declaration. Test every new rule before deploying — a single wrong Disallow: / line can block your entire site from Google.
Tip: Add a Sitemap: line at the bottom of your robots.txt pointing to your XML sitemap URL. This is not mandatory but it helps Googlebot find your sitemap even if you have not submitted it to Search Console. Format: Sitemap: https://yourdomain.com/sitemap.xml
Tags: robots, sitemap, robots.txt, pattern matching, regex, regular expressions, crawling, noindex
How-To
How to Block URL Patterns in Robots.txt Using Wildcards
Step-by-step guide to writing robots.txt Disallow rules that block entire URL patterns — query strings, file types, directories and parameter variants — using the asterisk wildcard and dollar sign anchor.
Block all URLs with a query string
If your site generates URL variants with query strings (tracking parameters, filters, sort options) that you do not want Googlebot wasting crawl budget on: Disallow: /*?* — the first * matches anything before the ?, then ? is literal, then the second * matches anything after it. This blocks /products?sort=price, /search?q=shoes, /page?ref=email — any URL containing a question mark.
Block all URLs of a specific file type
To block all PDFs: Disallow: /*.pdf$ — the * matches any characters before .pdf, the $ ensures the URL ends with .pdf (not just contains it). To block all XML files except your sitemap: Disallow: /*.xml$ then Allow: /sitemap.xml — the Allow overrides the Disallow for that specific file.
Block a directory and everything in it
To block /admin/ and every URL inside it: Disallow: /admin/ — the trailing slash is important, it ensures /admin-portal is not blocked, only URLs that start with /admin/. To also block the directory index itself: add Disallow: /admin as a second rule (without the trailing slash).
Block URLs containing a specific parameter
To block any URL containing a session ID parameter: Disallow: /*?*sessionid* — matches any URL with sessionid anywhere in the query string. To block print versions of pages (common pattern: /page?print=1): Disallow: /*?print=* — matches any URL with a print= parameter.
Block paginated URLs beyond page 1
Pagination creates many near-duplicate pages that waste crawl budget. To block /category/page/2 and beyond while keeping /category/page/1 accessible: Disallow: /*/page/* then Allow: /*/page/1 — the Allow rule is more specific and wins for page 1. Alternatively, keep pagination crawlable and handle duplicate content with rel="canonical" on paginated pages pointing to page 1.
Verify each rule works as intended
After writing your rules, go to Google Search Console > Settings > robots.txt and use the tester. Test the URLs you intend to block (they should show as Blocked) and the URLs you intend to allow (they should show as Allowed). Pay particular attention to edge cases — a rule that is too broad can block URLs you did not intend to block. Run our Robots & Sitemap tool to check for the most common misconfiguration errors.
Tip: Write one rule, test it, then write the next. Do not add five new Disallow rules at once — if something breaks you will not know which rule caused it. Deploy rules one at a time and verify each one before moving on.
Tags: robots, robots.txt, pattern matching, regex, regular expressions, crawling, wildcard
Tutorial
URL Pattern Matching in Site Crawlers: How Crawlers Use Regex to Filter Pages
When running a site crawl, URL pattern matching determines which pages are included and which are skipped. This tutorial explains how crawlers use regular expressions and wildcard patterns to control scope, and how to configure crawl rules for your site.
Why crawlers need URL pattern matching
A site crawler visits every URL it finds by following links. Without pattern matching rules, it would crawl session URLs, search result pages, printer-friendly versions, tracking parameter variants — thousands of near-duplicate URLs that waste crawl time and produce meaningless results. Pattern matching lets you tell the crawler: only visit URLs that match these patterns, and skip anything matching those patterns. This keeps crawls focused on real content pages and makes results actionable.
Include patterns — crawl only what matches
Include patterns define what the crawler should visit. A pattern like ^https://yourdomain\.com/blog/ tells the crawler to only visit URLs in the /blog/ section. This is useful for auditing a specific section of a large site without crawling everything. Multiple include patterns work as OR — a URL matching any one of them is included. If you define no include patterns, the crawler visits all URLs it finds within the starting domain.
Exclude patterns — skip what matches
Exclude patterns define what the crawler should skip. Common excludes: \?.*= to skip any URL with a query string parameter, /tag/ to skip tag archive pages, /author/ to skip author pages, \.(pdf|zip|docx)$ to skip file downloads, /page/[2-9] to skip pagination beyond page 1. Exclude patterns are matched against the full URL — including protocol, domain and path — so write them accordingly. A URL matching any exclude pattern is skipped even if it also matches an include pattern.
Regex syntax in crawler patterns
Most crawlers use standard regex syntax for patterns. The most useful operators: ^ anchors the match to the start of the URL, $ anchors to the end, . matches any character (escape as \. to match a literal dot), * means zero or more of the preceding character, + means one or more, ? makes the preceding character optional, [abc] matches a, b, or c, (pattern1|pattern2) matches either pattern. Example: \.(jpg|jpeg|png|gif|webp|svg)$ skips all image file URLs — the backslash escapes the dot, the pipe | separates alternatives, $ ensures the extension is at the end.
Handling dynamic URLs and parameters
E-commerce and CMS sites often generate URLs with sorting, filtering and session parameters: /products?sort=price&order=asc&page=2&session=abc123. These create thousands of crawlable variants of the same page. Exclude them with: \?.*sort= to skip sort variants, \?.*session= to skip session URLs, \?.*utm_ to skip tracking parameter URLs. Alternatively, a blanket \? exclude skips all parameterised URLs — only do this if your site uses clean URLs for all real content.
Testing crawler patterns before running a full crawl
Before launching a full site crawl, test your include and exclude patterns against a list of sample URLs from your site. Write 10 to 20 representative URLs — including ones that should be crawled and ones that should be skipped — and verify each pattern decision manually. Our Site Crawler lets you run a limited crawl of 50 pages first to check which URLs are being visited. If unexpected URLs appear, add an exclude pattern. If expected URLs are missing, review your include patterns. Spending five minutes testing patterns saves hours of waiting for a misdirected full crawl.
Tip: After a crawl, sort results by URL to spot unexpected patterns — clusters of similar URLs that should have been excluded. These tell you exactly what exclude pattern to add before the next crawl.
Tags: site crawler, crawling, regex, regular expressions, pattern matching, url, technical
How-To
How to Configure URL Include and Exclude Patterns for a Site Crawl
Step-by-step guide to setting up crawl scope rules so your site crawl covers the pages you care about and skips the noise — session URLs, filters, pagination, file downloads and tracking parameters.
Start by listing what you want to crawl
Before writing any patterns, write down the sections of your site you want to audit. Example: all pages under /products/ and /blog/, but not /account/, /cart/, /search/ or any URL with a query string. This list becomes your include and exclude patterns.
Write your include patterns
Convert your list to patterns. For /products/ and /blog/: include pattern 1: /products/, include pattern 2: /blog/. These simple string patterns match any URL containing that path segment. If you need to be more precise, add ^ to anchor to the domain start: ^https://yourdomain\.com/products/.
Write your exclude patterns
For each section or URL type you want to skip: /account/ excludes account pages. /cart/ excludes cart pages. /search excludes search result pages. \? excludes all URLs with query strings. \.(pdf|zip|docx|xlsx)$ excludes file downloads. /page/[2-9] excludes pagination beyond page 1. Enter each as a separate exclude pattern.
Handle e-commerce filter URLs
Filter and sort URLs (/products?colour=red&size=M&sort=price) are the biggest source of crawl noise on e-commerce sites. Exclude them with: \?.*colour= to skip colour filter variants. Or use a blanket \? exclude if all your real product pages use clean URLs. Check a sample of your product URLs first to confirm they are clean before blanket-excluding query strings.
Run a test crawl of 50 pages
Run the crawl limited to 50 pages. Review the list of crawled URLs. Are they all pages you expected? Are there any unexpected URLs that should have been excluded? Adjust your patterns and run another 50-page test. Only run the full crawl once you are confident the patterns are correct.
Save your pattern set for reuse
Once you have a working set of include and exclude patterns for your site, save them in a text file alongside your site documentation. The next time you run a crawl — or onboard a new tool — you can paste them straight in without redoing the analysis. Good pattern sets for a site rarely need changing unless the URL structure changes.
Tip: If your crawl returns fewer pages than you expected, your exclude patterns may be too aggressive. Remove them one at a time, running a 50-page test after each removal, until the missing pages reappear. That tells you exactly which pattern was over-excluding.
Tags: site crawler, crawling, regex, regular expressions, pattern matching, url, howto
Tutorial
Hreflang and URL Patterns: Managing Multilingual Sites Correctly
Hreflang tells Google which version of your content to show to users in different languages and regions. URL patterns are central to getting this right. This tutorial covers hreflang implementation, common URL structures, and how pattern matching is used to validate and audit multilingual sites.
What hreflang does and why URL patterns matter
Hreflang tags tell Google: "this page is the English UK version, this other URL is the French version, and this one is the English US version". Google uses this to show the right version to the right user. URL patterns are central to hreflang because every language/region version of a page must have a consistent, predictable URL structure. Without a consistent pattern, implementing hreflang across hundreds of pages becomes unmanageable and error-prone.
Common URL structures for multilingual sites
There are four standard patterns for multilingual URL structures. Subdirectory: yourdomain.com/en/, yourdomain.com/fr/, yourdomain.com/de/ — recommended by Google, easiest to implement. Subdomain: en.yourdomain.com, fr.yourdomain.com — works but requires separate GSC properties for each. Country domain: yourdomain.co.uk, yourdomain.fr — strongest geographic signal but expensive and complex. Query parameter: yourdomain.com?lang=en — least recommended, harder for Google to handle. Whichever structure you choose, the pattern must be completely consistent across every page.
Hreflang tag implementation
Each language/region version of a page must include hreflang tags for every other version, including itself. In your page head: <link rel="alternate" hreflang="en-GB" href="https://yourdomain.com/en/page/"> — <link rel="alternate" hreflang="fr" href="https://yourdomain.com/fr/page/"> — <link rel="alternate" hreflang="x-default" href="https://yourdomain.com/en/page/">. The x-default tag points to the version shown when no other version matches the user's language. Every page in the set must declare every other page in the set — if page A references page B, page B must reference page A.
Using URL patterns to audit hreflang at scale
On sites with hundreds of pages, auditing hreflang manually is impossible. Pattern-based auditing works by defining the expected URL structure for each language variant and checking that every page has hreflang tags matching that pattern. For a subdirectory structure, the pattern for the French version of any page is: ^https://yourdomain\.com/fr/(.+)$. An audit tool applies this pattern across all crawled URLs and flags any page where the French hreflang URL does not match the pattern — catching typos, missing pages, and structural inconsistencies.
Common hreflang mistakes caught by pattern checking
Mismatched URL patterns between language versions: the English page references /fr/about-us but the French page is actually at /fr/a-propos — the pattern does not match, the hreflang is broken. HTTP/HTTPS mismatch: one version uses http://, the other https:// — always use https:// in all hreflang tags. www vs non-www mismatch: yourdomain.com in one tag and www.yourdomain.com in another — pick one and use it consistently everywhere. Wrong language code: en-uk instead of en-GB (the correct ISO format uses a capital two-letter country code after the hyphen). Our Hreflang Checker detects all of these automatically.
Using hreflang in XML sitemaps as an alternative
Instead of adding hreflang tags to every page, you can declare all the relationships in your XML sitemap. This is easier to manage for large sites because it centralises all hreflang data in one file. The sitemap approach uses xhtml:link elements inside each url block. Each language version of a page gets one url block containing hreflang alternate links for all other versions. The URL pattern rules are identical — consistency and reciprocity are required. Submit the sitemap to Google Search Console and verify with the Hreflang Checker that all relationships are correctly formed.
Tip: Always implement hreflang in phases. Start with your most important pages (homepage, top 10 landing pages), verify they are working correctly in Search Console, then roll out to the rest of the site. Implementing hreflang incorrectly across thousands of pages at once causes large-scale indexing confusion that can take months to recover from.
Tags: hreflang, international, multilingual, pattern matching, regex, regular expressions, url
How-To
How to Check and Fix Hreflang URL Pattern Errors
Step-by-step guide to auditing your hreflang implementation for URL pattern mismatches, missing reciprocal tags, wrong language codes and other common errors that break multilingual SEO.
Step 1 — Run the Hreflang Checker
Enter your page URL in our Hreflang Checker. It reads all hreflang tags on the page, fetches each referenced URL, and checks that every referenced page has a reciprocal tag pointing back. This reveals the most common hreflang error: page A references page B in its hreflang, but page B does not reference page A.
Step 2 — Check URL pattern consistency
List all the hreflang URLs reported by the checker. They should follow a completely consistent pattern. For a subdirectory structure: https://yourdomain.com/en/page-name, https://yourdomain.com/fr/page-name, https://yourdomain.com/de/page-name. If any URL breaks the pattern — different subdirectory name, different slug, http vs https, www vs non-www — that is a hreflang error. The correct URL exists but the hreflang tag points to the wrong one.
Step 3 — Verify language and region codes
Hreflang uses IETF language tags. Language only: en, fr, de, es, zh. Language + region: en-GB, en-US, en-AU, fr-FR, fr-CA, zh-Hans, zh-Hant. The language code is lowercase, the country code is uppercase. Common mistakes: en-uk (should be en-GB), en-gb (country code must be uppercase), zh-CN (should be zh-Hans for simplified Chinese). Our Hreflang Checker validates codes against the IETF standard and flags any non-standard values.
Step 4 — Check for the x-default tag
Every set of hreflang pages should include one x-default tag pointing to the fallback version shown to users whose language does not match any specific version. If you have en-GB, fr-FR and de-DE versions, add hreflang="x-default" pointing to the en-GB version (or whichever is your primary market). Without x-default, Google may show unexpected versions to users in non-targeted markets.
Step 5 — Fix errors in order of severity
Fix in this order: 1. Missing reciprocal tags — the other page must reference back. 2. URL pattern mismatches — the hreflang points to a URL that does not exist or is wrong. 3. Wrong language codes — replace non-standard codes with correct IETF tags. 4. Missing x-default — add to all pages in the set. 5. HTTP/HTTPS and www/non-www inconsistency — pick one canonical form and update all tags.
Step 6 — Verify in Google Search Console
After fixing hreflang errors, go to Google Search Console > International Targeting > Language. GSC reports hreflang errors it has detected across your site. After your fixes are deployed and Googlebot has recrawled, the error count should drop. Give it two to four weeks — hreflang changes take time to propagate through Google's index. If errors persist after six weeks, re-run the Hreflang Checker and look for any remaining pattern inconsistencies.
Tip: If you manage hreflang across hundreds of pages, maintain a spreadsheet mapping every page slug to its equivalent in each language. This becomes your pattern reference — any URL that does not fit the pattern is wrong. Update the spreadsheet every time you add new pages.
Tags: hreflang, international, multilingual, pattern matching, regex, regular expressions, url, howto
Tutorial
A plain-English walk-through of the 6-step wizard that generates your privacy policy, cookie policy, consent banner, data rights page, and DSAR widget.
What the kit does
The GDPR Kit Builder generates five legal documents for your website: a privacy policy, a cookie policy, a consent banner snippet, a data rights page, and a DSAR (data subject access request) widget. Before it can generate anything, it needs two things — a quick scan of your site and your answers to six short questions about your business.
Why a scan first
The scan costs £1.99 and looks at what's actually running on your website: which cookies are set, which trackers are loaded, which third-party domains are involved, how many forms you have, and whether basic security headers are present. This data pre-fills the wizard so you don't have to type everything in from scratch. The scan is saved against your account so you can return to it any time.
Step 1 — Your business
Enter your legal entity name (the one at Companies House, or your full personal name if you're a sole trader), your registered address, a contact email for privacy queries, and your website. Pick your sector from the dropdown. The DPO checkbox is almost always left unticked — most small businesses don't need a formally appointed Data Protection Officer.
Step 2 — Data categories
A data category is a group of information you collect for one specific reason. Contact form submissions, newsletter signups, and analytics cookies are typically three separate categories. We pre-fill what we spotted on your site — edit the field lists to match reality, remove anything that doesn't apply, add what we missed. Be specific about retention periods ("24 months from last contact" not "as long as necessary").
Step 3 — Lawful basis
For each category, pick one of the six lawful bases under UK GDPR Article 6 and explain why it applies. The safe defaults: legitimate interests for contact forms, consent for marketing and non-essential cookies, contract for customer orders, legal obligation for things like invoicing. A one-sentence justification is enough.
Step 4 — Third parties
Name every third party that processes your visitors' data: Stripe, Mailchimp, Google Analytics, Meta, your accountant if they see customer records. Generic labels like "our payment provider" fail ICO guidance — name them specifically. For each one, note the country and the safeguard (UK IDTA, vendor DPA, UK adequacy regs).
Step 5 — International transfers
If any of the third parties above are outside the UK, answer yes. Most small sites answer yes because Google, Meta, Mailchimp, Stripe and Cloudflare all involve US processing. If yes, go back to Step 4 and make sure every recipient has country + safeguard filled in.
Step 6 — Retention and automation
Review the retention periods per category (you set these in Step 2). Tick the automated decision-making box only if you use software to make significant decisions about people without a human in the loop (rare for small businesses). Describe what the automation does if you tick it.
Generating the documents
On the final screen, pick which documents to generate. Each costs £9.99. You always need at least a privacy policy if you collect any personal data. You need a cookie policy and consent banner if you use any non-essential cookies (analytics, marketing). The data rights page and DSAR widget are optional but build trust. After generation, each document opens in a preview; you can copy the HTML, download it as a ZIP, or open it in a new tab. All outputs are saved to your account so you can re-download any time.
Tags: gdpr, kit, builder, walkthrough, wizard, privacy-policy, cookie-policy, consent-banner, dsar, data-rights
Tutorial
Do I Need a Data Protection Officer (DPO)?
Most small businesses do not need a formally appointed DPO. This guide explains when you do, when you do not, and what to put on your privacy policy either way.
What a DPO actually is
A Data Protection Officer is a named individual inside (or hired by) your organisation whose job is to monitor GDPR compliance, advise on data protection, act as a point of contact for the ICO, and represent data subjects internally. A DPO must be independent — they cannot also be the person who decides how data is used, so the CEO cannot also be the DPO. They must have expert knowledge of data protection law.
When appointing a DPO is mandatory
UK GDPR Article 37 says you must appoint a DPO in only three situations. One: you are a public authority or public body (councils, NHS trusts, schools). Two: your core activities involve regular and systematic monitoring of individuals on a large scale — CCTV networks, ad-tech profiling, location tracking. Three: your core activities involve large-scale processing of special category data (health, biometrics, political views, religion, sexual orientation) or criminal convictions data.
What "large scale" means in practice
There is no exact number but the ICO's guidance points to things like: a hospital processing patient data, an insurance company scoring risk from medical records, a social network profiling users for ads, a transport company tracking passengers across a city. A local dentist with 2,000 patient files is probably not large scale. A chain of 40 dental clinics sharing one database probably is.
Why most small businesses do NOT need a DPO
A typical small business website — a shop selling products, a SaaS tool, a professional services site, a blog with a newsletter — almost never hits the thresholds. You process personal data, yes, but not at "large scale" and not of "special categories" as their core activity. You can run your privacy operation with your existing staff and this tool's documents. Appointing a formal DPO when you are not required to would create obligations (independence, reporting, training) that you do not need.
What to list on your privacy policy when you have no DPO
If you have no DPO, list a named contact or team email address for privacy queries. Example: "Data protection contact: privacy@yourdomain.com" or "Data protection queries should be addressed to the Director, Bob Smith, at contact@yourdomain.com." The ICO has said this is acceptable when a DPO is not required — do not invent a fictional DPO.
Outsourced / fractional DPOs
If you are on the threshold and do need a DPO, you can outsource. Several UK firms offer fractional DPO services for a few hundred pounds a month, which is cheaper than hiring in-house. The DPO's contact details (name or company, email, phone) must be published in your privacy notice and registered with the ICO.
What to do next
If you are a small business and your GDPR Kit Builder wizard asked whether you have a DPO, leave the box unticked and provide your contact email instead. If you run a large-scale monitoring or special-category operation, tick the box and enter the DPO's name and email — which must be a real person who agreed to the role.
Tags: gdpr, dpo, data-protection-officer, privacy, governance
Tutorial
Categorising the Personal Data You Collect
Before you can write a privacy policy you need to list every type of personal data you collect, what you use it for, and how long you keep it. This guide shows how small businesses typically break this down.
What a data category is
A data category is a group of personal information you handle for one specific purpose. Two sets of data go in different categories if you use them for different reasons, even if they overlap. Example: the email address on your newsletter signup form and the email address on your contact form are two different categories — one is used for marketing (consent), the other for customer support (legitimate interests). Keep them separate on the policy.
The 5 categories most small websites have
Category one: contact form submissions (name, email, message — for replying to enquiries). Category two: newsletter subscribers (email only — for marketing). Category three: website cookies and analytics (cookie IDs, IP address, session data — for understanding site usage). Category four: customer accounts if you have login (name, email, hashed password — for delivering your service). Category five: order and payment records if you sell (name, address, last 4 digits of card, order history — for fulfilling orders and tax records).
Being specific about the fields
List the actual fields collected, not general labels. "Contact form" by itself is not enough. "Name, email address, phone number, and free-text message" is. This matters because ICO guidance says individuals must be told what is collected — and you must only collect what is necessary. Writing out the fields forces you to check you are not collecting something you do not actually need.
Retention — be precise
The law requires you to delete personal data when it is no longer necessary. "As long as necessary" is not a retention period; it is a promise to think about it later. Pick a number. Examples that pass ICO scrutiny: contact form messages retained for 24 months from last contact, then deleted; newsletter subscribers retained until they unsubscribe; server logs kept 90 days; invoices kept 6 years (HMRC requirement); customer account data kept for the life of the account plus 30 days.
What counts as personal data
Obvious: name, email, phone, address, photo, date of birth. Less obvious but still personal data: IP address, cookie ID, device fingerprint, GPS coordinates, a username, a customer reference number, a ticket ID. If it can be tied back to a specific individual — alone or combined with other info you hold — it is personal data and goes in a category.
Special category data
Some data gets extra protection under Article 9: health data, biometrics used for identification, race or ethnic origin, political views, religion, trade union membership, sexual orientation, genetic data. You need a stronger lawful basis to process these. Most small websites do not collect special category data — but a gym tracking member health metrics, or a clinic holding patient records, does. If you are unsure, assume you do not and consult the ICO's guidance if in doubt.
Mapping it out before writing anything
Before you click Generate on the privacy policy, write a simple list: what data, for what purpose, on what lawful basis, for how long. One line per category. The wizard's Step 2 and Step 3 walk you through this; the output ends up in a table in your privacy policy. If you cannot fill in the line, you probably should not be collecting the data.
Tags: gdpr, data-categories, data-mapping, privacy, retention, lawful-basis
Tutorial
Choosing a Lawful Basis for Processing Personal Data
You must have a legal reason to use someone's personal data. The six lawful bases explained in plain English, with the defaults that apply to most small business cases.
Why you need a lawful basis
UK GDPR Article 6 says every single act of processing personal data must have one of six legal reasons behind it. If none of the six apply, you cannot process the data — full stop. You pick the basis before you start collecting, not afterwards, and you must document which one applies to which category.
Basis 1 — Consent
The user actively opts in. Pre-ticked boxes do not count. Silence does not count. Consent must be specific (one topic at a time), informed (they know what they are agreeing to), freely given (no bundling with service access), and withdrawable (they can change their mind any time). Use consent for marketing emails, for non-essential cookies (PECR also requires it), and for profiling for advertising.
Basis 2 — Contract
You need the data to deliver something the user asked for. A customer places an order — you need their address to ship it. A client books a consultation — you need their email to send the calendar invite. Use contract only for data you genuinely cannot fulfil the request without.
Basis 3 — Legal obligation
A law forces you to keep or collect the data. HMRC wants you to keep invoice records for 6 years. Anti-money-laundering rules require identity checks. Use this basis for the specific regulatory requirement — not as a catch-all.
Basis 4 — Vital interests
To save someone's life. This is for emergency services, hospitals, and rare edge cases. A typical small website never uses this basis.
Basis 5 — Public task
You are a public authority carrying out an official function, or exercising a statutory power. Councils, schools, government departments. If you are a normal business, skip this one.
Basis 6 — Legitimate interests
You have a sensible business reason to process the data, and the user's rights do not outweigh that reason. The flexible option — but you have to justify it. Use it for: replying to contact form enquiries, preventing fraud, keeping internal records, basic security logging. You cannot use it for direct marketing if the user is a consumer (consent applies there).
The safe defaults for common cases
Contact form submissions → legitimate interests (reason: to respond to enquiries). Newsletter signups → consent (marketing always needs opt-in). Non-essential cookies → consent (PECR requires it). Customer order fulfilment → contract. Keeping invoices → legal obligation. Fraud prevention → legitimate interests. Internal staff payroll → contract + legal obligation.
Documenting your choice
ICO guidance says you must be able to show WHY a basis applies. In the wizard this is the "justify" field. One sentence is enough — "To respond to enquiries from prospective customers" is a valid justification for legitimate interests. Keep this record; you may need it if the ICO asks.
Tags: gdpr, lawful-basis, article-6, consent, contract, legitimate-interests, legal-basis
Tutorial
Listing Third-Party Recipients Correctly
Under Article 13 you must name every organisation that handles your visitors' data. Generic labels like 'our payment provider' fail. This guide shows what to list and how.
Who counts as a third party
Anyone outside your own business who handles your visitors' personal data. This includes payment processors, email marketing platforms, analytics providers, ad networks, CRMs, help-desk tools, your hosting provider, your accountant or bookkeeper if they see customer records, and any freelancer or contractor with access to your systems.
Why generic labels fail
The ICO's 2023 enforcement action against TikTok cited vague third-party disclosures as a breach. "We share data with our advertising partners" or "We use industry-standard analytics tools" is not enough. Article 13 requires you to name recipients (or at least the categories of recipient) specifically. The safer route is always to name them.
Typical recipients for a small website
Stripe or PayPal for payments. Mailchimp, Brevo, ConvertKit, or MailerLite for newsletter delivery. Google (Analytics, Ads, Search Console). Meta Platforms (Facebook/Instagram Pixel). LinkedIn Insight Tag. Intercom or Crisp for chat. Zendesk or Freshdesk for support. Calendly for bookings. Your hosting provider. Google Workspace or Microsoft 365 for email. Each one that processes personal data goes on the list.
What info to list per recipient
For each third party, your privacy policy should state: the recipient's name, the purpose (why they receive the data), the country they process in, and the safeguard if they are outside the UK. The wizard asks for all four. Fill every field — empty countries or safeguards look like you did not check.
Safeguards for international recipients
If the recipient processes data outside the UK, you need one of: UK adequacy regulations (covers EEA, Switzerland, Japan, Korea, and a few others — no extra contract needed); the UK International Data Transfer Agreement (UK IDTA — used for the US and most other countries now); the UK Addendum to EU Standard Contractual Clauses (older contracts you may still have); or Binding Corporate Rules for intra-group transfers.
Data Processing Agreements (DPAs)
Every major vendor offers a DPA you must read and sign (usually just by ticking a box in their admin). Stripe, Google, Meta, Mailchimp, and Cloudflare all have them. The DPA is the safeguard you list — but you should not list "we have a DPA" in isolation; name the contract mechanism behind it (UK IDTA, SCCs, etc).
Processors you do NOT need to name individually
Subprocessors of a named processor (e.g. Stripe's own cloud provider) are usually covered by your named processor's DPA and do not need separate disclosure. Sysadmins, cleaners, and anyone else without planned access to personal data are not recipients. If in doubt, name them — over-disclosure is not a breach.
Tags: gdpr, third-party, recipients, processors, article-13, privacy-policy
Tutorial
International Data Transfers Explained
If any of your vendors process data outside the UK, that is an international transfer and needs legal cover. This guide covers what counts, what is allowed, and what to write in the privacy policy.
What counts as an international transfer
A transfer happens whenever personal data leaves the UK. This includes: data stored on a server outside the UK, data accessed from outside the UK (a US support agent viewing a UK customer's ticket is an access = transfer), data processed by an overseas vendor. It does not matter if the vendor has a "UK region" — if the parent company can access the data, that is usually a transfer.
Why most small websites DO transfer internationally
Google is US-based. Meta is US-based. Mailchimp is US-based. Stripe processes through the US and Ireland. Cloudflare is global. If you use any of these, you are transferring data internationally. Answering "no" to this question in the wizard is correct only if you genuinely use zero overseas services — rare for a modern web business.
UK adequacy regulations
The UK Government has decided that certain countries have strong-enough data protection laws that transfers to them need no extra safeguard. As of writing, adequate countries include: all EEA member states, Andorra, Argentina, Canada (commercial organisations), Faroe Islands, Guernsey, Isle of Man, Israel, Japan, Jersey, New Zealand, Republic of Korea, Switzerland, Uruguay, and the US (via the UK-US Data Bridge, for organisations certified under the Data Privacy Framework).
Transfers outside adequate countries
For anywhere not on the adequacy list, you need an "appropriate safeguard". The main options: the UK International Data Transfer Agreement (UK IDTA) — a standard contract template; the UK Addendum to the EU Standard Contractual Clauses — for older contracts; Binding Corporate Rules — for intra-group transfers in multinationals; or approved certification schemes and codes of conduct.
Using a vendor's DPA as the safeguard
When you sign up to Google, Stripe, or Mailchimp, their Data Processing Agreement typically embeds UK IDTA or SCCs — meaning the safeguard is already in place. List it on your privacy policy as "[Vendor Name] Data Processing Agreement incorporating UK IDTA" or similar. Read the actual DPA to confirm which mechanism it uses.
What to write in the privacy policy
Your privacy policy must state: whether you transfer data outside the UK; to which countries; the safeguard used for each. A simple one-line example: "Our email marketing provider Mailchimp processes data in the United States. The transfer is covered by Mailchimp's Data Processing Agreement incorporating the UK IDTA." The GDPR Kit Builder wizard lists each third party individually with country and safeguard — that output is compliant.
Transfer risk assessments
For non-adequate countries, ICO guidance now expects you to do a Transfer Risk Assessment (TRA) checking whether the destination country's laws respect your safeguard in practice. The ICO has published a template. For small businesses using mainstream vendors, the vendor usually provides a TRA summary. Keep a copy in your records.
Tags: gdpr, international-transfers, idta, adequacy, transfers, cross-border
Tutorial
Setting Sensible Data Retention Periods
You cannot keep personal data forever just in case. This guide covers what the law requires, what ICO guidance says, and sensible defaults for common data types.
What the law says
UK GDPR Article 5(1)(e) — the storage limitation principle — says personal data must be kept "for no longer than is necessary for the purposes for which the personal data are processed." Once the purpose is fulfilled, delete or anonymise the data. You cannot keep it forever "just in case".
Why vague is worse than wrong
Writing "for as long as necessary" on your privacy policy is not a retention policy — it is an absence of one. ICO guidance treats vague statements as evidence of non-compliance. You must decide on specific periods, document them, and publish them. The user must be able to read your policy and know how long their data will be kept.
Sensible retention defaults
Contact form submissions — 24 months from last contact, then delete. Newsletter subscribers — until they unsubscribe. Customer account data — for the life of the account plus 30 days. Order records — 6 years (HMRC tax requirement). Invoices and payment records — 6 years. Website analytics (GA4) — 14 months by default, configurable. Server logs — 30 to 90 days (security purpose fulfilled). Support tickets — 3 years. Job applications from unsuccessful candidates — 6 months (some sectors, longer with consent).
Legal retention requirements you must respect
Some laws mandate minimum retention. HMRC requires tax records for 6 years. The Money Laundering Regulations require 5 years after the business relationship ends. Employment records have varying requirements (pension contributions indefinitely, payroll 6 years, applications short). Where a legal obligation exists, the lawful basis for retention is "legal obligation" and the retention period is whatever the regulator requires.
Different retention by category
Different categories of data can have different retention periods. In the wizard, each data category has its own retention field. A customer's name and email (for order fulfilment) might be kept for 7 years; the same customer's newsletter subscription might end the moment they unsubscribe. This is fine — you just need to implement both rules.
How to actually delete data
A retention policy without deletion is just a lie on a webpage. Practical deletion options: a quarterly cron job that deletes records older than the retention period; a cleanup script run whenever a user requests deletion; a "last activity" date stamp on every record; anonymisation instead of deletion for statistical records (strip identifiers so the data is no longer personal). Document the deletion process in a record so you can prove it happens.
What to publish
Your privacy policy must show retention periods per data category. The GDPR Kit Builder produces a table listing each category's retention in plain language. Aim for the kind of sentences a non-lawyer can parse: "We keep contact form submissions for 24 months after your last message, then delete them." Not "Data is retained for the period permitted by applicable law and internal policy."
Tags: gdpr, retention, storage-limitation, deletion, data-minimisation, article-5
Tutorial
How to Write a Plain-English Privacy Policy
Your privacy policy is the biggest piece of user-facing compliance. This guide shows how to write one a normal human will read, while still meeting Article 13.
Why most privacy policies fail
A privacy policy nobody reads is a policy nobody agreed to. Policies written by lawyers in US English, running to 5,000 words, with no structure, fail the "clear and plain language" requirement of Article 12. ICO guidance explicitly favours short, layered, plain-English policies over exhaustive legalese. If your users cannot understand it, you have not really informed them.
The layered approach
Instead of one giant wall of text, use layers: a short summary at the top (3-5 bullet points — what data, what for, who with, how long, your rights), then the full detail underneath in sections. This satisfies the ICO's "just-in-time notice" guidance and means most users get the gist in 30 seconds.
Mandatory Article 13 content
Every privacy policy must include: your identity and contact details (Companies House name + registered address + contact email); your DPO contact if you have one; the purposes of processing and the lawful basis for each; the categories of data collected; the recipients (named) of the data; whether data goes outside the UK and what safeguards apply; retention periods per category; the user's rights; how to complain to the ICO; whether giving the data is a legal/contractual requirement. Miss any of these and you are non-compliant.
Voice and tone rules
Use "we" and "you", not "the data controller" and "the data subject". Prefer short sentences (one idea per sentence). Define any technical term the first time you use it. Avoid "may" when you mean "do" — if you share data with Mailchimp, write "we share" not "we may share". Vague language is treated by ICO as evidence of non-compliance.
Structure that actually works
A proven structure: 1) Who we are and how to reach us; 2) The data we collect and what we do with it (as a table by category); 3) Who we share it with (named recipients, country, safeguard); 4) International transfers (if any); 5) How long we keep it (per category); 6) Your rights and how to exercise them; 7) Complaints to the ICO; 8) Automated decision-making (if any); 9) Changes to this policy. The GDPR Kit Builder produces exactly this structure.
Last-updated date and version history
Always display a "last updated" date at the top. Keep an internal version log — when you changed what, and why. This matters for accountability (Article 5(2)) and for showing a court or regulator what a specific user actually saw at a point in time. For material changes (new purpose, new recipient, expanded retention), notify users by email.
Linking from the site
Your privacy policy must be accessible within two clicks of every page. Add "Privacy Policy" to the footer on every page template — the words "Privacy Policy" specifically, not cryptic alternatives. Add a second, contextual link directly next to every form that collects personal data.
Testing your policy
Give it to someone non-technical and ask: "Using this, can you tell me what happens to your email if you sign up?" If they cannot answer in 30 seconds, rewrite. Run it through the free GDPR audit tool (`#gdpr` on this site) — it will flag missing elements. Re-read it yourself after each vendor change, annually at minimum.
Tags: gdpr, privacy-policy, article-13, writing, transparency
Tutorial
How to Install a Cookie Banner Properly
A cookie banner that just says "OK" is illegal. This guide covers PECR requirements, Google Consent Mode v2, and how to wire the banner so trackers only fire after consent.
Why PECR, not just GDPR
The rule that cookies need consent comes from the Privacy and Electronic Communications Regulations (PECR) — not GDPR directly. PECR predates GDPR and survives it. PECR says you need consent before setting any cookie that is not strictly necessary to deliver a service the user explicitly requested. That means analytics, advertising, social-sharing pixels, and heatmaps all need consent BEFORE they set anything.
What "strictly necessary" really means
Cookies you can set without consent: session cookies for logged-in users, shopping-basket cookies, load-balancer cookies, security tokens, and the cookie that remembers a user's cookie choice. Cookies you CANNOT set without consent: Google Analytics (even anonymised), Meta Pixel, Hotjar, Clarity, any advertising retargeting cookie, any social-media cookie, fonts served from a third party that logs IP addresses.
The "reject" button must be as easy as "accept"
ICO guidance (and a 2023 CNIL fine against Google) is explicit: the banner cannot give users easy "Accept" but hide "Reject" behind sub-menus. Both options must be equally prominent on the first screen. A banner with only "Accept all" and "Cookie settings" is non-compliant. You need a visible "Reject" button.
Pre-ticked boxes are illegal
If your banner has toggle switches for analytics/marketing and any are pre-selected ON, you have violated PECR and GDPR consent requirements. All non-essential toggles must default to OFF. The user must actively turn them on. The Planet49 ECJ ruling settled this in 2019 — pre-ticking is not consent.
Wiring your trackers correctly
The biggest mistake: tracker scripts in the page HTML so they fire on page load regardless of what the user clicks. Fix: move tracker code inside an event handler that only runs on consent. For Google Analytics 4, use Google Consent Mode v2: GA4 will still load but will not set cookies or send personal data until consent is granted. Tag Manager has a Consent Mode template. Meta, LinkedIn, and Hotjar all have equivalent guards.
Google Consent Mode v2 specifics
Since March 2024 Google requires Consent Mode v2 for EEA/UK traffic on ads-related products (otherwise remarketing and measurement silently degrade). Implementation: call `gtag('consent', 'default', {...})` BEFORE your GA4 tag loads, set all non-essential consent types to 'denied', then call `gtag('consent', 'update', {...})` with 'granted' values when the user accepts. Pass it through consent types: analytics_storage, ad_storage, ad_user_data, ad_personalization.
Storing and respecting the choice
Store the user's choice (in a first-party cookie or localStorage) and respect it on every future page load. Do not re-prompt on every page. Do re-prompt if your categories or vendors change. Give users a permanent way to change their mind — a "Cookie settings" link in the footer that re-opens the banner is the standard pattern.
Third-party fonts and embedded content
Google Fonts served from fonts.googleapis.com logs IPs — a German court ruled in 2022 that this is unlawful without consent. Either self-host fonts (copy them to your server, reference locally), use a privacy-respecting CDN, or gate the font behind consent. Same issue for YouTube embeds (use the privacy-enhanced "youtube-nocookie.com" embed) and for social share buttons (use static links rather than iframes).
Test before you launch
Open your site in a fresh browser (private mode). BEFORE clicking anything on the banner, open DevTools > Application > Cookies and Network. Confirm: zero analytics/marketing cookies set; zero requests to tracker domains. Click Reject. Confirm the same. Click Accept. Confirm trackers now load. Automate this in a monthly monitor — use Cookiebot's free audit or the scan built into this tool.
Tags: pecr, cookie-banner, consent, consent-mode, gdpr, tracking
Tutorial
How to Respond to a DSAR (Subject Access Request)
Step-by-step for handling a user's request to see their data. The 1-month clock, identity verification, fees, exemptions, and what to actually send.
What a DSAR is
A DSAR (or SAR) is a request under Article 15 for a copy of the personal data you hold about the requester. It does not need to be in a specific format — they can email, call, post, tweet, or fill in a form. Any communication that clearly asks "what data do you have on me" is a DSAR and the clock starts. You cannot ignore it or tell them to fill out your form if they did not.
The 1-month clock
You have one calendar month from the day you received the request to respond. The clock does NOT pause while you verify identity (it pauses once — after you ask for verification, from when you asked until when they reply, capped at one extension). For complex or repeated requests you may extend by two further months — but you must tell the requester within the first month, with reasons.
Verifying identity
You may ask for reasonable proof of identity before disclosing data — but only as much as is necessary, and only if there is a genuine doubt. If the request comes from the email address you already have on file for them, asking for ID is usually disproportionate. If it comes from a new address or a third party claiming to represent them, verification is reasonable. Do not demand ID for every request as a delay tactic — the ICO has fined organisations for this.
Fees
The first DSAR is free. You may charge a reasonable fee only when the request is manifestly unfounded or excessive — for example, the same person sending identical requests weekly. You may also charge for additional copies after the first. The fee must reflect administrative cost only. You cannot charge a refundable deposit or a search fee as a matter of course.
What to search
Search every system that holds personal data about the requester: your CRM, your email archives (inbox AND sent folder AND anywhere CC'd), customer database, support ticket system, chat logs, accounting records, marketing platform, analytics if user-identifiable, backup tapes if reasonably accessible. You do not have to search systems it is not reasonable to search — but you have to be able to justify any exclusion.
What to include in the response
A copy of the personal data itself (in a commonly used format — PDF, CSV, or a secure download). The purposes of processing. The categories of data. The recipients (named). The retention period or criteria. Information about their other rights (rectification, erasure, restriction, objection). The right to complain to the ICO. Whether the data came from somewhere other than them. Whether there is any automated decision-making with significant effects.
Redaction and third-party data
If the data includes information about another person (e.g. an email exchange where a colleague's name appears), you can either: get the third party's consent to disclose; disclose the data with the third party's name redacted; or refuse to disclose that specific item if disclosure would reveal the third party's data unreasonably. Redact, do not refuse the whole request. Keep a record of what you redacted and why.
Exemptions
Some data can be withheld — notably: legal professional privilege; data subject to ongoing legal proceedings; data prejudicing prevention/detection of crime; data identifying an informant. These are narrow and you must document which exemption applies to which item. "It is inconvenient" is not an exemption.
Logging the response
Keep a record of every DSAR: who sent it, when received, when acknowledged, what was searched, what was disclosed, what was redacted, what exemptions applied, when the response was sent. This is part of your accountability obligation under Article 5(2). If the requester complains to the ICO, you will need this record.
Tags: gdpr, dsar, sar, subject-access-request, rights, article-15
Tutorial
How to Report a Personal Data Breach
A breach happens — what now? The 72-hour clock, when to tell the ICO, when to tell users, how to write a breach report that does not incriminate you.
What counts as a breach
A personal data breach is "a breach of security leading to the accidental or unlawful destruction, loss, alteration, unauthorised disclosure of, or access to, personal data". Examples: an email sent to the wrong recipient, a laptop stolen with customer records, a database left publicly accessible, a phishing attack that exposed logins, a staff member who leaked data, a ransomware attack on customer-containing systems. It does not need to be a hack — a sent-to-wrong-person email is a breach.
The 72-hour clock
You have 72 hours from the moment you BECAME AWARE of the breach (not the moment it happened) to notify the ICO — unless the breach is unlikely to result in a risk to individuals' rights and freedoms. The clock runs across weekends. If you cannot complete the full report in 72 hours, submit what you know and update later (phased reporting is explicitly allowed). Silence for 72+ hours is itself a breach.
Assessing the risk
If the breach is unlikely to result in any risk to individuals, you do not need to notify the ICO — but you must document your reasoning. A low-risk breach: a single email sent to the wrong recipient containing only a name and meeting time, then immediately retrieved. A high-risk breach: any financial data, health data, login credentials, or data that could lead to identity theft, discrimination, or physical harm — these always need ICO notification, often need user notification.
What to tell the ICO
Use the ICO's online form ("report a breach"). Required content: nature of the breach and categories of data affected; approximate number of people affected; name and contact of DPO or equivalent; likely consequences; measures taken or planned to address it and to mitigate effects. Be factual, specific, and do not speculate. Saying "we are investigating" is fine when true.
When to tell the users
If the breach is likely to result in a HIGH risk to the rights and freedoms of individuals, you must notify them "without undue delay". High risk examples: leaked passwords, full card numbers, national insurance numbers, health data, leaked sensitive personal info. The notification to users must be plain-English and describe: what happened, likely consequences for them, what you are doing, what they can do to mitigate, how to contact you.
Exemptions from user notification
You do not need to notify users individually if: the data was encrypted and the key was not compromised (so the breach is not really a data exposure); you have taken subsequent measures so the risk is no longer likely to materialise; individual notification would be disproportionate (then a public communication is required instead). Document the exemption reasoning in your breach log.
Incident response pre-work
Do not wait for a breach to figure out who does what. Have ready: a short playbook listing who investigates, who drafts the ICO report, who drafts user comms, who the DPO contact is. A breach register template. Pre-drafted user notification email ready to be filled in. The ICO's breach reporting URL saved in the bookmark bar. A rough map of what data is where (see ROPA guide). Practice once per year.
After the breach: lessons learned
Every breach should trigger a root-cause review. Did a process fail (training, access control, configuration, vendor)? Update the process. Did a technical control fail (encryption, logging, MFA)? Fix it. Did you find the breach quickly enough (detection)? Improve monitoring. Document the remediation in the breach register — the ICO will ask. A breach that repeats because you did not address the root cause is the kind of thing that turns a warning into a fine.
Tags: gdpr, breach, ico, article-33, article-34, incident-response
Tutorial
How to Write a Legitimate Interests Assessment (LIA)
Legitimate interests is the most flexible lawful basis but you must justify each use with a documented assessment. This guide shows the 3-part test and a template.
Why you need an LIA
Legitimate interests (Article 6(1)(f)) is popular because it is flexible — but the price of flexibility is that you have to prove the basis genuinely applies. ICO guidance requires a documented Legitimate Interests Assessment (LIA) for every distinct use of the basis. Without an LIA you cannot prove the basis was validly chosen, which collapses the lawfulness of the processing.
The 3-part test
An LIA applies three sequential tests. Part 1, Purpose: What is your legitimate interest? Is it real, specific, and lawful? Part 2, Necessity: Is processing the personal data necessary to achieve that interest? Could you achieve it with less data or without processing? Part 3, Balancing: Do your interests override the individual's rights and reasonable expectations? Only if you pass all three can you use the basis.
Part 1 — Purpose
Write the legitimate interest in concrete business language. Good: "Responding to enquiries from prospective customers to grow our business." "Preventing fraud on our e-commerce platform." "Internal record-keeping for audit and accountability." Bad: "Operational purposes." "Business interests." Any purpose that is unclear fails the test. The interest can be commercial, not just charitable.
Part 2 — Necessity
Can the purpose be achieved another way with less data or no data? If yes, the processing is not necessary. Example: you want to send a user an order confirmation. Do you NEED their email? Yes, or you cannot send it. Could you do it with just a first name? Yes — but you need an email to send. Necessity passes. Another example: tracking every page a user visits to "understand our business". Could you understand your business with aggregated analytics? Yes. So individual page tracking is probably not necessary.
Part 3 — Balancing
The hardest test. Weigh your legitimate interest against the user's rights and reasonable expectations. Factors that strengthen your side: the user reasonably expects the processing (they contacted you first); the processing is low-risk; you have safeguards (pseudonymisation, access control). Factors that weaken your side: the user is a child; the data is sensitive; the processing is unexpected; the data could harm the user if misused; there are less-invasive alternatives.
Safeguards to add
If the balancing test is close, safeguards can tip it in your favour. Document the ones you will add: pseudonymisation (replace direct identifiers with codes); strict access controls (only named staff can read); short retention (delete within days/months rather than years); an opt-out link in every communication; exclusion of minors. Each safeguard reduces risk and makes the LIA defensible.
Template structure
A usable LIA template has: 1) Date and reviewer; 2) The processing (what data, from whom, for what); 3) Part 1 — your purpose and why it is legitimate; 4) Part 2 — why the processing is necessary; 5) Part 3 — the balance: user expectations, risks, mitigations; 6) Conclusion — basis passes or does not; 7) Review date (annually). Keep each LIA to 1-2 pages. Store them with your other privacy records.
When to review
Review each LIA annually, and any time the processing changes, any time you add a new data source, or when a legal case or ICO guidance affects the area. The Cambridge Analytica-era assumption that "we have an LIA so we're fine" no longer flies. An LIA is a living document.
Tags: gdpr, lia, legitimate-interests, article-6, assessment, balancing-test
Tutorial
How to Do a Data Protection Impact Assessment (DPIA)
A DPIA is required for high-risk processing. This guide explains when it is mandatory, when it is sensible, and what a short-form DPIA actually contains.
When a DPIA is mandatory
Article 35 requires a DPIA before processing that is likely to result in high risk to individuals. The ICO has published a list of operations that trigger a mandatory DPIA, including: systematic monitoring of a publicly accessible area on a large scale (CCTV); processing special category data or criminal convictions data on a large scale; systematic profiling that could have legal/significant effects; data matching across multiple datasets; innovative technology use (AI, biometrics, IoT).
When a DPIA is a good idea
Even where not strictly mandatory, a short DPIA is sensible when: you are launching a new product feature that collects personal data; you are switching a major vendor; you are introducing a new tracking or analytics approach; you are processing children's data at any scale; you are considering AI or machine learning on personal data. If the output says "high risk", you must consult the ICO before processing.
Sections of a DPIA
A usable DPIA covers: 1) Description — what processing, why, what data, whose data; 2) Necessity and proportionality — why not less data; 3) Consultation — who have you asked (DPO, affected people, regulators); 4) Risks — identified risks to rights and freedoms, with likelihood and severity; 5) Measures — what you will do to reduce each risk; 6) Residual risk — is it now acceptable? 7) Sign-off — named person approving, date.
Identifying risks
Be specific. Not "risk of privacy breach" but "risk of the third-party vendor being subject to a ransomware attack that exposes customer order history". Each risk should have: source (what could happen), likelihood (low/medium/high), severity (low/medium/high), combined score. Brainstorm with someone other than yourself — fresh eyes see risks you have normalised.
Mitigations
For each identified risk, list the mitigation: technical (encryption, pseudonymisation, access controls, backup integrity checks); contractual (DPA with vendor, security clauses, audit rights); organisational (staff training, clear processes, access review cycle). A risk with no mitigation = a risk you are accepting.
Residual risk
After applying mitigations, re-score each risk. If any residual risk is still "high" (high likelihood AND high severity), you must consult the ICO before processing — Article 36. If all residual risks are medium or low, you may proceed and keep the DPIA on file. Revisit if anything material changes.
Consultation
The DPIA should reflect consultation with: your DPO if you have one; the people whose data will be processed (for anything significant, e.g. staff if workplace monitoring is planned); regulators if residual high risk; IT security if technical controls are involved. Record who was consulted and what feedback was incorporated.
Keeping it updated
A DPIA is not a one-off document. Review every 12 months or whenever the processing changes materially. Treat it as a living document rather than a compliance tick-box. It can be short — 4-5 pages — but it must genuinely reflect the processing as it actually happens, not as it was originally designed.
Tags: gdpr, dpia, data-protection-impact-assessment, article-35, risk
Tutorial
How to Handle a Right-to-Erasure Request
A user asks you to delete their data. Step-by-step: when you must comply, when you can refuse, and what "delete" actually means technically.
The right in plain English
Article 17 says in certain circumstances a user can demand you delete all their personal data. It is commonly called the "right to be forgotten". It is not absolute — there are grounds for refusal — but in most cases for a small business, you should comply unless a legal retention obligation applies.
When you MUST delete
You must delete without undue delay (within one month) when: the data is no longer necessary for the original purpose; the user withdraws consent (if consent was the lawful basis) and no other basis applies; the user objects to processing based on legitimate interests and there are no overriding grounds; the data was processed unlawfully; erasure is required to comply with a legal obligation.
When you can refuse
You can refuse to delete when: processing is still necessary for exercising the right to freedom of expression and information; for compliance with a legal obligation (e.g. HMRC requires 6-year invoice retention); for reasons of public interest in the area of public health; for archiving in the public interest, scientific/historical research, or statistical purposes; for the establishment, exercise, or defence of legal claims. Refusal must be documented — you cannot just say no.
Identifying where the data lives
Before you can delete you need to find every copy. Customer database. Email archives (inbox, sent, cc/bcc). CRM. Support tickets. Chat logs. Backups (see next step). Marketing platform. Analytics if identifiable. Exported reports. Spreadsheets shared by staff. A map of data locations — a basic ROPA — makes this instant rather than a panic.
Backups are complicated
Deleting live data is easy; deleting from backups is harder. ICO guidance: you do not need to restore each backup, delete the user's record, and re-backup. You can put the data "beyond use" — effectively deleted even if technically still on tape — by: stopping routine access; ensuring any restore will re-delete before use; deleting at the next natural backup rotation; not using the backup for any purpose except restoration. Document this approach in your retention policy.
Notifying third parties
If you have shared the user's data with third parties, you must take reasonable steps to inform them of the erasure request. Contact each processor (Mailchimp, analytics, etc) via their support channel and request the deletion. Document that you did so. You are not liable for the third party's compliance — they have their own controller duties — but you must try.
What to send back
After deletion, send a clear confirmation: what was deleted, what was retained (with the legal reason if anything was kept), the date of deletion. Keep a record of the request and response for at least 3 years for accountability — a minimal record: request date, request method, response date, what was deleted, any retained exceptions. Ironically, this minimal log itself contains the user's email — which you also delete when the retention period expires.
Anonymisation as an alternative
True anonymisation (data that can never be linked back to the individual) takes data outside GDPR. You can sometimes meet an erasure request by anonymising rather than deleting — replacing the user's name and email with a random token in analytics tables, for example. This preserves aggregate insights. But pseudonymisation is NOT anonymisation — if you still hold the key, the data is still personal and still subject to the request.
Tags: gdpr, erasure, right-to-be-forgotten, article-17, deletion
Tutorial
How to Handle a Right-of-Access Request (SAR)
The practical mechanics of a SAR — beyond the overview in the DSAR tutorial. Identity verification, searching systems, redaction, format, and the exemptions you are likely to hit.
SAR vs DSAR — terminology
Subject Access Request (SAR) and Data Subject Access Request (DSAR) are the same thing — a request under Article 15 for a copy of personal data. Some organisations distinguish but the law does not. Use whichever term the requester uses. The process is the same.
What the user is entitled to
Article 15 entitles the user to: a copy of the personal data; the purposes of processing; the categories of data; the recipients (who it was shared with); retention periods or criteria; their other rights; the source of the data if not from them; whether automated decision-making is involved with significant effects. Not just the data — the context. Many organisations send just the raw data and fail the other elements.
Identity verification, done right
You may verify identity where there is reasonable doubt — but only to the extent necessary. If the request comes from the email address on file, usually no further verification is needed. If from a new address or a third-party representative, ask for a copy of photo ID plus proof of address (redacted to only what you need). Be proportionate. Do NOT demand notarised documents unless the request is extremely high-risk. Over-asking is itself a compliance breach.
Searching systems
Make a list of every system that could hold data about the requester. Typical list for a small business: CRM, email (yours + any staff who corresponded with them), support ticketing, order database, marketing platform, analytics if user-identifiable, backups if reasonably searchable, exported reports, shared drives. Search each one. Keep a log of what you searched and what you found.
What format to send
The user is entitled to a copy in a commonly used electronic format, unless they requested otherwise. PDF is fine for documents. CSV or JSON is ideal for structured data (preserves portability). Compile into one package. Include a contents page. For very large responses, offer a secure download link rather than a giant email attachment.
Redacting third-party data
When the requester's data mentions other people (an email thread with a colleague, a meeting note with other attendees), you have to balance their access right against the third party's privacy. Options: 1) Get the third party's consent; 2) Redact the third party's identifiable information; 3) Refuse disclosure of that specific item if the third party would be unreasonably affected. Redaction is usually the answer. Keep the redacted originals on file to show the regulator if asked.
Common exemptions
You may withhold data that would: prejudice the prevention/detection of crime; identify a confidential informant; be subject to legal professional privilege; be part of ongoing legal proceedings; contain another person's personal data that cannot be separated. For each exemption you rely on, document: which exemption, which data items, and why it applies. Blanket refusals are a breach.
Charging fees
The first copy is free. You may charge a reasonable admin fee for additional copies or for manifestly unfounded/excessive requests. A fee must reflect actual cost of compiling the additional copy. You cannot charge a fee to discourage the request — the ICO has fined organisations for this pattern.
Timeline and extension
One month from receipt, extendable by two additional months for complex or multiple requests. If you extend, tell the requester within the first month with the reason. The clock stops once while you wait for identity verification from the requester — from your verification request to their response, capped at one pause.
Tags: gdpr, sar, subject-access, article-15, rights, access
Tutorial
How to Configure Google Analytics 4 for GDPR
GA4 can be compliant but only if you configure it correctly. IP anonymisation, data retention, consent mode, and the settings most sites miss.
The compliance picture for GA4
GA4 processes IP addresses, device identifiers, and behaviour data — all personal data under UK GDPR. GA4's own processing is covered by Google's UK IDTA, and EU regulators have raised concerns about US data transfers in some enforcement actions (the French CNIL and Italian Garante have both ruled GA3 non-compliant in specific configurations; GA4 with proper config is generally accepted).
Before you touch GA4 — consent first
GA4 cookies are non-essential under PECR. You must NOT let GA4 set cookies or collect data before the user consents. Use a proper consent banner (see the cookie banner tutorial) and wire GA4 so it loads only after consent. If you are using Google Consent Mode v2, GA4 will still load but run in "cookieless" mode until consent is granted.
Enable IP anonymisation / IP cropping
GA4 anonymises the last octet of IP addresses by default in the EU/UK — but verify. In Admin > Data Collection and Modification > Data Streams > click your stream > Configure tag settings > Show all > Internal traffic rules — confirm IP is anonymised. The default "collect universal analytics events" should be OFF unless you need it.
Shorten data retention
GA4 default retention is 2 months — which is fine. Do not extend it unless you have a specific purpose. Admin > Data Settings > Data Retention > set to the shortest period that serves your purpose. 2 months is the ICO-friendly default. 14 months is acceptable with justification. "Auto-reset on new activity" ON is fine for active users.
Disable Google Signals (for EEA/UK traffic)
Google Signals links GA4 data with Google's signed-in users to produce cross-device reports, demographics, and remarketing audiences. For EEA/UK users it relies on consent that you must collect. If you do not need cross-device reporting and remarketing, turn Google Signals OFF. Admin > Data Settings > Data Collection > Google Signals Data Collection > toggle off. This simplifies your consent stack considerably.
Turn off data sharing with Google
In Admin > Account Details > Account Data Sharing Settings, there are optional toggles sharing your analytics data with Google for "modelling" and "account specialists". These are OFF by default in new accounts but worth verifying. Each one adds complexity to your consent/DPA picture.
Sign the Google DPA
By using GA4 you have already accepted Google's Data Processing Terms — but it is worth confirming in your account. Admin > Account Details > scroll to Data Processing Terms > check signed. This is your safeguard for the UK-to-US transfer. Reference this in your privacy policy as the safeguard against international transfer.
Document in your privacy policy
Your privacy policy must name Google as a recipient. Standard wording: "We use Google Analytics 4 (provided by Google LLC, 1600 Amphitheatre Parkway, Mountain View, CA 94043, USA) to understand site usage. The service processes IP address (anonymised), device ID, and interaction data. The international transfer to the US is covered by Google's Data Processing Terms incorporating the UK IDTA. You may opt out by rejecting analytics cookies on our banner or by installing Google's opt-out browser add-on."
Test the setup
Open your site in a fresh browser/incognito. Open DevTools > Network, filter by "google-analytics" or "gtag". BEFORE consenting on the banner: there should be zero requests to these domains (or only consent-mode "signals" with zero PII). Click Reject. Same — zero tracking. Click Accept. Now you should see analytics events. If events fire before consent, something is misconfigured.
Tags: gdpr, ga4, google-analytics, consent, pecr, analytics
Tutorial
How to Write a Cookie Policy
A cookie policy is not just a second privacy policy. It names each cookie, why it exists, and how to opt out. This guide covers structure, content, and common mistakes.
Why cookie policies exist separately
Your privacy policy covers personal data generally. A cookie policy is a specific disclosure required by PECR for the tracking technologies on your site. They can live on the same page or different pages — but the cookie content has to be specific and enumerated, which usually means its own page at `/cookies` or `/cookie-policy`.
The required structure
A compliant cookie policy has: 1) What cookies are (a short definition); 2) Why you use them (in plain English); 3) The categories — strictly necessary, functional, analytics, marketing; 4) A list/table of each cookie: name, category, purpose, duration, who sets it; 5) How to manage preferences (link to your banner/settings); 6) How to block cookies at browser level; 7) Contact info for questions. Without the per-cookie table you are not really telling users what is happening.
The four categories
Strictly necessary: cookies essential to the service — session tokens, basket contents, CSRF tokens. No consent needed. Functional: remember preferences (language, region). Consent usually needed unless strictly necessary for a specific feature the user requested. Analytics: understand site use — Google Analytics, Matomo, Plausible. Consent required. Marketing: advertising, retargeting, conversion tracking — Meta Pixel, LinkedIn, Google Ads. Consent required.
The per-cookie table
For each cookie, list: name (exact cookie name like `_ga`, `_fbp`); provider (your site or a named third party); purpose (one sentence); duration (how long until it expires); category. This matters for transparency AND for users who want to block specific cookies at browser level. If you use a consent manager, most have an auto-populated table you can embed.
Third-party cookies
Third-party cookies are set by services loaded from other domains. Facebook Pixel, YouTube embedded videos, Google Fonts, Stripe checkout — each may set cookies under its own domain. List them with the third-party's name and link to their cookie policy. Under PECR you are responsible for getting consent even though the third party sets the cookie.
How to manage preferences
Users must be able to change their consent choice after the initial decision. The cookie policy should include a link or button that re-opens the banner. A common pattern: "Change your cookie settings" link in the footer that fires the banner. The policy should also link to instructions for browser-level cookie blocking (all major browsers publish these).
Common mistakes
Generic list with no actual cookie names — fails specificity. No distinction between necessary and non-necessary — all described as "essential to make the site work". Durations not listed or listed as "varies" for all cookies. Third-party cookies not separated or named. No way to change preferences from within the policy. No "last updated" date.
Keeping it current
Cookie policies go stale quickly. Every time you install a new tag (analytics, tracker, chat tool) you probably add 1-10 new cookies. Set a quarterly calendar reminder to audit cookies actually set on your site — open DevTools, check Application > Cookies, compare to your policy, update. A consent manager that auto-scans your site (Cookiebot, OneTrust, Cookiefirst) keeps this current automatically.
Tags: pecr, cookie-policy, cookies, gdpr, tracking
Tutorial
Handling an Angry User Before They Complain to the ICO
Most ICO investigations start because a user felt ignored. This guide covers how to handle complaints constructively, when to escalate, and how to stop small issues becoming investigations.
Why this matters
The vast majority of ICO enforcement actions against small and medium businesses begin with a user complaint after they felt ignored, dismissed, or fobbed off by the organisation. ICO guidance explicitly says they prefer organisations resolve complaints internally — but they will step in if the user is not getting anywhere. Handling complaints well is the single cheapest compliance measure you can invest in.
The first 24 hours
Acknowledge the complaint within one working day. Use plain language. Say what you understand the issue to be, who is looking into it, and when you will respond substantively. Do not respond defensively, do not cite policy at the user, do not copy-paste a template. Personalise. This alone resolves a large proportion of "I will complain to the ICO" situations.
Taking the complaint seriously
Read what they actually said, not what you assume they mean. Common real complaints: "I did not consent to this" (did they?); "I asked to be deleted and you did not" (did you?); "You emailed me after I unsubscribed" (did you?); "You shared my data with X and never told me" (did you?). Investigate the specific allegation. Check the consent log, the deletion log, the vendor list.
Your response structure
A good complaint response has: 1) a clear acknowledgement of what they raised; 2) what you found when you investigated; 3) what you are doing about it (fixing the issue, deleting the data, updating a process); 4) what they can do next if still unhappy (including contacting the ICO — you must tell them about their right to complain). Aim for under 400 words. Be human.
Fixing the root cause
Every complaint, whether the user was right or wrong, should trigger a brief review: could this have been prevented? Common fixes: a clearer privacy notice at a form; a more prominent unsubscribe link; a shorter retention period; a better deletion process; clearer consent language; a trained staff member rather than a confused one. Log the complaint and the fix.
What not to do
Do not: deny you hold any data without checking; demand ID verification for a simple request they could answer differently; quote regulation verbatim at them; blame a processor ("it was Mailchimp, not us" — legally it is you); threaten them; offer to resolve "in exchange for" silence; or ignore follow-ups. Any of these dramatically raise the chance of an ICO complaint.
When to acknowledge you got it wrong
If the user is right — you did keep marketing them after unsubscribe, you did share their data with a vendor you had not mentioned, you did retain data past your published period — apologise directly. Explain what happened without excusing it. Fix it. Offer the immediate remedy they want (usually: delete me, stop emailing me, name the vendors). Most users who receive an honest apology stand down.
When to escalate internally
Escalate to a director or DPO when: the user alleges a breach affecting others, not just themselves; the user threatens legal action or ICO complaint and you suspect they are right; the complaint reveals a systemic issue (not just this one user); the complaint could become newsworthy (high-profile customer, social media traction). These need senior attention AND documented response.
Telling the user about ICO
Article 77 gives every user the right to complain to the ICO. You must tell them this — at minimum in your privacy policy, but also in response to a complaint if unresolved. Typical wording: "If you are unhappy with how we have handled your complaint, you have the right to complain to the Information Commissioner's Office. You can reach them at ico.org.uk or on 0303 123 1113." Being upfront about this often de-escalates, because the user feels heard rather than stonewalled.
Tags: gdpr, complaints, customer-service, escalation, ico, communication
Tutorial
What to Do If the ICO Contacts You
The ICO reaches out in many ways — a casework letter, an audit notice, a breach investigation. This guide covers how to respond in each scenario.
Types of ICO contact
ICO contacts range in seriousness: general guidance enquiry (rare, informational); casework letter (a specific complaint has been forwarded); preliminary investigation letter (they have concerns, want evidence); information notice (formal request for specified info, statutory deadlines); assessment notice (they are coming in for an audit); enforcement notice (they are going to act). Each has different response requirements.
Acknowledge within 24 hours
Whatever the letter, acknowledge receipt within one working day. State who will be handling it, provide a direct contact (usually the DPO or senior person), confirm the deadline you are working to. Even if you need time to prepare the substantive response, an early acknowledgement sets a cooperative tone. Silence is the worst possible response.
Casework letters
A casework letter passes along a user's complaint and asks for your side. You will typically have 28 days to respond. Prepare: what happened, what you found in your logs, what you did (or will do), your policies that apply, any relevant records (consent logs, retention schedules, the ROPA entry). Respond factually. The ICO closes most casework with no further action when the response is substantive and the organisation has taken reasonable steps.
Information notices
An information notice is formal. You must respond with the exact information requested by the exact deadline, and failure is a criminal offence under Schedule 15 Data Protection Act. Read the notice carefully. Scope your response exactly — over-sharing creates problems, under-sharing is a breach. Consider legal advice. Keep a copy of everything sent.
Assessment notices (audits)
An assessment notice means the ICO is coming to inspect. You have 7 days' notice (minimum). Prepare: your ROPA; policy documents (privacy, retention, security); relevant DPIAs and LIAs; breach register; consent logs; training records; the list of processors with their DPAs. Brief the staff who will be interviewed. Have a named person to escort the inspectors and record what they asked and saw.
Enforcement notices and fines
An enforcement notice compels action (delete data, stop a practice, publish a correction). A monetary penalty notice imposes a fine. Both come with a right of appeal to the First-tier Tribunal (Information Rights) within 28 days. If you receive either, get specialist legal advice immediately — the grounds of appeal are narrow and deadlines are fatal.
Breach reports you made
If the ICO follow up on a breach you reported, they may ask for: the incident timeline; the data categories affected; the number of people affected; what you did to contain and remediate; what you did to notify users; your post-incident review; changes to processes. Have this ready — it should already be in your breach register. Most ICO follow-ups on self-reported breaches close without enforcement when the organisation can demonstrate proportionate response.
What NOT to do
Do not: delay beyond the deadline without requesting an extension in writing; respond selectively to only the easy questions; destroy or alter any documentation relevant to the investigation (this is itself a criminal offence); contact the complainant to pressure them to withdraw; go off-record or informal with the ICO caseworker; speculate or admit liability in writing before investigating properly. Each of these worsens the outcome.
When to get lawyers
For casework letters, probably not needed — substantive factual responses suffice. For information notices, assessment notices, enforcement notices, or any scenario where fines are plausible: engage specialist privacy/regulatory counsel immediately. The cost is small compared to mishandling the response. Do not rely on a general business lawyer — specialist privacy firms know the ICO's priorities and templates.
Tags: gdpr, ico, investigation, audit, casework, enforcement
Tutorial
Pseudonymisation vs Anonymisation — Plain English
Two similar-sounding terms with very different legal effects. This guide covers when data is 'no longer personal' and when it still is, with practical examples.
The critical difference
Anonymisation removes personal data from GDPR's scope — once truly anonymous, the data is not personal data and the law does not apply. Pseudonymisation reduces risk but the data is still personal data. Mixing these up is one of the most common compliance mistakes. "We hashed the emails so it is anonymous" is wrong — hashing is pseudonymisation unless the source is destroyed.
What pseudonymisation is
Replacing direct identifiers with substitutes while preserving the ability to re-identify using additional information held separately. A customer's name becomes "User_3f71b"; the mapping of names to IDs lives in another table. The data is more private, but anyone with access to both datasets can re-identify. GDPR still applies to the pseudonymised dataset and the mapping table.
What anonymisation is
Transforming data so that no one can identify any individual, by any reasonable means, taking into account all information likely to be available now or in the future. If one person out of a million could be re-identified using external data, it is not truly anonymous. Anonymisation is hard — the ICO's 2022 Anonymisation Code says "anonymisation is a risk-based decision", and many "anonymised" datasets fail on re-identification tests.
Techniques that usually pseudonymise
Hashing (especially without salt — emails hash to the same value, so lookup tables can reverse); replacing with sequential IDs; encrypting (key holders can decrypt); tokenising (a vault maps tokens to originals); k-anonymity at low k values. All useful, all still personal data under GDPR.
Techniques that can anonymise (if done right)
Aggregation to groups ("120 users aged 25-34 in North West") — no individual visible. Differential privacy (adding mathematical noise so queries cannot reveal individuals). Data synthesis (generating a statistically similar dataset where each record is artificial). Random sampling with destruction of the mapping. All need careful implementation — k-anonymity at k=5 may still be re-identifiable when combined with other data.
Why GDPR encourages pseudonymisation
Even though GDPR still applies to pseudonymised data, Article 32 treats pseudonymisation as an appropriate technical measure — it reduces risk and supports security. Article 89 allows pseudonymised research without individual consent in some cases. Pseudonymising production data for dev/test environments is a best practice. It is a risk-reduction tool, not a GDPR escape hatch.
When anonymisation counts as erasure
A right-to-erasure request can sometimes be met by anonymising rather than deleting — removing identifiers so the record is no longer personal data. This preserves your aggregate analytics. But the ICO says this only works if anonymisation is genuine and irreversible; simply pseudonymising does not meet an erasure request because the user is still in your records, just under a different name.
Practical test
Ask: could someone (you, a hacker, the user themselves) re-identify any individual by combining this data with other information likely to be available? If yes, it is pseudonymous and GDPR applies. If no — after serious testing against publicly available datasets, your own other systems, and auxiliary data — it is anonymous. Document the test, because you will have to defend the judgement.
Tags: gdpr, pseudonymisation, anonymisation, data-minimisation, technical
Tutorial
Data Protection for Job Applicants
Recruitment is one of the most data-heavy things a small business does but it is often missed in privacy notices. This guide covers applicants' rights, retention, and recruitment-specific risks.
Applicants are data subjects too
When someone applies for a job, they are sending you personal data (CV, cover letter, contact details, references). They have the same rights as your customers under GDPR: access, rectification, erasure, restriction, objection. Most small businesses have no privacy notice for applicants — that is an Article 13 breach.
Create a recruitment privacy notice
A short, dedicated privacy notice for applicants should cover: your identity; what data you collect (CV, application form, interview notes, references); what you do with it (decide who to shortlist, interview, hire); how long you keep data for unsuccessful candidates (typically 6 months); whether you share with third parties (recruitment agencies, skills-testing platforms); applicants' rights. Link to it from every job ad and application form.
Lawful basis for recruitment
The usual basis is legitimate interests (hiring the right person) — with an LIA documenting the balance. Some elements need stronger basis: reference checks require the reference provider's cooperation and the candidate's consent; criminal record checks require specific legal basis (employment-related); health information (fitness for role) typically consent plus Article 9 basis.
Retention for unsuccessful applicants
The ICO-friendly default is 6 months for unsuccessful applications. Rationale: long enough to defend against discrimination claims (6-month time limit for most employment tribunal claims) or keep candidates on file for near-future roles. Longer retention needs explicit consent. At the 6-month mark, anonymise or delete — and tell the candidate this retention period in your recruitment notice.
What to do with interview notes
Interview notes are personal data about the candidate. They can be disclosed in a subject access request. Write them as if the candidate will read them — professionally, specifically, factually. Do not write "seemed overqualified and probably bored"; write "demonstrated deep technical knowledge; would benefit from a role with more senior responsibilities". Destroy notes when you destroy the application file.
Third-party recruitment platforms
Platforms like Workable, Greenhouse, Lever, Indeed, LinkedIn Jobs all process applicants' data. Each needs a DPA; each should be named in your recruitment privacy notice; each has its own retention default (often 2 years unless you change it). Audit your platform settings and set retention to your chosen period.
Rejection comms and feedback
Silent rejection is a poor experience but not unlawful. A short, respectful rejection email is better. If the candidate asks for feedback, you may give specific, factual feedback — but anything you say can surface in a later discrimination claim. Stick to role-relevant factors ("the other candidates had more experience in X").
Successful applicant becomes an employee
Once hired, the data moves into the employment relationship. Retention becomes much longer (employment records typically 6 years, pensions indefinitely). The lawful basis typically shifts to contract (employment contract) and legal obligation (tax, payroll). Delete what you no longer need (interview notes after probation passed) and archive what you must keep.
Tags: gdpr, recruitment, hr, applicants, privacy-notice, retention
Tutorial
The One-Page Staff Data Protection Briefing
You do not need expensive training to meet Article 39's training requirement. This guide shows a practical one-page briefing and an annual refresh process.
Why you have to train staff
If staff handle personal data, they must be able to do so without breaching GDPR. That requires awareness — what counts as personal data, what the basics of the law are, what to do if something goes wrong. Many breaches start with a staff member who did not know (wrong recipient email, weak password, left a laptop on the train, sent a file without encryption).
The legal baseline
UK GDPR Article 39(1)(b) requires the DPO to run awareness and training. Article 32 treats training as part of appropriate security measures. Article 5(2) requires you to demonstrate compliance, which includes documenting training. No specific curriculum or duration is mandated — you can do this with a single well-designed briefing annually.
The one-page briefing
Fits on one side of A4. Sections: 1) What is personal data (brief + examples). 2) Our lawful bases (the 2-3 you use most). 3) What to do if you receive a user request (escalation path). 4) What to do if you suspect a breach (who to tell, how fast). 5) Practical dos and don'ts (encryption, CCs, locked screens, strong passwords, no personal devices for work data). 6) Who to ask for help.
Tailoring by role
A blanket briefing is fine for most staff. For specific roles, add a short role-specific annex: customer-facing roles (handling user rights requests); developers (pseudonymisation, access controls, data minimisation in new features); marketers (consent, unsubscribe, retention of marketing lists); HR (employee data, recruitment data). Two-page max.
Delivery
Walk staff through the briefing — do not just email a PDF. 30 minutes at an all-hands meeting, a new-joiner on their first day, or a Zoom call for remote staff. Let people ask questions — these reveal gaps in your processes. Record attendance (name, date, briefing version). Repeat annually.
The 3-question quiz
A short quiz helps you prove understanding. 3-5 questions max: Is a cookie ID personal data? (yes). What do you do if a user asks for their data? (escalate to [named person/email]). Who do you tell about a suspected breach? (same). Log answers. The ICO does not require quizzes but they are evidence of effective training.
Incident-triggered refresher
If something goes wrong — wrong email, lost laptop, weak password compromised — update the briefing with a fresh example. Re-brief affected staff within a week. Short incident summary + what changed. This shows the training is alive, not a tickbox.
Recording training
A simple log: name, role, briefing version, date completed, quiz score. Keep for 3 years. If an investigation asks "what training did staff get", you have the answer. Most HR tools have a training log feature; a spreadsheet is fine if you do not.
Tags: gdpr, training, staff, briefing, awareness, article-39
Tutorial
Syncing Data Rights Requests Across Your Tools
A deletion request applies to every system holding the user's data. This guide covers how to coordinate across CRMs, email, analytics, and support tools without missing anything.
Why this trips people up
When a user says "delete my data", they mean everywhere. Most small businesses delete from the main database and forget: email archives, old support tickets, marketing automation, backups, exported spreadsheets, the accountant's copy, shared drives. Partial deletion is still a breach — the ICO has ruled so in several cases.
Start with a data map
Before you can coordinate requests across systems you need to know which systems hold what. Your ROPA should list each processing activity and the systems involved. Map per data subject type: "a customer exists in these systems", "a newsletter subscriber exists in these", "an employee exists in these". Keep it short and true.
Designate a request inbox
One email address for all data rights requests (e.g. privacy@yourdomain.com). All requests land there regardless of system. Requests that arrive elsewhere (support ticket, tweet, phone) get forwarded to that inbox the moment they are identified. This creates one audit trail.
Build a request checklist
For each data-subject type, write a short checklist: "For a customer erasure request, delete from: [main database], [Mailchimp], [Stripe metadata where permitted], [Zendesk tickets, subject to retention], [Google Drive exports], [email archive]. Confirm each step in the request log." This makes the process repeatable. Keep the checklist current when you add or remove systems.
Use processor tools
Major processors provide endpoints or UIs for bulk rights-fulfilment: Mailchimp has GDPR compliance tools; Stripe has customer deletion and anonymisation; Zendesk has redaction and deletion. Where available, use these and log what was done. Where not, contact support with the request.
Dealing with backups
You do not need to restore every backup to delete a user's record. ICO-accepted approach: mark the data as "for deletion", stop routine use, let the next scheduled backup rotation naturally expire the data, never restore the intermediate backups for live use. Document this approach in the retention policy and refer to it when responding to the user.
Confirmation to the user
After the request is fully actioned, respond to the user with: what was deleted, any data retained with the legal justification (e.g. "order records retained for 6 years per HMRC requirement"), date of action. Include the list of systems touched if relevant. Keep the confirmation in your request log.
Audit trail
A single place that logs: when the request arrived, who handled it, which checklist items were completed, when, by whom, any exceptions with justification, when the response went out. Spreadsheet, ticketing tool, whatever. At least 3 years retention. This is your defence if the user later says "you did not delete."
Tags: gdpr, dsar, erasure, data-mapping, systems, coordination
Tutorial
Writing a Data Retention Schedule You Will Actually Follow
Retention rules on paper are useless if nobody deletes anything. This guide covers how to write, automate, and audit a practical retention schedule.
A schedule is an operational document
Your privacy policy publishes retention periods to users. Your retention schedule is the internal operational document that says: which system, which data, how long, who reviews, how deletion happens. Without the internal schedule, the policy is a promise you cannot keep.
Structure — a row per data type per system
Row columns: data type (e.g. "customer email"); system (e.g. "main database"); purpose; lawful basis; retention trigger (e.g. "account closure"); retention period; deletion method; responsible person; next review date. One row per combination. 20-50 rows for a typical small business.
Triggers matter more than periods
"24 months" is not a trigger — 24 months from what? Define the triggering event: "from last contact", "from order completion", "from account deletion", "from end of subscription". The trigger is what your deletion process watches for. Ambiguous triggers mean the data never actually deletes.
Automated vs manual deletion
Automated is better when practical. A cron job that deletes records older than the trigger + period. A scheduled query that flags candidates for review. Analytics platforms that auto-expire data. Where automation is not possible, a calendar reminder (quarterly) with a checklist owner. Write the decision down: "this data type is deleted by cron X weekly" or "this data type is reviewed by [person] quarterly".
Exceptions and legal hold
Sometimes you cannot delete — there is a live legal claim, or an ongoing investigation, or a specific legal retention rule. The schedule should allow for "legal hold" flagging: a data type normally deleted at X is held until the hold is released. Document why and who authorised it. Remove the hold when it ends and delete immediately.
Backup rotation consistency
Your retention schedule and your backup rotation must align. If you keep daily backups for 30 days and your retention policy says customer data is deleted at account closure, data persists for up to 30 days in backups after deletion. That is usually acceptable — state it explicitly in your schedule and in the policy ("deleted within 30 days of request to account for backup rotation").
Quarterly audit
Once a quarter, pick a data type and verify deletion actually happens. Query the system for records that should have been deleted. If found, run the deletion. Investigate why the retention was not enforced. Fix. Log the audit. This is the single most undervalued accountability practice — without it, your retention schedule is aspirational.
Communicating retention to users
The privacy policy shows external retention periods. The wizard's Step 6 captures these. If you add a new retention period to the schedule, check whether the policy needs updating too. The schedule is usually more detailed than the policy — that is fine, but the policy must be a true summary, not a different story.
Tags: gdpr, retention, storage-limitation, deletion, schedule, accountability
Tutorial
Chatbots and AI Assistants That Process Personal Data
If you use a chatbot, AI assistant, or LLM-powered feature, you are processing personal data in ways the user may not expect. This guide covers transparency, consent, and vendor choices.
What users expect — and what happens
When a user types into your chatbot, they see a conversation. Behind the scenes: the text is sent to a third-party AI provider (OpenAI, Anthropic, Google, your own infra), possibly used for model training, possibly logged for quality control, possibly seen by human reviewers. Each of those is a processing activity the user should be aware of.
Disclose the provider
Your privacy policy must name the AI provider, jurisdiction, safeguards for international transfer. Example: "Our chatbot is powered by OpenAI (OpenAI, L.L.C., San Francisco USA, covered by OpenAI's Data Processing Addendum incorporating the UK IDTA). Messages you send to the chatbot are processed by OpenAI to generate responses." Generic "we use AI" is not enough.
Training opt-out
Most major providers now offer enterprise/API terms where your data is not used for model training. Check this is enabled. OpenAI's API does not train by default; ChatGPT web does unless turned off. Anthropic API does not train. Google Gemini API settings vary. Verify in your vendor admin, document the setting in your ROPA, and state it in the privacy policy.
Lawful basis
For a chatbot that helps customers, usual basis is legitimate interests (improving customer experience) — with an LIA covering: what data the chatbot sees, how long it is retained, the vendor's handling, the user's alternative (can they reach a human easily?). For chatbots that handle orders, add contract. For chatbots that try to learn user preferences for marketing, add consent.
Data minimisation
Do not send more to the AI than needed. If the bot can answer from a knowledge base, do not send the user's full account history. If it needs context, send only relevant fields (their current issue, not their entire past). If it processes PII, consider pseudonymising before sending (strip names, replace with IDs).
Retention
AI-chat logs are conversations. They are personal data. Set retention: typically 30-90 days for quality-improvement purposes, longer only if specific purpose. Most AI platforms store indefinitely by default — go into settings and set data retention to match your policy. Document the setting in the ROPA.
Sensitive inputs
Users often share more than they should in chat — health issues, financial details, grievances. The bot should recognise and redact sensitive patterns before storing. At minimum, your privacy notice should explicitly warn: "Please do not share highly sensitive information (health, card numbers, passwords) with our chatbot." Many compliance-first bots have input filters.
Human escalation
If the chatbot makes any decision with legal or significant effects, you must offer human review — Article 22. For most informational chatbots this does not apply. For a chatbot that auto-approves or denies something, it does. Provide a clear "talk to a human" escape hatch regardless — it improves trust and covers edge cases.
Logging and audit
Keep a retention-limited log of: conversations (to improve service), the AI provider used, any moderation actions, any human escalations. When a user asks for their data, include chatbot transcripts if you hold them. When they ask for deletion, delete their transcripts from your logs AND ask the provider to purge if their retention is separate.
Tags: gdpr, ai, chatbot, llm, automated, transparency, vendor
Tutorial
Live Chat Transcripts — Retention and Access
Live chat transcripts can sit forever in support tools and surface in the worst moments. Covers retention, staff access, and how to respond when a user asks for their transcript.
Transcripts are personal data
A live chat transcript contains the user's name or contact info (usually), plus everything they said. It is personal data and subject to all GDPR rules. Your support tool's default is almost always "keep forever" — which is usually wrong.
Choosing a retention period
Short retention is fine for most support: 12-24 months lets you see a returning issue. Longer retention only if you have a specific purpose (training material, regulatory evidence). Set the retention in your tool's settings — most let you auto-delete after N days. Document the choice in the retention schedule.
Who sees the transcripts
Access control: not every staff member needs to read every chat. A support supervisor and the agent handling the ticket is usually enough. Analytics features that show "top issues" should use aggregate data, not raw transcripts. If training uses transcripts, redact names and specific identifying details.
Training materials
A common trap: "we use past chats to train new agents". Fine in principle. Problem: you are using the user's data beyond the original support purpose, without their knowledge, often across many other users. Mitigate: anonymise transcripts before using as training; get consent for specific case studies; publish this use in your privacy notice.
Integrations that leak
Chat platforms integrate with CRMs, Slack, email, analytics. Each integration copies transcript data to another system — which must then also have a retention rule. Audit integrations periodically. Turn off copies you no longer need. Ensure the destinations are in the ROPA.
DSAR for live chat
If a user requests their data, include their chat transcripts — they are theirs. Search by email or customer ID. Redact other users' info (other agents' names are often disclosable as part of their professional role; other customers' info is not).
Erasure of chat
Erasure requests must reach the chat tool. Most tools have a delete customer function. Use it. Confirm to the user that chat was deleted as part of the wider response. If the tool cannot actually delete (some platforms archive rather than delete — check), get a newer tool or document why actual deletion is not possible.
Sensitive content
Customers sometimes share sensitive info in chat — medical, financial, grievances against other customers or staff. Train agents: do not prompt for more than needed; if sensitive info is shared unprompted, consider redacting the transcript or shortening retention. The privacy notice should warn users not to share highly sensitive info in chat — same as chatbots.
Tags: gdpr, live-chat, transcripts, retention, support, access
Tutorial
Discovering a Historical Compliance Gap — What to Do
You find out your privacy notice has been wrong for 2 years, or you missed a breach, or you shared data you should not have. This guide covers triage, disclosure, and remediation without panic.
Do not panic and delete everything
Deleting evidence of a gap can turn a compliance issue into obstruction, which the ICO takes very seriously. When you discover a past problem, the first step is to preserve everything: logs, communications, affected data, processes as they were. Work from preserved evidence, not from a cleaned-up version.
Assess the scope
Questions to answer: what was wrong (what did we do/not do that violated law/policy); how long was it wrong (start date, end date if fixed); who was affected (how many people, what data); what is the impact (low = paperwork gap, high = data exposed, users misled). Document each answer with supporting evidence.
Decide if it is a breach
A breach is unauthorised access, loss, alteration, or disclosure of personal data. Some historical gaps are breaches (data shared with unauthorised parties). Many are not (a policy that was wrong on paper but matched actual practice). Apply the 72-hour rule ONLY if it is a breach; many historical gaps are process issues to remediate, not incidents to report.
When to self-report to the ICO
Self-report if: it was a breach AND likely resulted in risk to individuals; or the problem was material AND structural (large numbers affected, long duration, significant data types). Self-reporting is not a fine multiplier — it is usually a mitigating factor. Not self-reporting when you should have is an aggravator.
When to tell affected users
Tell users if: it was a breach with high risk; or the misrepresentation was material to their consent or decision. You do not need to tell every user of a typo fix in a privacy notice. You do need to tell users whose data was shared with an unnamed vendor for months. Proportionality is your guide; so is transparency.
Fix it now
Whatever the gap, the first fix is to stop doing the wrong thing. Stop the unlawful sharing, fix the broken consent capture, update the privacy notice, revise the retention. Then implement the process change that prevents recurrence (new checks, new sign-off, automated monitoring, training update).
Document everything
Your discovery, assessment, decisions, actions all go in writing. If the ICO ever asks, "why did you not notify in 2024 when this started?" — the answer needs to be defensible. "We discovered X on date Y, assessed impact as Z because of [evidence], decided to self-report / not report because [reasoning], took actions [list] with [dates]." Without this record you have no defence.
Updating your compliance posture
Every historical gap teaches you something about your compliance programme's weaknesses. Add a review step, a training item, an automated check. Update the ROPA if the activity was missing. Update the retention schedule if retention was wrong. Update staff training with the specific example. Use the incident to strengthen the whole, not just patch the specific hole.
Tags: gdpr, compliance, historical, remediation, disclosure, breach
Tutorial
Writing Privacy Notices Users Will Actually Read
Article 12 requires notices to be clear, intelligible, and accessible. This guide covers tone, layered notices, and the techniques that turn a 5,000-word policy into something readable.
The law on readability
Article 12(1) requires information provided to data subjects to be "concise, transparent, intelligible and easily accessible, using clear and plain language, in particular for any information addressed specifically to a child". The ICO has specifically criticised overly long, legalistic notices as failing this requirement — even when they technically include everything Article 13 demands.
Write as if talking to one person
Swap "the data subject" for "you". Swap "the data controller" for "we". Use active voice ("we send your email to Mailchimp") not passive ("your email is transmitted to our processing partner"). One idea per sentence. Short sentences (max 20 words). Specific words ("Stripe") not vague categories ("our payment service provider").
Layer your notices
Most users do not need all the detail all at once. Use a layered approach: Layer 1 — a short summary at the top (3-5 bullets: what data, what for, who with, how long, your rights). Layer 2 — full detail in sections below. Layer 3 — supplementary documents (DPIAs, ROPA excerpts) linked from relevant sections. Users self-select the depth they need.
Just-in-time notices
A contact form has one purpose — say so, right next to the form. "We use your email to reply to your enquiry. We keep it for 24 months then delete it. See our privacy policy for your rights." Two sentences. Linked from the full policy. This is a just-in-time notice. Each data collection point should have one.
Visuals and structure
Use headings, tables, and icons. A table listing each data category with its purpose, lawful basis, retention, and recipients is far easier to scan than prose. Visual hierarchy (what is important) matters. The ICO's own example notices use this approach.
Reading level
Aim for the reading age of your audience. For general consumer sites, 12-14 years old (equivalent to Year 8-9 English). Tools like Hemingway App highlight complex sentences. Any sentence with more than one qualifying clause probably needs splitting. Any sentence with jargon needs either defining or simplifying.
Jargon glossary
If you must use legal terms, include a short glossary. "Lawful basis" — the legal reason we need to use your data. "DPO" — Data Protection Officer, our person responsible for how we handle data. "Processor" — a company we hire to handle your data on our behalf. Put the glossary at the bottom, linked from every jargon term.
Testing
Give the notice to someone non-technical — a friend, a family member, a junior team member from a different department. Ask: "Using only this page, tell me what happens to your email if you sign up to the newsletter." If they cannot answer in 60 seconds, rewrite. Repeat until they can. This is the single best test of Article 12 compliance.
Tags: gdpr, privacy-notice, writing, plain-english, article-12, transparency
Tutorial
Running Marketing via SMS, WhatsApp, and Voice
PECR's rules for SMS, WhatsApp, and phone calls are stricter than email. This guide covers what counts as electronic marketing, consent rules, and opt-out requirements.
PECR covers more than email
PECR Regulation 22 applies to any "electronic mail" marketing — defined broadly to include SMS, push notifications, voicemail, instant messages, and similar. WhatsApp Business messages fall into this. The same consent/soft-opt-in rules that govern email apply to these channels.
SMS marketing consent
For SMS, consent or soft-opt-in is required exactly as for email. Soft opt-in applies if: the recipient gave you their number in the course of a sale or negotiation; you only market similar products; they had a clear opt-out at the time; every SMS has a clear way to stop. Otherwise you need explicit consent — "yes, please text me marketing".
The STOP/unsubscribe rule
Every marketing SMS must include an easy opt-out mechanism. The standard is "Reply STOP to unsubscribe" — that or an unambiguous equivalent. The opt-out must work within 28 days (usually instantly with a proper platform). Do not make the user reply multiple times or "confirm" — one reply must stop.
WhatsApp Business
WhatsApp business messaging requires: explicit opt-in to receive messages (WhatsApp's own terms require this, independent of PECR); the opt-in must be captured in a way Meta can audit (double opt-in, a form); every message template must be pre-approved by Meta; every broadcast must include clear identification of the sender. Treat WhatsApp like SMS plus extra platform rules.
Phone and voice calls
Unsolicited live marketing calls to individuals are regulated under PECR Regulation 21. You cannot call: numbers registered with the Telephone Preference Service (TPS) unless you have specific consent from that individual; at any time if they have said "do not call". You must identify yourself and the organisation, and provide a contact address. Automated recorded-message calls require consent plus TPS check.
Automated voice / robocalls
PECR Regulation 19 makes automated marketing calls subject to explicit consent regardless of TPS. Fines have been issued in the millions for robocall campaigns without proper consent. Do not use robocalls unless: you have verifiable consent from every recipient; the call identifies you; there is an easy opt-out from the call itself.
B2B SMS and calls
The PECR corporate subscriber exemption applies to SMS and calls the same way as email: you can SMS or call a corporate number (limited company, LLP, etc.) without prior consent, provided you identify yourself and offer opt-out. Sole traders and consumers need consent or valid soft-opt-in. Be sure the number you are calling is actually a corporate subscriber.
Records and audit
For any SMS/WhatsApp/voice campaign: log consent capture (when, how, what was agreed), every opt-out received, and the date-of-suppression. Most SMS gateways and WhatsApp BSP platforms provide this as standard — export the logs quarterly and file them. This is your evidence if the ICO asks.
Tags: pecr, sms, whatsapp, marketing, phone, consent, electronic-marketing
Tutorial
Handling Old Consent When the Rules Change
Your old consent records may not meet today's standards. When a new regulation, ICO guidance, or your own policy change happens, you need to decide what to do with existing consents.
Why old consent can become invalid
Consent that was valid under the pre-GDPR rules (or under looser interpretations of GDPR) may not be valid today. Typical reasons: the consent was bundled with other things, not specific; the opt-in language was ambiguous ("we may contact you"); pre-ticked boxes were used; the user was not told about specific vendors/purposes. ICO guidance has tightened repeatedly since 2018.
The re-permission exercise
If you realise your existing consents do not meet current standards, the safest path is a re-permission campaign. Email affected users with a clear explanation of what you want their consent for, and a fresh opt-in. Anyone who does not respond with a fresh opt-in gets moved out of the list. This was famously done industry-wide around GDPR launch ("GDPR email storm") but remains the right tool for later gap-fixes.
Going broad vs going narrow
A full re-permission is thorough but attrition-heavy (most users ignore re-permission emails, so your list shrinks by 60-90%). A narrower approach: review consent for specific use cases, and re-permission only where the original consent is clearly inadequate. Document the rationale. Accept some shrinkage as the price of compliance.
When re-permission is NOT needed
If your existing consent was genuinely clear, specific, informed, and freely given — it usually remains valid. ICO guidance has not retroactively invalidated historical consents that met the standard at the time. You do not have to re-ask every 12 months just because you heard it is good practice. Do re-ask if your processing materially changes.
Purpose changes
Adding a new purpose not covered by the original consent requires fresh consent for the new purpose. "You consented to receive our newsletter" does not cover "we now also want to profile you for targeted ads". Each purpose needs its own clear consent. Silent expansion of purpose is unlawful.
Vendor additions
Adding a new processor/recipient should be disclosed. If you previously said "we share your data with Mailchimp" and now you also share with Customer.io, your users need to be told. Update the privacy policy; for material changes, email users; for trivial sub-processor changes within the same vendor, disclosure in the policy update is usually enough.
Logging the change
A change to consent rules is a change to your compliance posture — document it. Who decided; when; based on what trigger (new ICO guidance, new vendor, new purpose); what action was taken (re-permission campaign, policy update, nothing); what the outcome was. Keep the record for at least 3 years.
Communicating to users
If you do a re-permission campaign or a material change, communicate clearly. Subject line: "Your privacy choices — please confirm". Body: explain what has changed and why, show what you want to do with their data, give a clear Yes/No. Do not use dark patterns. Do not make "No" hard to find. Users who say no are not churned users — they are users whose data you no longer have lawful basis to use, which is worse if you keep emailing them.
Tags: gdpr, consent, rules, grandfathering, re-permission, review
Tutorial
Data Protection in Email Signatures and Auto-Responders
Email signatures and auto-responders often expose more personal data than intended. This guide covers what to include, what to omit, and when to add privacy notices.
The under-noticed risk
Staff email signatures often expose: direct mobile numbers, personal assistant names, office addresses, photographs, LinkedIn links. Auto-responders (out-of-office) often expose: holiday dates, alternative contact's home number, sensitive trip details. Each can be personal data (about the sender AND the replacement contact) and can enable social engineering.
Minimum signature content
A professional email signature should include: name, role, organisation, work email (already visible), work phone number (switchboard or direct — your choice). Optional: address if relevant, photo if user chose it. Do NOT include: direct mobile unless essential; personal phone numbers; home addresses; emergency contacts.
Legal identification requirements
UK limited companies must display on business emails: registered company name, registration number, place of registration, registered office address. This is a Companies Act requirement, not GDPR, but fold it into the signature. Other organisations have their own formats (charities show charity number, LLPs show LLP details).
Confidentiality footers
"This email is confidential and may be legally privileged" footers are common but have limited legal effect — they do not create a contract, they do not bind an unintended recipient, and courts largely ignore them. Use them if you wish but do not rely on them for compliance. They do not replace actual access controls or encryption for sensitive content.
Out-of-office messages
Auto-responders announce your absence to everyone who emails you — including attackers running reconnaissance. Keep out-of-office content minimal: expected return date, who to contact in your absence (their work email, not mobile). Do NOT include: destination, exact dates if long leave, home-back-up contact details, reasons for absence (illness, bereavement). Turn off auto-reply for external senders if you can.
Covering staff
If your auto-responder redirects to a colleague, that colleague's data appears in potentially many autoresponses a day. Confirm they agreed; use their work contact only; consider a generic "support@" or "info@" address instead. Do not volunteer their mobile.
Signatures in signed-in tools
Many SaaS tools (Zendesk, Intercom, etc.) have per-user signatures that may be visible to customers. The same rules apply. Audit the signatures staff have set up in these tools periodically. It is common for staff to set up a rich signature on first login and forget it.
Template and standardise
Provide staff with a template signature, deployed via Google Workspace / Microsoft 365 admin or a tool like Exclaimer. This avoids: inconsistent branding; staff adding their own inappropriate content; legal footer drift; personal photos/mottos that do not belong on business emails. Review the template annually.
Tags: gdpr, email, signature, auto-responder, out-of-office, privacy
Tutorial
The Re-identification Test — Is Your Data Really Anonymous?
Practical follow-up to the anonymisation tutorial. Covers k-anonymity, l-diversity, and the auxiliary-data problem with worked examples.
Why the test matters
Calling something "anonymised" has legal consequences — the data leaves GDPR's scope. If the anonymisation is weak and re-identification is possible, you mislabelled the data and every use of it has been outside the law. Academic literature is full of "anonymised" datasets that were re-identified with a few hours' work. The ICO expects a documented re-identification risk assessment before labelling anything anonymous.
K-anonymity
A dataset is k-anonymous if every combination of quasi-identifiers (attributes that could identify someone) appears at least k times. Example: if every combination of (postcode, age, gender) appears at least 5 times, the dataset is 5-anonymous. k=5 is often cited as a minimum; k=20 or higher for sensitive datasets. Generalise attributes (round age to 10-year bands, truncate postcode to first 3 chars) until each combination has enough cover.
Why k-anonymity is not enough
A classic counter-example: if in a 5-anonymous medical dataset all 5 people in one group have the same diagnosis, you know someone in that group has that diagnosis. The sensitive attribute is exposed even if the identity is not. L-diversity addresses this — within each k-anonymous group, the sensitive attribute should have at least l different values.
Auxiliary-data attack
Re-identification is often done by combining the "anonymous" dataset with outside information. Example: an anonymised medical dataset combined with voter rolls (name, postcode, date of birth) re-identifies patients. Netflix's "anonymous" movie ratings were re-identified by matching to IMDb ratings. Never assume your dataset exists in isolation — consider what public or purchasable data could be combined with it.
The test itself
1) Identify all quasi-identifiers in the dataset (attributes that could narrow to an individual when combined). 2) Check k-anonymity on those attributes — count unique combinations and their frequency. 3) Check l-diversity on any sensitive attributes. 4) Attempt re-identification using publicly available data (voter rolls, public records, LinkedIn, Companies House). 5) Document the result and decide: anonymous, pseudonymous, or personal data.
Synthetic data
An alternative approach: generate a synthetic dataset with similar statistical properties but no real individuals. The synthetic data preserves analytics value with zero re-identification risk (if done right). Tools: SDV, Mostly AI, Synthea (medical). Synthetic data is a rising practice and the ICO has written positively about it.
Differential privacy
A mathematical approach: add calibrated noise to each query or release so that individuals cannot be identified even with auxiliary data. Used at scale by Apple, Google, and the US Census. Complex to implement right but is the gold standard for "definitely anonymous" publication. Worth considering for any dataset you want to share publicly.
Practical decision
For most small business use cases, true anonymisation is harder than it looks. Pseudonymisation is usually enough: it provides real protection, keeps the data useful, and GDPR still applies (so you use it within your lawful-basis framework rather than outside it). Reserve the anonymisation label for genuinely public releases that have passed a proper test.
Tags: gdpr, anonymisation, re-identification, k-anonymity, technical, privacy
Tutorial
A Monthly Compliance Checklist for Small Businesses
Compliance is a rhythm, not a one-off. This practical 30-minute-a-month checklist keeps your GDPR posture current without a full-time compliance team.
Why a rhythm matters
One-off compliance efforts rot. New vendors get added, policies get out of date, staff forget the rules, processes drift. A predictable monthly touchpoint — 30 minutes with a checklist — catches most drift before it becomes a problem. This tutorial is that checklist.
Week 1 — processor and vendor review
Open your vendor register / ROPA. For each processor: check their status page for DPA changes, subprocessor additions, data-region changes. Review any that changed. Flag anything that might need privacy policy updates. Check your own account's data retention settings in each major tool (GA4, Mailchimp, Stripe, Clarity) — confirm they match your published policy.
Week 2 — review pending requests and complaints
Open the privacy inbox (privacy@yourdomain.com). Review: all DSARs received this month (status, deadlines, completed); all complaints received (resolution, any pattern); any open breaches in the breach register (status, remediation). Anything still open — escalate. Anything closed — ensure the audit trail is complete.
Week 3 — consent and retention audit
Pick one category from your retention schedule and verify deletion actually happened. For example: "Contact form submissions older than 24 months from last contact should be deleted." Query the database for records older than 24 months. If any exist, run the deletion. If the deletion process is broken, fix it. Rotate which category you audit each month so every category gets checked annually.
Week 4 — staff and training checkpoint
Check: any new staff onboarded this month who need the data-protection briefing; any staff who left, whose access should be removed from each system; any incidents or near-misses that should trigger a team-wide reminder. Quarterly, re-brief the whole team. Annually, do the full briefing for everyone. Log attendance.
Monthly items across the month
Throughout the month, handle as they arise: a new processor onboarded — update ROPA and privacy policy immediately; a new data category added to the site — update the policy; a new marketing campaign — verify consent/soft-opt-in; a vendor DPA update notification — read it, assess impact, file. Do not batch these for the next month; fix when they appear.
Quarterly add-ons
Every 3 months: review the ROPA for accuracy (does it reflect current processing?); review the privacy policy for accuracy (does it match current processing?); review pending LIAs and DPIAs — any that need refresh? Rotate audit focus: Q1 — marketing; Q2 — HR/employees; Q3 — product; Q4 — vendors. Each quarter pick one area and go deeper than the monthly routine.
Annual big-ticket items
Once a year: full policy rewrite / regeneration (Kit Builder makes this easy); staff-wide training session; breach register review (lessons learned patterns); vendor register full-audit with DPA re-read; review whether any new ICO code of practice applies to you. Schedule these — usually the calendar quarter where your business is quietest.
Tags: gdpr, checklist, maintenance, monthly, compliance, operational
Tutorial
How Site Audit Pro Works — Real Data, Honest AI
AI cannot crawl your site or count your backlinks. AI can only guess. This audit does not guess — every number comes from a real industry-standard database. Here is exactly what the AI does, what it does not do, and why the difference matters.
The plain truth about AI and data
A large language model — ChatGPT, Claude, Gemini, any of them — is a frozen snapshot of public internet text from when it was trained, usually 6 to 18 months ago. It cannot crawl. It cannot query Google. It cannot count backlinks on your site. When asked "how many referring domains does this site have?" the model does not know. It has no live access to anything. If it answers with a specific number, that number is invented — pattern-matched from similar text it saw during training, presented confidently as fact. This is called hallucination.
Why this matters for SEO tools
A growing number of products are sold as "AI SEO audits" or "AI-powered SEO insights." Many of them are nothing more than a chatbot wrapper. You give them a URL; the chatbot writes a confident audit; the numbers in the report are fabricated. Backlink counts, ranking positions, domain authority scores, traffic estimates — invented. Decisions get made on bad data. Money gets spent fixing problems that do not exist. Real problems go undetected because the AI did not know about them. People lose trust in SEO tooling generally, often without realising the tool they used was a fake.
The three kinds of AI SEO tool
One: AI without tools. A pure chatbot pretending to do SEO. Numbers are hallucinated. Avoid. Two: AI with real data tools — the AI orchestrates real API calls to real databases, then explains the real results in plain English. This is what we built. Three: tools that look like type two but secretly are type one — they market AI heavily, fetch a tiny scrap of real data, then have the chatbot extrapolate confidently from it. The danger of category three is that the surface looks legitimate. You see the brand, you see the report, you trust it. Only by understanding what data each number specifically came from can you tell whether you are looking at real measurement or AI fiction.
What we actually do — real APIs end to end
Every section of Site Audit Pro runs against a real data source. Our own crawler fetches your page, parses the HTML, and inspects it directly for on-page signals (title, meta, headings, schema, canonical, hreflang, accessibility, internal links, images). Performance scores come from Google's official PageSpeed Insights API and Google's official Lighthouse audit — the exact same checks Google itself uses. Backlink, ranking, and authority data come from an industry-standard SEO database that has indexed billions of links and tracks live Google rankings across hundreds of countries — real infrastructure, real crawling, real storage, costing real money to operate. Brand SERP data is fetched from live Google search results in your selected country. Whois data comes from public domain registration records. Technology stack detection scans the actual page bytes. Every figure has a real source we can point to.
Why real data costs money
Maintaining a real SEO database requires servers crawling the web 24/7, storage for billions of indexed links going back years, daily querying of Google's actual search results across hundreds of thousands of keywords, and proxy networks to do that querying without being blocked. This is millions of pounds of infrastructure per year. That is why a real database charges per query, and why every Site Audit Pro run costs credits. We pay for each query we make on your behalf. If a competitor offers unlimited "AI SEO audits" for £10 a month, ask yourself where their data is coming from. Real data is not free, and any tool that claims to deliver it for free is either lying about what it delivers or burning investor money to acquire customers before the bill comes due.
Where AI fits — and where it does not
AI in this tool is purely cosmetic — used only after the real numbers have come back from real APIs. It picks the right plain-English wording for each issue ("Your writing is too hard to read" instead of "Flesch score 38 — difficult"). It selects the right "fix this by..." suggestion from a library of pre-written remedies. It organises issues by severity and groups them by category. None of those tasks require knowing anything about your specific website — they are pure language work. AI never invents a measurement. AI never estimates without a measurement to estimate from. If a number is shown, it came from real data.
What AI is genuinely good at
AI is excellent at: translating technical findings into plain language, categorising large volumes of issues, spotting patterns across many real data points, summarising long reports, suggesting prioritisation, and writing remedies. These are language tasks, not data tasks. We use AI heavily for the surface layer of every report — writing, organising, presenting. We do not use AI to invent the numbers underneath because, simply, AI cannot. Hand any LLM a real data feed and it will do brilliant explanatory work. Ask it for raw measurements without a feed and it will hallucinate.
How to spot a fake AI SEO tool
Three questions reveal it. One: ask the tool which specific external APIs or databases it queries to produce its numbers. A legitimate tool can name them in general terms. A fake will dodge or wave its hands about "proprietary AI." Two: run the same audit twice on the same URL within a short window. A real tool returns the same numbers (the underlying data has not changed). A hallucinating tool returns slightly different numbers each time because the AI generates fresh fabrications. Three: cross-check a single specific number — say, referring domain count — against a free tool like Google Search Console (for your own site) or another reputable database. A real tool agrees within a small margin. A hallucinating tool can be wildly off.
What this audit cannot do
Real data has limits. We do not crawl entire sites — the audit checks the single URL you submit. We cannot see private analytics, conversions, or revenue. We cannot tell you whether your traffic is "good enough" — only what positions you hold and what those positions typically deliver. We cannot guarantee a fix will move your rankings, only that the issue we flag is something Google considers a ranking signal. We cannot predict the future. The audit is a diagnostic tool. It tells you what is broken, what is unusual, and what is worth investigating. It does not replace ongoing strategy work, and it does not replace human judgement.
How to use this honestly
Run the audit when something changes — a redesign, a content overhaul, a domain migration, after fixing an issue you want to verify is gone. Use the History feature to track issue counts month over month. Use the "vs Previous" diff after major changes to confirm what actually moved. Use the CSV or PDF exports to share findings with team members or clients. Trust the numbers. Question the wording — the AI-written explanations are reviewed but not infallible. If something feels off in the prose, the underlying data is still solid; just consult the source link or ask us to refine the wording.
Tip: If anyone selling you SEO software cannot tell you exactly which APIs return the numbers in their report, the numbers are probably hallucinated. Ask: "where does this backlink count come from? What database returned it?" If they cannot answer in concrete category terms, walk away. Real data sources can always be named in general terms — even when specific vendors are kept commercially confidential.
Tags: audit, methodology, data-sources, about, how-it-works, ai-honesty
Tutorial
A backlink is when another website links to yours. Backlinks are one of Google's strongest ranking signals. This guide explains what counts, what does not, what makes a good backlink versus a bad one, and how to earn them without crossing into territory that gets you penalised.
What a backlink actually is
A backlink is a clickable link from one website pointing to another. If the BBC writes an article and links to your shop, that is a backlink. If a forum post mentions your product and links to it, that is a backlink. The link itself is just an HTML <a href="..."> element. What matters is the website it sits on — its trust, relevance to your topic, and how Google ranks it.
Why Google treats them as votes of confidence
Google's original ranking algorithm — PageRank — was built on a simple idea. If lots of other websites link to a page, that page is probably useful. If important websites link to it, it is probably very useful. Each backlink acts like a vote. Decades of refinements have added nuance — the link's context, the linking site's topic, the anchor text used — but the core idea has not changed. Backlinks are still one of the top three Google ranking factors.
Dofollow versus nofollow
There are two flavours of backlink. A normal link passes authority — Google counts it as a vote. A "nofollow" link has rel="nofollow" added by the linking site, which tells Google "I am linking to this but I do not necessarily endorse it." Wikipedia, Reddit, social media platforms, and most user-comment sections use nofollow on outbound links. Nofollow links pass less SEO value but are not worthless — they still drive traffic, build brand awareness, and Google increasingly treats them as soft signals rather than ignoring them entirely.
What makes a backlink valuable
Five things matter. One: the linking site's authority. A link from a major newspaper is worth more than a link from a personal blog with no readers. Two: relevance. A link from a website in your industry is worth more than a link from an unrelated site. Three: editorial nature. A link a journalist chose to include is worth more than a link in a paid placement or sidebar widget. Four: anchor text. The clickable words tell Google what the linked page is about. Five: traffic. A link that real humans actually click on is worth more than one buried in a footer.
What makes a backlink toxic
Some backlinks actively hurt you. Links from spam farms, hacked sites, sites Google has already penalised, link-exchange networks, comment spam, and forum signatures from low-quality forums all signal manipulation. If too many of your backlinks come from these sources, Google may issue a manual action — which can wipe out your rankings entirely. Modern Google can usually ignore the worst offenders automatically, but a heavily manipulated profile is still risky.
How to earn them legitimately
Real backlink building takes time. The strategies that work in the long run: publish content other sites genuinely want to link to (data, original research, free tools, definitive guides); guest articles on relevant industry sites; PR and outreach to journalists who write about your space; partnerships and supplier relationships where mutual linking is natural; podcast appearances; broken-link building (find dead links on relevant pages, suggest your URL as the replacement); HARO/Help A Reporter style services. None of these are instant. Backlink growth is measured in months, not days.
What to avoid at all costs
Buying backlinks. Renting backlinks. Joining link-exchange networks ("I link to you if you link to me"). Posting comment spam on blogs. Forum signature spam. Private blog networks (PBNs). Auto-generated content sites built to host outbound links. Anything that promises hundreds of backlinks for a small fee. Google has spent over a decade hunting these patterns. They are detected. They are punished. The shortcuts cost more in lost rankings than the slow legitimate path.
How to check your profile
Site Audit Pro's backlinks summary section pulls real numbers from an industry-standard backlink database — total backlinks, total referring domains, plus a domain rank score. The spam score, top anchor texts, and top referring domains sections give you the texture of the profile. New sites should expect very low numbers. Established sites should see steady growth. A sudden drop usually means a major linking site removed your link or got penalised itself.
Tip: Quality always beats quantity. Ten backlinks from real journalists at relevant publications will outrank a thousand backlinks from low-quality directories every single time. Stop counting backlinks; start counting referring domains, and ask yourself whether each of those domains is somewhere a real person would actually visit.
Tags: backlinks, links, authority, ranking-signals, off-page-seo
Tutorial
Backlink Spam Score Explained — and What to Do About a High One
Backlink spam scores measure how toxic your link profile looks to search engines. A high score can wipe out your rankings. This guide explains what the score actually measures, how to identify the worst offenders, and how to disavow toxic links cleanly.
What a spam score is
Industry-standard SEO databases assign each backlink a "spam score" — a probability (0 to 100) that the linking domain is spammy or low-quality. The score is calculated using dozens of signals: the linking site's age, content thinness, outbound link patterns, suspicious anchor text distribution, presence of typical spam markers (auto-generated content, hidden text, link selling, excessive ad placement). Your overall profile spam score is the aggregate across all your backlinks.
Why the number matters
Google does not publicly use a "spam score" itself, but it uses similar signals when evaluating link profiles. A high spam score usually correlates with one of three things: manipulative link building (the site owner bought low-quality links), being targeted by negative SEO (a competitor pointing toxic links at you to harm your rankings), or having attracted natural links from genuinely low-quality sites in your industry. The first two are problems you must fix. The third is usually noise Google ignores automatically.
What is "high"
Under 10: clean profile, nothing to worry about. 10 to 20: typical for any established site, mostly noise. 20 to 30: yellow flag, worth investigating. 30 to 50: real risk, you should audit and likely disavow. Over 50: serious risk, you may already be experiencing ranking suppression. The scores are not absolute — context matters. A small site with 10 backlinks scoring 30 average is more concerning than a large site with 10,000 backlinks where 5% are spammy.
Identifying the worst offenders
Open the backlink data and sort by spam score, highest first. Look at the top 50 to 100 worst-scoring backlinks. For each, ask: do I recognise this site? Did I (or a paid SEO agency) build this link deliberately? Is the linking page real content or auto-generated spam? Is the linking site in a language I do not target, with no business reason to link to me? Toxic links usually have several red flags: foreign-language sites unrelated to your topic, sites with thousands of outbound links per page, sites whose homepage looks auto-generated.
When to disavow versus ignore
Google explicitly says disavow is "an advanced feature, only use it if you really believe links are causing problems." If you have not personally built or paid for the toxic links, and your rankings are stable, the safest thing is to leave them alone — Google's algorithm handles most spam automatically. Disavow when: you have a manual action notification in Search Console; you bought links and want to clean up before getting penalised; you have a clear pattern of negative SEO (sudden burst of garbage links from one source); your ranking dropped suddenly with no other explanation.
How to actually disavow
Build a text file (.txt, UTF-8). Each line is either a single URL ("http://spammy-site.com/page") or an entire domain ("domain:spammy-site.com"). Domain-level disavow is more aggressive — it tells Google to ignore every link from that domain, present and future. Comment lines start with #. Upload via Google Search Console > Disavow links tool. Wait 4 to 8 weeks for Google to recrawl and reprocess. Track ranking changes. Be conservative — over-disavowing legitimate links can hurt you.
Preventing future problems
Audit incoming links monthly. Avoid services that promise huge backlink volumes. Be cautious with directory submissions and forum signatures. If you hire an SEO agency for link building, ask exactly which sites they will pursue, get a sample list, and approve each domain individually. Most penalties come from cheap link-building campaigns done in bulk without owner review.
Tip: A high spam score is rarely fatal on its own. The pattern that triggers Google penalties is a sudden flood of low-quality backlinks all using exact-match commercial anchor text ("buy cheap insurance" pointing to your insurance page). Spread-out, varied, gradual — even if some are low-quality — usually rides through.
Tags: backlinks, spam-score, disavow, toxic-links, penalty
Tutorial
Anchor Text Distribution — How to Avoid Looking Like a Spammer to Google
Anchor text is the clickable text in a backlink. Get the distribution wrong and Google flags you for manipulation. This guide explains what a healthy ratio looks like and how to fix one that has tipped over.
What anchor text is
When site A links to site B, the visible clickable words are the anchor text. <a href="https://yoursite.com">click here</a> has anchor text "click here." <a href="https://yoursite.com">best running shoes London</a> has anchor text "best running shoes London." Anchor text is one of Google's strongest topical signals — it tells Google what the linked page is about.
Why distribution matters more than individual anchors
Google does not look at one anchor text in isolation. It looks at the pattern across all your inbound links. A page where 80% of incoming links use "buy cheap car insurance" as anchor text looks manipulated — real people linking organically would use a mix: the brand name, the URL itself, descriptive phrases, generic phrases. A heavily skewed pattern triggers the Penguin algorithm to suppress that page's rankings.
The five anchor text categories
Branded: contains your brand name only ("aiwebpageseo" or "aiwebpageseo.com"). Naked URL: just the URL itself ("https://aiwebpageseo.com"). Generic: meaningless phrases ("click here," "this article," "more info," "read more"). Partial-match: includes some target keywords plus other words ("the SEO audit guide on aiwebpageseo"). Exact-match: the exact target keyword ("SEO audit tool"). Each category sends different signals.
What a healthy ratio looks like
For a normal website with no manipulation, you expect roughly: 50-60% branded, 15-25% naked URL, 10-20% generic, 5-15% partial-match, under 5% exact-match. The exact percentages vary by industry and content type, but the shape is always brand-heavy with exact-match as a small minority. Sites with too much exact-match anchor get flagged.
How over-optimisation happens
It almost always comes from paid or manipulative link building. An SEO agency promising "ranking improvements for SEO audit tool" builds 100 links all using "SEO audit tool" as the anchor. The result: you rank for that term for a few weeks, then Penguin catches up and you lose all rankings. Genuine organic linking never produces a 100% exact-match profile because real people use varied language.
How to diagnose your profile
Look at your top anchor texts in the audit's "Anchor text distribution" section. Calculate roughly what percentage falls in each category. Red flags: any single keyword phrase makes up more than 10% of all anchors; commercial phrases ("buy", "cheap", "best", or money keywords) dominate; the brand name appears in less than 30% of anchors. If you have the ratio wrong, this is fixable.
How to rebalance
Two paths. Path one (slow but safe): keep building backlinks naturally, focusing on branded and naked-URL anchors, until the bad anchors become a small percentage of the total. Path two (faster but risky): identify the worst-offending links and disavow them, then continue building branded links. Path two requires confidence that the bad links were paid or manipulative — if they were natural editorial links from real journalists who happened to use a keyword phrase, disavowing damages your authority unnecessarily.
Building anchor diversity from scratch
When pursuing new backlinks, do not request specific anchor text from the linking site. Let them choose. Most editors will use your brand name or a partial-match phrase naturally. Resist the temptation to ask for "best [keyword]" anchors — Google flags this. Trust that real editorial backlinks with varied natural anchors rank you better long-term than forced exact-match anchors that ride high briefly then crash.
Tip: If you spot a single keyword phrase making up more than 20% of your incoming anchors, you have a problem worth investigating immediately. Real editorial links never produce this pattern naturally. Either you (or someone you paid) built those links, or someone is conducting negative SEO against you.
Tags: backlinks, anchor-text, over-optimisation, penguin, on-page-seo
Tutorial
Referring Domains — Why 100 Links from 10 Sites Beats 100 Links from 1 Site
Total backlink count is a vanity metric. The number that actually moves rankings is unique referring domains. This guide explains why diversity matters and how to grow yours legitimately.
The difference, simply
Total backlinks counts every link pointing to you, even multiple from the same site. Referring domains counts only unique websites linking to you. If a single site has linked to you 50 times, that is 50 backlinks but only 1 referring domain. Google cares far more about the second number.
Why diversity weighs heavier
Imagine two sites. Site A has 1,000 backlinks all from a single forum that mentions it in every thread. Site B has 100 backlinks from 100 different websites — newspapers, blogs, business directories, partner sites. To Google, Site B looks like a credible business that lots of independent people are talking about. Site A looks like one obsessive fan, and Google heavily diminishes the value of repeated links from the same source. Site B will outrank Site A almost every time.
Why a single source has limited value
Once Google sees the first link from a domain, additional links from the same domain pass progressively less authority. The 100th link from a site is worth perhaps 1% of what the 1st link was worth. From an algorithmic standpoint, one domain can only "vote" for you once with any meaningful weight. After that, you need votes from other sources to keep climbing.
What a healthy growth rate looks like
Brand new sites: gaining 3-10 new referring domains per month is realistic in the first year. Established sites in active content marketing: 20-50 per month is healthy. Major brands: hundreds per month. Sudden spikes (from 5 to 500 in one week) usually mean either a viral content moment or a paid link campaign — Google scrutinises spikes carefully. Steady, gradual growth is far less suspicious than burst patterns.
How to grow them
Each strategy targets adding a new domain, not just more links. Guest posting on industry sites, getting featured in journalist roundup articles, sponsoring industry awards (which often link from organiser sites), creating tools or data others reference (each citation = a new domain), running webinars with co-hosts (everyone's site links yours), partnerships with suppliers or vendors that include "our partners" pages, getting listed in legitimate niche directories. Each conversation focuses on one new domain.
Domains to actively avoid
Even good domains can become bad. Avoid: domains ending in unusual TLDs (.xyz, .top, .click) that have a high ratio of spam usage; domains whose entire purpose appears to be hosting links to other sites (link farms); private blog networks (clusters of sites owned by one person, all interlinking); newly-registered domains with no real business purpose; sites that link to obviously unrelated topics in many languages. A link from a junk domain dilutes your profile.
How to track progress
Site Audit Pro's referring-domains panel lists your unique linking domains, sorted by backlink count from each. Over time you want to see this list growing — not necessarily growing faster from existing entries, but growing by adding new entries at the bottom. Use the audit's history feature to track this month-over-month. If your total backlinks go up but referring domains stay flat, someone is just adding more links from the same sites — not real growth.
When fewer is better
A clean profile of 50 high-quality referring domains beats a profile of 500 referring domains where half are toxic. If you inherit a site with 1,000+ referring domains and most are low-quality, the work is removing or disavowing the bad ones, not adding more. Quality trumps everything.
Tip: Treat referring domain growth as a key health metric of your SEO. Set a quarterly target — say, 30 new high-quality referring domains per quarter — and track progress against it. This single number predicts ranking growth better than almost anything else.
Tags: backlinks, referring-domains, authority, link-diversity
Tutorial
How Google Ranking Distribution Works — and Where Your Quick Wins Hide
Of all the keywords you rank for in Google, where do they sit? Most sites have lots of keywords ranking on page 2 or 3 that, with a small push, would land on page 1. This guide explains how to read your distribution and find them.
What ranking distribution shows
Imagine plotting every keyword your site ranks for in Google on a chart by position. Position 1 is the best (top of page 1), position 100 is the worst Google will track. You probably have a long tail — maybe a handful in positions 1-3, more in 4-10, hundreds in 11-30, thousands further down. The distribution shape tells you a lot about the state of your SEO.
Reading the shape
A site with most rankings in positions 50-100 is "barely visible" — Google knows you exist but is not rewarding you with traffic. A site with most rankings in 11-30 is "knocking on the door" — close to page 1 for many terms, and small improvements (better content, a few new backlinks, internal-link reshuffling) can push them onto page 1. A site with strong distribution in 1-10 is well-established. Truly dominant sites have hundreds in positions 1-3.
Where the easy wins are
Positions 11 to 20 are the goldmine. These keywords are ranking on Google's page 2. The user has clicked through page 1 and not found what they wanted. They are seeing your result. Improving these by even 3-5 positions moves them onto page 1, where click-through rates are 5-10x higher. The work is usually small: a content refresh, a few new internal links, a few new backlinks pointing at that specific page.
What positions matter for traffic
Click-through rates by position drop sharply. Position 1 typically gets 25-30% of clicks for that keyword. Position 2 gets 12-15%. Position 3 gets 8-10%. Position 5 gets about 6%. Position 10 gets under 3%. Anything below position 10 (page 2 onwards) typically gets under 1%. This is why "ranking" alone is not enough — ranking outside the top 5 means losing most of the available traffic.
How visibility is calculated
Visibility scores combine your ranking positions with the search volumes of those keywords. Ranking position 1 for a 10,000-monthly-search keyword is worth far more than ranking position 1 for a 50-search niche term. Industry-standard databases assign each keyword a weighted value and sum them across your portfolio. Watching this number trend month-over-month tells you whether you are gaining or losing presence in Google overall.
How to identify your specific quick wins
In Site Audit Pro's ranked keywords panel, sort by position and filter for positions 11-20. Then sort by search volume — the high-volume ones in this range are your priority list. For each: review the page that currently ranks (is the content depth right? are images optimised? does the page load fast?), check whether your competitors at positions 1-10 have better backlink profiles, and identify what you would need to add to leapfrog them.
Realistic timeline
Moving from position 15 to position 5 typically takes 1-3 months of focused work — content improvement plus a small handful of strong new backlinks. Moving from position 50 to position 10 takes longer (6-12 months) because the gap suggests deeper issues. Moving from position 5 to position 1 is the hardest of all because the existing top results are usually well-defended; it can take a year of sustained work or never happen at all.
Tip: Your competitors are watching the same data. The keywords sitting in positions 11-20 are also their quick wins if they happen to rank a few positions ahead of you. The race is who improves first. Move on quick-win keywords aggressively when you spot them, before someone else does.
Tags: rankings, visibility, keyword-research, serp-positions
Tutorial
How Organic Traffic Estimation Works
Estimated traffic is not the same as Google Analytics. This guide explains how it is calculated, when it is useful, and when to ignore it.
What traffic estimation is
Estimated traffic is a calculation, not a measurement. The system looks at every keyword you rank for, multiplies the keyword's monthly search volume by the typical click-through rate at your ranking position, and sums the result across all your keywords. The output: roughly how many visitors should arrive on your site from Google each month.
Why it differs from Analytics
Google Analytics shows actual visits — what really happened. Estimated traffic shows what should happen if click-through rates exactly match the average. They will never match exactly. Your branded keywords (where users searched your name) often beat the average CTR. Your highly competitive informational keywords might fall below it. Brand recognition, SERP features (rich snippets, ads above you), seasonal trends, and a hundred other factors mean the actual number drifts above or below the estimate.
When to trust the number
Use traffic estimation for: comparing your visibility to competitors (apples to apples — both numbers calculated the same way); tracking visibility trend over time (if estimated traffic is up 20% month-over-month, your real traffic almost certainly is too); spotting sudden drops (drops of 30%+ usually indicate a Google algorithm hit, lost backlinks, or technical problems); estimating traffic potential for a new market or country before you invest there.
When to ignore the number
Do not use traffic estimation for: business reporting (use Google Search Console clicks and Google Analytics instead — they are real); revenue forecasting (the gap between estimate and reality could be 30% in either direction); ranking-monitoring decisions on individual pages (check actual GSC clicks for that page); paying suppliers based on "delivered traffic" (only Analytics is auditable).
How country selection affects it
Traffic estimates are country-specific. The keyword "running shoes" has different search volume in the UK vs USA vs Australia. Make sure the country selected for the audit matches your target market. If you trade across multiple countries, run the audit for each — your traffic estimate from a UK perspective will look very different from a US perspective.
How to use it strategically
Three strategic uses. One: pre-launch traffic forecasting for a new content cluster — what is the maximum visibility the keyword set could earn? Two: competitor benchmarking — how much organic traffic are similar-sized rivals capturing, and what keywords are driving theirs? Three: lost-traffic recovery — when traffic drops, the audit's ranked-keywords data shows which specific keywords you have lost positions for, narrowing the recovery investigation.
Combining estimation and Analytics
The best approach uses both. Google Analytics tells you exactly what happened — every visit, every page, every conversion. Estimated traffic tells you what could happen if you owned the rankings the data shows you having. The gap between them is your CTR optimisation opportunity: if estimate says 10,000 visits and actual is 6,000, your titles and meta descriptions are likely under-performing — visitors are seeing your result but not clicking. Test new title formulations.
Tip: When a tool quotes you a precise traffic figure (47,832 visits per month), treat it as a range. The honest number is "between roughly 30,000 and 65,000 visits per month, with something around 45,000 most likely." Use estimates for direction, not for spreadsheet inputs.
Tags: traffic, analytics, estimation, visibility, forecasting
Tutorial
Reading Ranked Keyword Reports — Find What is Actually Driving Your Traffic
The list of keywords your site ranks for in Google is your real-world SEO inventory. This guide explains how to read the data and find which keywords are worth investing in.
What ranked-keywords data shows
Industry-standard SEO databases watch Google search results across millions of queries. When your site appears in any result, the database records: the keyword, the page on your site that ranks, your position, the search volume of the keyword, and the keyword difficulty. Over time it builds a complete picture of every term your site shows up for.
The five columns that matter most
Position: where you rank (1 is best). Volume: monthly searches for that keyword. Difficulty: how hard the keyword is to rank for, 0-100 (low = easy). Your ranking URL: which page on your site ranks for it. CPC (cost per click): how much advertisers pay per click in Google Ads — a strong indicator of commercial value. A keyword with low difficulty, decent volume, and high CPC is gold.
Finding "money keywords"
High-CPC keywords reveal what businesses are willing to pay to acquire customers from. If a keyword has a £15 CPC, advertisers are paying £15 every time someone clicks an ad for it — meaning each visitor is valuable enough to justify that. Sort by CPC descending. Keywords above £5 CPC where you currently rank in positions 4-20 are your highest-leverage targets — even modest ranking improvements can produce big revenue impact.
Recognising informational vs commercial
Keywords split into intent buckets. Informational ("what is X", "how to Y", "Z explained") brings visitors who are researching, not buying. Commercial ("best X", "X reviews", "compare X and Y") brings visitors evaluating options. Transactional ("buy X", "X pricing", "X discount code") brings visitors ready to purchase. Each bucket has its purpose. Informational builds authority and email signups. Commercial captures comparison shoppers. Transactional drives revenue. Your portfolio should target all three.
Brand vs non-brand split
Group your keywords by whether they include your brand name. Brand keywords ("yourbusiness reviews", "yourbusiness pricing") are usually 30-60% of your traffic — these visitors already know you. Non-brand keywords are new audience reach. The healthy ratio depends on age and marketing spend, but new sites should aim to have non-brand keywords outnumber brand keywords by at least 3:1 — otherwise you are not growing your reach.
Keyword difficulty in context
Difficulty scores estimate how strong the existing top-10 results are. A score of 80 means the current top 10 have strong domains, lots of backlinks, well-optimised content. To outrank them you would need to be at least as strong. A score of 20 means the current top 10 are weaker — possibly you can outrank them with a single well-written article and a small handful of backlinks. New sites should focus on keywords scoring under 30 until authority is built.
Spotting cannibalisation
When two pages on your site rank for the same keyword, neither ranks as well as one consolidated page would. Look for keywords where multiple URLs from your site appear in the top 100. The fix is consolidation: pick the strongest of your competing pages, redirect the others to it, and any backlinks they had now flow to one page instead of being split.
Building from this data
Three practical actions. One: identify keywords where you rank position 11-30 with high volume and low difficulty — focus content updates and link building on those. Two: identify keywords where competitors rank but you do not — content gap analysis. Three: identify your top 20 ranking pages and ask "what keywords does each one NOT rank for that it should?" Often a small content expansion captures 5 to 10 additional related keywords per page.
Tip: Ranked keyword data is most valuable when paired with Google Search Console clicks data. The audit tells you what positions you hold; Search Console tells you which positions actually convert into clicks for your specific titles and descriptions. Together they reveal your title/meta-description optimisation gaps.
Tags: keywords, rankings, visibility, keyword-research
Tutorial
Tracking Your Visibility Trend Over Time
Are you gaining or losing in Google? Historical rank data answers that. This guide explains how to read the trend, when to investigate dips, and what changes typically cause them.
What historical rank tracks
Industry-standard databases sample Google rankings across hundreds of thousands of keywords daily. Over time this builds a month-by-month chart of your overall visibility. The visibility score is a weighted aggregate — your ranking positions multiplied by keyword search volumes — converted into a single trendable number. Up means more visibility. Down means less.
Reading the shape
Steady upward growth is the goal. Flat with slight upward drift indicates you are maintaining position. A sharp upward spike usually means you broke into a high-volume keyword group. A sharp drop is almost always one of: a Google algorithm update; lost backlinks; technical issues (bad redirects, crawl problems, accidental noindex); a competitor gaining strength; a domain migration gone wrong.
Investigating drops
A 10-15% drop in a single month usually does not need investigation — it is normal volatility. A 30%+ drop almost always needs investigation. First check: did Google issue a named algorithm update around the drop date? (Resources like SERoundtable and the SEO industry press track these.) Then check Search Console for manual actions and crawl errors. Check your top ranking URLs are still indexable (not accidentally noindexed or 404'd). Check whether any large referring domains have removed your backlinks. Check whether competitors have gained strength.
Algorithm-update signatures
Google updates have characteristic signatures. Core updates affect almost everyone, with winners and losers across all industries — typically rolling out over 1-2 weeks. Helpful Content updates target sites with thin or AI-generated content — drops are sharp and broad. Spam updates target manipulative practices — drops are localised to sites with iffy backlinks or content. Product Reviews updates target review sites specifically. The signature in your traffic data plus the date often identifies which update hit you.
When growth stalls
Plateaus happen. Common causes: you have hit the ceiling of your current content footprint and need to publish on new topic clusters; backlink growth has stagnated and you are not gaining new authority; competitors are improving faster than you; your content has aged and needs refreshing. Plateaus are usually a content or linkbuilding signal — keep doing what is working but expand the scope.
Comparing year-over-year
Some industries are seasonal. A retailer might see traffic drop every January regardless of SEO performance — Christmas shopping is over. Comparing this January to last January (year-over-year) controls for seasonality. Always compare the same period rather than month-to-month if your industry has seasonal patterns. The audit's historical view shows 12 months of data, which is enough to spot seasonality.
Acting on the trend
Use historical rank as a single key metric in monthly SEO reporting — alongside organic traffic, keyword count, and referring domains. When all four are trending up together, your strategy is working. When they diverge, the divergence reveals what is happening. Visibility up but traffic flat? CTR problem. Backlinks up but visibility flat? Quality of new links is poor. The combinations tell the story.
Tip: New sites should expect zero or near-zero visibility for the first 3-6 months while Google builds trust. If your historical rank graph is flat at zero, that may not be a problem — it may just be too early. Visibility usually starts moving in months 4-9 if the underlying SEO is sound.
Tags: rankings, visibility, historical-data, trends, algorithm-updates
Tutorial
Identifying Your Top-Performing Pages — Where the 80/20 Rule Hides
On most websites, 20% of pages drive 80% of traffic. This guide explains how to find your winners, double down on them, and decide what to do with the rest.
Why this analysis matters
Pages do not contribute equally. A few pages on your site do most of the heavy lifting in Google. They earn most of the rankings, attract most of the backlinks, and bring most of the traffic. Identifying these pages is the first step to scaling what works. Without this analysis, you waste effort improving pages that have no measurable upside while ignoring the pages that could deliver real returns.
How "relevance" is measured
Industry-standard SEO databases score every page by combining its ranking keywords (how many it ranks for, in what positions, with what volumes) with its incoming links (how many backlinks, from how many domains). The output is a per-page score. Sorting by this score reveals your top performers — typically the homepage plus a handful of cornerstone content pages.
The shape you usually see
The top 10 pages on most sites earn 60-80% of the total search visibility. Pages 11-50 earn another 15-25%. Pages 51-onwards earn the remainder, often individually negligible. This Pareto distribution is normal and predictable. The exception is huge content sites (10,000+ pages) where the top performers might be only 1% of total pages but still dominate.
What to do with top performers
Protect them, then leverage them. Audit each top-10 page quarterly: is the content still accurate? Has Google added new SERP features (rich snippets, FAQ dropdowns) that you should target with schema? Are images properly optimised? Update them with fresh information. Add new sections covering recently emerging questions. Internal-link new content to these pages so they keep accumulating authority. A small improvement to a top page earns far more traffic than a large improvement to a tail page.
Finding orphans
Compare your top-pages list to your sitemap. Pages in your sitemap that do not appear in the top-pages list (or appear far down) are not being found by users. Common reasons: poor internal linking from elsewhere on your site, weak content, no backlinks, or unfortunate URL structure. These pages are candidates for either consolidation (merge into a stronger page and redirect), revival (substantial content rewrite + internal linking + outreach), or retirement (noindex or delete).
Spotting unexpected winners
Often a "top page" surprises you — a blog post you wrote three years ago and forgot about turns out to be your second-biggest traffic source. These accidental winners are valuable lessons. Why did that post catch on? What backlink did it pick up? What keyword does it own? Replicate the formula deliberately. Conversely, a page you expected to perform but does not is a signal something is wrong with it — usually content depth, keyword targeting, or technical issues.
Building a content clustering strategy
Top-performing pages are usually about a few specific topics. Clustering means writing more content around those same topics, with all of it internally linked to your top performer (the "pillar"). The pillar gains authority from cluster content. Cluster content gains authority from being linked from the pillar. Rankings rise across the entire cluster. This is the single highest-ROI content strategy for established sites.
Quarterly review
Run this analysis every quarter. The top-10 list shifts as new content gains traction or old content fades. Notice which pages enter the list, which fall off, and what changed. Over time this rhythm of review-and-double-down compounds — each quarter you have more high-performing pages contributing to your overall visibility.
Tip: Many site owners discover that 30-50% of their pages contribute essentially nothing — no traffic, no rankings, no backlinks. Aggressively pruning these (consolidating into stronger pages or removing entirely) often improves rankings on the remaining pages, because Google's crawl budget concentrates on what is actually valuable.
Tags: pages, content-strategy, pareto, top-pages, visibility
Tutorial
Brand SERP — Auditing What Google Shows When Someone Searches Your Name
When someone Googles your brand name, the first page of results is your digital reception desk. If it shows the wrong things, you lose customers. This guide explains how to audit and improve it.
Why brand SERP is critical
Branded searches are the highest-intent traffic you ever get. Someone typing your exact business name into Google has either heard about you and wants to find you, or already knows you and is on the way to becoming a customer. The first page of results decides whether they get there. If your homepage is position one, fantastic. If a competitor, an old negative review, or a Wikipedia stub about a different business entirely is position one, you are losing those visitors.
What "ideal" looks like
For a healthy brand SERP, position 1 is your homepage. Positions 2 to 10 ideally include: your About page, your contact page, your top product or service pages, your social media profiles (Facebook, Instagram, LinkedIn, X), your Google Business profile, possibly a Wikipedia page if you are large enough, and possibly recent press coverage. The whole first page should be content you control or content that is positive. The Google Knowledge Panel (the box on the right) should show your logo, location, and key facts.
Common problems
A competitor running ads or organic results for your brand name. Negative reviews dominating the first page. Old social profiles you abandoned years ago ranking ahead of your active ones. A Wikipedia page about something or someone else with the same name. AI-generated spam pages mentioning your brand. Disgruntled customer threads on Reddit or forums. Each of these is fixable but requires deliberate work.
Claiming your owned channels
First step: claim and optimise everything you own. Your Google Business profile (free to claim, free to populate). Trustpilot, Yelp, your industry-specific directories. Social profiles on Facebook, Instagram, X, LinkedIn, YouTube, TikTok, Pinterest, Threads, Bluesky — even if you do not actively post, the handle is yours and it ranks. Consistent business name, consistent logo, consistent description. Add your URL on every profile.
Competitor displacement
When competitors rank for your brand name organically, you usually need fresh well-optimised content on your own site to push them off. If they are running paid ads on your brand name, you can either: report it (some jurisdictions consider this a trademark violation, depending on context); run your own protective ads above theirs; ignore it and let your superior organic ranking take the click anyway. Most major brands accept brand-bidding ads as table stakes.
Reputation cleanup
Negative reviews on the first page are the hardest. You cannot delete them. You can: respond professionally and visibly to each one (Google rewards engaged businesses); generate a steady flow of positive reviews on the same platform to dilute the bad ones; address the underlying business issues that caused the bad reviews. For genuinely defamatory or false content, legal channels exist but are slow and expensive. Reputation is built; it cannot be deleted.
Schema and Knowledge Panel
Add Organization schema to your homepage with your name, logo URL, sameAs array (linking to all your social profiles), contactPoint, and address if relevant. This tells Google "this site, that brand, those social profiles, this contact info — same entity." Over time Google builds your Knowledge Panel from this data plus other signals. Consistent NAP (Name, Address, Phone) across the web reinforces it.
Ongoing monitoring
Run a brand SERP audit monthly. Watch what changes. New result appearing? Investigate. Existing result dropping? Check whether it should be reinforced. Site Audit Pro's brand SERP section gives you the snapshot. Combine with Google Alerts for your brand name to catch new mentions in real-time.
Tip: Try this from a private browser window, logged out, in your target country: search your brand name and screenshot the first page. Then do it once a month. The screenshot history reveals exactly when problems appear and what changed. Far more useful than just rankings data.
Tags: brand, serp, reputation, branded-search
Tutorial
Brand Sentiment Analysis — Spotting Reputation Issues Before They Cost You
Of all the mentions of your brand across the web, how many are positive, neutral, negative? This guide explains how sentiment scoring works, what is normal, and what to do about it.
What sentiment analysis does
A natural-language processing system reads each web mention of your brand — articles, reviews, forum posts, blog comments — and classifies the tone as positive, neutral, or negative. Aggregating these gives you a sentiment ratio: out of 100 mentions, how many were positive, how many negative. Tracking this ratio over time reveals reputation trends invisible in raw mention counts.
The healthy ratio
For most established businesses: 60-80% neutral (factual mentions, descriptions, listings), 15-30% positive (reviews, recommendations, satisfied customers), 2-10% negative (complaints, comparisons-where-you-lost, criticisms). Negative is normal — every business has unhappy customers. The question is whether your negative percentage is rising, and whether the negatives are random one-offs or a pattern.
Patterns that demand attention
Sentiment shifting from 5% negative to 15% negative over three months: a real reputation problem developing. Negative mentions clustered around a specific issue (poor delivery, broken feature, billing problem): a product or operational problem that needs fixing at source. Negative mentions on high-authority sites (major newspapers, popular YouTubers): potentially severe damage if not addressed. Positive mentions clustered around a competitor: that competitor is gaining mindshare relative to you.
Why Google cares
Google uses brand sentiment as part of its E-E-A-T signals (Experience, Expertise, Authoritativeness, Trust). Sites with predominantly negative reputation signals may be down-ranked. Sites with strong positive reputation signals get the benefit of the doubt on borderline ranking decisions. The signal is indirect and measured at scale — Google cannot read minds, but it reads patterns across thousands of mentions and reviews.
Distinguishing volume from sentiment
A surge in negative mentions can mean two things: a real reputation crisis, or simply more visibility (PR campaign, viral content, expansion into new markets — any of which exposes you to more critical eyes). Look at the absolute count of positive AND negative mentions together. If both are growing proportionally, that is healthy growth. If negative grows faster than positive, investigate.
Acting on negative sentiment
For each negative mention worth addressing, there is a playbook. Reviews on major platforms (Google, Trustpilot, Yelp) — respond professionally, offer to make it right, ask the reviewer to update if you resolve the issue. Forum complaints — engage where appropriate, do not get defensive, address the substance. News articles — only respond if there is a factual error to correct; rebuttals to opinion-based negative coverage usually backfire. AI-generated spam mentioning your brand negatively — these are everywhere and not worth engaging.
Building positive sentiment proactively
Positive sentiment does not appear from nothing. Make it easy for happy customers to leave reviews — email them after purchase, link to Trustpilot, automate it. Sponsor industry events, podcasts, and creators who reach your audience. Publish content that demonstrates expertise (case studies, original research). Engage authentically on social — every helpful answer is a small positive sentiment data point. Over time these compound into reputational armour that absorbs the inevitable negatives.
When to be alarmed
Trust your instincts on this one. A normal business has a steady trickle of small negatives. A business in real reputational trouble has a clear pattern — repeated complaints about the same issue, aggressive media attention, rising volume. If your sentiment shows signs of cascading, treat it as a top-priority business issue, not just a marketing one. Reputation problems left to fester rarely fix themselves.
Tip: Sentiment is a lagging indicator. By the time bad sentiment appears in audits, the problem has been brewing for weeks. Monitor reviews and social mentions in real-time using free tools (Google Alerts, social search) and respond within hours, not days. Speed of response is the single biggest factor in reputation recovery.
Tags: brand, sentiment, reputation, monitoring, eeat
Tutorial
Reading Brand Mention Summary Statistics
Where in the world is your brand being talked about? In what languages? On what kinds of sites? Mention summary data reveals hidden markets and growth opportunities.
What summary stats show
Mention summary aggregates every public mention of your brand across the web (news, blogs, forums, social) and breaks it down by country, language, content category, and source type. The result is a high-level map of where your name is being discussed. Most brands are surprised by what they find — there is almost always at least one country or topic area where you have organic momentum you did not know about.
Country distribution
Look at where mentions cluster geographically. If 95% of your mentions come from your home country and 5% are spread across the rest of the world, you are a domestic brand. If you see 20-30% in a country you do not actively serve, that country is either a genuine market opportunity (people want what you sell) or background noise (random pickups in news aggregators). Investigate the actual mentions to tell which.
Language distribution
Some brands appear in mentions across multiple languages even when they only operate in one. This usually happens when your product or content is internationally relevant. If French-language mentions are 10%+ of your total but you have no French website, that is a free signal — translating your top content might capture a market with no competition because no one else has bothered.
Source category breakdown
Mentions split into categories: news (mainstream press), blogs, forums, social, e-commerce/review sites, etc. The mix tells you about your visibility shape. News-heavy means you have PR working. Blog-heavy means content marketers cite you. Forum-heavy means real users discuss you. E-commerce-heavy means you appear in product comparison content. Each shape suggests different growth strategies.
Spotting unexpected patterns
Look for outliers. A single category that suddenly dominates this month but was small last month: investigate why. A specific country that produces 0% mentions despite your products being available there: marketing under-investment. A specific blog or news outlet that mentions you many times: a relationship worth nurturing. Each anomaly is a clue.
Comparing periods
Mention volume should grow if your brand is gaining traction. Falling volume signals declining mindshare. Site Audit Pro's history features let you compare snapshots over time. Watch the rate of growth — slow steady growth is healthy. Sudden spikes usually mean either a press moment (good) or a controversy (bad — check sentiment).
Acting on the data
Three actions usually emerge from this analysis. One: identify a country with surprising mention volume and consider expanding there (or at least translating key content). Two: identify a content category over-representing in mentions (e.g. forum discussion) and decide whether to engage there directly. Three: identify the top 10 sources mentioning you most often and turn them into a relationship list — these are your existing supporters and worth investing in.
Tip: Mention summary data is most useful when checked monthly and compared period-over-period. A snapshot in isolation is just numbers. Three months of trend data tells a story — what is rising, what is falling, where the action has shifted.
Tags: brand, mentions, global, content-discovery
Tutorial
Brand Presence and sameAs Schema — Connecting the Dots for Google
Having a Facebook page is good. Telling Google that your Facebook page belongs to your business is much better. This guide explains how to consolidate your brand identity in Google's knowledge graph using schema sameAs and other signals.
The problem this solves
You have a website. You have a Facebook page, an Instagram account, a LinkedIn company page, a YouTube channel, a Trustpilot listing. Each lives independently. Google sees them all but does not necessarily connect them — or worse, may attribute some to other businesses with similar names. The result: weaker brand identity in search, less authority flowing between your properties, and missed Knowledge Panel opportunities.
How sameAs schema solves it
Schema.org provides a property called "sameAs" — designed exactly for this. You add structured data to your homepage saying "this Organization (us) is the same entity as: this Facebook URL, this LinkedIn URL, this Instagram URL, this YouTube URL." Google reads it. From that moment on, all those properties are linked in Google's knowledge graph as one entity — yours. Every reputation signal from each property reinforces the others.
The minimum schema to add
On your homepage, add a JSON-LD script tag with type Organization. Include name (your brand name), url (your homepage), logo (a link to your logo image, ideally 600x60+ pixels on a transparent background), and a sameAs array of every official URL you control: Facebook, Instagram, X (Twitter), LinkedIn, YouTube, TikTok, Pinterest, Threads, Bluesky, Mastodon — plus business platforms like Google Business profile, Trustpilot, Yelp, your industry-specific directories, and your Wikipedia page if you have one.
Choosing platforms strategically
Do not list a profile you no longer maintain. A dormant TikTok account from 2020 with one bad video is worse than no TikTok at all. Audit each platform: claim it, populate it (logo, description, link), and decide whether to keep it active or quietly deprioritise. Active or static-but-decent profiles help. Active and embarrassingly out-of-date profiles hurt.
The other half: links from your site
Add visible links to your social profiles in your site footer. These reinforce the sameAs schema with normal HTML. Search engines find them through normal crawling. Real users use them too. Belt and braces.
NAP consistency across platforms
NAP stands for Name, Address, Phone. Wherever your business appears online (Google Business, Trustpilot, your own site, your social profiles, directories), the NAP must match exactly. "Smith & Jones Ltd" and "Smith and Jones Limited" look the same to humans but different to algorithms. Pick one canonical version and use it everywhere. Inconsistencies fragment your identity.
How long before Google catches up
After adding sameAs schema, expect 2-8 weeks for Google to fully reprocess. The Knowledge Panel may appear or update during that window. Site Audit Pro's brand presence section verifies whether each profile URL you provide is reachable and whether your site links to it. Run the audit monthly until everything shows green.
When you have an entity Wikipedia entry
If you are large enough to have a Wikipedia page, link it in your sameAs array. Wikipedia is one of Google's most trusted sources for entity verification. The presence of a Wikipedia entry combined with consistent sameAs across major platforms is the fastest path to a populated Knowledge Panel with full business details.
Tip: sameAs is one of the highest-leverage SEO actions because it is one-time work that compounds forever. Add it once, maintain it as you create new profiles. Every reputation signal from any platform now strengthens your overall search presence rather than fragmenting it.
Tags: brand, schema, same-as, identity, knowledge-graph
Tutorial
Auditing Your Tech Stack — Why It Matters Even If You Are Not a Developer
What software runs your website? Out-of-date tech is a security risk and an SEO problem. This guide explains how to read your tech stack audit and what to do about it.
What tech detection finds
A tech detection scan inspects your page for fingerprints — specific HTML patterns, JavaScript libraries, server response headers, cookie names — that reveal what software is running underneath. Common categories: content management system (WordPress, Shopify, Webflow, Wix), JavaScript frameworks (React, Vue, Angular, Next.js), web server (Apache, Nginx, Cloudflare), analytics (Google Analytics, Plausible, Matomo), advertising tags, security and CDN providers, payment systems, and hundreds more.
Why it matters for security
Outdated software is the leading cause of website hacks. WordPress 4.x sites with old plugins are routinely compromised. End-of-life PHP versions have known unpatched vulnerabilities. Old jQuery versions have public exploits. The tech detection report tells you exactly what versions you are running. Cross-reference with the published security advisories for each. Anything end-of-life or with known critical vulnerabilities needs urgent updating.
Why it matters for SEO
Some tech stacks help SEO. Some hurt it. Sites built entirely in client-side JavaScript without server-side rendering can struggle to be indexed properly. Sites with heavy advertising stacks load slowly and damage Core Web Vitals. Sites without HTTPS lose ranking ground. Sites without a CDN serve slowly to international visitors. The audit reveals these issues so you can investigate.
Reading the security headers section
Modern best practice: HTTPS Strict Transport Security (HSTS), Content Security Policy (CSP), X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy. Each header protects against a different category of attack. Most sites are missing several. Adding them is usually a matter of editing your web server config or, on managed hosts, ticking a box. Modern hosting platforms (Cloudflare, Vercel) often add them by default if enabled.
Spotting outdated libraries
jQuery 1.x: ancient, has known security issues, replace with modern jQuery or remove if not needed. Old Bootstrap versions: minor risk, mostly cosmetic. End-of-life PHP versions (PHP 5.x, 7.0, 7.1): critical, must upgrade for both security and performance. WordPress core older than current minor version: should auto-update, check why it has not. Themes and plugins not updated in over a year: red flag, replace if possible.
The "kitchen sink" problem
Some sites accumulate dozens of analytics, tracking, and advertising scripts over the years. Each one slows the page down and adds privacy compliance burden. Audit what is actually being used: do you really need Google Analytics + Hotjar + Mixpanel + Segment all running? Most sites can drop 30-50% of their tracking without losing useful data, gaining significant page-speed improvement.
Competitor research uses
Run the audit on competitors. Understanding their tech stack reveals tools your industry uses, what is working, what is overkill. If five of your top six competitors all use the same e-commerce platform, that platform is probably worth evaluating yourself. If they all use a specific A/B testing tool, they are doing experimentation work — you should be too.
Action checklist
Each quarter: update CMS core, themes, and plugins to latest stable versions. Each year: review entire tech stack for end-of-life software. Anything end-of-life: remove or upgrade. Add any missing security headers. Audit tracking scripts and remove unused ones. Verify HTTPS is enforced site-wide. Verify your CDN is active and caching properly.
Tip: If your CMS, server, or any major framework is end-of-life and not upgradable, you are running on borrowed time. Plan a migration within 6 months. Sites running unsupported software get hacked, get penalised by Google for security issues flagged in Search Console, or simply break when their hosting provider eventually stops supporting them.
Tags: technical, security, cms, frameworks, tech-stack
Tutorial
Domain Age and SEO — How Much Does It Really Matter?
Older domains often rank better than newer ones. But why? And does it mean buying an aged domain is a shortcut to success? The honest answer is more nuanced.
What "domain age" means
Domain age usually refers to how long ago the domain was first registered. A domain registered in 2005 is 20+ years old. A domain registered last week is days old. Whois data — the public record of domain ownership — exposes the registration date. Industry-standard SEO tools track it and show it as part of overall site profiles.
Why age is correlated with rankings
Older domains tend to rank better, but the causation is indirect. Older domains have had more time to accumulate backlinks, build content libraries, develop branded search presence, and gain user trust. Each of those is a real ranking signal. Domain age itself is not the signal — it is just correlated with the signals that actually move rankings. A 20-year-old domain with no backlinks ranks no better than a fresh one with no backlinks.
When age is a real signal
Google has confirmed (through John Mueller and similar Search Liaisons) that domain age, as a raw number, is not directly used in ranking. However, "trust signals" related to time absolutely are: how long Google has been seeing this domain produce content, how stable the domain has been (no major suspicious changes), how long the backlink profile has been growing. New domains do face a quasi-sandbox period where rankings come slowly while these patterns establish.
Buying aged domains as a shortcut
Some SEO operators buy old expired domains to skip the trust-building period. This works if the previous owner had a clean profile and the new content is genuinely good. It fails if the previous owner had a manipulative backlink profile or a manual penalty — those follow the domain forever. Always check the full history (Wayback Machine, backlink archives, Google Search Console transferral) before buying. Most expired domains are expired for a reason.
Whois privacy and what it means
Most newer domains hide owner details behind privacy services. This is normal, legal, and does not affect SEO at all. Google does not penalise privacy-protected domains. Older registrations may show real owner data, which is just a quirk of when the domain was registered, not a signal in itself.
When new domains struggle
Brand new domains (under 6 months) usually rank poorly even with good content and good backlinks. Google is being cautious, watching for spam patterns. Be patient. Months 6-12 typically see the first real ranking traction. Months 12-24 see steady growth. Year 2 onwards is when the cumulative trust starts paying off. Pushing too hard with aggressive backlink building in months 1-6 often triggers manual review and can permanently harm a young domain.
Domain history red flags
Before buying or relaunching on an aged domain, check: was it ever penalised by Google? (Google Search Console history is best, but archives like Wayback Machine show suspicious past content.) Was it ever used for adult, gambling, pharmaceutical, or other high-risk content? Did it ever host malware? Each of these can leave residual algorithmic suspicion. A clean history matters more than just age.
What to do regardless of age
For a new domain: focus on patient, high-quality content publication, modest backlink building from real sources, technical SEO basics, and giving it time. For an established domain: focus on the same things, but with the advantage of accumulated trust. Domain age is a tailwind, not a substitute. The work is the same; older domains see results faster.
Tip: Do not pay a premium for "aged domains" from broker sites unless you have personally verified the full history. The vast majority of expired domains for sale have either toxic backlink profiles or thin/spammy histories that will limit how far the domain can rank. A fresh domain with a clean slate often outperforms a 10-year-old domain with baggage.
Tags: domain, age, whois, authority, history
Tutorial
What is SEO? A Plain-English Explanation
SEO stands for Search Engine Optimisation — the practice of getting your website to show up when someone searches for what you sell or do. This guide explains it without the jargon, with real examples.
What the three letters stand for
SEO is short for Search Engine Optimisation. "Search engine" means Google, Bing, DuckDuckGo and similar — the websites people use when they need to find something online. "Optimisation" means making changes to your website so search engines understand it well and rank it highly. SEO is the entire practice of getting your site to appear when someone searches for the products, services or information you offer. Get it right and your phone rings. Get it wrong and you stay invisible to the people most likely to buy from you.
What SEO actually involves
SEO breaks down into three rough categories. Technical SEO: making sure your website loads fast, works on mobile, and search engines can crawl every page. On-page SEO: writing useful content, using the right words on your pages, structuring your headings properly. Off-page SEO: getting other reputable websites to link to yours, which signals to Google that your site is trusted. Most small business owners focus mainly on on-page SEO — writing good content for the right keywords — because it gives the biggest gains for the least technical knowledge.
A real-world example
Imagine you run a plumbing business in Sheffield. Someone in Sheffield types "emergency plumber Sheffield" into Google. Google looks through every plumber website it knows about and ranks them in order of relevance and trust. If your website has a page titled "Emergency Plumbing Services in Sheffield", loads quickly, has a clear phone number, and is linked to by a few local Sheffield directories — Google is far more likely to show your site at position 2 or 3 than a competitor whose homepage just says "Welcome to ABC Plumbers". The first three results on Google get roughly 60% of all clicks. Position 11 onwards (page 2) gets less than 5% combined. Where you rank determines whether the phone rings.
What SEO is NOT
SEO is not paying Google to put your website at the top — that is paid search advertising (Google Ads), which is something different. SEO results are "organic" — they appear because Google judges your site to be the best answer, not because you paid for the placement. SEO is also not a one-time fix. It is an ongoing process: search engines change their rules, competitors improve their sites, and your content needs updating. Treat SEO like maintaining a shop window — never finished, always being improved. Anyone promising "permanent #1 rankings" or "instant SEO" is misleading you.
How long it takes to work
SEO is slow. New websites rarely rank for competitive keywords in their first 6 months no matter what they do — Google needs time to assess them. Established sites with existing content can see meaningful results in 3 to 6 months from focused SEO work. The biggest gains usually come at the 9 to 18 month mark when content has had time to be indexed, links have built up naturally, and Google has had repeated opportunities to assess and re-rank the site. If someone promises page-one rankings in 30 days, they are either targeting words nobody searches for, using risky tactics that will get the site penalised, or lying.
Why SEO matters more than ever
About 68% of all online experiences start with a search engine. For most local services (plumbers, accountants, restaurants, dentists, solicitors), Google is THE primary way new customers find them. For e-commerce, organic search typically drives 30 to 40% of total revenue at sites that invest in SEO. Even in the age of social media and AI tools, search remains how most people find specific information and specific businesses. A business that ignores SEO is leaving an enormous channel of free customers on the table — customers actively searching with intent to buy.
The four things you need to do first
If you are starting SEO from scratch, do these in order. One: make sure your website works on mobile and loads in under 3 seconds (use Google PageSpeed Insights to check). Two: claim and verify your Google Business Profile if you serve customers locally — this gets you in Google Maps and the local pack. Three: write one well-optimised page per main service or product you offer, with the keyword in the title and a clear description of what you do. Four: get listed in 5 to 10 trustworthy industry directories or local business directories. These four steps cover roughly 80% of the SEO basics for most small businesses.
Where to learn more on this site
For practical hands-on guides, look at: "How SEO Actually Works" (the next-step explanation), "The 5 Pillars of SEO" (the framework most agencies use), and "Can I Do SEO Myself?" (honest assessment of when DIY works and when to hire). For specific technical fixes use the error documentation library — every issue we flag in our audits has a fix guide attached. The audit tool itself will tell you exactly which of these areas your website needs work in, with prioritised recommendations.
Tip: SEO works best when treated as a long-term investment, not a quick fix. The websites that dominate their niches usually got there by doing the basics consistently for 2 to 3 years — not by finding a clever trick. The good news: most competitors give up or do SEO badly, so even modest, consistent effort puts you ahead of the field.
Tags: seo, basics, beginners, what-is-seo, introduction
Tutorial
How Does SEO Actually Work? The Honest Beginner Guide
Search engines follow links to discover pages, read the content, and decide what each page is about. Then they decide which page to show first when someone searches. Here is how that actually happens, in plain English.
The three stages: crawl, index, rank
Search engines do three things with the web. First they crawl — they send out automated programs (called bots, spiders or crawlers) that follow links from page to page, downloading a copy of each page they find. Second they index — they store everything they crawled in a giant database, organised by what each page is about. Third they rank — when someone searches, they pull all relevant pages from the index and put them in order of which one they think best answers the search. SEO is about making each of these three stages go in your favour: making your pages easy to crawl, easy to understand for indexing, and worthy of high ranking.
How crawling works in practice
Googlebot (Google's crawler) starts with a list of websites it already knows about. It visits one, reads its HTML, follows every link on the page to discover new URLs, then visits those next. This goes on continuously, 24 hours a day, across the entire public web. New websites are usually discovered when an existing site links to them. This is why backlinks matter — they are not just trust signals, they are literally how Google finds you in the first place. If no other website links to yours and you have not submitted a sitemap, Google may never find your site at all.
How indexing works
Crawling only downloads the page. Indexing means actually understanding it. Google reads the title, the headings, the body text, the alt text on images, the structured data (schema), the URL, and dozens of other signals. From all that, Google decides what topics and keywords your page covers and how thoroughly. A page about Italian leather handbags gets indexed under topics like "leather handbags", "Italian fashion", "designer accessories" and dozens of related terms. Pages with thin content (under 300 words), duplicate content, or technical errors may be crawled but never indexed — meaning they cannot appear in search results at all.
How ranking actually happens
When someone searches, Google checks its index for pages relevant to the query and runs them through over 200 ranking factors to decide the order. The biggest factors: relevance (does the page match what the searcher is looking for), authority (do trustworthy sites link to it), quality (is the content well-written and helpful), user experience (does the page load fast and work on mobile), freshness (is the information current), and intent matching (does the page satisfy what the searcher actually wants — to buy, to learn, to find a location). Google's algorithms then rank the page from 1 to whatever position. Position 1 to 3 get most of the clicks. Page 2 gets almost none.
What an SEO professional actually does
An SEO's job, day to day, breaks down into roughly four parts. Keyword research: finding what real people are searching for that connects to your business. Content: writing or improving pages to rank for those keywords. Technical work: fixing site speed issues, broken links, mobile problems, schema markup, sitemaps. Link building: getting reputable websites to link to yours through outreach, partnerships, content marketing or being mentioned in industry publications. They also monitor — checking rankings, traffic, conversions and competitor changes weekly to inform what to do next. Good SEOs spend about 60% of their time on content and technical work, 30% on link building, 10% on monitoring and reporting.
Why ranking takes so long
New websites have what SEOs informally call the "sandbox" — a 3 to 6 month period where Google is essentially still assessing whether the site is real, useful and trustworthy. Even with perfect SEO, a brand new site rarely ranks for competitive keywords in those first months. Established sites move faster: a new article on a 5-year-old domain with existing authority can rank within weeks. The bigger the keyword (higher search volume, more commercial value), the longer it takes. "Best running shoes UK" is contested by every shoe retailer in the country with massive SEO budgets. "Vegan trail running shoes Sheffield delivery" might rank in 30 days because almost nobody is targeting it.
The role of Google updates
Google changes its ranking algorithm hundreds of times a year. Most are minor. A few times a year there are major "core updates" that can shake rankings significantly across the entire web. After a core update some sites lose half their traffic; others double theirs. The pattern is always the same: Google rewards sites that match what searchers actually want, and demotes sites trying to game the system with thin content, fake reviews, manipulative link building or pages designed for keywords rather than users. The safest strategy is to write genuinely useful content for actual humans and let the rankings follow.
How to track that SEO is working
Three free tools tell you almost everything you need to know. Google Search Console shows which queries your site appears for, where it ranks, and what gets clicked — direct from Google. Google Analytics shows what visitors do once they arrive (pages viewed, time spent, conversions). Bing Webmaster Tools does the same for Bing. Set all three up before doing any SEO work — without baseline data you cannot prove what worked. Track three numbers monthly: total organic clicks, average position for your top 20 keywords, and conversions from organic traffic. If those three are trending up, your SEO is working.
Tip: Most SEO failures happen because people focus on tactics (write 2000 words, get 50 backlinks, use exact keyword match in H1) without understanding the underlying mechanics. Once you understand how crawl-index-rank actually works, you can decide which tactics make sense for YOUR situation rather than copying generic advice that may not apply.
Tags: seo, basics, beginners, how-seo-works, crawling, indexing, ranking
Tutorial
Can I Do SEO Myself? An Honest Answer for Small Businesses
Most small business SEO can absolutely be done in-house with a few hours a week and the right tools. This guide explains exactly what you can do yourself, what you should pay for, and when hiring becomes worthwhile.
The honest answer: yes, mostly
For most small businesses serving local customers or a defined niche, SEO is well within reach without hiring an agency. The skills required are: writing clear content about what you do, checking a few technical things on your site, and being patient. None of that requires a degree or a £2000-a-month retainer. The work is more boring than complicated. The reason most small businesses do not rank well is not that SEO is hard — it is that nobody has done it consistently for them. If you can spend two hours a week on SEO for six months, you will be ahead of 80% of your local competitors.
What you should absolutely do yourself
These tasks are simpler than they sound and you do not need an expert. Setting up Google Business Profile (free, takes 30 minutes, biggest local SEO win there is). Writing a clear page for each service or product you offer (you know your business better than any agency ever will). Asking happy customers for Google reviews (drives both rankings and conversions). Adding alt text to your images. Writing a meta title and description for each page. Submitting your sitemap to Google Search Console. None of these need technical skill — they need attention.
What requires basic tools but is still DIY
These need a tool but no special skill. Keyword research: use Google's free Keyword Planner or any of several free Chrome extensions to see what people search for in your area. Page speed checking: Google PageSpeed Insights is free and tells you exactly what is slow and how to fix it. Technical audits: our free Site Audit tool flags everything wrong with any URL — broken links, missing meta tags, slow images, mobile issues, schema problems — and gives you the fix for each one. The doing is still up to you, but the diagnosis is automated.
What is genuinely harder and worth paying for
Some SEO work has a real learning curve and bad attempts can hurt your site. Technical SEO on a complex site: large e-commerce stores, multilingual sites, JavaScript-heavy frameworks, sites with thousands of pages — these need someone who knows what they are doing because mistakes can deindex sections of your site. Link building: doing this badly leads to Google penalties; doing it well requires industry contacts, time and judgement. Recovery from a Google penalty: if your site has been hit by an algorithm update or manual action, get specialist help. Content at scale: if you need 50 articles a month, you need writers, editors and quality control — that is an operation, not a side task.
A realistic 6-month DIY plan
Month 1: set up Google Business Profile, Google Search Console, Google Analytics. Run a Site Audit. Fix any errors flagged as critical. Month 2: rewrite your homepage and main service pages with clear titles, descriptions and 500+ words of useful content per page. Add structured data using a schema generator. Month 3: write 4 blog posts answering questions your customers actually ask (the Q&A tool finds these automatically). Month 4: get listed in 10 industry directories and 5 local business directories. Ask 5 happy customers for reviews. Month 5: improve your slowest 5 pages — image compression, lazy loading, removing unused scripts. Month 6: review Search Console data, see what is ranking, double down on what works. By month 6 you should see traffic growth on at least your homepage and one or two service pages.
How to know when to hire help
Hire an agency or freelancer when one of these is true. You are spending more than 6 hours a week on SEO and not seeing results — something is wrong with your strategy that an outsider can spot. You are in a competitive industry (legal, finance, insurance, gambling, supplements) where DIY rarely scales fast enough. You have specific technical issues you have tried and failed to fix (e.g. JavaScript indexing problems, hreflang errors). You have the budget but not the time — your hourly rate doing client work is higher than what an SEO would charge. Even when you hire, stay involved: the best results come from a business owner who knows their customers working with an SEO who knows the technical side.
Red flags when hiring SEO services
Avoid anyone who: guarantees rankings (impossible, Google forbids it), promises results in 30 days (mostly impossible, especially for new sites), uses the phrase "secret techniques" or "Google insider knowledge" (all good SEO is publicly known), refuses to explain what they are actually doing, will not show you previous client results, charges per backlink or per keyword (transactional pricing usually means low quality), or pressures you to sign a 12-month contract upfront. Good SEO providers will explain their methodology, show case studies, offer monthly rolling contracts, and educate you alongside doing the work.
The cost vs benefit calculation
A good local SEO freelancer charges £500 to £1500 a month. A reputable agency charges £1500 to £5000+. DIY costs your time — typically 4 to 8 hours a month once set up — plus maybe £30-50 a month for tools (a keyword research tool and a rank tracker if you want one). For most small local businesses, the maths works like this: if SEO brings you one extra customer worth £500+ per month, even paid SEO at £1000/month pays for itself. DIY at zero cash cost almost always pays for itself unless you would otherwise be earning more than £75/hour with the time spent. The question is not really "can I afford SEO" but "can I afford NOT to do SEO when 68% of my potential customers find businesses through search."
Tip: Start with two things: claim your Google Business Profile and run a Site Audit on your homepage. Together these take an hour and reveal 80% of what your business needs to do. Everything else is a follow-on from those two pieces of information. Stop reading SEO advice until you have done both — without your own data, all advice is generic.
Tags: seo, diy, beginners, small-business, self-service, hiring
Tutorial
The 5 Pillars of SEO Every Beginner Needs to Know
Most SEO advice scatters into hundreds of tactics and rules. In reality, all SEO breaks down into five pillars. Understand these and you have a framework for everything else.
Pillar 1: Technical SEO — making your site crawlable
Technical SEO is everything that makes search engines able to find, read and understand your site. It includes: site speed (pages should load in under 3 seconds), mobile-friendliness (your site must work on phones — Google indexes the mobile version primarily), HTTPS (the green padlock — non-HTTPS sites are flagged as insecure and rank lower), clean URL structure (/services/plumbing not /p?id=247), proper sitemap.xml and robots.txt files, valid HTML, structured data (schema markup), and fixing broken links and 404 errors. Without technical SEO, the other four pillars cannot help — Google might not even be able to read your pages.
Pillar 2: On-Page SEO — what is on each page
On-page SEO is what appears on each individual page and how it is structured. The big elements: a unique, keyword-relevant title tag (under 60 characters), a compelling meta description (150-160 characters), one clear H1 per page describing the topic, well-organised H2 and H3 subheadings, content that thoroughly covers the topic (typically 800+ words for blog posts, 500+ for service pages), internal links to related pages on your site, and descriptive alt text on every meaningful image. On-page SEO is where you tell Google what each page is about. Get this right and you can rank for competitive terms even without huge budgets.
Pillar 3: Content — answering what people actually search for
Content is the substance Google ranks. Without it, you have nothing to optimise. Good SEO content: directly answers the question or need behind the search, is written for humans not for keywords, covers the topic thoroughly enough that the searcher does not need to look elsewhere, includes specific facts, examples, numbers and quotes (not vague generalities), is structured to be scannable with subheadings and short paragraphs, and is updated regularly so it stays accurate. The biggest SEO mistake small businesses make is having a homepage with three paragraphs of marketing fluff and nothing else. Google has nothing to rank — there is no there there.
Pillar 4: Off-Page SEO — what other sites say about you
Off-page SEO is everything that happens outside your own website but affects your rankings. The biggest factor by far: backlinks — links from other websites pointing to yours. A link from BBC.co.uk is worth thousands of links from random low-quality sites. Other off-page signals: brand mentions (even without a link, being mentioned in respected publications builds authority), local citations (consistent business listings in directories — name, address, phone number always identical), social signals (Google denies these affect ranking but mentions on platforms drive traffic and brand awareness which feeds back), and reviews (Google reviews especially affect local pack rankings and click-through). Off-page SEO is the slowest pillar to build but the strongest moat against competitors once established.
Pillar 5: User Experience — what happens after they click
Increasingly Google rewards sites that satisfy the searcher and demotes ones that disappoint them. Key signals: bounce rate (do people leave immediately, suggesting your page did not deliver), dwell time (how long they stay), pages per session (do they explore further), and Core Web Vitals (the three speed and stability metrics Google explicitly uses for ranking — LCP, INP, CLS). User experience also covers: clear navigation, working contact forms, accessible design (working on screen readers, properly contrasted text), no intrusive popups, and the overall feeling that your site is professional and trustworthy. A site can have perfect technical SEO and great content and still rank poorly if users hate the experience.
How the pillars fit together
The pillars are sequential, not separate. Without technical SEO, Google cannot read your content. Without good content, your on-page SEO has nothing to optimise. Without content other people want to read, you cannot earn backlinks for off-page SEO. Without a good user experience, none of the above keeps users on your site. They reinforce each other: a fast, well-structured site (technical) with thorough content (content) is more likely to be linked to (off-page) and is more pleasant to use (UX). Most agencies sell you packages weighted heavily toward one pillar — but neglecting any one of the five caps your overall ranking potential.
How to assess your weakest pillar
Look at your data and identify which pillar is letting you down. Technical: run a Site Audit — if you have 50+ critical errors, fix those first. On-page: do your top 10 pages have unique titles and descriptions, one H1, and 500+ words of relevant content? On-page wins are often quick. Content: how many pages do you have on the topics your customers actually search for? If the answer is "three", that is your bottleneck. Off-page: how many other sites link to yours (Google Search Console shows this)? Fewer than 20 means you have almost no link profile. UX: what is your bounce rate, your average page speed, your Core Web Vitals? Whichever pillar shows the biggest weakness is where the highest-leverage work is.
How long each pillar takes
Rough timelines for visible impact. Technical fixes: 1 to 4 weeks (you fix it, Google recrawls, results follow). On-page improvements: 4 to 8 weeks. New content: 3 to 6 months for fresh pages to rank competitively. Off-page link building: 6 to 18 months for meaningful authority growth. UX improvements: 4 to 12 weeks once changes are deployed. Plan accordingly — do not start a content campaign expecting results in 30 days, and do not give up on technical fixes after a week because you have not seen rankings move yet.
Tip: If you are starting fresh, fix technical issues first (you cannot build on a broken foundation), then focus on on-page and content together (they go hand in hand), then build off-page authority through outreach and content marketing once you have something worth linking to. UX runs alongside everything — if your site is slow or hard to use, fix that immediately regardless of which pillar you are working on.
Tags: seo, basics, beginners, pillars, framework, strategy
Tutorial
Will AI Replace SEO? The Realistic Answer
AI tools like ChatGPT, Perplexity and Google's AI Overviews are changing how people search — but they have not killed SEO. They have made it more important and more strategic. Here is what is actually changing and how to adapt.
What is actually happening with search
A generation of new search experiences has emerged. Google's AI Overviews summarise multiple sources at the top of search results. ChatGPT, Claude, Perplexity and Gemini answer questions conversationally with citations. Bing Copilot does the same on Bing search. The pattern across all of them: the user asks a question, the AI synthesises an answer from the web, and links to the original sources. So organic search is not dying — it is splitting into two paths. Path one: traditional ranking results (the blue links). Path two: AI-generated answers that cite source pages. Both still depend on Google or Bing being able to find, index, and trust your content — which is what SEO has always been.
Why "SEO is dead" is a myth that returns every few years
"SEO is dead" has been declared on every major shift in the last 20 years. When social media took off in 2008, SEO was supposedly dead because search would be replaced by social discovery. When Google introduced featured snippets, SEO was dead because Google would answer everything itself. When voice search emerged with Siri and Alexa, SEO was dead because voice would replace typing. None happened. Search volume on Google has grown every single year, including 2024. AI tools have added new layers but have not replaced the underlying mechanism of search. The death of SEO has been "imminent" for two decades and counting.
What HAS changed: AEO (Answer Engine Optimisation)
A real shift is happening: search is moving from blue links to direct answers. AI Overviews are now shown for an estimated 40-60% of US queries with growing rollout in the UK. ChatGPT alone gets 300+ million weekly users. So AEO — Answer Engine Optimisation — has emerged as a parallel discipline to SEO. AEO is about getting your content cited by AI tools, not just ranked in search results. The good news: AEO is mostly a subset of good SEO. Sites that already have authoritative, well-structured, factually accurate content with clear schema markup tend to dominate AI citations. Sites that built rankings through manipulative tactics rather than quality are losing visibility in AI answers fast.
What ranks well in AI answers
AI tools cite sources based on different criteria from blue-link rankings, but the patterns are now clearer than they were a year ago. Direct answer format wins: pages that lead with a clear, specific answer in the first paragraph get cited more than pages that bury the answer five paragraphs in. Structured data helps: FAQ schema, Article schema, How-To schema all improve the chance of being cited. Authority still matters: AI tools heavily favour established, trusted sites — though smaller specialist sites can break through if their content is uniquely thorough on a niche topic. Recency matters more than for traditional SEO: AI tools strongly prefer content updated within the last year. Citations are usually from the top 10-15 organic results, so traditional ranking still feeds AEO performance.
The traffic risk and the opportunity
There is a real concern: when AI summarises your content directly, the user may not click through. Several studies show 30-60% reduced click-through on queries with AI Overviews compared to those without. So sites that depend purely on top-of-funnel informational traffic ("what is X" content) may see declines. The opportunity: AI tools have driven a corresponding increase in commercial-intent traffic. People research with AI then click through to buy from the cited sources. Conversion rates from AI-driven traffic are reportedly 2-3x higher than traditional organic traffic because the visitor is already informed. Sites that adapt their content to be useful both as AI source material AND as the destination for the eventual purchase win on both sides.
What to do practically
Five concrete actions to future-proof your SEO for the AI era. One: structure content to lead with the answer. The first 100 words of any page should answer the main question; everything after is supporting detail. Two: add comprehensive schema markup — FAQPage, HowTo, Article, LocalBusiness as appropriate. AI tools rely heavily on schema to determine what content is. Three: write for E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness) — show first-hand experience, name specific authors, link to authoritative sources, keep content updated. Four: get cited by other authoritative sources — earned mentions in respected publications increase AI citation likelihood. Five: monitor your performance in both traditional rankings AND AI tool citations using newer tracking tools that measure AI visibility.
The skills SEOs need now
The job is shifting, not disappearing. Old-school SEO skills (keyword stuffing, link manipulation, thin content at scale) are increasingly punished. The skills that matter now: deep topic expertise (you cannot fake it past current AI quality detectors), technical SEO competence (schema, Core Web Vitals, JS rendering all still matter), content strategy aligned with user intent (not keyword volume), conversion optimisation (more important than ever as click-through declines), and data analysis (interpreting Search Console + AI citation tools). The SEO of 2030 will be more strategic and less tactical than the SEO of 2010. The actual day-to-day work will look more like content strategy and brand-building than like the link-spamming era ever did.
The honest forecast for 2025-2030
Three predictions with a reasonable evidence base. One: search volume on Google will keep growing, not shrink. Most users do not switch entirely to AI tools — they use them additionally. Two: AI Overviews will reduce informational traffic by an estimated 20-40% over the next 3 years for sites that do not adapt. Sites with strong AEO practices may see flat or growing traffic. Three: brand and authority will become much more important relative to technical optimisation. Sites that establish themselves as the trusted source on specific topics will dominate both blue links and AI citations. Sites trying to compete purely through technical SEO and content quantity will lose ground to fewer, deeper, more authoritative competitors.
Tip: If you are doing SEO right (genuinely useful content, technical health, real expertise, building authority over time), AI changes very little for you. If you are doing SEO wrong (keyword stuffing, thin content, link schemes, generic AI-spun articles), AI accelerates your decline. The advice has not changed: write the best resource on your topic, make it technically sound, get reputable sites to reference it. That has worked for 20 years and will keep working for the next 20.
Tags: seo, ai, aeo, future, ai-overviews, chatgpt, answer-engine
Tutorial
Most competitor analysis tools give you a one-shot snapshot. The real value is in seeing how competitors change over time — what schema they add, what content they ship, what AEO signals they introduce. Here is what Competitor Watch is and why monitoring beats single audits.
The problem with one-shot competitor audits
A traditional competitor audit gives you a snapshot: today, competitor X has Product schema, FAQ schema, and an llms.txt file. You note it down, maybe close some gaps. Six weeks later you check again — and now they have added Review schema, expanded their FAQ, and shipped a new content hub. You missed the entire change. Single audits tell you the static state of the world today. They do not tell you what is changing or how fast. In SEO, the speed and direction of change matter much more than the snapshot. Sites are not static — they get redesigned, restructured, re-platformed, and re-optimised constantly. By the time your annual SEO audit catches up, your competitor has shipped 50 changes you did not see.
What Competitor Watch actually does
Competitor Watch fetches and analyses a set of pages on a recurring schedule (daily, weekly, or monthly), stores each result, and produces a diff between consecutive runs. The diff highlights everything that changed: title text, meta description, canonical URL, robots meta, schema types added or removed, AEO score, FAQ presence, llms.txt presence, security headers, mixed content, broken links, even word count and link counts. You set up the watch once with your URL plus up to 9 competitor URLs and pick a cadence. The system runs in the background and you check in weekly to see what has actually shifted. Done well, this turns competitor monitoring from a quarterly chore into an ongoing intelligence stream.
Why "what changed" beats "what is", every time
Knowing that a competitor has Product schema today is mildly interesting. Knowing that they added Product schema yesterday is actionable — it tells you they are actively investing in ecommerce SEO right now. The change tells you about their priorities. A competitor adding FAQ schema across product pages is signalling investment in AI search visibility (FAQs are heavily cited by ChatGPT and AI Overviews). A competitor removing pages from their sitemap suggests a content pruning project or a strategic pivot. A competitor changing canonical URLs may be in the middle of a re-platforming. Each of these is intelligence about competitor strategy that a static snapshot cannot give you.
The pricing model and why it works for PAYG
Competitor Watch is offered as prepaid bundles: one-shot (run now), 1 week (7 daily runs), 1 month (4 weekly runs), 1 quarter (13 weekly runs OR 3 monthly runs). You pay once for the bundle. There is no subscription, no auto-renewal, no surprises on your card. When the runs are used up, the watch ends — your historical data stays in your account forever. This means you can experiment cheaply (50p one-shot, 130p quarterly) before committing to longer monitoring on accounts that matter. Compare that to SaaS competitor tools that charge £30+/month with annual contracts and you see why PAYG works for an SEO consultancy or in-house team that just needs to monitor 5-10 high-priority competitors.
When to use one-shot vs scheduled
One-shot: you want to know what a competitor looks like today. Useful for: quick checks before a sales call, baselining a competitor you have just discovered, comparing a single product page on yours vs theirs. 50p, results in 30 seconds. Daily watch: you suspect a competitor is making frequent changes (a re-platform, a launch, an algorithm-chasing campaign). Daily catches small iterations. Weekly watch: the default for most monitoring. Catches meaningful strategic changes without burning credits on noise. Monthly watch: low-priority competitors or stable sites where weekly monitoring is overkill. Quarterly watch: strategic baseline check across many competitors at once.
The data model and why your history is yours forever
Every run is stored as a complete snapshot in your account. When you cancel a watch, all the runs to date stay accessible — you can still load any historic snapshot, run diffs between any two points in time, and export the data. The watch ending only means no new runs are scheduled. This matters because the longer you have data, the more valuable each datapoint becomes. A 12-month dataset of weekly snapshots tells a story. A single snapshot tells you nothing about direction or speed.
What it does not do (be honest about limitations)
Competitor Watch is HTML-level analysis. It reads what the server returns and parses what it sees. It cannot: render JavaScript-only sites that have empty initial HTML (though most modern sites server-side render now), run Lighthouse-quality performance audits (different category of tool), see behind login walls, infer business strategy from public signals alone, or replace human analysis. The watch surfaces facts; you still need to interpret them. A competitor switching their schema from Product to ItemPage might mean a deliberate strategic pivot, or it might mean a developer made a typo — the tool will not tell you which.
Tip: Start with one watch on your single most important competitor at weekly cadence (175p for a month). Spend 5 minutes a week reviewing the diffs. After a month you will have a clear picture of how active they are and whether expanded monitoring justifies its cost. Most users find 3-5 watches at weekly cadence is the sweet spot — enough signal, not too much noise.
Tags: competitor-watch, competitor-analysis, monitoring, seo, aeo, strategy
How-To
Step-by-step walkthrough of creating a Competitor Watch — picking competitors, choosing the right cadence, interpreting the first run, and acting on the first diff.
Pick the right competitors (this matters more than you think)
The most common mistake people make is monitoring the wrong competitors. Do NOT just track the biggest names in your industry — they are often too far ahead to learn from and their changes are noise (massive companies ship A/B tests constantly). Track competitors who are: (1) one or two notches above you in your weight class, (2) ranking for the same long-tail keywords as you, (3) similar in size and budget. These are the competitors whose moves you can actually learn from and copy. Ignore the FAANG of your industry — what works for them rarely works for you. Use Ahrefs or Semrush keyword overlap to find genuine competitors, not just the obvious brand names.
Pick the right pages
Watching competitor homepages is rarely useful — homepages change for marketing reasons that have nothing to do with SEO. Watch instead: their highest-ranking product or service pages (the ones that actually compete with yours), their pillar/cluster content pages on shared topics, their pricing page (signals positioning shifts), their landing pages for specific high-value keywords. For ecommerce, watch a representative product page — not their entire catalogue. For SaaS, watch their pricing page and main feature pages. The watch tracks one URL per competitor; choose the URL that best represents the page you want to compete with.
Pick the right cadence for the situation
Daily (300p for 7 runs over a week): use when you suspect a competitor is actively shipping changes — they recently launched, are running a campaign, or you have intelligence they are re-platforming. Daily catches every small edit. Weekly (175p for 4 runs over a month, or 550p for 13 runs over a quarter): the default. Catches all meaningful strategic changes without daily noise. Monthly (130p for 3 runs over a quarter): low-priority monitoring or stable sites. The cadences are designed so you can switch between them — start weekly, escalate to daily if a competitor goes active, drop to monthly if they go quiet.
Setting up the watch in 30 seconds
Go to /competitor-watch.html. Enter your URL in the YOUR URL field. Enter 1-9 competitor URLs in the competitor section. Click the bundle that matches your chosen cadence. Click Buy watch. You will be charged from your PAYG balance the bundle price. The first run starts immediately and takes 10-30 seconds to complete (it fetches each URL, parses, runs the audit, stores the result). You will see a results card for each URL with a score, schema types found, AEO score, content stats, and any "what competitors have that you do not" insights at the bottom. The next run is automatically scheduled at the cadence interval.
Reading the first run results
The first run is your baseline — there is no diff yet because there is nothing to compare against. Use it to: confirm all URLs were fetched successfully (failed URLs show in red — fix any 404s or bot-blocking issues by switching URLs), spot any obvious gaps where competitors already beat you (FAQ schema you do not have, llms.txt they have and you do not, higher AEO score), note your starting score for each competitor so you can track your relative position over time. Do not panic if competitors look stronger on the baseline — the value of a watch is the change tracking, not the static comparison.
Reading the first diff (the moment of truth)
The first diff appears after the second run. It shows every field that changed between run 1 and run 2 — new schema types appearing, AEO scores moving, llms.txt being added, security headers introduced. Each change is severity-tagged: critical (an indexability change — page going noindex, HTTPS dropping), high (schema additions/removals, title rewrites), medium (description changes, AEO score moves), low (word count changes). Focus your attention on critical and high — they are the changes that matter strategically. Low-severity changes are often noise (small word count fluctuations, single image alt changes).
Acting on the diff
For every meaningful diff, ask: should I copy this? Three categories of action. Direct copy: a competitor added FAQ schema and you do not have it — add it within the week. Investigate further: a competitor changed canonical structure across multiple URLs — they may be in a re-platform; bookmark to revisit in a month. Ignore: small wording changes in titles or descriptions are usually A/B tests; do not chase. The discipline of weekly diff review is what makes the watch valuable. A diff with no action taken is a waste of credits. A diff that surfaces 1-2 changes you copy or investigate is a credit well spent.
Iterating after the first month
After 4-7 runs, evaluate: are the competitors you picked still relevant? Are you actually acting on the diffs? Is the cadence right? Common adjustments: drop competitors that turn out to be too quiet (they ship nothing, no useful diffs), add competitors who keep coming up in your ranking research, switch a competitor from weekly to daily because they are very active, switch from daily to weekly to save credits if changes are slow. The watch model is finite-bundle for exactly this reason — at the end of each bundle you can re-evaluate without contract lock-in.
Tip: Block 30 minutes in your calendar each week to review your watch diffs. Treat it as competitor intelligence time, not "SEO admin." The diff itself takes 5 minutes to read; the value is in deciding which 1-2 things to act on. Without the recurring review slot, the watch data sits unused and you wasted the credits.
Tags: competitor-watch, howto, monitoring, setup, competitor-analysis
Tutorial
App Store Optimisation is SEO for mobile apps. The principles are similar to website SEO but the rules are different — Apple and Google use distinct ranking factors. Here is what ASO is, why it matters, and how the App Audit tool fits in.
The 70% problem nobody talks about
Around 70% of all app downloads come from in-store search and category browsing — not from Google search, not from social ads, not from word-of-mouth. People open the App Store or Google Play, type what they want ("budget app," "running tracker"), and pick from the top results. If your app does not appear in those top results for relevant searches, you are competing for the remaining 30% — paid ads and external referrals. ASO is the discipline of making your app appear in the in-store search and category results that drive 70% of installs. It is not optional. It is roughly half of the work of getting an app to scale.
How Apple ranks apps (App Store)
Apple's App Store ranking algorithm is opaque, but the major factors are well-documented through observation and developer interviews. Title (most heavily weighted for keyword matching), Subtitle (also indexed for search), Keyword field (100 characters of comma-separated keywords, hidden from users — critical for ASO), App description (NOT indexed for keywords on Apple — different from Google), Bundle ID and developer name (minor signals), Download velocity (recent downloads matter more than total), Conversion rate from listing view to download (very heavily weighted), Rating and review velocity (recent reviews matter more), Update frequency (active apps rank higher), and Crash rate (technical signal Apple measures via XCode). Apple does NOT use the description text for keyword matching — only Title, Subtitle, and the Keyword field.
How Google ranks apps (Google Play)
Google Play's ranking is more like web SEO. The ranking signals: Title (indexed), Short description (80 chars, indexed), Full description (4000 chars, fully indexed for keyword matching — unlike Apple), Developer name and website, Install count and uninstall rate, Daily/monthly active users, Crash-free user percentage, Rating and rating count, Review text content (Google reads reviews for keyword signals), Update recency, Backlinks from the web to the Play Store URL (genuinely — Google uses normal SEO signals here too), and Engagement metrics (session length, return rate). Critically, Google's description IS indexed — so keyword optimisation in the long description matters far more on Android than iOS.
What the App Audit tool actually checks
The App Audit pulls data via our system's App Data API and Labs API. For each app it gathers: full listing metadata (title, subtitle on Apple, description, screenshots count, video presence, category, developer, version, last update date, rating, rating count, price), the top 50 keywords the app is currently ranking for in App Store / Play search (Labs API), the apps that compete for the same keywords (the "ASO competitors" — usually different from your obvious brand competitors), and 50 most-recent reviews with rating distribution. The tool then computes an ASO score (0-100) and produces a list of issues — missing subtitle, thin description, low screenshot count, stale updates, low rating, and so on.
The most common ASO failures
In rough order of frequency: empty Subtitle field on Apple (30 chars of indexed real estate wasted), description under 500 characters on Google (huge missed keyword opportunity since Google indexes the description), fewer than 5 screenshots (reduces conversion rate by 30-50% versus the 5-10 best practice), no app preview video (apps with videos convert at significantly higher rates), rating below 4.0 (causes ranking demotion in both stores), no recent updates (stale apps drop in rankings), generic icon (low recognisability in search results), and missing privacy policy URL (a removal risk during app review).
Why iOS and Android need separate strategies
A common mistake is treating both stores identically. They are not. On iOS: optimise the Title, Subtitle, and Keyword field for search visibility. The description does not matter for keyword ranking but matters for conversion. On Android: optimise the Title, Short description, and Full description heavily for keywords — Google indexes them all. The same keyword strategy will not be optimal for both. The App Audit lets you audit either or both stores; if you publish on both, run separate audits and apply platform-specific recommendations to each. Common mistake: copy-pasting the iOS description into the Android long description (or vice versa). This wastes the keyword opportunity Google offers.
The iteration cycle for ASO improvements
ASO is iterative. The standard cycle: audit (run App Audit, get baseline ASO score and issue list), prioritise (fix the highest-severity issues first — missing subtitle, weak description), ship (push an app update with the changes — Title, Subtitle, Keyword field, description can be edited without a code change in App Store Connect / Play Console), measure (give changes 2-4 weeks to settle in rankings, then re-audit), iterate (compare the new audit to the previous, refine). The App Audit Watch product (450p for 7 daily runs) is built for this — schedule a daily watch during an active ASO project to see ranking changes day-by-day, then drop to monthly during stable periods.
Tip: Run a one-shot audit on your app plus 3 of your closest ASO competitors (75p). Compare scores and issue lists. The most actionable insight comes from "what they have that you do not" — usually 2-3 specific gaps you can close with a single app store metadata update. That update alone often produces a measurable ranking lift within 2 weeks.
Tags: aso, app-store, google-play, app-audit, mobile, seo
How-To
Step-by-step guide to fixing the most common ASO failures the App Audit surfaces — title, subtitle, description, screenshots, video, keywords. Includes specific examples for both stores.
Optimise your Title (the single most important field)
The Title is the highest-weighted ranking signal in both stores. Apple allows 30 characters. Google Play allows 50 characters. Use them. Format: AppName: Most Important Keyword. Examples: "Strava: Run, Bike, Hike" (Strava), "Headspace: Sleep & Meditation" (Headspace), "1Password: Password Manager" (1Password). The colon-separator pattern is universal because it works — your brand name first, then the keyword that captures what the app does. Do NOT keyword-stuff. "MyApp: Best Free Cheap Easy Quick Awesome Photo Editor" reads like spam, ranks worse, and triggers App Store review concerns. One brand + one or two strong keywords is the formula.
Fill the Subtitle field on Apple (this is FREE keyword real estate)
Subtitle is Apple-only. 30 characters, indexed for search, displayed under the Title in the listing. The most common ASO mistake is leaving it empty. Use Subtitle for high-intent secondary keywords or a clear value prop. Examples: "Cycling, Running, Hiking" (covering keyword variants), "Daily Habit & Mood Tracker" (multiple keyword combinations), "Save Time. Stay Organised." (value prop). Important: do NOT repeat words from the Title — both fields are indexed, so duplication wastes a slot. If the Title says "Photo Editor," the Subtitle should not also say "Photo Editor."
Fill the Keyword field on Apple (the secret 100 characters)
Apple has a hidden field — the Keyword field — visible only to developers. 100 characters, comma-separated, indexed for App Store search but NOT shown to users. Use it for keywords you cannot fit naturally into Title or Subtitle. Best practice: do not include words already in your Title or Subtitle (Apple deduplicates). Do not include your own brand name (already indexed). Do not include common stop-words (Apple already understands "the," "and," "or"). Use singular forms — Apple matches plurals automatically. Use the local language for each region you publish in. The 100 chars goes further than expected because Apple combines individual keywords across all fields (Title, Subtitle, Keywords) for matching.
Write a strong description (very different rules per store)
For Apple: the description does NOT affect search ranking, only conversion rate. Lead with the value prop in the first 2-3 lines (this is what users see before "more"). Then social proof, then features, then call to action. For Google Play: the description IS indexed for search, so use 1500-3000 characters of natural prose with target keywords sprinkled in. Lead with the value prop (same as Apple), then expand into feature sections with subheadings. Sprinkle 5-10 target keywords naturally — never list them or repeat the same one 20 times (Google penalises this). Include the most important keywords in the first 167 characters (the "short description" on Play, also indexed).
Upload 5-10 high-quality screenshots
Screenshots drive conversion rate from listing view to install. The data is unambiguous: 1-2 screenshots = poor CTR, 5-10 = best. Apple shows 3 screenshots in search results; Google Play shows 2-3. Make the FIRST screenshot the strongest — it is what users see in search before tapping. Best-practice composition: each screenshot has a single feature/benefit overlaid in clear text, in your brand colours, with the actual app UI behind. Do not just take raw screenshots — design them. Tools: Figma, AppMockUp, Mockuphone, Screely. Localise screenshots for major regions if you serve them. Apple requires specific dimensions per device class (iPhone 6.7", iPhone 6.5", iPad 12.9" etc).
Add an app preview video (Apple) / promo video (Google)
Apple App Store: app preview video, up to 30 seconds, autoplays muted in the listing. Google Play: promo video, up to 30 seconds, hosted on YouTube and embedded. Both significantly improve conversion rate. The video should: show the app actually being used (not a marketing animation), be silent-ready (assume the user has sound off), focus on 2-3 core features, have on-screen captions/text overlays explaining what is happening. Common mistake: a "trailer" with brand intro animations and slow pacing. App preview videos work like product demos, not commercials.
Localise everything for major regions
If you publish your app in multiple countries, localise the listing for at least the top 3 by user base. Localisation is not just translation — research keywords in each language separately. The literal translation of your English keywords is rarely what users in that country actually search for. Examples: "todo app" might translate as "tareas" in Spanish but local users actually search for "lista de tareas" or "agenda." The App Audit tool runs per-country (you specify country in the URL or it defaults to US), so audit each localised listing separately to check it is well-optimised in each region.
Ship updates regularly to maintain ranking
Both stores favour actively-maintained apps. Even if you have nothing major to ship, push a maintenance update every 4-6 weeks. Each update is an opportunity to: refresh the description, swap in better-performing keywords, update screenshots if you have new features, refresh the changelog (a fresh ranking signal in both stores). Apple specifically demotes apps that have not been updated in a long time as "legacy" and may eventually delist them. Google's algorithm is more forgiving but still penalises stale listings against active competitors. The release notes field also gets indexed on Google Play — use it to highlight new features with relevant keywords.
Tip: Use the App Audit Watch (450p for 7 daily runs) during an active ASO push — say, a 2-week sprint where you ship a metadata update on day 1 and want to track ranking response. Daily snapshots show keyword position changes faster than weekly. Drop back to monthly after the project completes for low-cost ongoing monitoring.
Tags: aso, howto, app-store, google-play, listing-optimisation, app-audit
Tutorial
Why Ecommerce SEO Is Different (and Why Generic Audits Miss It)
Ecommerce sites have structural problems generic SEO audits do not check for. Faceted nav, variant URLs, out-of-stock products, Product schema completeness, MerchantReturnPolicy. Here is what makes ecommerce audits genuinely different and why we built a separate tool.
The structural difference: scale
A typical content site has 50-500 pages. A typical ecommerce site has 5,000 to 500,000+ pages. This single fact changes everything about how SEO audits work. On a content site, you can manually review every important page in an audit. On an ecommerce site, the audit must work at scale — sampling representative pages by type (homepage, category, product, info) rather than checking every page. This is why our Ecommerce Audit has a Full Crawl mode (10/25/50/100 pages) that detects page types automatically and aggregates issues across the sample. A blog SEO audit applied to an ecommerce site misses 90% of the important problems because it cannot reach product pages.
The Product schema problem (specific to ecommerce)
Product pages are uniquely valuable in search results because Google can show: price, availability (in stock / out of stock), star rating, review count, brand, return policy. All of these come from Product schema with the right fields. Most ecommerce sites have Product schema BUT it is incomplete — they have name and image but no price, no priceCurrency, no availability, no aggregateRating. So they get partial rich results, or none at all. A generic SEO audit checks "is there schema, yes/no" — but the answer for ecommerce is rarely useful. The right question is "is the schema complete enough to unlock rich results?" Our Ecommerce Audit checks 8 specific Product schema fields (name, description, image, sku, gtin, brand, offers, price, currency, availability, aggregateRating, review) and reports the missing ones. That is the actually-useful check.
Faceted navigation — the silent killer
Most ecommerce platforms create a category page like /category/dresses. Then they add filters: colour, size, brand, price. Each filter combination becomes a unique URL like /category/dresses?colour=red&size=10. Multiply 5 colours × 10 sizes × 8 brands × 4 price ranges = 1,600 URL variants of the same category page. If these are crawlable and indexable, Google sees 1,600 nearly-duplicate pages competing with each other. This dilutes ranking power for the original /category/dresses URL. Generic audits do not catch this. Our Ecommerce Audit looks for: noindex on filter URLs (good), canonical pointing back to the base category (good), or robots.txt blocking the filter parameters (good). If none of these are set up correctly, faceted nav is silently destroying the category's ranking potential.
Out-of-stock products — should they be indexed?
A common ecommerce mistake: when a product goes out of stock, the page stays live with a "Currently unavailable" message. Google indexes the page. The page ranks for the product name. A user clicks through, sees "out of stock," bounces. Bad experience, bad signal to Google, missed sale to a competitor. Three correct approaches: (1) keep the page live, update the schema availability to OutOfStock, show alternatives or "notify when back in stock" — the page still ranks but the user gets value. (2) For permanently discontinued products, return HTTP 410 (Gone) so Google de-indexes promptly. (3) For seasonal stock-outs, redirect to the most relevant alternative or category page. Our Ecommerce Audit detects Product schema availability state and flags inconsistencies between the schema and what the page actually shows.
The platform fingerprint advantage
Different ecommerce platforms have different default behaviours and platform-specific issues. Shopify auto-generates Product schema — but only basic fields. WooCommerce ranges from excellent (with Yoast WooCommerce SEO) to broken (default themes with no schema at all). Magento has powerful native SEO controls but they are off by default. Wix and Squarespace have limited control over schema and URL structure. Knowing which platform a site is on tells you which problems to prioritise. Our Ecommerce Audit auto-detects platform from HTML signatures (Shopify CDN URLs, WooCommerce CSS classes, Magento URL patterns) and tags the audit results with the platform — so the recommendations can be platform-specific in the howto guides.
AEO and ecommerce — the new frontier
AI search engines (ChatGPT, Perplexity, Claude, Gemini) cite ecommerce content surprisingly often. "Best running shoes for flat feet under £100" is a query AI tools love to answer with specific product recommendations. The pages that get cited tend to have: clean Product schema, structured FAQ schema on product pages, review schema with actual review text (not just ratings), comparison content (X vs Y), and clear product specifications in plain prose. Generic SEO audits do not check FAQ schema on product pages because content sites do not need it. Ecommerce sites do — it is one of the highest-leverage AEO additions you can make. Our audit specifically checks for FAQPage schema on product/category pages and flags it as a high-value missed opportunity when absent.
Trust signals as ranking factors
For ecommerce, trust is functional: people will not buy from a site they do not trust. Google measures this through visible trust signals on every page. Returns/refund policy link, contact information, secure HTTPS, customer reviews, third-party badges (Trustpilot, Verified Buyer), shipping information, FAQ pages addressing common purchase concerns. Our Ecommerce Audit specifically checks for visible policy links (returns, privacy, contact, terms) and warns when they are missing. Generic SEO audits do not check these because they are not relevant for content sites — but for ecommerce they are conversion AND ranking signals. Google's algorithm rewards trust signals visibly, especially after the 2022 Helpful Content Update that explicitly looks at "would a user feel they can trust this purchase decision."
Tip: If you run an ecommerce site and you have only ever used a generic SEO tool (Ahrefs, Semrush) to audit it, you are missing roughly half of the issues that matter. The Product schema completeness check alone is usually worth running — most ecommerce sites have schema that is "present but incomplete," which means they get no rich results despite having implemented the markup. A 30p audit catches this in 30 seconds.
Tags: ecommerce, seo, product-schema, faceted-navigation, site-audit
How-To
A field-by-field guide to Product schema for ecommerce. Which fields are required, which unlock rich results, which are commonly missing, and how to validate.
The required vs recommended distinction
Google's Product rich results have two tiers of fields. REQUIRED for any rich result: name, image, AND either offers (with price) or aggregateRating (with rating). Without these three, you get nothing. RECOMMENDED but not required: brand, sku, gtin, description, review, mpn. The recommended fields make the rich result richer (more attributes shown) but their absence does not block the basic rich result. Most ecommerce sites have name + image + offers but stop there — getting basic rich results but missing the brand/sku/gtin that would let Google match the product across retailers and include it in Knowledge Panel and Shopping comparisons.
Get the offers block right (this is where most sites fail)
The offers block is where the most common errors happen. Required fields inside offers: price (number as string, no currency symbol), priceCurrency (3-letter ISO 4217 code like GBP, USD, EUR). Recommended: availability (use schema.org URL not text), url (link to the product), priceValidUntil (date the price expires), itemCondition (NewCondition usually). Common errors: putting "£29.99" in price (the £ breaks parsing), using "in stock" as availability text instead of the schema.org URL, omitting priceCurrency. Use https://schema.org/InStock or /OutOfStock or /PreOrder or /BackOrder or /Discontinued — not free-text strings.
Add aggregateRating from real reviews only
aggregateRating is what gives you star ratings in search results. Format: { "@type": "AggregateRating", "ratingValue": "4.6", "reviewCount": "284" }. The numbers must come from real reviews on your site — Google validates this by checking that the page actually displays at least one review (or links to a reviews page). Faking aggregateRating without real reviews on the page is a manual action waiting to happen — Google has dedicated review-spam enforcement. If you have under 5 real reviews, do not add aggregateRating yet — the rich result will not show consistently and you risk a penalty if Google thinks you are gaming it.
Always include both image and brand
image is required but most sites give a single image URL. Better: provide an array of images at multiple aspect ratios (1:1, 4:3, 16:9). Google can pick the right ratio for different SERP layouts. Recommended size minimum: 1200x675 pixels (so it qualifies for Discover and other large-image rich results). brand is recommended and unlocks brand-name rich result attributes. Format: { "@type": "Brand", "name": "Nike" }. For your own brand, use your brand name even if it is also your store name — Google treats them as related entities.
Use sku and gtin to enable Shopping cross-matching
If you sell products that exist with the same identifier across other retailers (any branded product, basically), include gtin13 (EAN), gtin12 (UPC), or gtin8 depending on the barcode format. gtin13 is most common in UK/EU. This lets Google match your product to the same product on other sites — enabling Google Shopping comparison features and Knowledge Panel inclusion. sku is your internal code; mpn is the manufacturer part number. For products you make yourself with no global identifier, skip gtin and use mpn + brand instead. Never invent or copy GTINs — Google validates them and demotes sites that fake the data.
Place the schema in the page correctly
JSON-LD goes inside a <script type="application/ld+json"> tag. Best location: in the <head>, before any <link> or <meta> tags. Acceptable: anywhere in <body>. Do not use Microdata or RDFa — they are older formats Google supports but JSON-LD is the recommended approach and is much easier to maintain. One Product per page; do not create multiple Product schemas in the same page (Google will pick one and ignore the rest). For pages with variants (sizes, colours), use a single Product schema with offers as an array (one Offer per variant) or use AggregateOffer.
Validate every schema before deploying
Use the Rich Results Test at search.google.com/test/rich-results. Paste your URL, run the test, fix every error and warning before pushing live. Common validation errors: missing required field, wrong data type (number where string expected), invalid availability URL, malformed JSON. The Schema Markup Validator at validator.schema.org gives more detailed feedback for non-Google schema use cases. After deployment, monitor in Google Search Console under Enhancements → Products. Google reports parse errors and impression/click data per rich result type, so you can see how the schema is actually performing.
Maintain schema as the page changes
Schema must match the visible page. If the price changes on the page, the schema price must update at the same time. If a product goes out of stock, the schema availability must update. Mismatches between schema and visible content are the #1 cause of manual actions for "structured data spam." For dynamic sites (Shopify, WooCommerce, Magento), the schema is auto-generated from the product database and stays in sync automatically. For static sites or hand-coded schema, you need a process to update both at once. The Ecommerce Audit Watch can monitor your schema for unexpected changes — set it to weekly cadence to catch drift before it becomes a problem.
Tip: Run the Ecommerce Audit on your single most important product page first. The audit specifically checks for the 8 Product schema fields most commonly missing (price, currency, availability, sku, gtin, brand, image, aggregateRating). Fix the gaps it surfaces. Re-audit. Most sites can move from "Product schema present but no rich results" to "full rich results with stars and price" in a single deploy — usually 1-2 hours of dev work for a measurable CTR lift.
Tags: ecommerce, product-schema, howto, rich-results, json-ld
How-To
How to Add BreadcrumbList Schema to Ecommerce Pages
Breadcrumbs in search results replace your URL with a clean clickable hierarchy and improve CTR by 5-10%. Here is how to add them to product and category pages, the right way.
What breadcrumbs in search results look like
Without BreadcrumbList schema, search results show your raw URL: "store.com › collections › womens-clothing › product-handle". With BreadcrumbList schema, Google replaces the URL with the breadcrumb path: "Home › Womens › Dresses › Floral Print Maxi Dress". Cleaner, more readable, more clickable. Google's own studies show 5-10% CTR improvement on average from breadcrumbs in search results. The schema is required even if breadcrumbs are visually present on the page — visual breadcrumbs alone do not produce the search-result feature.
The required structure
BreadcrumbList contains an array of ListItem entries. Each item has: position (sequential integer starting at 1), name (the visible text — keep it short and human-readable), and item (the URL of that level — must be the canonical URL with proper protocol). Position 1 is your homepage. Position 2 is the top category. Position 3 is the subcategory. Position 4 is the product (or wherever you are). Do not skip positions; do not use position 0; positions must increment by exactly 1.
Where to place it
Same as Product schema: inside <script type="application/ld+json"> in the <head> or anywhere in <body>. You can have BOTH a Product schema AND a BreadcrumbList schema on the same product page — they coexist fine. You should add BreadcrumbList on every product page, every category page, and any nested content. The homepage itself does NOT need BreadcrumbList (it has nowhere to go up to).
Match the visible navigation
The breadcrumbs in your schema MUST match what the user sees on the page. If your schema says "Home › Womens › Dresses" but your visible breadcrumb shows "Home › Clothing › Womens Dresses," Google detects the mismatch and may flag the schema as deceptive. The visible breadcrumbs do not have to be in the same exact text — slight stylistic differences are fine — but the hierarchy and link structure must match.
Handle multi-category products correctly
A product might belong to multiple categories (a "yellow leather wallet" is in /wallets AND /yellow-products). You should pick ONE primary category for the breadcrumb — the one that best represents the product. Do not create multiple BreadcrumbList scripts on the same page; that confuses Google. The primary category is usually whichever has higher search volume or is more strategically valuable for ranking that product.
Common implementation mistakes
Mistake 1: putting trailing slashes inconsistently in the URLs (https://store.com vs https://store.com/). Pick one form site-wide and stick with it. Mistake 2: omitting the protocol (use https://store.com/, not just store.com or //store.com). Mistake 3: putting query strings in breadcrumb URLs (filter parameters do not belong in breadcrumb hierarchy). Mistake 4: not including the homepage at position 1 — Google expects the path to start from your root. Mistake 5: using long names like "Womens Dresses Long Floral" — short single-step names work better visually.
Validate and monitor
Test with Rich Results Test (search.google.com/test/rich-results). Paste a URL, see the parsed BreadcrumbList preview. Check Google Search Console → Enhancements → Breadcrumbs for any errors at scale. The Ecommerce Audit specifically checks for BreadcrumbList schema and flags it as missing on product/category pages — useful for spot-checking that the implementation rolled out site-wide and there are no gaps.
Tip: Most ecommerce platforms generate BreadcrumbList schema automatically when you turn it on: Shopify themes do it via theme settings, WooCommerce via Yoast SEO. If you are coding it manually, build a single template that takes the page hierarchy as input and outputs the schema — never copy-paste the schema per page (it gets out of sync as your category structure evolves).
Tags: ecommerce, breadcrumbs, schema, howto, rich-results
How-To
How to Add Open Graph Product Tags for Social Sharing
When your product is shared on Facebook, WhatsApp, LinkedIn or Slack, OG product tags make the preview show price and availability. Here is how to add them and why JSON-LD Product schema alone is not enough.
Why OG product tags matter (not the same as Product schema)
Product schema (JSON-LD) is for Google. OG product tags (HTML meta) are for social platforms. They serve different consumers: Google reads JSON-LD, ignores OG tags for product info. WhatsApp, Facebook, Messenger, LinkedIn, Slack, Discord read OG tags, ignore JSON-LD. So a product page with only JSON-LD Product schema will show as a generic article when shared on WhatsApp — no price, no nice image preview. Adding OG product tags fixes this without changing anything else.
The required OG tags
Five tags handle 95% of cases: og:type=product (the base tag — must be exactly "product" not "og:product"), og:title (the product name — same as your <title> usually), og:description (a short product description, 1-2 sentences), og:image (the product image URL — should be 1200x630 minimum for best preview), og:url (the canonical product URL). Then the product-specific extensions: product:price:amount (numeric price, no symbol), product:price:currency (3-letter ISO code like GBP), product:availability (in stock / out of stock / pending).
Where to put them
In the <head>, after the meta charset and viewport tags. Order does not matter to crawlers but conventional order is: title, charset, viewport, OG basic tags (og:title etc), OG product extensions (product:price etc), Twitter card tags. Put all OG tags in one section so they are easy to maintain. Do not duplicate (each tag should appear once).
Image requirements specifically
og:image needs special attention. Best practice: 1200x630 pixels (the maximum size most platforms display). Minimum: 600x315. Below 600x315 and platforms fall back to a small thumbnail or no image at all. Image must be served over HTTPS with a publicly-accessible URL. Cannot be behind authentication. Cannot be a relative URL — must be absolute (https://yourstore.com/img/product.jpg). Many platforms cache the image after first share — if you change it, debug with the Facebook Sharing Debugger or LinkedIn Post Inspector to force a re-scrape.
Testing tools
Three free tools to check OG tags work correctly: Facebook Sharing Debugger (developers.facebook.com/tools/debug) — shows exactly what Facebook will display when the URL is shared and lets you force a re-scrape after updates. LinkedIn Post Inspector (linkedin.com/post-inspector) — same for LinkedIn. Twitter Card Validator (cards-dev.twitter.com/validator) — though now mostly for Twitter Card tags, also reads OG. WhatsApp does not have a public debugger; test by sharing the URL to yourself in WhatsApp.
Add Twitter Card tags too (related but separate)
Twitter (now X) reads OG tags but prefers its own twitter:card tags when present. Add: twitter:card=summary_large_image (the format with a big image), twitter:title (same content as og:title), twitter:description (same as og:description), twitter:image (same as og:image), twitter:site=@yourhandle. With both OG and Twitter Card tags, you get optimal display on every major platform.
Common mistakes that break previews
Mistake 1: og:image set to a relative URL like /img/product.jpg — must be absolute. Mistake 2: og:image hosted on a CDN with hot-link protection — preview crawlers fail to fetch. Mistake 3: og:type set to "og:product" — must be just "product" (without the og: prefix). Mistake 4: putting product:price:amount with currency symbol included ("£29.99") — must be just the number ("29.99"). Mistake 5: forgetting to update OG tags when the product price changes — the cached preview on Facebook shows the old price for weeks.
Tip: Most ecommerce platforms add OG basic tags but skip the product:* extensions. Shopify's default theme is one example — it includes og:title and og:image but not product:price:amount. Check your platform's output by viewing the page source. If the product:* tags are missing, add them via theme customisation or a plugin (Shopify: app called "JSON-LD for SEO" handles this; WooCommerce: Yoast SEO has it built in).
Tags: ecommerce, open-graph, social-sharing, howto, og-tags
Case Study
A mid-size UK fashion retailer's product pages had Product schema "present but incomplete." An Ecommerce Audit identified the missing fields. After a 4-hour dev fix, organic traffic rose 18% in 3 weeks. Here is what happened and what they actually changed.
The starting situation
A UK-based fashion retailer with ~15,000 SKUs across women's clothing, accessories, and footwear. Annual revenue mid-7-figures. Google organic was their largest acquisition channel (38% of revenue). Their product pages had Product schema implemented — they could see it in the page source — but they were not getting star ratings or price displays in search results despite having thousands of customer reviews. They assumed schema was working. Generic SEO audits had not flagged any issue. Conversion rates were lower than industry average and they could not figure out why.
What the Ecommerce Audit revealed
A Full Crawl audit (50 pages, £3.00) on the site surfaced the problem in 2 minutes. Product schema was present on every product page (so generic audits saw it as "passing"), but it was missing: priceCurrency (the price was just a number, no GBP code), availability (no in-stock/out-of-stock status), aggregateRating (despite having reviews on every page, the schema did not include the rating data), brand (despite every product having a brand field in their CMS), and gtin (despite having barcodes for every SKU in their inventory). Without priceCurrency, Google could not show the price. Without aggregateRating, Google could not show stars. Without brand, products did not appear in brand-specific searches. Five missing fields blocked five separate rich result features.
Why their existing tools missed it
They had been running Ahrefs and Semrush. Both flagged "Product schema present" as passing. Neither tool checked field-level completeness. Their developer had implemented schema 18 months earlier following a Stack Overflow example that lacked priceCurrency. The schema validated as "valid Product schema" in any general validator (it was — just incomplete). Without a tool that specifically checked the 8-10 fields needed for rich results, the gap was invisible. The Ecommerce Audit's product schema completeness check (built specifically for this scenario) is what surfaced it. Lesson: "schema present" is not the right question. "Schema complete enough for rich results" is.
The fix took 4 hours of dev time
Their developer made three changes to the product page template: added priceCurrency by reading the existing currency field from the product database, added availability by mapping the existing stock status to the schema.org URL, added aggregateRating by reading the existing review aggregation that was already shown on the page (just not in the schema), added brand by reading the existing brand field, added gtin13 by reading the existing barcode field. Total dev time: 4 hours including testing. The data was all already in their CMS — it was just not flowing into the schema script. They deployed on a Friday evening and waited.
What happened in the SERPs over 3 weeks
Week 1: Google re-crawled the priority product pages within 48 hours. Star ratings started appearing in search results for the most-visited products by day 4. CTR on those products started ticking up by day 7 — measurable in Search Console. Week 2: more products got rich results as Google re-crawled the long tail. By end of week 2, an estimated 60% of product pages were showing star ratings. Week 3: the full catalogue was re-indexed with rich results. The CTR lift was now visible across nearly every ranking product page. Average CTR on product pages went from 1.4% to 2.1% — a 50% relative increase.
The traffic and revenue impact
In the 3 weeks following the deploy, organic traffic to product pages rose 18% (from a baseline of ~280k monthly product page sessions to ~330k). Conversion rate held steady (the visitors were the same quality, just more of them clicking through from search). Revenue from organic search rose roughly 19%. Their estimated additional monthly organic revenue: £40k+. The total cost of identifying and fixing the issue: one Ecommerce Audit (£3) plus 4 hours of developer time (£300). Total ROI: roughly £40,000 of additional monthly revenue from £303 of investment.
What they did differently going forward
They set up an Ecommerce Watch on monthly cadence (500p) on their flagship category page plus 4 competitors. The watch alerts them to schema regressions (a developer accidentally removing a field, a theme update breaking the markup) before they affect organic performance. They also added "schema completeness check" to their pre-deploy QA — any change to the product page template now requires running the audit before pushing live. Previously they deployed product page changes confidently because the schema "looked fine." Now they validate it.
The takeaway for other ecommerce sites
If your ecommerce site has Product schema but is not seeing star ratings or price displays in search results, the schema is almost certainly incomplete in a way generic tools do not flag. The check takes 30 seconds with our Ecommerce Audit. The fix is usually a small dev task. The traffic lift can be substantial — typically 10-25% on product pages, depending on how much catalogue you have and how dominant rich results are in your niche. The gap between "schema present" and "schema complete enough for rich results" is where 80% of ecommerce SEO opportunity hides.
Tip: If you have not validated your Product schema field-by-field, do it today. Run an Ecommerce Audit (75p one-shot) on your single most important product page. If the audit shows missing fields (price, currency, availability, aggregateRating, gtin, brand), you have the same problem this retailer had. Fix takes hours; gain takes weeks but lasts forever.
Tags: case-study, ecommerce, product-schema, rich-results, organic-traffic
How-To
How to Add Trust Signals to Ecommerce Pages (and Why They Affect Rankings)
Returns policy, shipping info, contact details, privacy policy, secure checkout indicators, customer reviews. Trust signals are conversion drivers AND ranking signals. Here is what to add, where, and how to mark up the trust-related ones with schema.
Why Google ranks trust signals (the Helpful Content Update)
In August 2022, Google rolled out the Helpful Content Update — a sitewide signal that asks: "would a user feel they can trust this site to make a purchase decision?" Sites that look polished but lack trust signals get demoted in this assessment. Sites that visibly demonstrate legitimacy get promoted. The signals Google reads include: visible contact information, returns/refund policy, shipping information, security badges, real customer reviews, and clear pricing without hidden fees. None of these are individual ranking factors — together they form the "trust" assessment that influences ranking. The Ecommerce Audit specifically checks for visible policy links because their absence is a strong negative signal.
Add a Returns/Refund Policy page (legally required and SEO-relevant)
UK consumer law requires a returns policy for online purchases (14-day cooling-off period minimum under the Consumer Contracts Regulations 2013). EU law is similar. So you need this anyway. For SEO and conversion: publish a dedicated /returns page with clear language covering: how long the return window is, who pays return shipping, what condition the item must be in, how long refunds take. Link to it from the footer (visible on every page) AND from each product page near the buy button. Both Google and conversion-rate research show that visible returns information increases purchase confidence. Mark up the policy with MerchantReturnPolicy schema for Google Shopping eligibility.
Add a Shipping Information page
Same logic as returns — required for compliance and visible on every product page. Cover: which countries you ship to, shipping options and speeds, costs (including any free-shipping threshold), order cutoff times for next-day delivery, what happens if a delivery fails. Mark up with OfferShippingDetails schema attached to your Product offers — this enables Google Shopping to show shipping costs directly in the rich result. Hidden or vague shipping info is one of the top conversion killers in ecommerce — if a user has to add to cart to discover the cost, many will bounce.
Display a clear Contact link with real options
Footer link to /contact. The contact page must include: a real email address (not just a contact form — let users reach you directly), a phone number if applicable to your business, postal address (required for UK Companies House compliance if you are a Ltd), business registration number and VAT number where applicable. A site that hides its contact info reads as low-trust to both users and Google. Add ContactPoint schema in your Organization markup to formalise the contact options.
Display the Privacy Policy and Cookie Notice
Required by UK GDPR and most other privacy regimes. Privacy policy at /privacy or /privacy-policy, linked in footer. Cookie consent banner if you set non-essential cookies. The Ecommerce Audit specifically checks for a visible privacy policy link in the footer — its absence is a major trust failure and a regulatory risk. The cookie banner is not directly checked but should be present if you use any analytics or marketing cookies (Google Analytics, Facebook Pixel, etc).
Show real customer reviews on product pages
Reviews drive both conversion and SEO. For SEO: aggregateRating schema unlocks star ratings in search results (assuming you have real reviews to back it up). For conversion: 89% of consumers consult reviews before purchasing. Display the actual review text (not just a star count) — Google reads it and uses it as additional content signal. Sources of reviews: post-purchase email asking for a review, integrate a third-party review platform (Yotpo, Trustpilot, Reviews.io) which auto-imports reviews and adds schema correctly, manually moderate a custom review system. Never write fake reviews — Google has dedicated review spam enforcement and the penalty is severe.
Add security and trust badges visibly
On checkout pages: SSL/HTTPS indicator (browser shows automatically but you can reinforce with a "Secure checkout" message), payment method logos (Visa, Mastercard, PayPal, Apple Pay — these signal legitimate payment processing), Trusted Shops or similar third-party trust mark if you have one. These do not appear in Google's schema but are part of the visible trust signals Google's algorithm assesses indirectly through user behaviour signals (lower bounce, higher conversion).
About page and FAQ — the under-used trust pages
A real About page tells the story of the business (when founded, by whom, what makes you different). Generic "we are passionate about quality" copy scores poorly. Specifics — names, dates, photos, certifications — score well. FAQ page covering common purchase concerns (How long does shipping take? What if it does not fit? Do you ship internationally?) is both a trust signal and an AEO opportunity (FAQ schema is heavily cited by ChatGPT and Perplexity). The Ecommerce Audit checks for FAQ schema specifically and flags it as a high-value missed opportunity when absent on product pages.
Tip: After implementing trust signals, run the Ecommerce Audit again. The audit checks for visible policy links (returns, privacy, contact, shipping) and the presence of FAQ schema, AggregateRating, and other trust-related schema. A site that scores well on this audit is also a site that scores well in Google's Helpful Content assessment — they correlate strongly because they measure the same underlying property: "is this a trustworthy commercial site?"
Tags: ecommerce, trust-signals, conversion, seo, howto, helpful-content
Fix
Search Console lists a page as an alternate page with a proper canonical tag when the page points Google at another URL as the main version. Most of the time this is correct; here is how to tell when it is not.
What the message means
Google found the page, read its canonical tag, and agreed that another URL is the main version. So it indexes the main URL and leaves this one out. This is a report, not an error: parameter URLs, print versions, tracking links and near-duplicates are meant to land here.
Decide whether the page should be indexed
Open the list and ask one question for each URL: should this exact address appear in Google on its own? If it is a filtered view, a sorted listing or a copy with tracking parameters, the answer is no and nothing needs fixing. Only act on pages that carry content of their own.
Check what the page actually declares
Use URL Inspection on a page you want indexed. Compare the user-declared canonical with the Google-selected canonical. If the page declares a different URL as canonical, the tag in your template or plugin is wrong and Google is simply following your instruction.
Fix a wrong canonical at the source
Change the canonical so the page points to itself with its full https address. In WordPress this usually comes from the SEO plugin; in Shopify from theme.liquid or a product template; in custom builds from the head template. Fix the template, not single pages, or the problem returns.
Line up the other signals
Google weighs more than the tag. Internal links, the sitemap, hreflang and redirects should all point to the same URL you call canonical. If your menu links to one version and your sitemap lists another, Google may keep choosing the alternate despite the tag.
Remove alternates from the sitemap
A sitemap should list only canonical URLs. If it contains addresses that canonicalise elsewhere, you are sending mixed messages. Regenerate the sitemap so every URL in it returns 200 and declares itself as canonical.
Worked example
A shop lists /shoes?colour=red and /shoes?sort=price in this report, plus /guides/running-shoes. The first two carry a canonical to /shoes, which is right: they are views of one page. The guide is different. URL Inspection shows its canonical pointing at /guides/, because the template prints the parent folder. Fixing the template to print the page's own address, <link rel="canonical" href="https://example.com/guides/running-shoes">, moves the guide out of the report after the next crawl.
Mistakes to avoid
Do not remove canonical tags from parameter URLs to clear the report; that invites duplicate pages into the index. Do not canonicalise paginated pages 2, 3 and 4 to page 1, because Google then cannot reach the items listed on them. And do not use a canonical where a redirect is the honest answer: if the old URL should never be visited, redirect it.
Validate and wait
After fixing, inspect a sample URL with the live test to confirm the new canonical is served, then request indexing for the most important pages. Search Console updates the report as Google recrawls, which can take days or weeks.
Tip: If Google keeps choosing a different canonical than the one you declared, look for duplicate content first: two pages with almost the same text will be merged however carefully you tag them.
Tags: search-console, canonical, indexing, fix
Fix
Google fetched the page and decided not to add it to the index, at least for now. The cause is almost always the page itself: thin, duplicated or weakly linked content. Here is how to work through it.
What the status means
Google crawled the URL successfully and chose not to index it. No technical block was involved, so robots.txt and noindex are not the cause. Google may index the page later, but usually only after something about the page or its links changes.
Check that the page is worth indexing
Read the page as a stranger would. Does it answer something no other page on your site answers? Short tag pages, empty categories, near-identical product variants and auto-generated location pages are the usual residents of this report.
Look for duplication
Search Google for a distinctive sentence from the page in quotes. If another page on your site, or someone else's, carries the same text, Google has little reason to index a second copy. Merge duplicates into one stronger page and redirect the others.
Strengthen internal links
Pages linked only from the sitemap or buried deep in pagination look unimportant. Link to the page from relevant, already-indexed pages with descriptive anchor text. A page two clicks from the home page is treated very differently from one eight clicks away.
Check what Google sees
Use URL Inspection and view the crawled page. If the main content is loaded by JavaScript and missing from the rendered HTML, or the page shows a cookie wall or error message to Googlebot, Google is judging an empty page.
Improve, then request indexing
Add real substance: original explanation, examples, data or answers to the questions people ask about the topic. Then request indexing once. Repeated requests on an unchanged page do not help.
Worked example
A site has 300 location pages, each the same service text with the town name swapped. Most sit in this report. Rewriting 20 of them with real local detail, such as jobs done in that area, travel times, local regulations and photos, and merging the rest into one service-area page with a list of towns, gets the rewritten pages indexed within weeks. The merged page then ranks for the wider searches the thin pages never did.
Mistakes to avoid
Do not resubmit the same unchanged URLs every day; Google has already seen them. Do not buy links to these pages in the hope of forcing them in, which breaks Google's spam policies. And do not assume the newest posts are stuck for good: a fresh post on a young site can sit here for a few weeks before being indexed without any change.
Prune what should not be indexed
For low-value URLs you need to keep for users, add noindex or consolidate them. A smaller set of strong pages gets crawled and indexed more readily than a large set of weak ones.
Tip: Sort the report by page type. If most entries share one template, fix the template once rather than editing pages one by one.
Tags: search-console, indexing, content-quality, fix
Fix
Google knows the URL exists but has not crawled it yet. This points to crawl priority: too many URLs, weak links to them, or a server that slows Google down.
What the status means
Google found the URL, usually through a sitemap or a link, but postponed crawling it. Search Console notes that this often happens when Google expects crawling to overload the site, or when it does not yet rate the URL as worth fetching.
Check server response times
Look at the Crawl stats report under Settings. Rising response times or many server errors make Google crawl less. Fix slow responses and 5xx errors first, because no amount of linking helps while the server pushes Googlebot back.
Cut the number of low-value URLs
Faceted filters, session IDs, calendar pages and endless parameter combinations can create thousands of URLs that compete for crawling. Block pure duplicates in robots.txt, link to filters with care, and keep the sitemap to pages you want indexed.
Link to the pages that matter
A URL known only from the sitemap has little priority. Add links from indexed, relevant pages and from your main navigation or hub pages where it makes sense. Links tell Google a page is part of the site, not an afterthought.
Check the sitemap is clean
Every URL in the sitemap should return 200, be canonical and be indexable. A sitemap full of redirects, 404s or noindex pages lowers Google's trust in the whole file and slows discovery of the good URLs in it.
Request indexing for key pages
Use URL Inspection to request indexing for the handful of pages that matter most. This is a nudge, not a guarantee, and it has a daily limit, so save it for important pages.
Worked example
An online shop adds colour and size filters that create a new URL for every combination. Within a month Search Console shows 40,000 URLs discovered but not indexed, while new product pages wait weeks. Linking filters with nofollow where they add no search value, adding the pattern to robots.txt with a rule such as Disallow: /*?*size=, and listing only product and category pages in the sitemap lets Google spend its crawling on the pages that matter.
Mistakes to avoid
Do not block URLs in robots.txt that you also want deindexed with noindex; Google cannot read a noindex on a page it may not crawl. Do not delete the sitemap to shrink the report, which removes your best discovery signal. And do not judge the fix after a few days; crawl patterns change over weeks.
Give it time and watch the trend
New sites and large batches of new pages often sit in this state for weeks. What matters is the direction: the count should fall as crawling catches up. If it keeps rising, you are adding URLs faster than they earn crawling.
Tip: If thousands of URLs are stuck here on a small site, the problem is almost never Google; it is a URL pattern on your site creating far more pages than you meant to publish.
Tags: search-console, indexing, crawl-budget, fix
Fix
Search Console reports pages excluded by a noindex tag when Google found an instruction not to index them. If those pages should be in Google, the instruction is coming from somewhere you need to find and remove.
Confirm the page should be indexed
Login pages, thank-you pages, internal search results and admin areas are meant to be noindexed. Only act on pages you want people to find in Google.
Find where the noindex comes from
A noindex can sit in a meta robots tag in the HTML or in an X-Robots-Tag HTTP header. View the page source and search for noindex, then check the response headers with your browser's network panel or curl -I. Headers are easy to miss and often set by the server or CDN.
Check the WordPress visibility setting
In WordPress, Settings then Reading has a box called Discourage search engines from indexing this site. If it is ticked, every page carries noindex. It is often left on after a site moves from staging to live.
Check the SEO plugin
Yoast, Rank Math and similar plugins can noindex single posts, whole post types, categories, tags and archives. Check the advanced settings on the page itself and the global settings for its content type.
Check server and CDN rules
An X-Robots-Tag header can be added by nginx, Apache, Cloudflare rules or a security plugin, sometimes for a whole folder. Search your server configuration and CDN rules for X-Robots-Tag.
Test the live page
After removing the noindex, run URL Inspection with the live test. It should say indexing allowed. If it still reports noindex, a cached copy or a second source is still sending it; clear caches and look again.
Worked example
After launching a new WordPress site, every page shows in this report. The page source contains <meta name="robots" content="noindex, nofollow">. The box Discourage search engines from indexing this site, under Settings then Reading, was left ticked from the staging build. Unticking it, purging the page cache and requesting indexing for the home page and main service pages starts the recovery, and the report clears as Google recrawls.
How to check for the header version
Run curl -I https://example.com/page/ and read the response. A line such as X-Robots-Tag: noindex means the instruction comes from the server or CDN, not the page. On nginx look for add_header X-Robots-Tag in the configuration; on Apache look for Header set X-Robots-Tag in .htaccess or the virtual host. Remove it for pages that should be indexed, reload the server and test again.
Validate the fix
In the Page indexing report, open the noindex issue and choose Validate fix. Google recrawls the listed URLs and moves them out of the report as it confirms the change.
Tip: Never block a page in robots.txt while trying to remove its noindex. If Google cannot crawl the page, it cannot see that the noindex has gone.
Tags: search-console, noindex, indexing, wordpress, fix
Fix
Two different statuses that both involve redirects. One is usually fine and the other always needs work. Here is how to tell them apart and fix each.
Page with redirect is usually expected
This status means the URL redirects somewhere else, so Google indexes the destination instead. Old URLs, http to https moves and www changes all land here. It is not an error.
Clean up where those URLs come from
If Google keeps finding redirecting URLs, something is still linking to them. Update internal links and the sitemap to point straight at the final URLs, so Google and visitors skip the extra hop.
Redirect error needs fixing
This status means Google could not follow the redirect to a working page. Search Console names the causes: a chain that is too long, a loop, a redirect URL that is too long, or a bad or empty URL in the chain.
Trace the chain
Run curl -I -L on the URL to see every hop and its status code. Look for loops, such as http going to https and back, or www and non-www rules fighting each other, and for chains of three or more hops.
Collapse chains to one hop
Change each old URL to redirect straight to the final address with a single 301. Rules written in several places, such as the server, a plugin and a CDN, are the usual cause of chains; keep each redirect in one place.
Fix loops at the source
A loop usually comes from two rules that disagree, or from a CMS setting that conflicts with a server rule, such as WordPress set to http while the server forces https. Make the site address, server rules and CDN agree on one version.
Worked example
Running curl -I -L http://example.com/old-page shows four hops: http to https, then non-www to www, then /old-page to /old-page/, then to /new-page/. Replacing them with one rule that sends every old URL straight to its final https://www.example.com/new-page/ address turns four hops into one. The Redirect error entries clear once Google recrawls, and visitors arrive faster too.
Mistakes to avoid
Do not redirect removed pages to the home page in bulk; Google often treats that as a soft 404 and the old URLs fall out anyway. Do not mix 301 and 302 at random in one chain. And do not leave redirecting URLs in the sitemap, which keeps feeding them back into the report.
Validate
Once the chain resolves in one hop to a page that returns 200, use Validate fix on the Redirect error issue.
Tip: Test with curl rather than a browser. Browsers cache 301 redirects, so a fixed rule can still look broken in your own browser.
Tags: search-console, redirects, indexing, fix
Fix
Googlebot asked for the page and your server answered with a 5xx error. Persistent server errors make Google crawl less and can drop pages from the index.
What the status means
Google requested the URL and received a 500, 502, 503 or 504 response, or the request timed out. The page might work when you open it, because the error happened at the moment Googlebot visited.
Find the moments in your logs
Inspect a sample URL to see when Google last crawled it, then search your server access and error logs for Googlebot requests around that time. The error log usually names the real cause: a PHP fatal error, a database timeout, memory exhaustion or an upstream app down.
Check firewalls and rate limits
Security plugins, WAFs and rate limits sometimes answer bots with 503 or block them outright. Confirm that requests claiming to be Googlebot really are, using a reverse DNS lookup, then make sure your rules allow the real ones.
Check server capacity
If errors cluster at busy times, the server is running out of workers, memory or database connections. Page caching, fewer heavy plugins, better database indexes or more resources reduce errors under load.
Check the CDN and proxy
A 502 or 504 from a CDN means it could not reach your origin in time. Check the origin was up, that the CDN can reach it, and that timeouts are long enough for slow pages.
Use 503 only for planned downtime
During maintenance, return 503 with a Retry-After header. Google treats short 503 periods as temporary. Long or frequent ones reduce crawling.
Worked example
The report lists 200 product pages with server errors, all crawled on the same morning. The error log for that hour shows the database hitting its connection limit while a nightly import ran. Moving the import to a quiet hour and raising the connection limit stops the errors. Validate fix then clears the URLs over the next fortnight.
How to read the logs
Search the access log for Googlebot with a command such as grep Googlebot access_log | grep ' 50[0-9] ' to list failed requests with times and URLs. Match the times against the error log to find the cause. If the errors come from a CDN rather than your origin, the CDN's own logs or analytics show which requests failed and why.
Validate
When the cause is fixed and pages return 200 consistently, choose Validate fix. Watch the Crawl stats report for the error rate falling.
Tip: If only some URLs fail, look for what they share: a slow template, a heavy query or a plugin that runs only on those pages.
Tags: search-console, server-errors, crawling, fix
Fix
Search Console shows Sitemap could not be read or Couldn't fetch when Google cannot load or parse the file. Sometimes it is temporary; often there is a real fault to fix.
Open the sitemap yourself
Load the exact sitemap URL you submitted. It should show XML, not an HTML page, a login screen, a redirect to the home page or an error. Check it with curl -I too: the status must be 200.
Check the content type
The server should send the file as XML, for example text/xml or application/xml. A sitemap served as text/html, often by a misconfigured rewrite rule, can fail to parse.
Check the XML is valid
Anything before the opening xml declaration, such as a blank line or a PHP warning, breaks the file. Open it in a browser and look for parse errors, or run it through an XML validator. Unescaped ampersands in URLs must be written as &.
Check access rules
The sitemap must not be blocked by robots.txt, password protection, a WAF challenge or a country block. Firewalls that challenge bots often serve Googlebot an HTML page instead of the XML.
Check size limits
A single sitemap may hold up to 50,000 URLs and 50 MB uncompressed. Larger sites must split into several sitemaps listed in a sitemap index.
Clear caches
Caching plugins and CDNs sometimes store a broken or empty copy of the sitemap. Purge the cache for the sitemap URLs after fixing the source.
Worked example
A sitemap submitted as /sitemap.xml shows Couldn't fetch. Running curl -I shows a 301 to the home page, because a new plugin moved the sitemap to /sitemap_index.xml and the old address now redirects. Submitting the new address, and adding Sitemap: https://example.com/sitemap_index.xml to robots.txt, fixes it the same day.
A minimal valid sitemap
A working sitemap can be as simple as an XML declaration, a urlset element with the namespace http://www.sitemaps.org/schemas/sitemap/0.9, and one url element per page containing a loc with the full https address and, optionally, a lastmod date in the format 2026-10-17. If yours looks very different, or has anything before the declaration, that is where to start.
Resubmit and allow time
Remove and resubmit the sitemap in Search Console. A new submission can show Couldn't fetch for a while before Google processes it, so check again after a day or two before changing anything else.
Tip: Submit the sitemap index rather than each child sitemap, and list the index in robots.txt with a Sitemap: line so every crawler finds it.
Tags: search-console, sitemap, crawling, fix
Fix
Google could not parse a block of structured data on the page, so it ignores the whole block. The cause is almost always broken JSON-LD.
What the message means
Search Console or the Rich Results Test found markup it could not read, usually reported as unparsable structured data. Unlike a missing field warning, a syntax error means none of that block counts.
Find the broken block
Open the page source and find each script of type application/ld+json. Paste each block into a JSON validator. The first error the validator names is usually the only one you need to fix.
Fix the common mistakes
Trailing commas after the last item, missing commas between items, unclosed braces or brackets, and comments are the usual culprits. JSON does not allow comments or trailing commas, even though JavaScript does.
Fix quotes and escaping
Curly quotes pasted from a word processor break JSON; use straight double quotes. Double quotes inside a value must be escaped as \" and backslashes as \\. Text with line breaks must use \n rather than a real line break.
Fix template output
If a CMS fills the JSON from page fields, one product name with a quote mark can break every page using that template. Escape values when generating the JSON, for example with json_encode in PHP or JSON.stringify in JavaScript, rather than pasting text in.
Remove duplicate generators
Two plugins or a theme and a plugin can each print schema, and a half-written block from one of them can be the broken one. Keep one source for each schema type.
Worked example
A product page fails with a syntax error. The JSON-LD reads "name": "12" Brass Hinge", where the inch mark ends the string early. Escaping it as "name": "12\" Brass Hinge" fixes that page, but every product with an inch mark in its name is broken too. Changing the template to build the block with json_encode fixes all of them at once.
Mistakes to avoid
Do not hide a broken block by deleting all schema from the page; fix the block instead, because the valid parts help search engines and AI systems understand the page. Do not paste JSON-LD into a rich text editor that may convert quotes. And do not leave an old block in place while adding a new one; two copies of one type confuse validators and search engines.
Retest and validate
Run the URL through the Rich Results Test and the Schema Markup Validator. When both read the block cleanly, validate the fix in Search Console.
Tip: Never build JSON-LD by joining strings by hand in a template. Build an object and let a JSON encoder write it, and the escaping takes care of itself.
Tags: schema, json-ld, structured-data, search-console, fix
Fix
The Rich Results Test found nothing on the page that qualifies for a rich result. That can mean the markup is missing, unsupported or incomplete, or simply that the type has no rich result.
Check the markup is there
Open the More info panel in the test or view the rendered HTML. If no structured data appears at all, it is either missing or added by JavaScript that the test did not run. Check the rendered page, not only your template.
Check the type has a rich result
Many schema.org types are valid but produce no rich result, for example Organization on most pages or WebPage. The test only reports the types Google currently supports, such as Product, Article, Breadcrumb, Event, Recipe and Video.
Know which types Google has limited
Google restricted FAQ rich results to well-known government and health sites in 2023 and dropped HowTo rich results. Valid FAQ or HowTo markup on other sites now shows no rich result, which is expected.
Add required properties
Each supported type has required properties. A Product without name and either offers, review or aggregateRating is not eligible. Fill every required field with values that match what visitors see.
Check the Schema Markup Validator
The Schema Markup Validator shows all structured data on the page, including types with no rich result. If your markup appears there but not in the Rich Results Test, it is valid but not eligible.
Check access for Google
If scripts or the page itself are blocked by robots.txt or a firewall, the test cannot see markup added by JavaScript. Make sure the files that build it are crawlable.
Worked example
A recipe blog shows No rich results detected. The Schema Markup Validator finds a Recipe block, but recipeIngredient and image are missing, and the block is added by a script after load. Printing the block in the server HTML and adding the missing required properties makes the page eligible, and the Rich Results Test then shows a valid Recipe item.
How to check the rendered markup
In the Rich Results Test, choose View tested page, then the HTML tab, and search for application/ld+json. This is the HTML Google built after running scripts. If your markup is missing there but present in your template, it is not being printed for that page, or it is being added too late.
Remember eligibility is not a promise
Even valid, eligible markup is shown only when Google decides it helps the search result. Correct markup still matters, because it describes the page to search engines and AI systems.
Tip: Put the markup in the HTML the server sends rather than adding it with JavaScript after load. Every crawler then sees it, not only the ones that render.
Tags: schema, rich-results, structured-data, fix
Fix
Search Console reports breadcrumb issues when BreadcrumbList markup is missing fields or out of order. The fix is usually a small change to one template.
Know the expected shape
A BreadcrumbList holds an itemListElement list of ListItem entries. Each ListItem needs a position starting at 1, a name, and an item with the URL of that level. The last item, the current page, may leave out its URL.
Fix missing field item
The most common error is a missing item on a middle level. Every level except the last must carry the URL of that page. Check templates that print a level without a link, such as a category with no page of its own.
Fix positions
Positions must be whole numbers in order: 1, 2, 3. Gaps or repeats, often from a loop that starts at 0 or skips a hidden level, cause errors.
Match the visible breadcrumb
The markup should describe the breadcrumb trail visitors see. Do not add levels that are not shown, and keep names the same as the visible labels.
Remove duplicate breadcrumb markup
Themes and SEO plugins often both print breadcrumbs, one in JSON-LD and one in microdata. Two conflicting trails confuse the report. Switch one of them off.
Use full URLs
Give each item an absolute https URL that returns 200. Relative paths, http links and redirecting URLs produce warnings and can point Google at the wrong page.
Worked example
A correct trail for a product looks like this in JSON-LD: a BreadcrumbList with three ListItem entries: position 1, name Home, item https://example.com/; position 2, name Garden Tools, item https://example.com/garden-tools/; position 3, name Pruning Shears, with no item because it is the current page. Each entry has @type ListItem, and positions run 1, 2, 3 without gaps.
Mistakes to avoid
Do not mark up a breadcrumb that visitors cannot see. Do not use the page title with your brand appended as the last item's name, such as Pruning Shears | My Shop; use the plain label shown in the trail. And do not point item at a URL that redirects; use the final address.
Test and validate
Check a few page types in the Rich Results Test, including deep pages and pages with long names, then validate the fix in Search Console.
Tip: Generate the breadcrumb markup from the same data that prints the visible trail. Then the two can never drift apart.
Tags: schema, breadcrumbs, structured-data, search-console, fix
Fix
PageSpeed Insights shows Core Web Vitals assessment failed when real visitors' data misses at least one threshold. The fix starts with finding which metric failed and for which group of pages.
Know what is being judged
The assessment uses field data from real Chrome users over the last 28 days, judged at the 75th percentile. It passes only if Largest Contentful Paint is 2.5 seconds or less, Interaction to Next Paint 200 milliseconds or less, and Cumulative Layout Shift 0.1 or less.
Find the failing metric
Look at which of the three is red or amber. One bad metric fails the whole assessment. Fixing a metric that already passes changes nothing.
Check page or origin data
If the page has too little traffic, PageSpeed Insights shows data for the whole origin. Then the failing pages may be different from the one you tested. Use the Core Web Vitals report in Search Console to see which groups of URLs fail.
Do not chase the lab score
The performance score below the assessment comes from a single simulated test. A score of 95 can sit beside a failed assessment, and the other way round. Use the lab report to find causes, and the field data to judge success.
Fix the metric that failed
For LCP, speed up the server and the main image or heading. For INP, cut long JavaScript tasks that run when people click and type. For CLS, reserve space for images, adverts, embeds and late-loading banners.
Fix templates, not pages
Search Console groups similar URLs. A fix to the product template improves every product page at once.
Worked example
A site fails on mobile with LCP at 3.4 seconds while INP and CLS pass. The lab report shows the hero image is 1.8 MB and lazy-loaded. Serving a 120 KB WebP sized for phones, removing loading="lazy" from it and adding fetchpriority="high" brings lab LCP under 2 seconds. Four weeks later the field data passes and the assessment turns green.
Mistakes to avoid
Do not treat a single lab run as proof of a fix; results vary between runs. Do not fix desktop when mobile is failing, as mobile is usually the harder case. And do not install several speed plugins at once; they often clash and can make CLS or INP worse while improving the score.
Wait for the data to update
Because the data covers 28 days, an improvement takes up to four weeks to show fully. Validate the fix in Search Console to track it.
Tip: Test on a mid-range phone over mobile data, not on your office computer. Most failing sites fail on mobile, where the real visitors are.
Tags: cwv, performance, pagespeed, lcp, inp, cls, fix
Fix
Search Console has many reports and not every warning needs action. This card explains what each main report covers and which issues to deal with first.
Page indexing
Shows which URLs are indexed and why the rest are not. Act on server errors, redirect errors, wrongly applied noindex and soft 404s. Many other statuses, such as alternate pages with a proper canonical tag, are normal.
Sitemaps
Shows whether Google can read each sitemap you submitted and how many URLs it found. A sitemap that cannot be read, or one listing many URLs that are not indexed, deserves a look.
Core Web Vitals
Groups URLs by speed and stability for real visitors on mobile and desktop. Fix the groups marked poor first, one template at a time.
HTTPS
Lists pages that are not served securely. Any URL here should redirect to its https version or be fixed so it loads over https.
Enhancements and structured data
Reports such as Breadcrumbs, Products and Videos show markup errors and warnings. Errors stop a page being eligible for that rich result; warnings only mean optional fields are missing.
Manual actions and Security issues
These are the urgent ones. A manual action means a reviewer found a spam policy breach; a security issue means hacked content or malware. Fix the cause and request a review.
Worked example
A site owner logs in and sees 3,000 pages not indexed. Opening the Page indexing report shows 2,500 are alternate pages with a proper canonical tag and pages with redirect, both expected, 400 are crawled but not indexed, and 100 are server errors. The server errors are fixed first, the thin pages are improved or merged next, and the expected statuses are left alone.
How often to check
Look at Manual actions and Security issues every week. Review Page indexing and Core Web Vitals monthly, or after any release that changes templates, URLs or hosting. Search Console also emails the account owner about new issues, so keep the owner email address current.
Use validation
After fixing an issue, choose Validate fix on that issue. Google rechecks the affected URLs and tells you whether the fix held.
Tip: Check Manual actions and Security issues first every time you log in. They are rare, but nothing else in Search Console costs you more if missed.
Tags: search-console, indexing, technical-seo, fix
Fix
Site audits flag a redirect chain that contains an http hop when a secure URL redirects through an insecure one before reaching its destination. It wastes time and can break security settings.
What the warning means
A request for one URL passes through an http:// address on its way to the final page, for example https to http to https. Each hop adds delay, and the insecure step defeats the point of https.
Trace the chain
Run curl -I -L on the flagged URL and read each Location header. You will usually see which rule produces the http hop.
Find the rule writing http
The usual cause is a redirect written with a full http:// target, such as a trailing-slash rule, a www rule or a plugin redirect saved before the site moved to https.
Point every redirect at the final https URL
Change each rule to send visitors straight to the final https address in one step. Keep the order simple: force https and the preferred host in one rule, then any page-level redirects.
Fix the site address setting
If the CMS still has an http site address, such as WordPress Address and Site Address under Settings then General, it generates http links and redirects. Set both to the https version.
Update internal links
Replace http:// links to your own pages in content, menus, canonical tags and sitemaps. Then visitors and crawlers start on the right address.
Worked example
On nginx, a rule that adds a trailing slash was written as return 301 http://$host$uri/; so https://example.com/about first goes to http://example.com/about/ and then back to https. Changing it to return 301 https://$host$uri/; removes the http hop. The same mistake in Apache appears as a RewriteRule with a hard-coded http:// target.
Mistakes to avoid
Do not fix the chain by adding yet another redirect on top; remove or correct the rule that creates the http step. Do not forget other hosts, such as the www version or old domains, which often have their own chains. And do not turn on HSTS before the chain is fixed, or you hide the problem from your own testing while other visitors still hit it.
Add HSTS once https works
When every page works over https, an HSTS header makes browsers go straight to https, removing the first hop for returning visitors.
Tip: Search your database and theme files for your domain with http:// in front. Old hard-coded links are where most http hops begin.
Tags: redirects, https, technical-seo, fix
Fix
A 301 that does nothing is nearly always cached, overruled by another rule, or written in the wrong format for the platform. Here is how to find which.
Test without your browser cache
Browsers remember 301 redirects. Test with curl -I or a private window. A redirect you removed can still seem to work, and a new one can seem missing, because of the cache.
.htaccess on Apache
Rewrite rules need mod_rewrite and RewriteEngine On. Put your redirect rules above the WordPress block, because WordPress catches every request first. Check that AllowOverride lets the server read .htaccess at all.
Nginx ignores .htaccess
If the site runs on nginx, rules in .htaccess have no effect. Redirects must go in the nginx configuration, which on Plesk means the Additional nginx directives box.
Cloudflare
Cloudflare redirect rules only run on traffic that passes through Cloudflare, so the DNS record must be proxied with the orange cloud. Check the order of rules too; an earlier rule can catch the request first.
cPanel
The cPanel Redirects tool writes to .htaccess. A wildcard setting or a rule for the wrong domain, www versus non-www, is the usual reason it fails. Check the generated lines in .htaccess.
Wix, Squarespace and Webflow
These use their own redirect screens. Enter the old path without the domain, match the trailing slash exactly as the old URL had it, and publish the site after saving where the platform requires it.
Worked example for Apache
A working .htaccess redirect for one page looks like: RewriteEngine On, then RewriteRule ^old-page/?$ /new-page/ [R=301,L]. For a whole domain move: RewriteCond %{HTTP_HOST} ^old-example\.com$ [NC] followed by RewriteRule ^(.*)$ https://new-example.com/$1 [R=301,L]. Place these above the # BEGIN WordPress line so WordPress does not catch the request first.
Worked example for nginx
For one page, add location = /old-page { return 301 /new-page/; } inside the server block. For a domain, a separate server block with server_name old-example.com; and return 301 https://new-example.com$request_uri; moves everything with the path kept. Reload nginx and test with curl -I.
Look for competing rules
A plugin, the server and a CDN can all redirect the same URL. Keep each redirect in one place, then retest the full chain.
Tip: Write the redirect, then test it from the command line before telling anyone it works. curl -I shows the truth; a browser shows its memory.
Tags: redirects, 301, htaccess, cloudflare, fix
Fix
Removing a permanent redirect means deleting the rule, clearing every cache that stored it, and accepting that some browsers will remember it for a while.
Find where the redirect lives
A redirect can come from the server configuration, .htaccess, a CMS plugin, the hosting panel or a CDN rule. Run curl -I on the URL; headers such as Server or CF-Ray hint at which layer answered.
Delete the rule at its source
Remove the rule from that one place. If several layers each held a copy, remove every copy, or the redirect keeps working from the next layer.
Clear server and CDN caches
Purge page caches and the CDN cache for the old URL. Cached redirects can be served for hours after the rule is gone.
Clear your browser cache
Browsers store 301 responses and may reuse them without asking the server. Clear the cache or test in a private window to see the real behaviour.
Accept visitors' caches
You cannot clear other people's browsers. Visitors who received the 301 may keep being redirected until their cache drops it. That is why 301 should only be used for permanent moves.
Let search engines catch up
Google recrawls the old URL and sees that it now returns content. Request indexing for it if it matters, and make sure it is in your sitemap.
Worked example
A page was redirected to a new address by mistake. The rule sits in a WordPress redirect plugin and also as a Cloudflare redirect rule added during a test. Deleting both, purging the Cloudflare cache for the URL and testing with curl -I shows a 200 again. The owner's browser still redirects until its cache is cleared, which is expected.
How to stop browsers holding on to redirects
Browsers can cache a 301 for as long as they like unless told otherwise. If you must use a permanent redirect that might change, send a Cache-Control header such as max-age=3600 with the redirect response, so browsers check again after an hour. Better still, use 302 until you are sure.
Use 302 next time
If a redirect might be undone later, use 302 or 307. Browsers and search engines treat them as temporary and do not hold on to them.
Tip: If a redirect must be undone quickly, a new redirect back from the destination to the original URL reaches visitors whose browsers cached the first one.
Tags: redirects, 301, caching, fix
Fix
A 403 means the server understood the request and refused it. On your own site the cause is usually file permissions, a missing index file, a server rule or a firewall.
Check the error log
The server error log usually states the reason, such as permission denied, directory index forbidden or access forbidden by rule. Read it before changing anything.
Check file permissions and ownership
Files usually need 644 and folders 755, owned by the site's user. A file uploaded as root or with 600 permissions can be unreadable to the web server.
Check for an index file
If a folder has no index.html or index.php and directory listing is off, the server answers 403. Add the index file or fix the index setting.
Check nginx rules
Look for deny all lines, location blocks that block the path, or a root directive pointing at the wrong folder. A wrong root often produces 403 rather than 404.
Check .htaccess on Apache
Deny from all, Require all denied or hotlink rules can block more than intended. Comment rules out one at a time to find the one responsible.
Check Cloudflare and other firewalls
Cloudflare WAF rules, Bot Fight Mode, country blocks and rate limits can answer with 403. The Security Events log in Cloudflare shows which rule fired. Security plugins and ModSecurity do the same on the server.
Worked example
After uploading files with a new FTP client, the whole site returns 403. The nginx error log shows permission denied for every file. The new uploads are owned by the wrong user and set to 600. Running chown on the site files to the site user and chmod 644 on files and 755 on folders restores access at once.
Commands to check quickly
ls -l shows permissions and owners for the files in a folder. namei -l /path/to/file shows the permissions of every folder on the way to it, since one closed folder blocks everything under it. tail -n 50 on the error log shows the latest refusals with the exact path the server tried to read.
Check IP and login protections
Folder password protection, IP allow lists and admin-area rules can catch visitors or crawlers you did not mean to block. Test from another network to confirm.
Tip: If only bots get the 403, check your firewall's bot settings first. Blocking search engines by mistake is one of the most common causes of pages dropping out of Google.
Tags: http-errors, 403, nginx, cloudflare, security, fix
Fix
A 404 tells visitors and search engines the page does not exist. Some are normal. Others come from moved pages, broken links or a CMS setting, and each has its own fix.
Find the 404s
Use the Page indexing report in Search Console, your server logs and a site crawler. Logs show which 404s real visitors and bots actually hit, which helps you decide what matters.
Decide for each URL
If the content moved, 301 redirect the old URL to the new one. If it has a close replacement, redirect there. If it is gone for good with no replacement, let it return 404 or 410. Do not send every missing page to the home page.
Fix internal links
A 404 linked from your own pages is your fault, not the visitor's. Update the links to the right URL so nobody reaches the missing page.
WordPress: refresh permalinks
If all posts suddenly return 404, open Settings then Permalinks and click Save without changing anything. This rewrites the rewrite rules. On nginx, check the server has the try_files rule WordPress needs.
Shopify: add URL redirects
In Shopify, open Online Store then Navigation then URL redirects, or Content then Menus in newer admin layouts, to add a redirect from the old path to the new one. Changing a product handle can create the redirect for you if the option is ticked.
Make the 404 page helpful
A custom 404 page with search, popular links and the main navigation keeps visitors on the site. It must still return the 404 status code, or Google reports a soft 404.
Worked example
A blog changes its permalink setting from /2026/10/post-name/ to /post-name/. Every old link from social media and other sites now returns 404. A single redirect rule mapping the old date pattern to the new one fixes thousands of URLs at once, rather than one redirect per post.
410 or 404
Both tell search engines the page is not there. 410 says it is gone on purpose and is not coming back, and Google may drop it from the index slightly sooner. 404 is fine for most cases. What matters more is that real moved pages redirect, and that your custom error page returns the right status code.
Check external links
If other sites link to a URL that no longer exists, redirect it to the right page. You keep the visitors and the value of the link.
Tip: Before deleting a page, check its backlinks and traffic. A redirect set up at the time takes a minute; finding and fixing the lost links later takes far longer.
Tags: http-errors, 404, broken-links, shopify, wordpress, fix
Fix
How your server answers a request for robots.txt changes how Google crawls the whole site. A 404 is harmless; a 500 can stop crawling altogether.
A 404 or other 4xx means no rules
Google treats a robots.txt that returns 404, 403 or another 4xx error, apart from 429, as if there were no restrictions, and crawls everything. A missing file is not a problem if you have nothing to block.
A 5xx or 429 means stop
If robots.txt returns a server error or 429, Google treats the site as fully blocked for a while. Ongoing errors here can stop all crawling, so this is urgent.
What happens after 30 days
Google's documentation says that if robots.txt stays unreachable for more than 30 days, Google uses the last cached copy, or crawls without restrictions if it has none.
Check the response
Run curl -I on /robots.txt for each host, including www and non-www. It should return 200 with a plain text content type. Follow any redirects and check where they end.
Check firewalls and bot rules
WAFs and bot protection sometimes challenge or block requests for robots.txt. Make sure verified search engine crawlers can fetch it without a challenge page.
Check dynamic robots.txt
Many CMSs generate robots.txt on the fly. If the application is down or slow, robots.txt fails with it. A static file, or a cached copy, is more reliable.
Worked example
A site behind a bot protection service shows crawl rates falling to almost nothing. Search Console's robots.txt report shows fetch failures. The protection service answers Googlebot's request for robots.txt with a challenge page and a 503. Allowing verified crawlers to fetch /robots.txt without a challenge restores normal crawling within days.
A safe minimal robots.txt
If you have nothing to block, a file with User-agent: * and an empty Disallow: line, followed by a Sitemap: line with your sitemap address, allows all crawling and tells crawlers where your sitemap is. Serve it as a static file with a 200 status and text/plain content type, and it will keep working even when the application is down.
Confirm in Search Console
The robots.txt report under Settings shows when Google last fetched the file and whether it succeeded. Check it after any fix.
Tip: Treat robots.txt as part of your uptime monitoring. If it starts returning errors, it affects every page on the site.
Tags: robots-txt, crawling, server-errors, fix
Fix
In WordPress, robots.txt can come from the core, a plugin, a real file or a CDN. Edit it in the wrong place and nothing changes; here is how to find the right one.
Find out what is serving it
Open yourdomain/robots.txt. WordPress creates a virtual robots.txt when no real file exists. A physical robots.txt file in the site root always overrides the virtual one and any plugin setting.
Edit with Yoast
In Yoast SEO, open Tools then File editor. If no file exists, Yoast offers to create one. If file editing is disabled on your site, upload the file by SFTP instead.
Edit with Rank Math
In Rank Math, open General Settings then Edit robots.txt. Rank Math edits the virtual file, so the box is locked if a physical robots.txt already exists; delete or rename that file first.
Edit the file directly
Without a plugin, create a plain text file called robots.txt in the site root by SFTP or the hosting file manager. Save it as UTF-8, one rule per line.
Check Cloudflare
Cloudflare can add content to robots.txt, for example its managed rules for AI crawlers. If the live file contains lines you did not write, check the robots.txt and AI crawler settings in the Cloudflare dashboard.
Clear caches
Caching plugins and CDNs can keep serving the old file. Purge the cache for /robots.txt after each change.
Worked example
An owner edits robots.txt in Rank Math, but the live file never changes. The site root contains an old physical robots.txt left by a previous plugin, which overrides the virtual one. Renaming the old file by SFTP lets Rank Math's version show, and the change appears at /robots.txt straight away.
A sensible WordPress robots.txt
Most WordPress sites need very little: User-agent: *, Disallow: /wp-admin/, Allow: /wp-admin/admin-ajax.php, and a Sitemap: line with the full sitemap address. Do not block /wp-content/ or /wp-includes/, because search engines need the CSS and JavaScript there to render pages.
Test the result
Reload /robots.txt and confirm the live file says what you intended. Then check the robots.txt report in Search Console to see when Google fetches the new version.
Tip: Never use robots.txt to remove a page from Google. It stops crawling, not indexing; use noindex on a crawlable page instead.
Tags: robots-txt, wordpress, yoast, rank-math, cloudflare, fix
Fix
WordPress sitemaps can come from the core or from SEO plugins, each at its own address. A 404, a blank page or an XML error usually comes from rewrite rules, caching or stray output.
Use the right address
WordPress core serves /wp-sitemap.xml. Yoast and Rank Math serve /sitemap_index.xml and switch the core sitemap off. If you turned the plugin sitemap off, only the core one exists.
Refresh permalinks
Open Settings then Permalinks and click Save. This rebuilds the rewrite rules and fixes most sitemap 404s after a plugin change or migration.
Add nginx rewrite rules
On nginx, WordPress pretty URLs need the right try_files rule, and some sitemap plugins document extra rewrite lines. Without them, the sitemap returns 404 while normal pages work.
Fix XML declaration errors
An error such as XML declaration allowed only at the start of the document means something printed before the XML, usually a blank line at the end of a theme or plugin PHP file. Find it by switching plugins off one at a time.
Exclude sitemaps from caching
Caching and minification plugins can store or rewrite the XML. Exclude sitemap URLs from page caching and minification, then purge the cache.
Use one sitemap generator
Two SEO plugins, or an SEO plugin plus a sitemap plugin, fight over the same address. Keep one and turn the others' sitemaps off.
Worked example
After moving a WordPress site to a new host on nginx, /sitemap_index.xml returns 404 while pages load fine. The new server block lacks the try_files rule. Adding location / { try_files $uri $uri/ /index.php?$args; } and reloading nginx brings the sitemap back, along with every other pretty URL that had quietly broken.
How to find stray output
Open the sitemap and view source. If the very first character is not the < of the XML declaration, something printed before it. Switch to a default theme briefly; if the sitemap works, the theme's functions.php or another PHP file has a blank line after a closing ?> tag. Remove the closing tag or the space after it.
Resubmit
When the index loads as XML, submit it in Search Console and list it in robots.txt with a Sitemap: line.
Tip: After any migration, open the sitemap index straight away. It is often the first thing to break and the last thing anyone checks.
Tags: sitemap, wordpress, yoast, rank-math, fix
Fix
Lighthouse reports NO_LCP when it could not find a Largest Contentful Paint element while loading the page. The page painted nothing it could measure, or Lighthouse saw a different page from the one you expect.
What the error means
Lighthouse loaded the page but never recorded an LCP event, so it cannot score performance. It is a measurement failure, not a slow score.
Check what Lighthouse saw
Look at the screenshots and the final screenshot in the report. A challenge page from a firewall, a blank screen or a consent wall covering everything means Lighthouse never reached your real content.
Check content hidden on load
If the page starts with opacity 0, visibility hidden or a full-screen loader that fades out with JavaScript, the browser may not record a contentful paint. Show the main content from the first paint.
Check pages that only draw with JavaScript
If the whole page renders after a large script runs, slow test conditions can end before anything appears. Render the main content on the server, or at least the heading and first image.
Check that the test finished
Very slow pages can time out under Lighthouse's mobile throttling. If the page takes many seconds to show anything, the speed itself is the problem to fix.
Check bot protection
Some security tools treat Lighthouse and PageSpeed Insights as bots. Allow them, or test from Chrome DevTools on your own machine to compare.
Worked example
A landing page fails every Lighthouse run with NO_LCP. Its hero section starts with opacity: 0 and fades in after a JavaScript animation library loads. Under mobile throttling the library loads late and the content is never counted as painted in time. Removing the starting opacity, so the hero is visible from the first paint, fixes the error and gives a measurable LCP.
How to test without the CDN or firewall
Run Lighthouse from Chrome DevTools on your own machine and compare it with PageSpeed Insights. If DevTools works and PageSpeed Insights fails, something is treating Google's test servers differently, usually bot protection or a country rule. Allow the test, or check the firewall logs for blocked requests at the time of the run.
Retest
After changes, run the test several times. A single NO_LCP among good runs points to an intermittent block rather than the page itself.
Tip: Never fade in your main heading or hero image on page load. It looks smooth to you, and it delays or prevents the paint every speed test is measuring.
Tags: lcp, pagespeed, lighthouse, performance, fix
Fix
Time to First Byte is how long the browser waits before the first byte of the page arrives. A high TTFB delays everything after it, so it is often the first thing to fix.
Measure it properly
Run curl -w with time_starttransfer, or use the Network panel in Chrome DevTools and read Waiting for server response. Test several times and from more than one location.
Count the redirects
Redirects happen before the final page starts, and field TTFB includes them. A visitor arriving through http to https to www can lose hundreds of milliseconds before your server even builds the page.
Cache whole pages
For pages that are the same for every visitor, serve a cached HTML copy rather than building the page on every request. Page caching usually cuts TTFB more than anything else.
Find slow code and queries
If pages must be built per request, profile them. Slow database queries, missing indexes, API calls made during the request and heavy plugins are the usual causes.
Move content closer to visitors
Distance adds delay. A CDN that caches HTML near visitors helps sites with a spread-out audience; static files alone on a CDN do not fix HTML TTFB.
Check the hosting
Overloaded shared hosting, too few PHP workers or Node processes, and servers that sleep when idle all show up as high TTFB, often worse at busy times.
Worked example
A WordPress site shows a TTFB of 1.9 seconds. Testing a cached page shows 0.15 seconds, so the server is fast when it does not build pages. Profiling with Query Monitor finds one plugin running 400 database queries per page. Replacing it and turning on full-page caching brings real-visitor TTFB under 0.6 seconds.
A quick measuring command
curl -o /dev/null -s -w "dns %{time_namelookup} connect %{time_connect} tls %{time_appconnect} ttfb %{time_starttransfer} total %{time_total}\n" https://example.com/ splits the wait into stages. If DNS or connect is slow, look at DNS and distance; if the gap between tls and ttfb is large, the server is slow to build the page.
Aim for the target
Google suggests a TTFB of about 0.8 seconds or less as a rough guide for good loading. Fix it, then recheck LCP, which usually improves too.
Tip: Test TTFB on a page that is not cached and one that is. The gap tells you whether caching or your code is the bigger win.
Tags: ttfb, performance, server, caching, fix
Fix
Largest Contentful Paint is often fine on desktop and poor on mobile, because phones have slower processors and networks. The fix is to make the main element arrive and render sooner.
Find the LCP element
PageSpeed Insights names the LCP element for the mobile test. It is usually the hero image or the main heading. Every fix depends on knowing which one it is.
Do not lazy-load it
If the LCP image has loading="lazy", the browser waits to fetch it. Remove lazy loading from the main image, and from anything else visible on first load.
Give it high priority
Add fetchpriority="high" to the LCP image, and preload it if the browser only finds it late, for example as a CSS background.
Send a smaller image to phones
Use srcset and sizes so phones download an image sized for their screen, in a modern format such as WebP or AVIF. A 2,000-pixel hero on a 400-pixel screen is common and slow.
Remove render-blocking resources
Large CSS files and scripts in the head delay the first paint. Inline the critical CSS, defer non-essential scripts and load fonts without blocking text.
Fix server time
A slow TTFB pushes LCP back by the same amount. Page caching and a CDN for HTML help mobile visitors most.
Worked example
A hero image is set as a CSS background on a div, so the browser only finds it after the CSS loads. Moving it into an img element with srcset, sizes="100vw" and fetchpriority="high", plus a preload in the head for the mobile size, lets the browser start the download at once. Mobile LCP drops from 4.1 to 2.2 seconds in the lab.
Mistakes to avoid
Do not preload several images; preloading everything is the same as preloading nothing. Do not swap the hero image for a slider that loads many large images. And do not hide the hero on mobile with display: none while still downloading it; give phones a smaller image or none at all.
Check the field data
Lab results guide you, but the Core Web Vitals assessment uses real mobile visitors. Watch the field data over the following weeks.
Tip: Load the page on a real mid-range phone over mobile data and watch it. You will usually see exactly what the LCP is waiting for.
Tags: lcp, mobile, performance, images, fix
Fix
PageSpeed Insights flags CSS and JavaScript files that stop the page from appearing until they load. In WordPress these usually come from the theme and plugins.
List the blocking files
The PageSpeed Insights audit lists each file and how much time it costs. Note which plugin or the theme each file belongs to; the folder in the URL usually tells you.
Remove what you do not need
Plugins often load their CSS and scripts on every page, even where they are not used. Remove unused plugins, and stop others loading outside the pages that need them.
Defer JavaScript
Since WordPress 6.3, scripts can be registered with a defer or async loading strategy. Caching plugins such as WP Rocket, LiteSpeed Cache and Autoptimize also offer defer options. Test after switching it on, because some scripts rely on running early.
Handle jQuery carefully
Many themes and plugins expect jQuery to load first. Deferring it can break menus, sliders and forms. Defer it only if everything that uses it is deferred too.
Inline critical CSS
Critical CSS puts the styles for the first screen in the page and loads the rest later. Performance plugins can generate it; check the page for a flash of unstyled content afterwards.
Load fonts better
Host fonts on your own server, preload the main font file and use font-display swap so text appears straight away.
Worked example
PageSpeed Insights lists a contact form plugin's CSS and JavaScript on every page, though the form is only on the contact page. Using the plugin's own setting, or a script manager plugin, to load those files only on the contact page removes two render-blocking files site-wide without touching the form.
Deferring a script in code
In a theme or small plugin, wp_enqueue_script( 'my-script', $url, array(), '1.0', array( 'strategy' => 'defer', 'in_footer' => true ) ); registers a script that loads without blocking the page, on WordPress 6.3 or later. Scripts that depend on it are handled in the right order by WordPress.
Retest real pages
Test the home page, a post and a product or service page. Each template loads different files, so one good result does not prove the rest.
Tip: Switch one optimisation on at a time and check the site works. Turning everything on at once makes it impossible to know what broke.
Tags: render-blocking, wordpress, performance, javascript, css, fix
Fix
Native lazy loading is simple, but it often seems not to work because the browser loads more than you expect, or because something else on the page overrides it.
Know what lazy means to the browser
Browsers do not wait until an image is on screen. They start loading lazy images some distance before they scroll into view, further on slow connections. Images just below the fold often load straight away; that is normal.
Check the attribute is in the HTML
View the page source and check the img or iframe tag really carries loading="lazy". Some themes add it with JavaScript after the browser has already started loading the image.
Check for a lazy-loading script
Older lazy-load scripts move the address into data-src and leave src empty. Mixing a script with native lazy loading can stop images showing or make them load twice. Use one method.
CSS background images are not covered
loading="lazy" only applies to img and iframe elements. Background images set in CSS load when the CSS rule applies, whatever the attribute says.
Hidden elements still need care
Images inside hidden tabs or carousels may load or not depending on the browser. Test the components that matter to you.
Do not lazy-load the first screen
If the main image is lazy, it loads late and hurts LCP. Remove loading="lazy" from anything visible on first load and give the main image fetchpriority="high".
Worked example
A blog adds loading="lazy" to every image, but the Network panel shows all 40 images loading at once. The theme also runs an old lazy-load script that copies every data-src into src on page load. Removing the old script, so native lazy loading is the only method, lets off-screen images wait as intended.
Correct markup
A lazy image below the fold looks like <img src="photo.webp" width="800" height="600" alt="Kitchen after the refit" loading="lazy">. An iframe, such as a video embed, uses <iframe src="..." loading="lazy" title="Product demo video">. The width and height let the browser hold the space while the file waits.
Test properly
Open Chrome DevTools, choose the Network panel, filter by Img, reload, then scroll slowly. Lazy images appear in the list as you approach them.
Tip: Add width and height to every lazy image. The browser can then reserve its space before it loads, which also prevents layout shift.
Tags: lazy-loading, images, performance, html, fix
Fix
A quick way to confirm that off-screen images wait until needed, and that the images you see first are not delayed by mistake.
Open the Network panel
In Chrome, open DevTools, choose Network and filter by Img. Tick Disable cache so every test starts fresh.
Reload at the top of the page
Reload without scrolling. Note which images load straight away; it should be the ones you can see, plus a few just below.
Scroll and watch
Scroll slowly down the page. New image requests should appear as you get closer to each one. If every image loaded on page load, lazy loading is not working.
Check the attribute
In the Elements panel, select an image and check it has loading="lazy", or the data-src attribute your lazy-load script uses.
Check the first screen
Make sure the hero image and logo do not have loading="lazy". Lighthouse warns when the LCP image is lazily loaded.
Use Lighthouse
Run Lighthouse. The Defer offscreen images audit lists images that could be lazy but are not, and how much data they waste.
Worked example
On a category page with 60 product images, the Network panel shows 8 images on first load, then new requests in groups as the page scrolls. That is lazy loading working. If it had shown 60 requests before any scrolling, every image would be loading eagerly.
What a failing result tells you
If every image loads on page load, check whether the attribute is missing, whether a script is setting src early, or whether the images are CSS backgrounds. If the first image arrives late and Lighthouse warns about lazy-loaded LCP, the attribute is on an image that should load eagerly.
Test on a slow connection
Choose a throttled network in DevTools and repeat. Browsers load lazy images earlier on slow connections, so the pattern changes, but it should still load in stages.
Tip: Test on a long page with many images, such as a category or blog listing. A short page often shows no difference at all.
Tags: lazy-loading, images, performance, testing, fix
Fix
The Next.js image component resizes and converts images on request. When it fails you see broken images, full-size files or errors, usually because of configuration or the way the site is deployed.
Allow remote image hosts
Images from another domain must be allowed in next.config with images.remotePatterns. Without it, next/image refuses to load them and shows an error in development.
Static export needs a different loader
With output set to export, there is no server to resize images, so the default optimiser cannot run. Use a custom loader pointing at an image CDN, or set unoptimized and resize images yourself.
Install sharp for self-hosting
When you run Next.js on your own server, the optimiser uses the sharp package. Make sure it is installed in production, especially in standalone builds and Docker images.
Set sizes on responsive images
Without a sizes prop, an image set to fill may download a file far larger than its slot. Describe how wide the image is at each breakpoint so the right file is chosen.
Check unoptimized is not set
An unoptimized prop, or images.unoptimized in the config, switches optimisation off everywhere. It is often left in from testing.
Check caching behind a proxy
Optimised images are cached by Next.js and should be cached by your CDN. If every request is resized again, check cache headers and that the cache folder can be written to.
Worked example
A Next.js site deployed with output: 'export' shows errors that the default image loader is not compatible with export. Adding a custom loader that returns an image CDN URL with the requested width and quality restores optimised images, without changing any next/image components in the pages.
Allowing a remote host
In next.config.js, images: { remotePatterns: [ { protocol: 'https', hostname: 'images.example.com', pathname: '/products/**' } ] } lets next/image optimise images from that host and path only. Keep the pattern narrow; allowing any host lets others use your server to resize their images.
Look at the response
In the Network panel, an optimised image comes from /_next/image with a width parameter and a modern format such as WebP. If you see the original file, optimisation is not running.
Tip: Use the priority prop on the main above-the-fold image only. On every image it removes the benefit and slows the page.
Tags: nextjs, images, performance, react, fix
Fix
Google often writes its own snippet instead of your meta description, and changes only show after the page is recrawled. Here is what you can control.
Google chooses the snippet
Google uses your meta description when it thinks it describes the page for that search. For many searches it picks text from the page instead. A different snippet is not an error.
Check the live page
View the page source, not the editor, and confirm there is one meta name="description" with the text you expect. A cached page or a theme can be sending an old or second description.
Wait for a recrawl
Google shows the description it saw on its last crawl. After a change, inspect the URL in Search Console and request indexing; the snippet updates once the page is processed again.
Make it match the search
Descriptions that answer what people actually search for are used more often. Lead with what the page gives the reader, in plain words that match the main query.
Keep it a sensible length
There is no fixed limit, but Google cuts long snippets on screen width. Around 150 to 160 characters is a common working length; put the important part first.
Make each one unique
Identical descriptions across many pages make Google more likely to ignore them all. Write a specific description for each important page.
Worked example
A service page's description reads Welcome to our website, we offer great services. Google ignores it for most searches and shows a sentence from the page instead. Rewriting it to say what the service is, who it is for and what makes it different, in about 150 characters, leads Google to use it for the main searches after the next crawl.
Mistakes to avoid
Do not judge from one search; Google may show your description for one query and page text for another. Do not put the same description on every page to save time. And do not change descriptions every few days while waiting for an update; each change resets what Google has to pick up.
Avoid stuffing
A list of keywords or a sales slogan with no information is often replaced. Describe the page honestly.
Tip: Search for the page with the exact query you care about. The snippet you see is the one that matters; a site: search shows Google's view for a different search.
Tags: meta-description, serp, on-page, fix
Fix
You set the description in your SEO plugin, but the page shows something else, or nothing. The cause is usually the theme, a cache, a second plugin or the home page setup.
Check the page source
View source on the live page and search for name="description". If there are two, the theme or another plugin is adding its own. If there is none, the plugin output is not reaching the page.
Check the theme head
The theme's header file must call wp_head. Themes that skip it, or hard-code a description in header.php, stop the plugin from working. Check the theme and any child theme.
Use one SEO plugin
Running Yoast and Rank Math together, or one of them with an all-in-one SEO feature built into the theme, causes duplicate or missing tags. Keep one.
Clear caches
Page caching plugins, server caches and CDNs keep serving the old HTML. Purge them after changing descriptions.
Check the home page settings
If the home page shows your latest posts, its description is set in the plugin's home page or site settings. If it is a static page, the description is set on that page itself.
Check variables
Templates that use variables such as the excerpt print nothing when the excerpt is empty. Write a description by hand for important pages.
Worked example
A site shows two description tags: one from Yoast and one hard-coded in the theme's header.php, copied from a template years ago. Google picks the theme's generic one. Removing the hard-coded line from a child theme copy of header.php leaves Yoast's description as the only one.
How to find which plugin prints a tag
View source and read the comments around the tag; Yoast and Rank Math mark their output with HTML comments naming the plugin. A description with no comment around it often comes from the theme. Switching to a default theme briefly confirms it.
Headless and page builders
If the front end is built separately, such as a headless site, the plugin's tags only appear if the front end reads them from the API. Check the front end prints them.
Tip: After fixing, test with a URL inspection in Search Console. It shows the HTML Google receives, which is what decides the snippet.
Tags: meta-description, wordpress, yoast, rank-math, fix
Fix
Many pages sharing one meta description is usually a template problem. Fix the template and the pages that matter, and let near-duplicate pages point to one main version.
Find the duplicates
Crawl the site and sort by meta description. Group the duplicates by template: categories, tags, paginated pages, filters or products.
Fix the template default
Many themes print one site-wide description on every page without its own. Change the template to build a description from each page's data, or leave it out where you cannot write one.
Write descriptions for key pages
For the home page, main services, products and top posts, write a specific description by hand that says what that page gives the reader.
Handle paginated pages
Page 2 and beyond of a listing can share a description, or add the page number. They are rarely what people search for, so do not spend long on them.
Handle parameter and filter URLs
If filtered or sorted URLs duplicate a main page, give them a canonical tag pointing to the main version, or keep them out of the index. Then their descriptions stop mattering.
Merge true duplicate pages
If two pages share a description because they share their content too, merge them into one and redirect the other.
Worked example
A crawl finds 900 pages with the description Shop the best garden tools online. It comes from the theme's default, used whenever a page has none. Changing the product template to build descriptions from the product name, main feature and price range gives every product a unique description, and the 30 key pages get hand-written ones.
Mistakes to avoid
Do not fix duplicates by adding random words or numbers; it makes them unique but useless. Do not spend time on pages that are noindexed or canonicalised elsewhere. And do not stuff the description with keywords to make it different from its neighbours.
Recrawl and check
Crawl again to confirm the count has dropped, then let Google recrawl over the following weeks.
Tip: A missing description is better than a wrong one. Google writes its own snippet for a page with none, but a copied description tells it nothing about the page.
Tags: meta-description, duplicate-content, on-page, fix
Fix
Alt text should say what an image means in its place on the page, in as few words as that takes. Decorative images and icons beside text should be silent.
There is no strict limit
No standard sets a maximum length. Screen readers read the whole text. Aim for a short phrase or sentence; many guides suggest keeping most alt text under about 125 characters as a working habit.
Describe the purpose, not the pixels
Ask what the image adds to the page. A photo of a product needs the product named. A chart needs its main point, with the detail in the text or a caption.
Linked images describe the destination
If an image is the only content of a link or button, its alt text is the link's name. Say where it goes or what it does, such as Download the price list.
Decorative images get empty alt
Borders, background flourishes and stock photos that add nothing should have alt="". An empty alt tells screen readers to skip the image. Leaving alt out completely makes some read the file name.
Icons next to text stay silent
An icon beside a word that already says the same thing, such as a phone icon beside Call us, should be hidden from screen readers with alt="" or aria-hidden="true".
Icon-only buttons need a name
A button showing only an icon, such as a magnifying glass, needs an accessible name on the button: alt on the image, visually hidden text or aria-label.
Worked examples
A product photo: alt="Oak dining table with six matching chairs". A logo that links home: alt="AI Web Page SEO home". A decorative swirl: alt="". A search button with a magnifying-glass icon: <button aria-label="Search"><svg aria-hidden="true">...</svg></button>. A chart: alt="Bar chart: sales doubled from 2024 to 2026", with the figures in the text below.
Mistakes to avoid
Do not start with Image of or Picture of; screen readers already announce an image. Do not use the file name, such as IMG_2041.jpg. Do not stuff alt text with keywords. And do not give a decorative image a description just to clear an audit warning; empty alt is the correct answer for decoration.
Do not repeat the caption
If the caption already describes the image, keep the alt short or empty so screen reader users do not hear the same thing twice.
Tip: Read the page aloud with the alt text in place of each image. If it sounds natural and nothing is missing or repeated, the alt text is right.
Tags: alt-text, accessibility, images, wcag, fix
Fix
Alt text is not a tooltip. Browsers do not show it when you hover, and that is correct. Here is what alt text does, and what to use if you want hover text.
What alt text is for
Alt text is read by screen readers, shown when the image fails to load and used by search engines to understand the image. It is not meant to be seen by sighted visitors while the image is visible.
Why you remember tooltips
Very old versions of Internet Explorer showed alt text on hover. Modern browsers do not, so nothing is broken.
Where hover text comes from
The tooltip you see on hover comes from the title attribute. If an image shows a tooltip, it has a title, which may be a copy of the alt text added by a CMS.
Be careful with title
Title tooltips do not appear on touch screens or for keyboard users, and screen readers treat them inconsistently. Never put important information only in a title.
Use a caption for visible text
If information about the image matters to everyone, put it in a visible caption, for example in a figcaption element under the image.
Check your alt text properly
To see alt text, inspect the image in your browser's developer tools, use an accessibility checker, or turn images off in the browser.
Worked example
An owner adds alt text to every gallery image and reports that it is not working because nothing appears on hover. Inspecting an image shows alt="Finished bathroom with walk-in shower", so it is working. To show a visible description as well, the gallery is changed to print a figcaption under each image.
Markup with a caption
A figure with a visible caption looks like <figure><img src="bathroom.webp" alt="Finished bathroom with walk-in shower" width="1200" height="800"><figcaption>Bathroom refit completed in March, including underfloor heating</figcaption></figure>. The alt text describes the image; the caption adds context everyone can read.
Do not duplicate alt into title
Some plugins copy alt text into the title. Screen readers may then read it twice. Turn that option off if your CMS has it.
Tip: If you want everyone to see a description, write a caption. If you want screen reader users to hear it, write alt text. They do different jobs.
Tags: alt-text, accessibility, html, images, fix
Fix
A link to #section should jump to the element with that id. When it does not, the id is wrong, the target is hidden behind a header, or a script is getting in the way.
Match the id exactly
The part after # must match the target's id exactly, including capital letters. #Pricing does not match id="pricing".
Make ids unique
If two elements share the same id, the browser jumps to the first. Duplicate ids often come from repeated blocks or copied sections.
Use id, not name
Old pages used a name attribute on an a tag as the target. Use an id on the element itself; the name attribute on links is obsolete in HTML.
Allow for a sticky header
If the page jumps but the heading hides behind a fixed header, add scroll-margin-top to the target in CSS, set to the header's height.
Check the element exists on load
If the section is built by JavaScript after the page loads, the browser cannot find it at the moment of the jump. Render it in the HTML, or scroll to it after it appears.
Check routers and scripts
Single-page apps and smooth-scroll scripts can catch clicks on # links and handle them their own way. Check the router settings, or make sure the script still finds the target.
Worked example
A long guide has a table of contents linking to #getting-started. Clicking it moves the page, but the heading is hidden under the fixed header. Adding h2[id] { scroll-margin-top: 80px; } to the CSS, matching the header height, makes every jump land with the heading in view.
Correct markup
The link: <a href="#getting-started">Getting started</a>. The target: <h2 id="getting-started">Getting started</h2>. Use lower-case ids with hyphens, no spaces, unique on the page, and keep them stable, because other sites and search results may link to them.
Link across pages correctly
To jump to a section on another page, include the path, such as /pricing#plans. On the same page, the fragment alone is enough.
Tip: Test each jump link with JavaScript turned off. If it works then, a script is the cause; if not, the id or the HTML is.
Tags: anchor-links, html, javascript, internal-links, fix
Fix
A link only counts for Google if Google crawls and indexes the page it sits on. Many backlinks never get that far, and some should not.
Check the linking page
Search Google for the exact URL of the page that links to you. If the page is not indexed, Google is not counting the link yet.
Check the page can be indexed
The linking page may be blocked by robots.txt, carry noindex, sit behind a login or need JavaScript to show the link. Then the link may never count.
Check the link type
Links marked nofollow, ugc or sponsored are hints not to pass ranking credit. Google may still crawl them, but they are not the same as an editorial link.
Check the link is in the HTML
If the link is added by a script, or appears only after a click, crawlers may not find it. View the page source and look for your URL.
Give it time
New pages on small sites can take weeks to be crawled. Links on popular, regularly updated pages are found far faster than links on forgotten pages.
Avoid indexing services
Services that promise to force backlinks into the index often use spam networks, and buying links to pass ranking credit breaks Google's spam policies. They can do more harm than good.
Worked example
A business gets a link from a supplier's new partners page, but nothing changes. The page is not indexed: it is linked only from a page behind a login. The supplier adds a link to it from their public footer, Google crawls and indexes it within a week, and the link starts to count.
Mistakes to avoid
Do not create pages on other sites purely to link to yourself, or ping link lists through indexing tools; both look like link schemes. Do not panic about links on low-quality pages that never get indexed; they were unlikely to help anyway. Put the effort into links that people will actually see and click.
Focus on links from indexed pages
Links from pages that are already indexed, relevant and well linked themselves are the ones that count. Aim your outreach there.
Tip: If you control the linking page, such as a partner or your own other site, link to it from somewhere crawled often. Then Google finds it quickly.
Tags: backlinks, indexing, link-building, fix
Fix
A profile that does not appear for customers is usually unverified, under review, suspended or simply not ranking for the search being tried.
Check verification
Open the profile while signed in. If it asks you to verify, the profile is not public yet. Unverified or pending profiles can appear to you while signed in and to nobody else.
Check for review or suspension
A notice saying the profile is under review, or a suspension message, means Google is checking it. Edits to name, address or category often trigger a review.
Search by name first
Search for the exact business name and town. If it appears, the profile is live and the issue is ranking for wider searches, not visibility.
Understand local ranking
Google ranks local results by relevance, distance and prominence. A new profile far from the searcher, with few reviews and little web presence, may not show for broad searches such as plumber near me.
Check categories and details
Use the most specific primary category, a correct address or service area, opening hours and a description. Missing basics weaken relevance.
Check for duplicates
Two profiles for one business can split signals or hide one of them. Search Maps for your phone number and address to find duplicates and request their removal.
Worked example
A new café cannot find its profile when searching for café near me on the high street. Searching for the café's exact name shows the profile straight away, so it is live. It is ranking below cafés with hundreds of reviews and years of history. Adding photos, menus, opening hours and asking customers for reviews improves its position over the following months.
Mistakes to avoid
Do not add the town or keywords to the business name to rank higher; it breaks the guidelines and is a common cause of suspension. Do not create a second profile to test. And do not change the address or category repeatedly while waiting, as each change can send the profile back for review.
Be patient after changes
New profiles and major edits can take several days to settle. Avoid repeated edits during that time, which can trigger further reviews.
Tip: Test from a signed-out browser, or a phone with location on, near your business. What you see signed in as the owner is not what customers see.
Tags: gbp, local-seo, google-business-profile, fix
Fix
Suspensions come from a breach, or a suspected breach, of Google's guidelines for business representation. The way back is to fix the cause and ask for reinstatement with evidence.
Find the likely cause
Common causes include keywords or locations added to the business name, a virtual office or PO box as the address, showing an address for a service-area business, several profiles for one business, or a business type Google does not allow.
Fix the profile first
Change the name to the real-world business name, remove a non-qualifying address, and correct categories and service areas so the profile follows the guidelines.
Gather evidence
Prepare proof the business is real and at the address: business registration, a utility bill or lease in the business name, photos of signage and the premises, and a website showing the same details.
Request reinstatement
Use the reinstatement request that Google links to from the suspended profile, and upload the evidence. Describe what you changed in plain terms.
Do not create a new profile
Making a fresh profile to get round a suspension is treated as a further breach and can make things worse.
Wait and answer requests
Google may ask for more evidence or a video of the premises. Reply promptly and keep details consistent everywhere.
Worked example
A plumber who works from home showed his home address on the profile and changed the name to Plumber Chesterfield 24 Hour. The profile was suspended. Changing the name back to the registered business name, hiding the address and setting service areas instead, then requesting reinstatement with a business registration document and a van photo with signage, brought the profile back.
What to include in the request
Explain in a few lines what the business is, where it operates and what you changed. Attach documents that show the exact business name and address as they appear on the profile. Make sure the website shows the same name, address or service area and phone number, because reviewers check it.
Prevent a repeat
Keep the name, address and phone the same on the website and directories, avoid frequent big edits, and keep only one profile per real location.
Tip: Read Google's Business Profile guidelines before editing your name or address. Most suspensions come from well-meant changes that break a rule the owner did not know about.
Tags: gbp, local-seo, google-business-profile, guidelines, fix
Fix
Security scans flag a missing HSTS header when your site works over https but does not tell browsers to always use it. One header line fixes it.
What HSTS does
The Strict-Transport-Security header tells browsers to use https for your site for a set time, even if someone types http or follows an old http link. It closes the gap that an http first request leaves open.
Make sure https works everywhere first
HSTS forces https. If any part of the site, or any subdomain you include, does not work over https, those visitors are locked out until the header expires. Check every subdomain before adding includeSubDomains.
Add the header on nginx
Add add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; to the https server block. The always flag sends it on error pages too. On Plesk, use the Additional nginx directives box or the HSTS option in SSL It!.
Add the header on Apache
Use Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" in the https virtual host or .htaccess, with mod_headers enabled.
Start with a short max-age
Begin with max-age=300 or a day, check nothing breaks, then raise it to a year. A long max-age is hard to undo for visitors who already received it.
Only send it over https
Browsers ignore HSTS sent over http. Keep your http to https redirect, and send the header on the https responses.
Worked example
A scan flags missing HSTS on a Plesk server running nginx. Adding add_header Strict-Transport-Security "max-age=300" always; to the Additional nginx directives, testing for a day, then raising it to max-age=31536000; includeSubDomains clears the finding, once every subdomain has been confirmed to work over https.
Mistakes to avoid
Do not set the header in two places, such as nginx and the application, with different values; browsers use one and the scan may report the other. Do not add includeSubDomains if an internal or old subdomain still runs on http. And do not test only the home page; error pages and files need the header too, which is what the always flag is for.
Check it
Run curl -I against the https site and look for the Strict-Transport-Security line, then rescan.
Tip: Leave preload out until you are certain every subdomain will stay on https for good. Getting off the preload list takes months.
Tags: hsts, security-headers, https, security, fix
Fix
A consent banner that does not appear is usually being hidden by an earlier choice, a cache, a script blocker or a region setting, rather than being broken.
Test in a private window
Once you accept or reject, the banner stores your choice and stays hidden. Test in a private window, or clear the site's cookies and local storage.
Check region settings
Many tools only show the banner to visitors from certain regions. Shopify's customer privacy settings, HubSpot, OneTrust and Cookiebot can all target by region, so the banner may not appear from your location or VPN.
Check the domain is set up
Cookiebot, OneTrust and HubSpot tie the banner to the domains you list. A new domain, www versus non-www, or a staging address may not be included.
Check it is published
OneTrust and some other tools need you to publish changes before they go live. A saved but unpublished banner shows nothing.
Exclude it from optimisation
Caching, minification and script-deferral plugins can delay or break consent scripts. Exclude the banner script from combining and deferring, then clear caches.
Check blockers and CSP
Ad blockers often hide consent banners. A Content Security Policy that does not allow the tool's script domain blocks it too. Check the browser console for blocked requests.
Worked example
A Shopify store owner sees no cookie banner. In a private window the banner still does not show. The store's customer privacy settings show the banner only to visitors in the EU and UK, and the owner is testing through a VPN exit in the United States. Testing from a UK connection shows the banner working.
Mistakes to avoid
Do not load tracking scripts before consent while the banner is missing; under UK and EU rules, non-essential cookies need consent first. Do not hide the reject option to get more acceptances. And do not assume a banner that shows on desktop also shows on mobile; test both.
Check for two tools
Running a plugin banner and a platform banner together can make one hide the other. Use one consent tool.
Tip: Check the banner from a phone on mobile data with no extensions. It shows you what a typical visitor sees, without your own settings in the way.
Tags: cookie-banner, consent, gdpr, pecr, fix
Fix
A canonical tag names the main address for a page so duplicates such as tracking links and filtered views consolidate into one result. This card covers the tag code, where to add it on each platform, and how to confirm what the page really sends.
Know what the tag does
The canonical is a link element in the head that votes for the preferred address of the content. Google groups duplicates and usually indexes the one you name, as long as your links, redirects and sitemap agree. It is a hint, so conflicting signals can override it.
Use the right code
Write a link element with rel set to canonical and the full https address as href. Use the host your site redirects to, keep trailing slashes consistent and make sure the target returns 200. One tag per page, always in the head of the served HTML.
Add it in plain HTML
Place the tag in the shared layout next to the title, built from each page's own path. Never paste the home page address into a shared header, or every page will claim to be a copy of the home page and drop out of the index.
Set it in WordPress
WordPress core prints a canonical on posts and pages, and Yoast or Rank Math replaces it. Leave the Canonical URL field in the plugin's Advanced section empty for normal pages, and fill it only on a real duplicate. Remove any copy hard-coded in the theme header.
Set it in Shopify
In the theme code, layout/theme.liquid should hold one canonical line using the canonical_url object. Shopify then points collection-scoped product URLs at the main product address. Switch off any app that adds a second tag and confirm the primary domain is right.
Check the served HTML
Fetch the page with curl and grep for canonical, or run a querySelectorAll in the DevTools console. One result with the right address is the goal. If curl and the browser disagree, a script is rewriting the tag and needs fixing at the source.
Compare with Google's choice
URL Inspection in Search Console lists the user-declared canonical and the Google-selected canonical. When they differ, look at internal links, sitemap entries, redirects and how similar the content is to the page Google picked.
Worked example
Example Bakery found its blog posts vanishing from Google. A curl check showed two canonicals on each post: one from the SEO plugin naming the post, and one hard-coded in header.php naming the home page. Removing the theme line left a single self-referencing tag, and URL Inspection showed the declared and selected canonicals matching after the next crawl.
Mistakes to avoid
Do not point paginated pages at page 1, do not combine a cross-page canonical with noindex, and do not canonicalise translated pages to the English version. Avoid canonicals that land on redirects or 404s, and do not add the tag only with JavaScript after load.
Tip: After any theme update or new SEO plugin, fetch one page of each template with curl and count the canonical tags. Two tags is the most common fault and the easiest to miss.
Tags: canonical, duplicate-content, wordpress, shopify, fix
Fix
Google Sites hides the server, so 404s, poor share cards, slow pages and broken phone layouts all have to be fixed from inside the editor. This card shows the likely cause of each problem and which ones the editor simply cannot solve.
Test as a stranger
Open the failing address in a private window, signed out. Editors can see drafts and restricted pages that the public cannot. A curl request for the headers tells you whether the address returns 200, 404 or a sign-in redirect.
Publish again
New pages and renamed pages only exist for visitors once you press Publish. Check the publish settings too: if viewing is limited to your organisation, customers will be asked to sign in rather than see the page.
Match the custom path
Every page has a custom path in its Properties. If the public link ends in an old path, set it back or update the links. Google Sites cannot redirect, so a renamed page breaks every link already shared.
Check the custom domain
A connected subdomain needs a CNAME to ghs.googlehosted.com, a domain verified to the owning account and an entry under custom domains. The bare domain needs forwarding at the registrar, since it cannot carry a CNAME.
Shape the share card
Google writes the Open Graph tags itself, from the site name and page titles. Write specific titles, keep the site public and place a strong image near the top. Refresh the social app's cache after changes.
Cut page weight
Embedded maps, calendars, forms, videos and documents each load their own scripts. Keep the essential ones, link to the rest, and resize photos to around 2,000 pixels wide before uploading.
Fix the phone view
Use Preview in phone mode. Replace fixed pixel widths in embed code, turn image text into real text, and space buttons out. Lighthouse in mobile mode flags small tap targets now that Search Console no longer has a mobile usability report.
Worked example
Example Bakery renamed its page from Cakes to Celebration Cakes, which changed the path from /cakes to /celebration-cakes. Its Google Business Profile, Instagram bio and printed flyers still pointed at /cakes and began returning 404. Setting the custom path back to cakes and republishing fixed every old link at once, while the page kept its new title.
Commands to confirm the fix
Run curl -sI on the address and look for 200 on the first line. Run dig +short on the www host with CNAME to see ghs.googlehosted.com. Grep the page HTML for og: tags to see the share card data. Then inspect the address in Search Console and request indexing.
Tip: Choose short, permanent page paths before you share a Google Site anywhere, because without redirects every later rename breaks links you can never fully track down.
Tags: google-sites, 404, link-preview, mobile, fix
Fix
A custom 404 page should guide a lost visitor onward and still tell browsers and search engines the page is missing. This card covers the content and image that help, the server settings on each platform, and the test that catches soft 404s.
Get both halves right
The status code is for machines and must be 404, or 410 for deliberate removals. The page body is for people and should look like your site. A friendly page sent with 200 OK is a soft 404, which Google reports and will not index.
Say what happened
Lead with a plain heading such as Sorry, we cannot find that page, and one line on why it may have happened. Skip long apologies and jokes that bury the message. Keep the usual header and footer so visitors know they are still on your site.
Offer the way forward
Add a labelled search box, a handful of links to your most wanted pages and a way to report the broken link. Your logs and analytics show which missing addresses get traffic, which hints at the pages people expected to find.
Choose a light image
An illustration, a photo from the business or a playful logo works well. Keep it to a small SVG or compressed WebP, add alt text, and link it from the site root so it appears at every depth. The words must still make sense without it.
Wire it into the server
Apache: ErrorDocument 404 with a local path, never a full address. nginx: error_page 404 plus an internal location. WordPress: the 404 template in the theme or Site Editor. Netlify, GitHub Pages and Cloudflare Pages: a 404.html at the publish root.
Remove soft 404 sources
Plugins that send every missing page to the home page, nginx rules with =200, and JavaScript apps answering 200 for every route all hide the real status. Turn them off and keep redirects only for pages with a genuine new home.
Test at depth
Request a missing address one folder deep and three folders deep. Both should return 404 with styles and images intact. In DevTools the document shows 404 and every asset 200. If assets fail only at depth, a relative path is the cause.
Worked example
Example Bakery's theme sent every missing address to the home page with a 302. Search Console listed dozens of soft 404s from old product links. Switching off the redirect plugin, building a 404 page with search and links to its three main cake ranges, and adding redirects only for the six renamed products cleared the soft 404 group after recrawling.
Commands to check it
Use curl with -o /dev/null and -w for the http_code on a made-up address and expect 404. Use curl -sIL to trace any redirect a missing page triggers. Run sudo nginx -t before reloading nginx. Then run the URL Inspection live test in Search Console on one missing address.
Tip: Make up a fresh nonsense address each time you test, because a CDN may have cached the result of the last one you tried.
Tags: 404, soft-404, error-page, apache, nginx
Fix
Deleted products, edited handles, merged collections and platform moves leave Shopify stores full of dead addresses. This card shows how to list them, redirect them singly or by CSV, and repair links inside the theme and content.
Know how addresses break
Every Shopify address is a fixed prefix plus a handle, such as /products/ or /collections/ followed by the item name. Delete the item or change its handle and the old address stops working. Draft and archived products also return 404 to shoppers.
Gather the dead addresses
Export the Not found (404) list from Search Console, add old paths from a previous platform or backup, and set aside random bot noise. What remains are former products, collections, pages and posts worth rescuing.
Confirm the status
Check each path against the live store with curl or a crawler and keep only those returning 404. A path that returns 200 is still owned by a live item, and Shopify will ignore any redirect on it until that item moves.
Pick the closest match
Send each dead address to its replacement product first, then its old collection, then a related page. Do not dump everything on the home page. Every target should answer 200 directly, not through another redirect.
Import by CSV
Build a file with Redirect from and Redirect to headers and one row per path, each starting with a slash. Upload it through Import on the URL redirects screen. For a single change, Create URL redirect does the same job. All Shopify redirects are permanent 301s.
Repair your own links
Crawl the storefront and fix every internal link that hits a 404 or a redirect, at its source: menus, theme editor buttons and banners, product descriptions, blog posts and any addresses typed into Liquid files.
Improve the 404 template
In the theme editor, give the 404 template a clear message, a search bar and links to main collections. It catches anyone reaching an address you chose not to redirect, and Shopify keeps the proper 404 status.
Worked example
Example Bakery moved from WooCommerce, where products lived at /product/name/. After launch, Search Console listed 140 of them as 404. A spreadsheet mapped each to its new /products/ handle, eight discontinued cakes went to their collection, and one CSV import restored the lot. A crawl then found eleven blog links still using the old form, which were edited directly.
Common mistakes
Clearing the redirect box when editing a handle, deleting products instead of marking them out of stock, adding a redirect for a path a live page still uses, chaining redirects across several batches, and relying on redirects rather than fixing internal links.
Tip: Before you delete any product, add its URL redirect first. Doing it in that order means there is never a moment when the old address is broken.
Tags: shopify, 404, redirects, broken-links, fix
Fix
An API 404 or 403 can be sent by your code, the web server, a CDN firewall or a security plugin, and each needs a different fix. This card shows how to tell which layer answered and how to clear the usual causes.
Read the codes precisely
404 says nothing lives at that path for that method. 401 says the caller is unknown. 403 says the caller is refused. 405 says the route exists but not for that method, though many frameworks send 404 in its place.
Replay it with curl
Send the identical method, address, headers and body with curl -i. The status line, the Server header, any request ID such as cf-ray and whether the body is JSON or HTML together reveal who produced the error.
Match the layer
JSON with an error code means the application. An HTML block page with a CDN Server header means the firewall. A plain nginx or Apache page means the web server. Confirm with the access log: present there but absent from app logs points to the server.
Repair the route
Compare base address, version prefix, trailing slash and method with the registered routes. For WordPress, load /wp-json/ for the route index, and if ?rest_route= works where /wp-json/ fails, resave permalinks or fix the nginx try_files line.
Repair the credentials
Check the auth type, token expiry and the user's role. On Apache running PHP as CGI or FastCGI, CGIPassAuth On lets Application Passwords reach WordPress. Front-end calls that rely on the login cookie must also send the X-WP-Nonce header.
Clear firewall blocks
Look up the request ID in Cloudflare Security Events or the ModSecurity log to find the rule. Write an exception for that rule on that API path only. Check WordPress security plugins for options limiting the REST API to logged-in users.
Check Postman and the browser
The Postman Console shows the real address after variables are filled in, which exposes empty base URLs and double slashes. If only the browser fails, look for a CORS message and make sure OPTIONS preflight requests are allowed.
Worked example
A bakery's order form posted to a custom WordPress route and got a 403 HTML page with a Cloudflare header. Security Events showed a managed rule matching the notes field. A path-specific exception moved the error to a JSON 401 from WordPress, which a missing nonce explained. Sending X-WP-Nonce produced 201 Created.
Common mistakes
Changing application code when the firewall answered, switching the whole WAF off instead of one rule, testing only in Postman when the browser is the real client, forgetting cached errors on the CDN, and calling an address that redirects and quietly turns a POST into a GET.
Tip: Make your API return JSON for every error it sends itself. Then any HTML error page is instantly known to come from another layer.
Tags: api, 404, 403, postman, wordpress
Fix
Next.js splits failures across several files, and a gap in any of them gives visitors a blank screen or gives Google a missing page with a 200 status. This card shows which file catches what, how to get real 404 and 500 codes, and how to cure MIDDLEWARE_INVOCATION_FAILED on Vercel.
Know which file handles which failure
not-found handles unknown URLs and notFound calls. error handles crashes inside a route segment. global-error handles a broken root layout. Middleware sits in front of everything, so when it throws, every matching request fails at once.
Check the status, not the look
Request a made-up path, a deleted item and a failing page with curl -sI. A missing page must answer 404. A 200 with a robots noindex tag means the page streamed too early; a plain 200 means notFound is never called.
Put not-found at the top of app
A not-found file in the root of the app folder catches every unmatched URL. Keep it light, with a heading, a short line of help and a link home. Files placed deeper only respond to notFound calls in their own segment.
Call notFound before streaming starts
In each dynamic route, call notFound as soon as the lookup comes back empty, and repeat the check in generateMetadata. Doing it before any Suspense boundary is what lets the server send a genuine 404 header.
Wrap segments in error boundaries
Each error file needs use client on its first line and a reset button. It protects the page and children but not the layout in the same folder, so place one a level higher wherever a layout could throw.
Give the root layout a safety net
global-error replaces the whole document when active, so it must output its own html and body. Keep it free of fonts, data calls and layout imports so it cannot break for the same reason as the page it replaces.
Read the Vercel logs for middleware crashes
The 500 page only shows a code and an ID. The Logs tab in the Vercel project, filtered by path, holds the actual exception. Typical findings: a variable set for Preview but not Production, a Node-only package, or an unguarded property read.
Worked example: a bakery shop
A shop's deleted cake pages answered 200 with a polite message, and 140 stayed in Google. Adding notFound to the page and its metadata turned them into 404s. A Monday outage came from a password variable missing in Production; adding it, guarding the read and narrowing the matcher ended it.
Commands to confirm the fix
curl -sI on a fake path should show 404. curl -s piped to grep robots should find nothing on live pages. Several real paths should return 200 after the middleware redeploy. Finish with a URL Inspection live test, then Validate fix in the Pages report.
Tip: Add a one-line check to your deploy pipeline that requests a nonsense URL and fails the build unless it gets a 404, so a refactor cannot quietly break it.
Tags: nextjs, error-pages, 404, 500, vercel, fix
Fix
An error page signed Microsoft-Azure-Application-Gateway/v2 was produced by the gateway itself. A 403 is normally the firewall refusing a request; a 502 means no backend passed its health check. This card shows how to trace each one in the logs and fix it without weakening security.
Read the footer first
The gateway's own error pages carry its server name. If you see that, your application never received the request, so its logs will be silent. If the page looks like your own site, the backend refused it and the gateway is only passing the answer on.
Switch on logging
Diagnostic settings on the gateway can send the access and firewall logs to a Log Analytics workspace. Nothing is kept before you do this, so enable it, repeat the failing request and check fresh rows are arriving.
Trace a 403 to its rule
Filter the firewall log by your IP address and recent time, then group rows by transaction. Rules marked Matched show what scored, the details column names the cookie, header or field involved, and a final Blocked row confirms the firewall stopped it.
Exclude the field, not the firewall
Add an exclusion in the WAF policy for the exact field, limited to the rule IDs that fired. Disabling a whole rule or leaving Detection mode on removes protection for every page; a narrow exclusion only stops inspecting one known-safe input.
Open Backend health for a 502
Each pool member shows a status and a message. Wrong status codes point at the probe. Certificate wording points at trust or naming. Unhealthy everywhere with no detail usually means traffic is being blocked by a network security group or route.
Let the gateway through
The gateway subnet must accept TCP 65200 to 65535 from the GatewayManager tag, and the backends must accept the gateway subnet on their port. Custom routes on the gateway subnet that steer traffic elsewhere can also break health checks.
Give the probe the right host and path
Point a custom probe at a light health page and send the host name your certificate covers. For App Service, pick the host name from the backend target. For internal certificates, upload the root to the backend setting.
Worked example: launch-day errors
A bakery's editors were blocked saving recipes because a content field tripped injection rules; an exclusion on that field fixed it. Visitors then got 502s because the backend setting still sent the old staging host name, which the server rejected. Correcting it made both servers healthy.
Commands that prove it is fixed
az network application-gateway show-backend-health should list every member as Healthy. curl -sI on public pages should return 200. A firewall log query for the earlier transaction pattern should show no Blocked rows, and the access log should show no fresh 502 entries.
Tip: Run a new WAF policy in Detection mode for a week of real traffic before switching to Prevention, and add exclusions for genuine false positives first.
Tags: azure, waf, 403, 502, backend-health, fix
Fix
nginx shows 502 Bad Gateway when the service it hands requests to, usually PHP-FPM or a Node app, does not answer properly. The version in the footer hardly matters. This card shows how to read the error log, match the message to its cause and correct the setting on Ubuntu.
Know what the 502 is pointing at
nginx is up, otherwise it could not show the page. Something behind it, such as PHP-FPM, Node, Python or Apache, refused, dropped or garbled the connection. A timeout would give a 504 instead, so a 502 is about reachability and crashes.
Open the error log
Tail /var/log/nginx/error.log with sudo while you reload the broken page. One line per request explains the failure: no such file, permission denied, connection refused, closed early or header too big. On Plesk use the domain's proxy_error_log.
List what nginx is aiming at
nginx -T prints the full loaded config. Filter it for fastcgi_pass and proxy_pass to see the socket path or address and port for each site, then compare that with the services actually running on the server.
Wake the upstream
ls /run/php shows which PHP sockets exist. systemctl status for php-fpm shows whether it is active. ss -ltnp shows which ports are listening. Start whatever is stopped, and read its own log if it falls over again.
Match the socket or port
After an Ubuntu or PHP upgrade the socket name often changes, for example from 8.1 to 8.3. Update fastcgi_pass or proxy_pass to the live path or port, use 127.0.0.1 rather than localhost, test with nginx -t and reload.
Sort out permissions and workers
Permission denied means the pool's listen owner, group and mode do not suit the www-data user. Requests dropped under load usually mean pm.max_children is too low or memory is running out; size it from the memory each PHP process uses.
Enlarge buffers for big headers
Too big header messages come from large cookies or many Set-Cookie lines. Raise proxy_buffer_size and proxy_buffers for apps, or fastcgi_buffer_size and fastcgi_buffers for PHP, then retry a logged-in page to check.
Worked example: after a 24.04 upgrade
A bakery server moved to Ubuntu 24.04 and every order page gave 502. The log asked for a php8.1 socket while only php8.3 existed. Changing one fastcgi_pass line fixed it. Later lunchtime 502s traced to five PHP workers; raising the limit to 20 ended them.
Commands to confirm it worked
sudo nginx -t should report the syntax is ok. curl -sI on dynamic pages should return 200. systemctl is-enabled for PHP-FPM should say enabled, or pm2 startup should be saved for Node. A reboot test should bring everything back without manual steps.
Tip: After any PHP or Ubuntu upgrade, compare ls /run/php with the fastcgi_pass lines in your nginx config before you reload, so a renamed socket never reaches visitors.
Tags: nginx, 502, php-fpm, ubuntu, fix
Fix
If visitors who click your site end up on scam or gambling pages, something has planted a redirect, usually in server rules, injected scripts or a bad plugin. This card shows how to see the hidden redirect, track down every piece of the code behind it and shut the door it came through.
Accept that you may not see it
Planted redirects tend to skip logged-in owners, desktops and repeat visits. They often fire only for phones coming from a search engine. A customer's report is worth believing even when your own browser shows a normal page.
Recreate what a customer sees
Ask curl to follow every hop while pretending to be an iPhone sent by Google. A Location header to a strange domain means the server is doing it. A clean 200 with redirects still happening on phones points at JavaScript in the page.
Copy everything, then change passwords
Save the infected files and database somewhere off the server for reference. Then change hosting, file transfer, database and registrar passwords and add two-factor sign-in, so the attacker cannot simply log back in while you clean up.
Search the server rules
Look through every .htaccess file, including ones tucked inside upload folders, and the nginx config. Rules that test the referrer or the user agent and then send people to an outside domain have no place on a normal site and should go.
Hunt for hidden code
Recently changed PHP files, files using eval with base64 or fromCharCode, PHP inside uploads and anything in mu-plugins are the usual hiding places. WP-CLI checksum commands list every core or plugin file that differs from the official release.
Replace rather than repair
Reinstall WordPress core and each plugin and theme from the official source instead of editing infected files. Remove nulled and unused plugins. Clean injected scripts from posts and options, correct the site address and delete unknown administrators.
Look outside the server
Check DNS records at the registrar, Cloudflare redirect and page rules, and any third-party ad or widget script. Purge page, CDN and browser caches afterwards, because a cached redirect can outlive the code that created it.
Worked example: a bakery's phone visitors
Customers on phones were sent to a fake prize page from Google. curl showed a 302. The owner found rewrite rules hidden in an uploads folder, an eval-based must-use plugin, a changed core file and a stranger in the admin list. Reinstalling, deleting and updating a vulnerable form plugin ended it.
Common mistakes
Deleting only the redirect line and leaving the backdoor, forgetting the database or mu-plugins, skipping password and salt changes, and not purging caches. Each of these lets the redirect return, often within days. Recheck checksums weekly for a month after cleaning.
Tip: Run the curl test as a phone arriving from Google once a week; it is the quickest way to catch a hidden redirect before your customers do.
Tags: security, redirects, malware, wordpress, htaccess, fix
Fix
A 301 tells browsers and search engines a page has moved for good and hands over the new address. Used well it keeps visitors and link value; used carelessly it creates chains and soft 404s. This card covers what a 301 means and how to add and test one on common platforms.
Understand the response
Instead of the page, the server returns status 301 Moved Permanently plus a Location header with the new address, and the browser fetches that straight away. It is not an error; the 300 range is for redirection. 302 and 307 mean temporary, 308 is a permanent twin of 301.
Know what it does for search
Google takes a 301 as a firm hint to show the destination instead of the old URL, and links to the old page count towards the new one. Point each page at its nearest match; dumping everything on the home page is often treated as a soft 404.
Map old to new first
Collect old addresses from the sitemap, analytics and Search Console. Pair each with one final destination that loads with a 200. This spreadsheet becomes both your rule list and your test list.
Apache and LiteSpeed
In .htaccess, a Redirect 301 line takes the old path and the full new URL. RedirectMatch handles folder patterns, and RewriteRule with R=301 handles whole domains. Put rules above the WordPress block and keep a backup of the file.
nginx
Use a location block with return 301 for single pages and rewrite with permanent for patterns. For long lists, a map file in the http context keeps hundreds of pairs tidy. Always run nginx -t before reloading.
WordPress, Cloudflare and others
Redirect plugins and SEO plugins can add 301s from the dashboard. Cloudflare offers Redirect Rules and Bulk Redirects that answer before your server. Shopify has a URL redirects tool, and Next.js sends a 308 when permanent is true.
Tidy up after redirecting
Change menus and internal links to the new URLs, rebuild the sitemap and make canonicals self-referencing. Otherwise every crawl and click passes through a hop that does not need to be there.
Worked example: renaming a shop folder
A bakery moved products to shop and merged two cake pages into one. One RedirectMatch line handled the folder, two Redirect lines handled the merge, and a curl loop confirmed each old URL returned a single 301 to a live page. Search Console later listed the old ones as Page with redirect.
Mistakes to avoid
Using 302 for a permanent move, building chains through older redirects, forgetting the trailing slash version, placing rules below the WordPress block, testing only in a browser that has cached a previous attempt, and removing redirects within the first year.
Tip: Test redirects with curl or a private window, never just your normal browser, because browsers cache 301s and can hide a mistake you have already fixed.
Tags: redirects, 301, htaccess, nginx, seo, fix
Fix
Moving to a new domain needs a permanent redirect that keeps every path and works on https. This card covers server rules, registrar forwarding and Cloudflare, and why the old domain still needs its own certificate.
Know what a good domain redirect looks like
Every request to the old hostname should get one 301 response pointing at the same path on the new domain, then a 200. Paths and query strings carry over, so a link to an old product page reaches the same product on the new site rather than the home page.
Cover all four starting versions
Visitors and links arrive on http and https, with and without www. Each of the four must go straight to the final https address on the new domain in a single hop, not through a chain of separate rules.
Understand the certificate problem
A browser checks the TLS certificate of the old domain before it sends the request that would receive the redirect. With no valid certificate for the old names, people see a warning page and the 301 never arrives. The old domain needs a certificate for as long as the redirect lives.
Choose where the old domain is answered
Your own server, a Plesk domain alias or Cloudflare are the dependable options. Registrar forwarding at GoDaddy, Network Solutions and others can work, but test whether it keeps paths and serves https correctly before relying on it.
Write one rule that keeps the path
In nginx, a server block for the old names returning 301 to the new domain plus $request_uri. In Apache, a RewriteRule matching the old host with R=301. In Cloudflare, a dynamic redirect rule with the query string preserved. Specific mappings for renamed pages go above the catch-all.
Test with curl, not the browser
Browsers cache permanent redirects, so check with curl -sIL on each version and on a few deep URLs. Look for exactly one 301, a full location header, a final 200 and no certificate error. Use openssl s_client to see which names the certificate covers.
Tell Google about the move
Verify both domains in Search Console, then use Change of Address in the old property. Submit the new sitemap, update internal links, canonicals and hreflang to the new domain, and change the profiles and listings you control.
Worked example
Example Bakery found its registrar forward returned a 302 to the new home page and failed on https with a hostname mismatch. Moving the old domain to its own server, issuing a certificate for both names and adding a single nginx return 301 rule gave one clean hop to the matching page on every version.
Mistakes to avoid
Masked or framed forwarding, temporary 302 status, sending every old URL to the home page, letting the old certificate lapse, and dropping the old domain after a few months. Keep the domain, certificate and redirect for at least a year and recheck after hosting or DNS changes.
Tip: Before you switch anything, save a list of your top old URLs from analytics so you can test real deep links rather than just the home page.
Tags: redirects, 301, domain-move, ssl, fix
Fix
robots.txt decides which paths search crawlers may request. This card shows where to find the file, what each line means, ready-made examples for typical sites, and why Disallow all belongs only on staging copies.
Look in the one place it can be
Crawlers only read the file at /robots.txt on the exact host and protocol, so type the domain plus /robots.txt into a browser. A 404 means there is no file and everything may be crawled. Each subdomain needs its own copy.
Remember what it can and cannot do
It controls crawling, not indexing. A blocked URL can still turn up in search results if other pages link to it. Pages you want out of Google need a noindex tag that Google is allowed to crawl and read.
Read the groups
Each group begins with User-agent lines and then lists Disallow and Allow paths matched from the start of the URL. A star stands for any characters and a dollar sign for the end. The longest matching rule wins, and Allow wins a tie.
Start from a simple file
Most small sites need only User-agent: * with an empty Disallow line and a Sitemap line giving the full https address of the sitemap. Add rules only for areas such as carts, checkouts, accounts and internal search.
Create it on your platform
On ordinary hosting, save a UTF-8 text file called robots.txt in the document root. WordPress SEO plugins have an editor, Shopify uses a robots.txt.liquid template, and Next.js can generate it from app/robots.ts.
Keep rendering files open
Do not block the CSS, JavaScript and image folders your pages use. Google renders pages, and blocked stylesheets or scripts can make a page look empty or broken to it.
Check the live result
Fetch the file with curl to confirm the status is 200 and the content matches what you wrote. CDNs, caching plugins and platform features can add or alter lines, so trust the live output over your source copy.
Worked example
A new bakery site had no pages in Google two weeks after launch. Its live robots.txt held User-agent: * and Disallow: /, copied from staging. Swapping in an allow-all file with a sitemap line and requesting a recrawl in the Search Console robots.txt report let crawling resume the next day.
Commands and checks
Use curl -sI yourdomain/robots.txt for the status and curl -s for the content. Then open Settings and the robots.txt report in Search Console to see the fetched version and any errors, and run URL Inspection on a key page to confirm it is not blocked.
Tip: Keep a separate robots.txt for staging that is never deployed to the live site, and check the live file straight after every launch.
Tags: robots-txt, crawling, search-console, fix
Fix
An XML sitemap lists the pages you want search engines to discover. This card covers where sitemaps usually live, the ways to build one, a minimal valid file, and the checks that catch redirects, errors and noindexed pages before submission.
Know what it does
A sitemap helps crawlers discover and revisit URLs. It is a hint, not an order: Google still chooses what to index. It earns its keep on new sites, big sites and pages that few internal links reach.
Look for an existing one
Check robots.txt for a Sitemap line, then try sitemap.xml, sitemap_index.xml and wp-sitemap.xml at the root. Search Console's Sitemaps report shows anything already submitted. Avoid running two generators side by side.
Pick a generator
Use the CMS or plugin sitemap on WordPress, Shopify and site builders, framework code such as app/sitemap.ts for custom builds, and an online generator or a hand-made file only for small static sites that seldom change.
List only pages worth indexing
Every entry should be a canonical page that returns 200 and has no noindex tag. Leave out redirects, missing pages, carts, logins, internal search and filtered duplicates, and switch off unwanted archives in plugin settings.
Use the right structure
A urlset element with the sitemaps.org namespace, one url element per page and a full https address in loc. Add lastmod only with real change dates. Stay under 50,000 URLs and 50 MB per file, and use a sitemap index beyond that.
Validate before submitting
Download the live file and run xmllint --noout on it. Confirm the address returns 200 with an XML content type, not an HTML page from a 404 handler or a redirect.
Test each listed address
Pull out every loc value and fetch its status code with a curl loop. Remove or fix any redirect or error, and change the setting that let it in. Spot-check canonicals on a few pages.
Worked example
A bakery's sitemap showed Success in Search Console, yet only a third of its URLs were indexed. A status check found redirected http product links and noindexed tag archives. Fixing the feed and excluding the archives cut the list to clean, self-canonical pages.
Declare, submit and watch
Add a full Sitemap line to robots.txt, submit the sitemap or index in Search Console, and check that the status reads Success. Compare it with the Pages report over the next weeks, and rerun the status loop after any migration or redesign.
Tip: If submitted pages are not being indexed, check the sitemap itself first: a list full of redirects and noindexed pages weakens trust in every URL it contains.
Tags: sitemap, xml, search-console, crawling, fix
Fix
noindex keeps a page reachable for visitors but out of search results. This card covers the meta tag and header, the CMS settings, why a robots.txt block defeats it, and how the Removals tool hides pages already in Google.
Pick the right method
Use noindex for pages that should exist but not rank, such as confirmations, internal search and thin archives. Delete with a 404 or 410 if the page should go, use a canonical for duplicates, and a password for anything private.
Let Google crawl it
Google has to fetch the page to read the instruction. If robots.txt disallows the URL, the noindex is never seen and the address can stay listed. Remove the block first and confirm with URL Inspection.
Add the meta tag to that page
Place a robots meta tag with the value noindex in the head of the single page. Keep it out of shared layouts, and make sure no second robots tag contradicts it, since Google follows the stricter one.
Send a header for files
PDFs and images have no head, so send X-Robots-Tag: noindex from the server. nginx uses add_header in a location block and Apache uses Header set in a FilesMatch block. Check the header with curl -sI.
Use your platform's setting
Yoast and Rank Math each have a per-page noindex option under Advanced. Shopify offers the seo.hidden metafield or a Liquid condition in theme.liquid. Next.js takes robots index false in the page metadata.
Hide it now if it is urgent
Search Console Removals hides a URL for about six months. It is not permanent on its own, so pair it with noindex, a 404 or a password so the page does not return when the temporary removal ends.
Tidy the sitemap
Take noindexed URLs out of the XML sitemap so you are not asking Google to index pages you are also telling it to drop. Most SEO plugins handle this automatically when you tick noindex.
Worked example
A bakery's order confirmation page appeared in Google because robots.txt blocked the checkout folder, so the page was never crawled. Lifting the block, adding noindex to the template and filing a temporary removal cleared it, and URL Inspection later reported it as excluded by noindex.
Commands to confirm it
Fetch the page with curl -s and search for robots to see the live tag, or curl -sI for the header. Run the live test in URL Inspection, then check the Pages report for the URL under Excluded by noindex tag after Google recrawls.
Tip: After any theme change or relaunch, view the source of your home page and search for noindex; a leftover staging setting is a common way whole sites disappear.
Tags: noindex, robots-meta, search-console, indexing, fix
Fix
Each indexing check answers a different question. This card shows how to read URL Inspection, use the Pages report, treat site: searches as a hint, confirm Googlebot visits in server logs, and act when a page is not on Google.
Separate the three stages
Crawled means Googlebot fetched the page, indexed means Google stored it and can show it, and shown means it actually appears for searches. Each check below looks at one of these, so be clear which one you are testing.
Start with URL Inspection
In Search Console, paste the exact address with the right protocol and www. The headline reads URL is on Google or URL is not on Google, and Page indexing gives the reason, the last crawl date and both canonicals.
Compare the canonicals
If the Google-selected canonical differs from the one you declared, Google has indexed another URL for the same content. That is a duplication question, not a discovery one, and needs a different fix.
Do not misread the live test
Test live URL tells you whether the page could be indexed right now, finding blocks, errors and noindex. It is not proof the page is in the index; the headline result from the last processed crawl is.
Look at the Pages report
Indexing then Pages splits URLs into indexed and not indexed with reasons. Filter by a submitted sitemap to focus on pages you care about, and open each reason to find patterns across a template or section.
Treat site: as a hint
A site: search for the URL, or a quoted sentence from the page, is a quick sign. A missing result is not proof, since the operator does not list everything and its counts are rough estimates.
Check your server logs
Search access logs for Googlebot requests to the path and note dates and status codes. Verify addresses with reverse and forward DNS. No visits points to discovery, errors to server issues, and steady 200s to a quality decision.
Worked example
A new cake page did not appear a week after launch. Inspection said Discovered, currently not indexed, and logs showed no Googlebot visit, because the only link was in a script-loaded banner. A plain link from the category page and an indexing request got it crawled and indexed within days.
Common mistakes
Inspecting the wrong URL variant or property, trusting the site: count, reading a passed live test as indexed, requesting indexing over and over, and assuming an indexed page with no impressions is missing. Check the Performance report for that page first.
Tip: Filter the Pages report by your sitemap: it turns a confusing site-wide list into a clear count of how many pages you actually want are indexed.
Tags: indexing, search-console, url-inspection, site-audit, fix
Fix
JSON-LD is structured data written as normal JSON, with reserved keys that give it a shared meaning for search engines and AI systems. This card explains the format, its special keys and how to write a block Google can read.
Know what the letters mean
JSON-LD is short for JavaScript Object Notation for Linked Data. It is a W3C standard for describing things as data that machines can connect. On a website it nearly always carries Schema.org terms, so the format is the envelope and Schema.org is the language written inside it.
See how it differs from JSON
The syntax is identical to JSON, so any JSON parser reads it. What it adds is agreement: @context names the vocabulary, @type says what kind of thing an object is and @id gives it a global name. Without those keys, the data means only what one developer intended.
Use the right script tag
Place the data inside a script element whose type is application/ld+json. Browsers do not run it and visitors never see it, while crawlers read it as data. It can sit in the head or the body, but it must be inline rather than loaded from a separate file.
Set @context and @type first
Every block starts with the context https://schema.org and a type spelt with its exact capitals, such as LocalBusiness, Product or Article. A lowercase or misspelt type is treated as unknown, and the whole object is then ignored for rich results.
Link things with @id
Give the business, the website and other repeated things an identifier made from your domain plus a fragment, for example https://example.com/#business. Other objects can then point to that identifier instead of repeating the details, and several blocks describing the same thing merge into one.
Group objects with @graph
When a page describes several things, such as a website, a web page and a business, put them in a @graph array under one shared context. This is what most SEO plugins output, and it keeps references between objects tidy and easy to follow.
Keep the JSON valid
One error breaks the whole block. Watch for trailing commas, curly quotes pasted from documents, comments, single quotes and unescaped closing script tags inside text. Paste the block into a JSON validator before it goes live.
Worked example
Example Bakery adds a block with the Bakery type, its name, the phone number +44 1632 960961, a nested PostalAddress and its opening hours. The Rich Results Test shows nothing at first. A validator finds a trailing comma after the last opening hours entry; removing it makes the item appear, and the Schema Markup Validator then confirms every property sits under the right type.
Check it worked
Fetch the page with curl and confirm the block is in the raw HTML. Run the URL through the Rich Results Test and the Schema Markup Validator. After Google recrawls, use URL Inspection in Search Console to see the structured data it detected, and recheck after any theme or plugin update.
Tip: Run both Google's Rich Results Test and the Schema Markup Validator: the first shows what Google can use for rich results, the second catches misspelt properties the first ignores.
Tags: json-ld, schema, structured-data, json, fix
Fix
Two free tools test structured data, and they answer different questions. This card shows when to use the Rich Results Test and when to use the Schema Markup Validator, how to read their results and how to confirm the fix in Search Console.
Pick the right tool
The Rich Results Test tells you whether Google can use your markup for a rich result. The Schema Markup Validator tells you whether it is correct Schema.org, including types Google never turns into a rich result. You need both, because a page can pass one and not the other.
Test one page per template
Markup comes from templates, so test the home page, a post, a category, a product and a contact page rather than many pages of one kind. Note which schema type you expect on each before you start, so you can spot what is missing.
Use URL mode for live pages
URL mode fetches and renders the page as Google does. It catches problems that pasted code hides: caching that strips the block, robots.txt rules, bot protection or JavaScript that never runs. Always finish with a live URL test.
Use code mode before publishing
Neither tool can reach localhost, a staging site behind a password or a page that needs a login. Paste the HTML or the JSON-LD block into the code tab instead, fix issues in place and copy the corrected markup back to your template.
Read critical issues first
A critical issue makes an item invalid, usually because a required property is missing or has the wrong kind of value. Click it to see the line, fix it at the source and re-run. One structural mistake can create several messages, so work one at a time.
Treat warnings as improvements
Non-critical issues point at missing recommended properties. The item is still eligible. Add them when the real value is visible on the page, and leave them when it is not, because invented values break Google's guidelines.
Explain an empty result
No items usually means broken JSON, a block missing from the rendered page, or a type with no Google rich result such as WebSite or Service. Check the HTML under View tested page, then run the validator to see whether the markup is there at all.
Worked example
A shop's product page shows no items in the Rich Results Test, yet pasted code passes. The HTML tab of the tested page has no ld+json block. A curl request returns the block, so the culprit is a cached copy from before the change. Purging the CDN and re-running shows a valid Product snippets item with one warning for a missing brand, which the shop then adds.
Confirm in Search Console
Use URL Inspection and Test live URL on each template to see what Google sees now. In the enhancement report for that type, open the issue you fixed and click Validate fix. Expect the report to update within a few days to two weeks as Google recrawls the affected URLs.
Tip: Keep a short list of one test URL per template and run it after every theme, plugin or app update; that is when schema most often breaks without anyone noticing.
Tags: schema, rich-results, testing, search-console, fix
Fix
Google stopped showing FAQ rich results for every site on 7 May 2026, government and health sites included. This card explains what FAQPage markup and FAQ content are still good for, what they no longer do, and how to decide page by page whether to keep, add or remove the markup.
Know what changed
From August 2023 Google showed expandable FAQ results only for well-known government and health sites. On 7 May 2026 it ended them for everyone. Google said the FAQ search appearance, the Search Console FAQ report and Rich Results Test support would go in June 2026, with API support following in August 2026. HowTo results had already ended in 2023.
Know the type is not deprecated
FAQPage remains a valid Schema.org type. Google says it may still use structured data to understand pages, and that leaving FAQ markup in place does no harm. Nothing requires you to strip it out, so there is no need for a panic clean-up across the site.
Know what value is left
The markup labels which text is a question and which is the answer. Google may use that for understanding, and other search engines and AI answer engines may read it too. The bigger value is the visible FAQ itself: real questions with direct answers help visitors and are easy to quote. None of this is a ranking factor.
Find every FAQPage block
Crawl the site or search template source for FAQPage. Note whether each block comes from an SEO plugin, an accordion plugin, a theme section or hand-written code, because that tells you where any change has to be made.
Remove what is wrong or wasteful
Take out markup for questions visitors cannot see, FAQ sets repeated across many pages, questions written as adverts, second FAQPage blocks on the same page and answers that have gone out of date. With no rich result to chase, these cost upkeep and give nothing back.
Use the right type
FAQPage suits questions the site answers once. A forum or community thread with one user question and several answers belongs in QAPage. Switching type on those pages makes the markup honest and easier for machines to read.
Improve the answers
Put the direct answer in the first sentence and keep detail factual. Answers that still make sense when quoted alone are the ones search features and AI tools pick up, whether or not markup is present.
Worked example
Example Bakery has the same six delivery questions in every product template, plus a real FAQ page. It keeps the markup on the FAQ page only, deletes a second FAQPage block written by an accordion plugin, rewrites two vague answers and drops a dashboard filtered by the FAQ search appearance. No FAQ result shows in Google, which is now true for every site.
Check it worked
Run a few pages through the Schema Markup Validator and expect one valid FAQPage each. Fetch a product page with curl, search for FAQPage and expect nothing. Do not rely on the Rich Results Test or the Search Console FAQ report for this. Track the pages by URL in the Performance report for eight weeks, expecting little change.
Tip: If clicks fell while average position held steady, in 2023 for most sites or from 7 May 2026 for government and health sites, you lost the extra space FAQ results gave you, not your rankings, so do not rewrite pages to win it back.
Tags: faq-schema, rich-results, aeo, structured-data, fix
Fix
Breadcrumbs show visitors where a page sits and give every page links back to its parent sections. This card covers what breadcrumb navigation does, the accessible HTML pattern, matching BreadcrumbList markup and how to add it on WordPress and Shopify.
Know what breadcrumbs are for
A breadcrumb trail is a row of links from the home page down to the current page. It orients visitors arriving from search, gives each page links to its parent sections, and, with structured data, states your site structure plainly to search engines.
Choose location breadcrumbs
Base the trail on your site hierarchy, not on the pages a visitor happened to click. Give each page one parent, and pick a primary category for any product listed in more than one place, so the trail never changes between visits.
Use the accessible pattern
Wrap an ordered list of links in a nav element with an aria-label of Breadcrumb. Mark the final item with aria-current set to page, or leave it as plain text. Screen readers then announce a named landmark, the number of steps and the current page.
Draw separators with CSS
Typed slashes or arrows get read aloud by screen readers. Add them with a CSS before rule on each list item after the first instead, and let the list wrap on small screens so a long trail never forces sideways scrolling.
Add BreadcrumbList markup
Output one ListItem per level with a position from 1 upwards, a name that matches the visible label and the full canonical URL as the item. The last level may leave out its URL. Build it from the same data as the trail on screen.
Use your platform's tools
On WordPress, enable breadcrumbs in Yoast SEO or Rank Math and place their block or theme function. On Shopify, use a theme setting or a small Liquid snippet rendered in product and collection sections. Make sure only one source prints the schema.
Avoid the common traps
Do not show a trail on the home page, link to levels that have no real page, send trail links through redirects or mark up a path that differs from the one shown. Each of these confuses visitors, search engines or both.
Worked example
Example Bakery adds a trail of Home, Cakes, Birthday cakes and the product above each product heading. It switches off its theme's two-step breadcrumb schema and prints a four-step block from the same data. The Rich Results Test now shows one valid Breadcrumbs item, and VoiceOver announces a breadcrumb navigation list of four items.
Check it worked
Test a product, a category and a post in the Rich Results Test for one valid Breadcrumbs item each. Move through the page by landmarks with a screen reader. After recrawling, open the Breadcrumbs report in Search Console and validate any fixes.
Tip: Generate the visible trail and the BreadcrumbList markup from the same array of pages, so a renamed category or moved product can never leave the two out of step.
Tags: breadcrumbs, internal-links, schema, accessibility, fix
Fix
Page speed tools give several different numbers, and the one in the big circle is not the one Google uses. This card explains how to check a website's speed, what each score and timing means, why results change and what to do when PageSpeed Insights will not run.
Start with PageSpeed Insights
Paste one public URL per template into pagespeed.web.dev and keep the Mobile tab selected. The page shows two things: real visitor data at the top and a single simulated Lighthouse test below. Read them separately, because they answer different questions.
Trust field data for the verdict
The top section comes from real Chrome visits over 28 days, reported at the 75th percentile. Its Core Web Vitals assessment, based on LCP, INP and CLS, is what Google uses for search and what decides whether you actually have a problem.
Use lab data to find the cause
The Lighthouse run emulates a mid-range phone on a slow connection. It is good for spotting which files, scripts or images hold the page back, but one run is not what your visitors felt. Use it to diagnose, not to judge.
Know the thresholds
Good means LCP within 2.5 seconds, INP within 200 milliseconds and CLS of 0.1 or less. Supporting timings are first byte under about 0.8 seconds and first content under 1.8 seconds. The 0 to 100 score is a lab summary and does not affect rankings.
Expect results to vary
Lab scores move between runs because of server load, caches, networks and third-party scripts. Run three to five tests and take the middle result, and compare medians before and after a change rather than single runs.
See the whole site in Search Console
The Core Web Vitals report groups similar URLs and shows which groups fail and on which metric. It finds slow templates you did not think to test, and it often has data when a single quiet page has none in PageSpeed Insights.
Handle a test that will not run
Errors such as failed or errored document requests usually mean bot protection, a firewall, an SSL problem or a server error stopped the test. Check the status code with curl, relax challenge rules while testing or run Lighthouse in DevTools instead.
Worked example
Example Bakery scores 94 on desktop and 48 on mobile, and its owner panics. The field data shows LCP at 2.1 seconds, INP at 140 milliseconds and CLS at 0.04, so the site passes. Three mobile lab runs give 48, 55 and 51. The diagnostics point to an oversized hero image, which is worth fixing, but nothing is failing for real visitors.
Commands to check it worked
Use curl with the time_starttransfer write-out to time the first byte. Run npx lighthouse with only the performance category for repeatable local tests. After changes, compare lab medians, then watch the field data and Search Console report over the next 28 days.
Tip: Write down the field LCP, INP and CLS for each main template once a month; a slow upward drift is far easier to fix when you spot it early than after a template has tipped into poor.
Tags: pagespeed, core-web-vitals, lighthouse, performance, fix
Fix
When LiteSpeed Cache, Elementor's optimiser or a similar plugin sits idle or reports success while pages still load large JPEGs, one of three stages has stalled: the queue, the processing or the delivery. This card shows how to spot which one and get it moving.
Know the three stages
An optimiser queues new uploads and their resized copies, processes them in the cloud or with PHP, then delivers the smaller file to visitors. Each stage can fail on its own, and they all look the same from the dashboard.
Look at the page, not the counter
View the page source and search for your image names. If they still end in .jpg while WebP copies sit in the uploads folder, delivery is the problem. If no copies exist at all, the queue or the processing stage has stopped.
Check connection and allowance
Cloud plugins need a live link to their service and some monthly allowance left. A domain change, a staging clone or an http to https move can break the link. Reconnect from the live address and test with a single image.
Get background jobs running
Queues are moved by WP-Cron, which only fires when someone visits. On quiet sites, or where DISABLE_WP_CRON is set, add a server cron every five minutes. In LiteSpeed Cache, turn on automatic request and automatic pull.
Unblock the service
Cloud services download your originals. Firewalls, bot protection and hotlink rules can answer them with 403. Search the access log for 403 responses on the uploads folder just after sending a batch, then allow the service through.
Run one optimiser
Themes, page builders and cache plugins often bring their own compression, WebP or lazy loading. Two tools working on the same files cause double compression and broken image addresses. Keep one and switch the duplicate features off elsewhere.
Match delivery to the server
Accept-header swaps live in .htaccess, which nginx never reads, and Plesk can serve images straight from nginx. Add nginx rules, or behind a CDN use the plugin mode that rewrites image URLs in the HTML, then purge every cache.
Worked example
A bakery site on Plesk showed 400 images optimised, yet DevTools listed only JPEGs. The uploads folder held hero.jpg.webp files, so processing worked. Static files were served by nginx, skipping the plugin's rules. Adding a map and try_files rule, reloading nginx and purging Cloudflare made the Type column show webp and cut the homepage image weight by about half.
Commands to check it worked
Count copies with find wp-content/uploads -name '*.webp' | wc -l. Test delivery with curl -sI -H 'Accept: image/webp' on an image URL and read the content type. Run wp cron test for the queue. In DevTools, filter the Network panel by images and confirm the Type column.
Tip: Never delete original images through the plugin until you have checked the optimised versions on several pages, because without them you cannot undo poor compression.
Tags: wordpress, images, webp, plugins, fix
Fix
Lazy loading holds back images, embeds and video until the visitor nears them. This card covers the native loading attribute, an IntersectionObserver pattern for backgrounds and video, and the first-screen images that must always load straight away.
Understand the idea
By default a browser fetches every image on the page as soon as it reads the HTML. Lazy loading defers anything off screen until the reader scrolls close to it, so the first view competes with fewer downloads and visitors who leave early never pay for the rest.
Mark the first screen
On both a phone and a desktop width, list the images and embeds that are visible before any scrolling. These stay eager. The hero gets fetchpriority=high and no loading attribute at all.
Use the native attribute
Add loading=lazy to each img and iframe below the fold. Keep a real src and srcset so nothing depends on a script. All current major browsers honour it and start the download well before the element reaches the screen.
Always give dimensions
Write width and height on every lazy image, or set an aspect-ratio in CSS. Without them the image has no reserved space and the text below jumps when it arrives, which hurts layout shift scores.
Script the awkward cases
CSS backgrounds, custom widgets and video cannot use the attribute. Store the address in a data attribute and let an IntersectionObserver with a rootMargin of a few hundred pixels set it, with a fallback for browsers lacking the API.
Treat video on its own
Video has no loading attribute. Use preload=none and a light poster image so only the poster downloads, and start background videos from your observer only when their section scrolls into view.
Keep content crawlable
Googlebot does not scroll, so never trigger loading from scroll events. Check the rendered HTML in the URL Inspection live test: each lazy image should carry its real address, and the text should be there without any interaction.
Worked example
A recipe page had a hero, twelve step photos and a video, about 3.1 MB on a phone. The hero and first step photo stayed eager, the other eleven photos and the iframe gained loading=lazy plus dimensions. The first-screen download fell to roughly 520 KB with no layout shift.
Mistakes to avoid
Lazy loading the hero or logo, running a plugin and the theme option together, leaving images with only data-src and no fallback, and forgetting dimensions are the usual errors. After any change, compare LCP and layout shift with your earlier figures.
Tip: If LCP got worse after adding lazy loading, search the first screen for loading=lazy before anything else; it is nearly always a hero image that was caught by a blanket setting.
Tags: lazy-loading, page-speed, images, javascript, fix
Fix
The title tag names a page for browsers and search results; the title attribute is an optional tooltip on one element. This card sets out the difference, why link and image titles add little for SEO, and where a title attribute is still needed.
Tell them apart
The title element lives once in the head and names the document. The title attribute can sit on any body element and adds a hover note to that element alone. Same word, unrelated jobs.
Get the page title right first
Each page needs one unique title in the head, led by the words people search and ending with your brand, around 50 to 60 characters. This is what Google most often shows as the headline of your result.
Know who sees the tooltip
Only mouse users who pause over an element see a title attribute. Phones and tablets have no hover, keyboard focus rarely shows it, and screen readers treat it in different ways, so many visitors never meet it.
Do not expect ranking gains
Search engines read the visible anchor text of a link and the alt text of an image. A vague link with a keyword title is still a vague link, so rewrite the words people can see instead.
Remove repeats
A title that copies the link text gives sighted users nothing new and can make a screen reader say the same words twice. Strip these from menus, product cards and theme templates.
Move vital details into view
If a title holds something people need, such as PDF and file size or a new tab warning, write it in the visible text or next to the link so phone users get it too.
Always title iframes
Embedded maps, videos and forms need a short title attribute describing their content. Here it is the accessible name of the frame, so screen reader users know what they are about to enter.
Worked example
A bakery menu had links like Read more with title Sourdough classes, plus image titles copied from file names and an untitled map iframe. The fix rewrote the links to Sourdough classes for beginners, removed the image titles, added alt text, and titled the frame Map of our shop. Screen reader testing then announced each link once and clearly.
Commands to check it worked
Use curl -s with grep '<title>' to confirm one title per page, and grep on the HTML for title= on links and images. In the DevTools console, list body elements with a title attribute and compare each with its text to spot duplicates.
Tip: In WordPress, unexpected link titles in menus usually come from the Title Attribute field, which you can reveal under screen options on the Menus screen and clear item by item.
Tags: title-tag, title-attribute, accessibility, links, fix
Fix
A skip link should let keyboard users leap over the header and menu in one key press. When it stays hidden, does nothing or leaves focus at the top, the cause is usually a missing target, a script or the CSS. This card walks through testing and repairing it.
Know what it is for
The skip link is the first Tab stop on the page. It lets keyboard, switch and voice users bypass the repeated header and menu, which is what WCAG 2.4.1 Bypass Blocks at Level A asks for.
Run the three key test
Click the address bar, press Tab, Enter, Tab. The link should appear first, and the second Tab should land on a link inside the content. If it lands in the menu, focus never moved. On a Mac, enable Tab highlighting in Safari before testing.
Match the target
The href, such as #main, must match exactly one id on the page. Page builders often drop the id from the content wrapper, and copied sections create duplicates. Count it in the console on every template.
Let the target take focus
Use a main element and give it tabindex=-1. Current browsers usually move the focus start point anyway, but the attribute makes it dependable with screen readers and scripts, without adding the element to the Tab order.
Hide it the right way
Elements set to display none or visibility hidden cannot be focused. Park the link off screen and bring it into view on focus with a clear outline and solid background, so it meets Focus Visible.
Put it first
Place the link straight after the opening body tag, ahead of cookie banners, announcement bars and chat buttons that would otherwise grab the first Tab press.
Watch for scripts and headers
Smooth scroll scripts and routers can cancel hash links, and a base tag can send them to the home page. A sticky header can cover the target, so set scroll-margin-top to the header height.
Worked example
A shop's link pointed at #content, but the builder template had no such id, a smooth scroll script cancelled every hash link, and an 88 pixel header hid the heading. Adding the id and tabindex, excluding the link from the script and setting a 6rem scroll margin cut the path to the content from 34 key presses to two.
Commands to check it worked
In the console, document.querySelectorAll('[id="main"]').length should return 1 and document.activeElement should be the main element after the jump. With curl, grep the HTML for the skip link href and for any base tag. Then confirm with NVDA or VoiceOver.
Tip: Build the skip link and the main target into the base template rather than individual pages, so every new layout inherits a link that already works.
Tags: accessibility, wcag, keyboard, skip-link, fix
Fix
Website SEO is easier to manage when you tackle it in order: access first, then pages, then links and local trust. This card gives small business owners a sequence to follow, the checks to run along the way, and the habits that keep a site improving.
Measure before changing anything
Verify the domain in Google Search Console and submit the sitemap. Record clicks, impressions, average position and indexed pages for the last three months, so you can tell later whether your changes helped.
Make sure pages can be found
Check that http goes to https in one redirect, that robots.txt blocks nothing important, and that your key pages carry no noindex and point their canonical at themselves. URL Inspection shows how Google sees each one.
Give each page one job
Write the main search beside every important page. Two pages chasing the same phrase should be merged, and a common customer question with no page becomes a page to write.
Write titles and headings that match
Lead each title with the search phrase and end with the brand. Use one H1 that names the page and H2s for the questions customers ask, then confirm the served HTML shows the new wording.
Answer the search in full
Say what you offer, who it is for, where you work, rough prices and how to book. Replace thin, repeated text with real detail and your own photos with descriptive alt text.
Join the pages up
Link the home page and menu to every main service, and each article to the service it supports, with descriptive link text. Keep key pages within three clicks and repair any broken or redirected links.
Build local trust
Complete your Google Business Profile, keep the name, address and phone identical everywhere, add LocalBusiness markup that matches the page, and earn links from relevant local organisations.
Worked example
A bakery had one Services page listing cakes, bread and classes in three lines, with the title Home. It split this into three pages, each titled for its own search, linked them from the home page and menu, and wrote opening hours and the town on the contact page. Within a few months, impressions for wedding cake searches in its area rose steadily in Search Console.
Mistakes to avoid
Buying links, stuffing keywords, copying text across pages, deleting pages without 301 redirects and rewriting everything at once are the common errors. Change one thing at a time, keep a dated log, and review Search Console monthly.
Tip: Begin with the handful of pages that bring in enquiries; a thorough job on five pages beats a light touch across fifty.
Tags: seo, site-audit, small-business, local-seo, fix
Fix
SEO shows results in stages, so judging it by sales alone hides real progress. This card shows the order the numbers move in, the reports and log checks that prove each stage, and the timelines that are realistic for a small business.
Know the order things move in
Search progress follows a fixed sequence: Googlebot fetches a page, Google indexes it, it starts appearing in results, positions climb, people click and some of them buy or enquire. Find out which of these stages has changed before deciding whether the work has paid off.
Expect a delay at every stage
Recrawling, indexing, rankings settling and links accumulating each take time, from days to months. Google itself suggests four months to a year for most SEO work to show its benefit. Technical fixes on a trusted site are the quick exception.
Prove the crawl from your logs
Grep your server access log for Googlebot and the path you changed. The date and the 200 status tell you the crawler saw the new version. Check one IP with a reverse DNS lookup so a fake bot does not fool you.
Confirm indexing per page
Run each changed address through URL Inspection. URL is on Google means it is in the index. Discovered or crawled but not indexed means Google is not convinced yet, so strengthen the content and add internal links.
Watch impressions before clicks
In the Performance report, compare the latest three months with the three before. Impressions normally rise first, sometimes weeks before clicks. Ignore the final two days, which are always incomplete.
Strip out brand searches
Use a Custom (regex) query filter that excludes your business name and its misspellings. People searching your name would find you anyway, so the non-brand figures are the fairest measure of what SEO has achieved.
Tie visits to outcomes
In GA4, filter Traffic acquisition and the Landing page report to Organic Search and add key events like form sends or purchases. Visits that rise without enquiries point to a page or offer problem rather than a search problem.
Worked example
A bakery saw average position fall from 14 to 20 and CTR drop after new pages went live, and thought SEO had failed. Non-brand impressions had tripled for cake delivery searches at positions 15 to 30. Two months of page improvements later, organic enquiries had tripled.
Common mistakes
Checking your own ranking in a browser, comparing a busy month with a quiet one, reading average position on its own, missing a removed GA4 tag, and giving up after six weeks. Each makes working SEO look broken or broken SEO look fine.
Tip: Keep a dated log of every change you make. When a line on a chart moves, you can match it to the work that caused it instead of guessing.
Tags: search-console, analytics, organic-traffic, seo-timeline, fix
Fix
On-page SEO is the work done on your own pages; off-page SEO is the trust you earn from the rest of the web. This card sets out the factors in each half, how to check them, and why most small sites should fix their pages before chasing links.
Split the work into three
Pages you edit are on-page. Site-wide crawling, indexing and speed are technical. Links, mentions and listings on other sites are off-page. Knowing which bucket a problem sits in tells you who can fix it and how quickly.
Give each page one job
List your key pages and assign each one main search phrase. Two pages chasing the same phrase compete with each other, so merge them or give each a distinct angle.
Make sure Google can index it
URL Inspection should report the page as on Google with its own address as canonical. A noindex, a robots block or a stray canonical cancels every other improvement.
Rewrite title and main heading
Lead the title tag with the target phrase plus a place or benefit, near 50 to 60 characters, and make the h1 a plain statement of the same subject. Check the live source, since themes and plugins often override what you typed.
Serve the intent behind the search
Look at what already ranks. If the leaders are guides, a bare sales page will struggle, and the reverse. Answer the same questions more clearly, with local detail, prices or examples, under descriptive subheadings.
Link pages together
Point at least three related pages at each important page with descriptive anchor text, add alt text to meaningful images and add structured data that mirrors what visitors can see.
Earn trust from outside
Suppliers, trade bodies, partners and local press are realistic sources of links. Offer them something worth citing. Keep your name, address and phone identical across directories. Mark any paid placement as sponsored.
Worked example
A heating firm's repair page sat at 18 with the title Services and 140 words. A new title, a full page and homepage links lifted it to 7 in six weeks. Trade body, supplier and local press links then took it to 3.
Common mistakes
Building links to pages that are not indexed, stuffing the keyword into every sentence, using one page for every service, changing URLs without redirects, and buying link packages that break Google's spam policies and can be ignored or penalised.
Tip: If a page has impressions but sits on page two, improve the page first; if it is already the best answer and still stuck, that is when off-page work pays off.
Tags: on-page-seo, off-page-seo, backlinks, site-audit, fix
Fix
Before creating a Business Profile, check whether one already exists, decide whether your address should be public, and prepare the evidence Google needs. This card walks through setup, the verification options and what to do when verification keeps failing.
Look for an existing listing
Sign in to business.google.com with the business account and search Maps for your name, number and any old addresses. Google often builds listings from public data, and a former agency may hold one. Claim or request it rather than starting a second.
Check you are eligible
Google wants face-to-face contact with customers during your hours. Shops and offices show an address; trades that travel to customers hide it and list areas; online-only firms do not qualify; mailboxes and unstaffed virtual offices are not locations.
Create it with exact details
At business.google.com/create, use the name on your sign with no extra keywords, the narrowest fitting category, a correctly formatted postal address, a phone that a person answers, and your website address.
Hide the address when you travel
Service-area businesses still give an address for checking but switch off its display, then add up to 20 towns or postcode districts within a sensible drive. Look at the public listing afterwards to be sure no street shows.
Gather proof before verifying
Have signage or a branded van, a street sign or landmark, your tools or stock, and something only a manager could show, such as keys, the till or a business letter with private details hidden, ready before you press Get verified.
Use the method Google offers
You may see phone, text, email, a recorded video, a live call or a postcard, and you cannot pick. Videos must be one unbroken take. Postcard codes expire after 30 days and are voided if you edit key details.
Leave it alone during review
Reviews usually finish within five business days. Editing the name, address or category meanwhile can restart them. Watch for a Review issues message and act on it rather than resubmitting blindly.
Worked example
A mobile dog groomer listed her home address publicly and named the profile Example Grooming Dog Groomer Leeds. Verification failed twice. She renamed it to her trading name, hid the address, added six towns and filmed her branded van, kit and insurance letter in one take. It verified in three days.
Common mistakes
Creating a duplicate instead of claiming, adding keywords to the name, using a mailbox address, giving a phone line with an automated menu, editing during a postcard wait and filming a cut-together video are the usual reasons verification fails.
Tip: Set the profile up under a Google account the business owns and add a second owner straight away, so access never depends on one person or one agency.
Tags: google-business-profile, verification, local-seo, service-area, fix
Fix
When a Business Profile belongs to an old agency or ex-employee, or your edits and phone number keep disappearing, there is a set route for each case. This card covers the roles, the ownership request and its three-day window, primary ownership handovers and edits that will not stick.
Learn the three roles
A profile has one primary owner, any number of owners and any number of managers. Owners control who has access and can delete the profile; managers edit details but cannot add or remove people. Your role decides what you can fix.
Try every account first
Before filing anything, sign in with each Google account the business has ever used and look for the profile. A surprising number of lockouts are just a forgotten shared login or a second Gmail address.
Get added by the owner
If you can reach whoever controls it, ask to be added as an Owner from People and access. It takes minutes, needs no review and avoids the claim process altogether.
Request access from Google
Otherwise go to business.google.com/add, find the listing and choose Request access. The current owner gets three days to approve or deny. Keep the confirmation email, as it holds the link to continue.
Claim after silence
With no reply after three days, the confirmation email may offer View request and Verify. Complete the verification method shown. If no claim option appears, or the business hides its address, go through Business Profile support instead.
Hand over primary ownership
New owners must wait seven days before primary ownership can pass to them. Add the person, wait a week, then transfer. Afterwards, drop the agency to Manager and remove anyone who has left.
Deal with edits that will not stick
Pending edits need time, and resubmitting resets them. Updates from Google or public suggestions can overwrite your changes, so review and reject wrong ones, and keep your site's details matching the profile.
Worked example
A bakery's profile was owned by an agency that had closed. The owner requested access, heard nothing for three days, used View request to verify by video, and became owner. A Google update had removed the phone number, so she rejected it, re-entered the number and matched it on her contact page.
Common mistakes
Creating a second profile instead of claiming, letting an agency hold primary ownership, sharing one login among staff, attempting a primary transfer within seven days, resubmitting edits under review, and ignoring Google update notices that quietly change your details.
Tip: Make a business-owned Google account the primary owner and keep a second internal owner, so no single departure can lock you out.
Tags: google-business-profile, ownership, profile-edits, local-seo, fix
Fix
AEO, GEO and AIO are often sold as replacements for SEO, but they share most of the same foundations. This card separates the terms, picks out the few things that really differ for AI answers, and gives an order of work for a small business site.
Untangle the acronyms
SEO is about ranking and clicks. AEO is about being the single answer, from featured snippets to voice and AI. GEO is about being quoted inside AI-written responses. AIO is used loosely, either for AI in general or for Google's AI Overviews.
See the shared base
Every one of them depends on pages that crawlers can fetch, that sit in an index, that answer a question plainly and that other sites trust. If those basics are weak, no AI tactic will make up for it.
Know the real differences
AI systems quote passages rather than whole pages, use extra crawlers such as OAI-SearchBot and PerplexityBot, reward brand mentions even without links and often answer without sending a click. Measurement is patchier than Search Console.
Test crawler access
Fetch your homepage with curl using each crawler's user agent and expect a 200 every time. A 403 usually comes from a security plugin or CDN bot rule. Then confirm real visits in your server log.
Get content into the HTML
Search the raw HTML of a key page for a sentence from its body. If the text only appears after JavaScript runs, some AI crawlers will miss it, so render it on the server or move it out of script-built widgets.
Write quotable passages
Under each question heading, open with one sentence that fully answers it, then support it with figures and examples. A passage that stands alone is easy for a snippet or an AI summary to lift.
Choose crawler rules on purpose
Allow search and answer crawlers you want citations from, and decide separately about training crawlers such as GPTBot and Google-Extended. Blocking Google-Extended does not take you out of AI Overviews.
Worked example
A bakery about to buy a GEO package found its security plugin returned 403 to AI crawlers, its cake text sat in a JavaScript slider and prices were hidden behind a form. Fixing those, adding markup and two directory listings lifted it to 4th in Google and into AI answers.
Checks that it worked
Look for AI search crawler hits with 200 status in your logs, referral visits from chatgpt.com or perplexity.ai in analytics, rising Search Console impressions, and your name appearing in a fixed set of prompts you rerun each month.
Tip: Before paying for a separate GEO service, run the crawler access test and the raw HTML check; most AI visibility problems on small sites show up there.
Tags: aeo, geo, seo, ai-search, fix
Fix
AI Overviews link to a handful of pages beside each generated answer. This card covers how those sources are picked, the technical checks that quietly rule a page out, and the writing changes that make a page worth citing.
Know what is being picked
The summary is written by Gemini from pages already in the Search index. Google may run several related searches behind the scenes and gather pages from each, so a strong answer to one narrow sub-question can earn a link even if the page ranks lower for the main search.
Choose searches that show one
Search your target questions signed out. Only some queries trigger a summary, mostly questions, comparisons and how-to searches. Record which ones do and which sites are linked, so you have a baseline to measure against.
Pass the hard gates
Googlebot must be able to fetch the page, it must be indexed, and it must be allowed to show a snippet. URL Inspection confirms the first two. Look for nosnippet, max-snippet:0 or data-nosnippet around your answer, because any of them stops Google quoting the text.
Check the logs, not your hopes
Search the server access log for recent Googlebot requests to the page. A 200 means Google can read it. A 403 or 5xx means a firewall, bot filter or server fault is in the way, and no content change will help until that is fixed.
Lead with the answer
Use the question as the heading and answer it in the first sentence or two, including the numbers and conditions that change the answer. That opening sentence should make sense if lifted out on its own. Put the explanation, steps and tables below it.
Add something nobody else has
Original measurements, tested steps, local prices and real examples give Google a reason to prefer your page over ten similar ones. Generic advice that matches every other result is rarely the source a summary chooses to link.
Support it with authorship and markup
Show a named author, why they know the subject and a last updated date. Article structured data with author and dates helps Google read these facts, but it is optional and will not rescue a page that fails the eligibility checks.
Worked example
A bakery guide on sourdough starters was indexed but never linked. curl showed max-snippet:50 set site-wide by a plugin. After removing it and moving a one-sentence answer, 7 to 10 days at 22 to 25 degrees, under a matching heading, the page appeared as a supporting link for two target questions within a month.
Common mistakes
Blocking Google-Extended and thinking it affects Search; it does not. Expecting a separate AI Overviews report in Search Console; the traffic sits inside Web totals. Adding dozens of thin question headings, which reads as search bait. Changing dateModified on every deploy without changing content.
Tip: Recheck your target questions monthly while signed out, because AI Overviews change as the index changes and a citation can disappear without any change on your side.
Tags: ai-overviews, aeo, google, citations, fix
Fix
AI Overviews sit inside Google Search, so there is no separate off switch. This card shows searchers how to avoid the summaries and shows site owners which snippet controls limit their use, what each one costs and where they stop working.
Searchers: use the Web filter
There is no account setting to hide the summaries. The Web tab under the search box lists plain links. It runs on the udm=14 parameter, so a custom browser search engine using that parameter makes it the default for every search.
Owners: know there is no AI-only switch
Google applies its normal preview controls to AI Overviews. Anything that keeps your text out of the summaries also changes the snippet under your ordinary listing, so weigh the lost clicks before you start.
Hold back one passage
Wrap the part you want kept out, such as a paid method or a price table, in a div, section or span carrying data-nosnippet. Leave a plain summary sentence outside it so your result still has a useful description.
Hold back a whole page
Add nosnippet to the robots meta tag, or use the googlebot meta name to target Google only. The page then shows no description in results and cannot be a featured snippet, which is the price of keeping it out of summaries.
Cover files and folders by header
PDFs have no HTML head, so send X-Robots-Tag: nosnippet from the server. On nginx use add_header with always; on Apache use Header set inside FilesMatch. Confirm with curl -sI that the header is actually returned.
Try a length cap instead
max-snippet with a character count limits how much text Google can quote anywhere in Search. Zero equals nosnippet and minus one means no cap. Small numbers give clipped results, so trial it on a few pages first.
Leave the crawler tokens alone
Blocking Google-Extended affects some Gemini uses outside Search and changes nothing in AI Overviews. Blocking Googlebot takes you out of Search altogether. Neither is the right tool for this job.
Worked example
A cookery school wanted its paid weekend timetable kept out of summaries but the course page kept in results. It wrapped the timetable in a div with data-nosnippet, kept a one-line course summary above it, purged the CDN and requested indexing. The access log showed Googlebot refetching the page two days later.
Commands to check it worked
Run curl -sI on the URL and look for x-robots-tag, then curl the page and search for the robots meta tag and data-nosnippet. Check that only one robots tag prints. Finally search the access log for the next Googlebot request and confirm it got a 200 with the new HTML.
Tip: Before switching anything on, compare a month of clicks for the page in Search Console, so you can tell afterwards whether the restriction cost more than it saved.
Tags: ai-overviews, nosnippet, robots-meta, google, fix
Fix
Telegram builds each preview card on its own servers and then reuses it. When the card is blank, wrong or out of date, the fetcher was blocked, the tags were missing from the server HTML, or the cached card needs refreshing. This card walks through each.
Understand who fetches the page
Telegram's servers, not the phone, request your URL with a user agent containing TelegramBot. They read the head, build a card and store it, so every later share of that address shows the stored version.
Check the response code
Use curl with the TelegramBot user agent on the shared address. Anything other than a 200, such as a 403 from bot protection or a redirect to a cookie page, means the card is built from the wrong response or not at all.
Check the tags in raw HTML
grep the curl output for og:title, og:description, og:image and og:url. The fetcher does not run scripts, so tags that only appear in the browser inspector are invisible to it and must be moved to the server output.
Set a proper share image
Use a wide JPEG or PNG at 1200 by 630 pixels, below 1 MB, on an absolute https address. Remove hotlink protection on that path and add width and height tags. Small or square pictures tend to show as a thumbnail.
Open the firewall to the fetcher
Look for TelegramBot lines in the access log after a test share. None at all suggests the CDN stopped it upstream; 403 or 429 lines point at your own server or a security plugin. Add a narrow allow rule for public pages.
Refresh the stored card
Send the exact address to @WebpageBot inside Telegram. It refetches the page and replaces the cached card for new messages. Purge your own page cache first, or the bot will simply store the old tags again.
Use one address per page
Each variant, with or without www, a trailing slash or tracking parameters, is cached on its own. Redirect variants to one form and use it in og:url and in every post so a single refresh covers all shares.
Worked example
A bakery's menu link showed last month's opening times. curl returned the right tags, so the page was fine. Sending the URL to @WebpageBot fixed it for the www form, but the channel post used the bare domain. Refreshing that form too, then redirecting it to www, cleared the old card.
Common mistakes
Relative og:image paths. Tags injected by JavaScript. Two plugins printing competing tags. Expecting the second link in a message to get a card. Forgetting that Instant View uses its own templates and has nothing to do with Open Graph tags.
Tip: Before posting a new page in a busy channel, send its address to @WebpageBot first, so the very first share already shows the correct card.
Tags: telegram, open-graph, link-preview, social, fix
Fix
None of these frameworks sends a Content Security Policy until you write one. This card shows where the header belongs in Spring Security, a Tomcat 9 filter and the server behind an Angular build, plus how nonces work.
Know which layer sends headers
The policy is one HTTP header, but the place you add it differs by stack. Spring code can set it on the response, Tomcat needs a filter, and Angular relies entirely on the server or CDN delivering its built files, because browser code cannot send headers.
See what the live site returns
Use curl with the -I option on several real URLs, including a missing page, and look for the policy line. Check it appears only once. Two separate policies are both enforced, which often explains a site that breaks in unexpected places.
Spring Security
Inside the SecurityFilterChain bean, call headers, then contentSecurityPolicy, and pass the directives string. Nothing is sent by default. Adding reportOnly sends the trial version of the header so you can watch for problems without blocking anything.
Spring Boot without Spring Security
A small OncePerRequestFilter registered as a component can call setHeader on every response. Keep the policy string in configuration so it can be changed without hunting through code.
Tomcat 9 filter
The bundled header security filter handles HSTS and framing but not CSP. Write a filter against the javax.servlet package (Tomcat 10 uses jakarta.servlet instead), pass the policy as an init parameter and map it to every URL, including the error dispatcher.
Angular builds
Configure nginx, Apache or the hosting platform to send the header with index.html. Angular inserts component styles inline, so a strict style rule needs a nonce. Put ngCspNonce on the root element and have the server swap in a fresh random value per response.
Grow the policy from reports
Start from self for everything and object-src none, then browse each template with the Console open. Every refusal names the directive involved. Add trusted hosts to the narrowest directive and leave unknown ones blocked.
Worked example
A bakery shop on Angular calls an API on a separate subdomain and loads a payment script. In trial mode the Console shows connect-src, script-src and img-src refusals. Adding those three hosts to the matching directives clears them, and after a quiet week the header is switched to enforcing.
Mistakes to avoid
Adding unsafe-inline to silence errors, reusing one nonce for every visitor because index.html is cached, setting the header in both Spring and nginx, and relying on a meta tag that cannot carry frame-ancestors or reporting. Re-test with curl after every framework upgrade.
Tip: Keep the policy string in one place under version control, with a short comment for every third-party host, so the next person knows why each source is allowed.
Tags: csp, spring-boot, tomcat, angular, security-headers, fix
Fix
The report-only version of the CSP header lets browsers list what a policy would block without breaking anything. This card covers finding where a policy is set, why default-src 'self' misbehaves, reading reports and switching to enforcement.
Understand the two headers
The enforcing header blocks anything not allowed. The report-only header uses identical directives but only warns and reports. Both can be sent together, which is handy for testing a stricter policy next to one already in force.
Find the layer that sets it
A policy can come from a CDN rule, the web server, control panel directives, application middleware, a CMS plugin or a meta tag. Compare curl output from the CDN and the origin, and search server configuration, to work out which one is responsible.
Keep only one source
Two enforcing policies are applied separately, so a resource must satisfy both. Turn off every copy except the one you will maintain, purge caches and confirm with curl that only one remains.
Know what 'self' covers
The self keyword allows files from the page's exact scheme, host and port. It does not allow inline code, event attributes, subdomains, a CDN or font hosts. Without the single quotes it is read as a hostname, and it never covers frame-ancestors.
Start the trial with reporting
Send the report-only header with a strict starter policy and a report-uri pointing at a small logging script that returns 204. Add report-to with a Reporting-Endpoints header as well. Neither works in a meta tag.
Sort the reports
Group entries by directive and blocked address. Allow trusted hosts in the narrowest directive, handle inline code with nonces or hashes, and ignore entries from browser extensions. Update the trial policy and let it run again.
Switch to enforcing
When only noise is left, rename the header to the enforcing version, keep the reporting directive, purge caches and test forms, logins and payments straight away. Revert to the trial header if something breaks.
Worked example
A bakery site trial produces thousands of reports that collapse into six groups: two Google Fonts hosts, a tag manager, a theme inline script, a video embed and extension noise. Five are allowed or given a nonce, the sixth ignored, and after a quiet week the header is enforced.
Commands to check it worked
Use curl with -sI and grep for content-security on several URLs, including a missing page. Confirm the header name, that it appears once and that the policy text matches. Then load each main template with the browser Console open and check for refusals.
Tip: Keep a report-only header running beside the enforced one whenever you plan to tighten the policy, so you can test the change on real traffic first.
Tags: csp, report-only, security-headers, fix
Fix
Browsers remember HSTS, so deleting the header does not switch it off. This card shows how to clear it for visitors with a zero lifetime, take a domain off the preload list, wipe it from your own browser and bring it back safely.
Why deleting the header fails
A browser that has seen the header stores the rule for the max-age it was given, often a year. Remove the header and those browsers carry on forcing HTTPS regardless, until the time runs out.
Find every source
Use curl on the apex, www and subdomains, and on the origin behind any CDN. Look in server configuration, Plesk settings, the Cloudflare HSTS panel, plugins and application frameworks, which may add the header by default.
Keep HTTPS valid
The header is only read over a trusted connection. If a certificate is broken, returning visitors cannot receive the change, so repair the certificate on every affected host first.
Send a zero lifetime
Set the header to max-age=0 with no subdomain or preload options. Browsers that receive it delete their stored rule at once. Send it from the host that originally set the rule, normally the apex, and on redirect responses too.
Keep it running
Only visitors who come back receive the zero value. Leave it in place for at least the length of the old max-age before removing the header completely, and recheck after server changes.
Preload list removal
Drop the preload token, then use the removal form at hstspreload.org. The change has to ship in new browser releases and reach users, which takes months. Whole endings such as .dev and .app can never be removed.
Clear your own browser
In Chrome or Edge open net-internals at the hsts section and delete the domain. In Firefox use Forget About This Site. This helps testing only and cannot remove preloaded entries.
Worked example
A site added includeSubDomains for a year and an internal staging subdomain without a certificate became unreachable. The owner switched the apex to max-age=0, cleared their browser, added a certificate to staging, then restored HSTS at five minutes, a day and finally a year.
Mistakes to avoid
Turning off the Cloudflare HSTS toggle instead of setting the lifetime to zero, sending the zero value only from the subdomain, leaving a plugin still sending the old value, and switching HTTPS off entirely. Each one leaves visitors stuck with the old rule.
Tip: Before adding includeSubDomains or preload in future, list every subdomain you own, including internal ones, and check each has a valid certificate.
Tags: hsts, https, security-headers, preload, fix
Fix
The HSTS warning is a certificate fault on a site that only allows secure connections, so visitors get no option to continue. This card shows how to read the code, find the broken certificate and fix it.
Separate the rule from the fault
HSTS is only the reason the warning has no way through. The fault is a certificate problem, shown in a code such as NET::ERR_CERT_DATE_INVALID in Chrome or SEC_ERROR_UNKNOWN_ISSUER in Firefox.
Accept there is no bypass
Browsers remove the proceed option for HSTS sites on purpose, so a fake certificate on public Wi-Fi cannot trick people. Asking visitors to clear browser settings is not a fix and does nothing for preloaded domains.
Match the code to a cause
Date errors mean expiry or a missed renewal. Common name or bad domain errors mean the host is not on the certificate. Authority or unknown issuer errors mean a self-signed certificate or a missing intermediate.
Test from outside
Run openssl s_client with the servername option for the exact host, print the dates and alternative names, and check the verify return code. Zero means the chain is trusted; 18 to 21 mean trust problems.
Check the subdomain rule
If the apex sends includeSubDomains, every subdomain inherits HSTS for anyone who has visited the main site. A staging or mail host with a self-signed certificate then becomes impossible to open.
Fix the certificate
Renew and reload the server, reissue with every host name, or point the server at the full chain file. On Cloudflare, remember the free edge certificate does not cover second-level subdomains.
Rule out the visitor side
If the site checks out but some people still see it, look at their clock, antivirus HTTPS scanning, captive Wi-Fi portals, workplace proxies and old devices that lack newer root certificates.
Worked example
A shop sends includeSubDomains from the apex. Its booking subdomain still uses a panel default certificate, so returning customers see the HSTS error with SEC_ERROR_UNKNOWN_ISSUER. Issuing a Let’s Encrypt certificate for the booking host and reloading the server clears it in every browser.
Commands to check it worked
Use openssl s_client with servername and look for a verify return code of 0, current notAfter date and the right names. Use curl -sI to read the HSTS header on the apex and subdomain. Then open the page on a phone using mobile data.
Tip: Set expiry alerts for every host name, including subdomains you rarely visit, because an expired certificate on an HSTS site locks everyone out at once.
Tags: hsts, ssl, certificates, chrome, fix
Fix
A not trusted warning means the client could not link your certificate to a root it knows. This card covers spotting a missing intermediate, a self-signed or panel default certificate, and the local issuer error from curl and git.
How trust is decided
Clients trust a certificate when they can follow it through intermediates to a root already on the device. Roots ship with the system; intermediates must be sent by your server alongside your own certificate on every connection.
Spot the symptoms
Chrome shows an authority invalid error, Firefox an unknown issuer, curl and git say they cannot get the local issuer, and scanners list a certificate that cannot be trusted. All point to the same broken chain of trust.
Inspect what the server sends
Run openssl s_client with servername and showcerts. One certificate in the chain means the intermediate is missing. Matching subject and issuer means self-signed. Verify code 0 means everything is fine.
Why it can look fine to you
Some desktop browsers fetch or remember missing intermediates, hiding the problem. curl, payment gateways, monitors, apps and scanners do not, which is why they complain while your own browser shows a padlock.
Serve the full chain
Use a file with your certificate first and intermediates after. With Let’s Encrypt that is fullchain.pem. Point nginx ssl_certificate or Apache SSLCertificateFile at it, or fill the CA bundle field in Plesk or cPanel.
Replace placeholders
Panels and Linux servers ship default self-signed certificates, and a request reaching the wrong virtual host, often over IPv6, falls back to one. Install a public certificate on the configuration that actually answers.
Client-side issuer errors
If the chain verifies elsewhere, the failing machine needs an updated CA bundle, the schannel backend for Git on Windows, or a company proxy root installed. Never switch verification off to make the error go away.
Worked example
A bakery site passes in Chrome but its payment provider rejects callbacks. openssl shows a single certificate in the chain and verify code 21. Changing nginx from cert.pem to fullchain.pem and reloading gives code 0, and the callbacks start working.
Commands to check it worked
Rerun openssl s_client and look for two or more certificates and verify return code 0. Run curl -sS against the site and confirm no certificate error. Repeat over IPv6 and on every port the scanner listed, then rescan.
Tip: Test certificates with curl rather than only a browser, because curl does not fill in a missing intermediate and shows the chain problem straight away.
Tags: ssl, certificates, certificate-chain, https, fix
Fix
A name mismatch means the certificate is valid but was issued for different names from the one in the address bar. This card shows how to find the missing name, reissue with every hostname and make the server send the right certificate.
Spot the pattern
Try the bare domain, www and each subdomain in a private window. If one loads with a padlock and another shows a privacy warning, the certificate is genuine but does not list every name you use.
Read the names on the certificate
Use openssl s_client with the servername option set to the failing host, then print the subjectAltName extension. Browsers only trust the names in that list; the old Common Name field is ignored.
Spot a default certificate
If the names shown belong to the hosting server or another site, your server has no HTTPS site for that hostname and is sending its fallback certificate. Installing a new certificate will not help until a site claims the name.
Check DNS for each name
Every hostname must resolve to the machine that holds the certificate. A www CNAME to a site builder, or a subdomain still pointing at an old host, means a different server is answering and must be fixed there.
Claim the name on port 443
Add the hostname to server_name in the nginx 443 block, or to ServerAlias in the Apache 443 virtual host. In Plesk or cPanel, create the subdomain or alias first. Reload and confirm with nginx -t or apachectl -S.
Reissue with the full list
Run certbot with --expand and every name, or reissue in the panel with www and subdomains ticked. Wildcards need the DNS challenge, and they cover one level only, so list the bare domain alongside them.
Redirect the extra names
With every name covered, send the spare hostnames to one main address with a single 301. A redirect never fixes the warning on its own, because the certificate is checked before the redirect is read.
Worked example
Example Bakery found www.example.com missing from its SAN list and shop.example.com receiving the server's default certificate. Adding a 443 block for the shop and expanding the certificate to all three names cleared both warnings in about twenty minutes.
Commands to confirm the fix
Run openssl s_client with -servername for each hostname and check the SAN list, use curl -vI to confirm no TLS error, run certbot certificates to see all names, and run certbot renew --dry-run so renewal does not fail on one name.
Tip: Keep a written list of every hostname you use, including ones that only redirect, and expand the certificate in the same change whenever you add a subdomain.
Tags: ssl, certificates, subdomains, https, fix
Fix
An installed certificate that seems not to work is almost always a setup problem around it: no redirect to https, the wrong server or hostname, insecure page resources, a CDN mode clash or a cached page. This card walks through finding and clearing each one.
Name the symptom
An http address marked Not secure, a full-page privacy warning, an https page that is not fully secure, and a timeout are four different faults. Check the bare domain and www in a private window and note which one you get.
Make sure it was issued
A certificate marked pending is not live. Finish the validation step the authority asks for, by email, DNS record or file, and wait for the status to show as issued or active before testing anything else.
Follow the DNS
Compare the domain's A record with your hosting plan's address. If the domain points elsewhere, the certificate you installed is never used. This is a frequent reason a certificate bought from a registrar seems to do nothing.
Check the chain
OpenSSL should report verify return code 0. Codes such as 20 or 21 mean the intermediate certificate is missing, which often works on a desktop browser but fails on phones, in curl and for crawlers.
Redirect to https
Add one 301 from every http address straight to the final https address, using the panel's force HTTPS option, .htaccess or an nginx return. Then set the site's own address setting to https so it stops writing http links.
Set the CDN mode
Behind Cloudflare, choose Full (strict) and give the origin a valid certificate. Flexible mode talks to your server over http, so a server-side https redirect sends visitors round in a loop.
Remove insecure resources
The DevTools Console lists every Mixed Content URL. Change them to https in templates, content and settings. Chrome blocks insecure scripts and stylesheets, so fixing these can also mend a broken layout or menu.
Worked example
A bakery's paid certificate sat on registrar hosting while the domain pointed at another host. Turning on the real host's free certificate for both names, adding the redirect and fixing one http logo URL got a clean result in OpenSSL and in Chrome.
Commands to confirm it works
curl -sI http://example.com should show a 301 to https; curl -sI on the https address should show 200 with no TLS error; openssl s_client should end with verify return code 0. Purge all caches first and test in a private window.
Tip: Since Chrome 117 there is no padlock on secure pages, only a neutral icon, so judge success by the https address and the connection details rather than by looking for a lock.
Tags: ssl, https, mixed-content, redirects, fix
Fix
IIS only offers a certificate in its bindings list when the certificate sits in the machine's Personal or Web Hosting store with its private key. This card covers finding the missing key, importing a PFX properly and binding each hostname with SNI.
Know what IIS will list
The binding drop-down only shows certificates from the Local Computer Personal or Web Hosting store that have a private key on this machine. Anything in a user store, a CA folder or without a key is left out on purpose.
Check HasPrivateKey
In an administrator PowerShell window, list Cert:\LocalMachine\My with Subject, Thumbprint and HasPrivateKey. False explains a missing entry. Note the thumbprint for checking the binding later.
Repair or recover the key
certutil -repairstore my with the serial number relinks a key that exists on this server. If it finds nothing, the key lives on the machine that made the CSR, so complete the request there and export a PFX.
Import a PFX into the machine store
Use IIS Manager Server Certificates Import, or Import-PfxCertificate into Cert:\LocalMachine\My. Double-clicking the file usually puts it in your own user store, where IIS cannot see it.
Watch the PFX format
OpenSSL 3 creates PFX files with encryption that Windows Server 2016 and older reject, often with a misleading wrong password message. Rebuild with the -legacy option and import again.
Bind each hostname with SNI
Add an https binding on 443 for every host name, with Require Server Name Indication ticked. Bindings without SNI share one certificate across the whole IP and port, so changing one can silently change another site.
Confirm with netsh
netsh http show sslcert lists Hostname:port entries for SNI bindings and IP:port entries for the rest. Each hash should match the thumbprint you expect; an old one means the binding was never updated.
Worked example
An agency completed a request on the live server for a CSR made on staging, so the certificate vanished on refresh. Completing it on staging, exporting a PFX with the chain and importing it on live, then binding both names with SNI, fixed it.
Mistakes to avoid
Generating the CSR on one server and completing it on another, importing only a .crt file, replacing a shared non-SNI binding, forgetting the intermediate certificate, and leaving bindings on the old thumbprint after renewal.
Tip: Generate the CSR and complete the request on the same server every time, or switch to PFX files from the start, and the vanishing certificate problem never comes up.
Tags: iis, ssl, windows-server, certificates, fix
Fix
A free certificate fails to issue when the authority cannot prove you control every name, and fails to install when the files do not fit together. This card shows how to read the error, test the challenge path and fix DNS, CAA, firewall and key problems.
Find the key phrase in the error
Look for NXDOMAIN, Invalid response, Timeout during connect, CAA or too many in the panel message or Certbot output. Each one points to a single failed check. Note which hostname failed, as one bad name sinks the whole request.
Make DNS point at this server
Look up the A and AAAA records for every name on the request. Let's Encrypt prefers IPv6, so an old AAAA record can fail validation even when IPv4 is correct. Fix or remove stale records and wait out the TTL.
Open port 80
The HTTP challenge always starts on port 80, even on a site that redirects to https. A timeout from outside your network means a firewall or cloud security group is blocking it.
Fetch a test file yourself
Put a file named test in .well-known/acme-challenge under the web root and request it with curl from outside. 200 is a pass, 404 means the wrong root or server, 403 means a rule is blocking the folder.
Exempt the challenge path
Add an nginx location ^~ block or an .htaccess skip rule for .well-known/acme-challenge above any rule hiding dot folders. Exempt it from security plugins, country blocks and CDN bot or attack modes too.
Check CAA and the limits
A CAA record that lists only another authority blocks Let's Encrypt; add issue letsencrypt.org. If a rate limit message appears, stop retrying, fix the cause and test with a Certbot dry run, which uses staging.
Match the files when install fails
Compare the sha256 of the public key taken from the certificate and from the private key. Different hashes mean the key belongs to another request. Panels want PEM text plus the CA bundle; Windows wants a PFX.
Worked example
A shop moved hosts and Plesk kept failing on www. dig showed www still pointing at the old server and an AAAA record left over from it. Updating www, deleting the stale AAAA record and waiting an hour let the certificate issue first time.
Commands to check it worked
dig +short A and AAAA for each name, curl -i on the challenge test file, certbot certonly --dry-run with every name, then openssl s_client with -servername to see the new expiry date and verify return code 0.
Tip: Every failed attempt counts towards a rate limit, so fix and test with a dry run first, then press the issue button once.
Tags: ssl, lets-encrypt, dns, plesk, fix
Fix
Mixed content in WordPress comes from http addresses left in settings, the database, theme files and plugins after a site moves to https. This card shows how to find each insecure URL, rewrite old links safely and stop them coming back.
Know why it breaks things
Browsers try insecure images over https and block them if that fails, and they block insecure scripts, stylesheets and frames completely. That is why a page can lose its layout or a slider can stop working, not just show a warning.
Make a list from the Console
Open several templates over https with DevTools open: home, a post, a page, an archive, the shop or form. Write down every insecure URL and whether it lives in uploads, the theme, a plugin or an outside service.
Switch both addresses
Under Settings, General, set the WordPress Address and Site Address to https. If they cannot be edited, the values are fixed by WP_HOME and WP_SITEURL in wp-config.php and must be changed there.
Back up and rewrite stored links
Export the database, then run wp search-replace from the full old http address to the new https one, with --skip-columns=guid. Try --dry-run first. A plain SQL replace can break serialised settings, so avoid it.
Tell WordPress about the proxy
Behind a CDN or load balancer that ends https, WordPress may think every visit is http and keep writing http asset URLs. Setting HTTPS to on when the forwarded proto header says https solves that.
Clean up the theme and plugins
grep the child theme for http:// and switch to home_url or get_stylesheet_directory_uri. Update plugins, check their stored URLs and regenerate page builder CSS so cached stylesheets drop the old addresses.
Purge and add a safety net
Clear the caching plugin, server cache, minified files and CDN. Then consider the upgrade-insecure-requests header, which asks browsers to load any leftover http resource on your pages over https.
Worked example
A bakery site had eleven insecure URLs: nine images in old posts, a slider script hard-coded in the child theme and one builder CSS background. One search and replace, a theme edit, a CSS rebuild and a CDN purge cleared all eleven.
Mistakes to avoid
Replacing just http:// across the whole database, which breaks links to other sites; editing serialised data with SQL; relying on a rewriting plugin forever; testing while logged in; and forgetting the CDN and minified file caches.
Tip: Always search for your full old address, such as http://example.com, never just http://, so links to other websites are left alone.
Tags: wordpress, mixed-content, https, wp-cli, fix
Fix
When TLS ends at a load balancer or proxy, Laravel and Keycloak think visitors arrived over http and generate http links, assets and redirects. This card shows how to pass X-Forwarded-Proto, trust the proxy and set the right hostname.
Why it happens
The browser talks https to the proxy, but the proxy talks plain http to your application. Without extra information the application assumes the visitor is on http and writes that scheme into every absolute address it builds, which the browser then blocks as mixed content.
Collect the blocked addresses
Open DevTools, reload and copy each mixed content message from the Console. Run curl on the page and search for http:// on your own domain. If the server output contains them, the application is the source rather than a cached copy or an extension.
Test what reaches the application
Create a short-lived route that prints the scheme, whether the request is secure, the X-Forwarded-Proto value and the remote address. Missing header means fix the proxy; header present but not secure means fix trust; http value means another layer is overwriting it.
Add forwarded headers at the proxy
In nginx set proxy_set_header X-Forwarded-Proto $scheme along with the host, port and client address headers. In Apache use RequestHeader set X-Forwarded-Proto https in the port 443 virtual host. Where two proxies are chained, pass the outer value through rather than replacing it.
Trust the proxy in Laravel
Laravel 11 and newer use trustProxies in bootstrap/app.php; older versions use the TrustProxies middleware. List the proxy addresses or private ranges. Only trust every address if the application port is closed to the public internet.
Fix background jobs and emails
Set APP_URL to the https address so queued mail and scheduled tasks build correct links. If the proxy is outside your control, URL::forceScheme('https') in production is an acceptable fallback, but it does not make the request itself count as secure.
Configure Keycloak
Start Keycloak with hostname set to the full https public address, proxy-headers set to xforwarded or forwarded, and http-enabled true when TLS ends at the proxy. Drop the old proxy edge option, which newer versions no longer accept.
Worked example
A Laravel app on port 8000 sits behind Plesk nginx. The test route shows X-Forwarded-Proto empty. Adding proxy_set_header X-Forwarded-Proto $scheme to the additional nginx directives makes the header arrive as https, and trusting 127.0.0.1 in bootstrap/app.php turns isSecure to true. Every stylesheet now loads over https and the Console is clean.
Checks and common mistakes
Fetch the Keycloak discovery document and confirm the issuer starts with https. Purge page and CDN caches after the change. Do not rely on upgrade-insecure-requests alone, and do not leave Cloudflare on Flexible SSL, which keeps the origin on http and often causes redirect loops.
Tip: Fix the proxy and trust first, then use forceScheme only if you must: an application that knows the real scheme also gets secure cookies and correct client addresses.
Tags: mixed-content, reverse-proxy, laravel, keycloak, https
Fix
Cookie banners that hide the reject option, pre-tick categories or nudge visitors towards Accept do not collect valid consent under UK GDPR and PECR. This card lists the tricks to remove and the checks that prove your banner offers a fair choice.
Know the standard
Non-essential cookies need consent that is freely given, specific, informed and given by a clear positive action. Saying no must be as easy as saying yes, and withdrawing must be as easy as agreeing in the first place.
Spot the effort trap
If accepting is one click and refusing means opening settings and switching things off, the design favours yes. Regulators treat a missing reject option on the first screen as one of the clearest problems.
Spot visual nudging
A filled, bright Accept button beside a grey text link for Reject steers people. Make every main choice the same size, shape and contrast so the labels carry the meaning rather than the styling.
Remove pre-ticked categories
Optional categories such as analytics and marketing must start off. Ticking them for the visitor is not consent. Also drop any legitimate interest switches for cookies and move wrongly labelled essential cookies to their real category.
Stop tags firing early
A well designed banner still fails if analytics loads on page view. Mark optional scripts as text/plain until consent, set Consent Mode defaults to denied and add consent conditions to every tag in Tag Manager.
Let people change their mind
Put a Cookie settings link in the footer of every page that reopens the choice. After a refusal, do not re-ask on every page; remember the answer for a sensible period, commonly six to twelve months.
Make it usable for everyone
Every button needs to be reachable by keyboard with a visible focus outline, and the banner should be announced to screen readers as a dialog with a heading. A banner some visitors cannot operate cannot gather their consent.
Worked example
Example Bakery's banner showed a large Accept all button and a small Manage link, with analytics on before any click. The redesign adds equal Accept all, Reject all and Choose cookies buttons, starts every category off, holds the analytics tag back until consent, and adds a footer link. A private window test now shows only the session and consent cookies after Reject all.
Checks to run
Open a private window with DevTools, note cookies and tracking requests before any click, then press Reject all and browse several pages. Run document.cookie in the console and look for _ga, _fbp or _clck names. Count the clicks for each choice and repeat after every new plugin or tag.
Tip: Style Accept and Reject with one shared class, so a future redesign cannot quietly make one more prominent than the other.
Tags: cookies, consent, dark-patterns, pecr, uk-gdpr
Fix
GDPR compliance for a small website comes down to knowing what personal data you hold, having a reason for each use, telling people plainly and keeping it safe. This card puts those jobs in order, from the data audit to rights requests and yearly reviews.
Find every source of personal data
Forms, checkout, accounts, newsletters, chat widgets, analytics, pixels, embeds and server logs all count. List the external hosts your pages contact with curl and the DevTools Network panel, then record each activity in one table.
Give each use a lawful basis
Most small sites use contract for orders, legal obligation for tax records, legitimate interests for enquiries and security logs, and consent for marketing and non-essential cookies. Decide before collecting and write the choice down.
Collect less
Drop form fields you never use and switch off features or tags nobody needs. Every field you remove is data you no longer have to explain, protect, retain or hand over when someone asks.
Tell people clearly
Write the privacy notice from your table: who you are, what you collect, why, the basis, recipients, transfers abroad, how long you keep it, rights and the right to complain. Link it from the footer and every form.
Get cookie consent right
Block analytics and advertising cookies until the visitor agrees, show Reject all beside Accept all, start optional categories switched off, and offer a footer link to change the choice later.
Sign processor terms
Accept and save the data processing agreement from your host, email service, form tool, payment provider and analytics service. Note where each stores data and which transfer safeguard applies.
Secure it and delete on schedule
Use https with HSTS, keep software updated, turn on two-factor sign-in and test backups. Automate deletion so retention periods actually happen, for example a scheduled job that removes enquiries older than 24 months.
Worked example
Example Bakery finds a contact form, a newsletter, an online order system, analytics and an embedded map. It removes the phone field from the enquiry form, delays the map and analytics until consent, accepts four processor agreements, writes a notice from its table and sets enquiries to delete after two years. The whole job takes an afternoon.
Checks and common mistakes
Common slips are a template privacy notice that mentions tools you do not use, analytics firing before consent, no processor agreement with the email service, and nobody recognising a subject access request in the inbox. Recheck external hosts every six months and confirm the ICO fee is paid.
Tip: Build the data table first and write everything else from it: the privacy notice, the cookie categories and the retention jobs then all agree with each other.
Tags: gdpr, uk-gdpr, privacy, compliance, checklist
Fix
GDPR covers people in the UK and EU, the CCPA covers larger businesses with California customers, and HIPAA covers US healthcare organisations and their suppliers. This card helps you decide which apply and lists the website changes each one needs.
Ask who each law protects
GDPR protects people in the EU, and UK GDPR people in the UK. The CCPA protects California residents. HIPAA protects patients' health information held within the US healthcare system. The question is never one or the other: each depends on different facts about your business.
Check GDPR scope
GDPR applies if you are established in the UK or EU, or if you offer goods or services to people there or monitor them online. Local pricing, shipping or ads aimed at Europe, and tracking cookies on European visitors, all point towards being in scope.
Check the CCPA thresholds
The CCPA applies to for-profit businesses in California with revenue over the inflation-adjusted $25 million, data on 100,000 or more Californian consumers or households, or half of revenue from selling or sharing data. Meet one and you are in.
Check whether HIPAA applies
Only healthcare providers that bill electronically, health plans, clearinghouses and their business associates are covered. A general wellness site is usually outside HIPAA, though other rules on health data may still apply.
GDPR changes on the site
Block non-essential cookies until consent with equal Accept and Reject buttons, publish a notice with lawful bases and retention, use unticked marketing boxes, and accept processor agreements with your providers.
CCPA changes on the site
Where you sell or share data, add a Do Not Sell or Share link or a Your Privacy Choices link, honour Global Privacy Control signals, add the California disclosures to your policy and offer request methods.
HIPAA changes on the site
Keep advertising and analytics tags off patient portals and appointment pages unless the vendor signs a business associate agreement, send health form data into covered systems, and get agreements from hosts and form providers.
Worked example
A US physiotherapy clinic bills insurers, has European visitors from its blog and sells nothing. HIPAA applies, so it removes the advertising pixel from its booking page. Its blog does not target Europe, so it decides GDPR is unlikely to apply and records why. Its revenue is under the CCPA threshold, so it notes that too.
Checks and common mistakes
Do not assume a US base keeps GDPR away, or that health data always means HIPAA. Check trackers in the DevTools Network panel while signed in, test Global Privacy Control with a supporting browser, and write down each scope decision with its date.
Tip: Build your setup to the strictest law that applies, then add the CCPA opt-out link and Global Privacy Control handling on top; one setup is easier to maintain than several.
Tags: gdpr, ccpa, hipaa, privacy-law, compliance
Fix
Scanners catch only part of WCAG. This card shows how to test a small site against WCAG 2.1 and 2.2 AA: choose a page sample, scan it, check keyboard, screen reader, zoom and contrast by hand, then log each issue against its criterion.
Choose the standard
Test to WCAG 2.2 level AA unless a contract asks for 2.1. The versions build on each other, so passing 2.2 AA also covers 2.1 AA and 2.0 AA, with 4.1.1 Parsing the only criterion dropped along the way.
Pick a page sample
Cover every template and every step of your key tasks, such as contact, sign-up and checkout, plus pages with video, tables or maps and a few at random. Ten to twenty-five pages is typical for a small business site.
Scan automatically
Run axe DevTools, WAVE or Lighthouse on each page, or test the whole list with the axe command line tool and WCAG A and AA tags, or Pa11y with the WCAG2AA standard. Confirm each finding by inspecting the element before you log it.
Use only the keyboard
Tab through every page and complete each task with Enter, Space, arrows and Escape. Look for controls you cannot reach, focus you cannot see, focus hidden under sticky headers, and dialogs that trap you.
Listen with a screen reader
Use NVDA on Windows or VoiceOver on Mac or iPhone. Move by headings and landmarks and fill in forms. Listen for unnamed buttons, unlabelled fields, missing or unhelpful image descriptions and errors that are never announced.
Zoom and respace
Check text at 200% zoom, then reflow at 1280 pixels wide and 400% zoom with no sideways scrolling. Apply the WCAG text spacing values and make sure no text is clipped, hidden or overlapping.
Check contrast, media and forms
Body text needs 4.5 to 1 contrast, large text, icons and borders 3 to 1. Do not signal errors or links by hue alone. Videos need captions, forms need visible labels and clear errors, and moving content needs a pause control.
Worked example
Example Bakery's axe scan of 12 pages shows two unlabelled fields and low contrast on a footer. The keyboard test then finds the delivery date picker cannot be opened without a mouse, and VoiceOver reads the basket icon as button. None of those last two showed in the scan, and the date picker blocked checkout entirely, so it is fixed first.
Checks and common mistakes
Do not treat a Lighthouse score of 100 as compliance, rely on an overlay widget, or skip third-party embeds and PDFs. Log every issue with page, criterion, level and severity, retest each fix the same way it was found, and add the automated scan to your releases.
Tip: Do the keyboard test before anything else on a new page; it takes five minutes and finds the problems that most often stop people completing a task.
Tags: wcag, accessibility, testing, screen-reader, keyboard
Fix
WCAG is the international accessibility standard and 2.2 is its current version. This card sets out what the guidelines cover, what the three levels mean, what 2.2 added, which criteria matter most for a business website and how to work through them.
Know what WCAG covers
WCAG describes outcomes for disabled visitors, grouped as perceivable, operable, understandable and robust. It covers every page, form, document and embedded tool a visitor uses on your site, including third-party widgets and cookie banners.
Use the current version
WCAG 2.2, published in October 2023, is current. It keeps everything from 2.1 except the obsolete Parsing criterion, so meeting 2.2 also meets the older versions at the same level. WCAG 3 is still a draft and is not a target yet.
Understand the levels
Level A removes complete blockers, AA adds the criteria laws and contracts ask for, and AAA is the strictest level that few whole sites can reach. Levels stack, so AA means passing all 55 A and AA criteria on every page of a process.
Learn the 2.2 additions
New at AA: focus must not be fully hidden by sticky bars, targets need to be 24 by 24 CSS pixels or well spaced, drag actions need a click alternative, and logins must not rely on memory puzzles. New at A: help in a consistent place and no repeated data entry.
Pick your target and priority pages
Aim for 2.2 AA. List the pages that earn you money or contact: home, key services or products, contact and booking forms, checkout and login. Work through those first, because a single broken step fails the whole process.
Scan each template
Run an automated audit on one page per template and group faults by criterion. Typical finds are missing alt attributes, unlabelled inputs, icon buttons with no name, low contrast text and a missing lang attribute. Fixing the template fixes every page built on it.
Test by hand
Tab through each key page without a mouse, zoom to 200 and 400 percent, and listen to the headings and links with NVDA or VoiceOver. Submit forms empty to see how errors are reported. Manual checks catch the majority of issues no scanner can judge.
Worked example
Example Bakery's order form passes Lighthouse but a keyboard test shows the date picker cannot be reached and the sticky basket bar hides the submit button when focused. Adding a plain date input with a format hint, and scroll padding equal to the bar height, fixes 2.1.1 and 2.4.11 in one afternoon.
Common mistakes
Treating a perfect automated score as compliance, installing an overlay toolbar instead of fixing templates, using red borders as the only error signal, removing focus outlines for looks, and forgetting PDFs and third-party widgets. Recheck after every theme or plugin update.
Tip: Write a short accessibility statement naming WCAG 2.2 AA as your target and listing known gaps; it gives visitors a way to ask for help and keeps your own work on track.
Tags: accessibility, wcag, wcag-2-2, compliance, fix
Fix
Forms that flag mistakes with a red border alone leave many visitors stuck. This card covers what WCAG asks of form errors, the markup that links a message to its field, error summaries, native validation messages and a test routine.
Know the three criteria
Error Identification (A) needs the failed field named and the problem described in text. Error Suggestion (AA) needs the fix explained where you know it. Error Prevention (AA) needs a review, check or undo for orders, payments and account changes.
Test with an empty submit
Submit each form blank, then with wrong values, first by sight and then with a screen reader. Note whether errors are words or only colour, whether focus moves, and whether earlier answers are kept. That tells you exactly what each form is missing.
Give hints before errors
A visible label plus a short format hint, such as the date pattern or postcode style, prevents many mistakes. Give the hint an id so it can be read with the field later.
Show the error as text
Place a message beside the failed field saying what to do, like Enter your postcode. Start it with a visually hidden Error: and add a thick border or icon so the state is clear without relying on red.
Connect it in code
Add the error id to the input's aria-describedby after the hint id, and set aria-invalid to true only while the error exists. Avoid aria-label for errors, because it overwrites the field name. aria-errormessage is not yet reliable enough on its own.
Summarise and move focus
On longer forms, show a There is a problem box above the form listing each error as a link to its field. Give it tabindex -1, focus it after a failed submit and prefix the page title with Error: so the failure is heard at once.
Handle native validation
Browser bubbles vanish quickly and show one error at a time. Reword them with setCustomValidity and clear it on input, or add novalidate and show your own persistent messages while still reading the validity object in your script.
Worked example
Example Bakery's order form outlined bad fields in red and nothing else. Adding hint text, inline messages linked by aria-describedby, aria-invalid, and a focused summary meant NVDA read 2 errors and each field's problem. Keeping answers after the error also satisfied the WCAG 2.2 Redundant Entry rule.
Common mistakes
Colour as the only signal, vague wording like Invalid input, aria-invalid set on page load, errors announced on every keystroke, live regions created at the moment of use, cleared forms after a failed submit, and paste blocked on password fields.
Tip: Select a failed field in the Chrome DevTools Accessibility pane: if the error text does not appear under Description, screen reader users cannot hear it.
Tags: accessibility, wcag, forms, aria, fix
Fix
An aria-label that is never read, never appears or stays in one language nearly always sits on the wrong element or clashes with another naming source. This card explains how accessible names are chosen and how to fix each case.
Know the naming order
Browsers name an element from aria-labelledby first, then aria-label, then native sources such as a label or alt, then its text for buttons and links, then title. aria-label replaces what comes after it in that list.
Understand it is invisible
aria-label is spoken, never drawn. It gives no tooltip and no visible text. If sighted users need to see a label, write it on the page; screen reader and visual names should match.
Spot the ignored placements
Plain div and span elements, paragraphs, bold and italic text, links without an href, and anything with role none are not allowed a name, so screen readers may drop the label. Labels inside aria-hidden areas vanish too.
Inspect the computed name
In the Chrome or Edge Accessibility pane, check Role and Name. A generic role, or a crossed-out aria-label beneath another source, tells you exactly why the label is not being used.
Use the right element
Turn clickable divs into real buttons, named areas into section, nav or form, and grouped options into a role of group with aria-labelledby. Extra words for a price or icon belong in visually hidden text inside the element.
Match visible button text
On buttons that show text, either drop aria-label or make it begin with the visible words. That satisfies Label in Name and lets voice control users say what they see. Keep aria-label for icon-only buttons and hide the icon.
Translate every label
Labels do not localise themselves. Run them through the theme or app translation function, prefer text that is translated with the page, and set the html lang attribute per language so screen readers pick the right voice.
Worked example
A shop's basket icon was a div with aria-label Basket that NVDA skipped and Narrator read only sometimes. Changing it to a button with aria-label Basket, 2 items, translated through the theme's t filter, and an aria-hidden SVG made every Windows and Mac screen reader announce it the same way.
Check it worked
Rerun a console listing of every aria-label and its element type, confirm none sit on generic elements, then Tab through with NVDA, Narrator and VoiceOver. Each control should say its role and name once, in the page language.
Tip: If the DevTools Accessibility pane shows the correct name, the browser has done its part; any remaining difference between screen readers usually means the label is on an element that should not carry one.
Tags: accessibility, aria, screen-readers, fix
Fix
A move to new hosting keeps its rankings when the domain and URLs stay the same and nothing breaks on the way. This card covers the inventory, DNS TTL, copying files and the database, SSL, testing first and checking crawling afterwards.
Separate it from a URL migration
Same domain and same URLs on a different server is a hosting move. Google needs no notice and no Change of Address. If URLs or the domain change too, you need a full migration plan with redirects.
List everything first
Write down files, databases, every DNS record, mailboxes, cron jobs, server rules such as redirects and headers, PHP version and certificates. Save a crawl of all URLs with status codes and response times as your baseline.
Shorten the TTL
A day or two ahead, cut the TTL on the A, AAAA and www records to 300 seconds and confirm with dig. When you switch, traffic then moves in minutes rather than over a day.
Build and copy
Match software versions on the new server, recreate redirects and headers, then pull files with rsync and the database with mysqldump using a single transaction. Shops need a final sync or short maintenance window so orders are not lost.
Have SSL ready
Copy the existing certificate or issue one through a DNS challenge so HTTPS is valid for the bare domain and www before any visitor arrives. Waiting to issue after the switch leaves a gap of browser warnings.
Test under the real domain
Use curl with the resolve option or a hosts file line to load the new server as example.com. Check pages, forms, images, old-URL redirects, robots.txt and sitemap, and compare status codes with the baseline crawl.
Switch and keep both running
Change the A and AAAA records, or the origin IP in a proxying CDN. Leave the old server serving identical content until its log shows traffic has stopped, which keeps downtime at zero.
Worked example
Example Bakery moved from shared hosting to a VPS. Testing with curl showed old menu URLs returning 404 because the .htaccess redirects did not apply under nginx. Rewriting them as nginx rules before the switch kept every old link working, and Crawl stats showed no errors in the following fortnight.
Commands to check it worked
Run dig against 8.8.8.8 and 1.1.1.1 for the new IP, grep the new access log for Googlebot and count status codes, verify bot IPs with host, and review Crawl stats host status and response time in Search Console for a few weeks.
Tip: Look for an AAAA record before the switch: updating only the IPv4 address leaves IPv6 visitors and crawlers on the old server.
Tags: migration, hosting, dns, technical-seo, fix
Fix
A quiet Shopify store usually has one of three problems: Google cannot see it, it has nothing worth ranking, or visitors arrive but do not trust or understand the offer. This card walks through the checks from the biggest blocker down.
Tell traffic from sales
Open Shopify Analytics and read sessions, referrers and the conversion breakdown for 30 days. Few sessions is a visibility problem; plenty of sessions with few carts or checkouts is a buying problem. Remove obvious bot spikes before judging.
Remove the password page
New stores are locked by default. Untick the visitor password under Online Store, Preferences. On a trial you must choose a paid plan first. Confirm in a private window and with curl that the homepage no longer redirects to /password.
Connect Search Console
Verify the primary domain with a DNS TXT record or the theme meta tag, then submit /sitemap.xml. Make sure the branded domain is primary so the myshopify.com address redirects to it.
Check indexing properly
Run URL Inspection on the homepage, key collections and best sellers instead of trusting a site: search. Fix any noindex or robots block, request indexing for the main pages and follow the Pages report over the coming weeks.
Write titles people search
In each Search engine listing, lead the title with the words a shopper would type, then the brand, in about 60 characters. Give the meta description a plain summary and a reason to click.
Make product and collection pages unique
Copied supplier text competes with every other seller. Rewrite descriptions with sizes, materials, uses, contents and delivery times, add your own photos, and give each collection an introduction and useful text.
Speed and trust
Remove unused apps and the code they leave behind, then retest a product page on mobile. Show delivery costs and times early, complete the refund and shipping policies, and add a real contact page and about page.
Worked example
Example Bakery's kit shop had 90 sessions a month and no orders. The password was off, but the sitemap had never been submitted and every product used the supplier's text. After submitting it, rewriting the top ten products and adding delivery costs to product pages, impressions climbed over two months and the first orders followed.
Common mistakes
Leaving the password on after launch, judging indexing by site: searches, creating many thin tag pages, keeping supplier descriptions, adding app after app, hiding delivery costs until checkout, and expecting a new domain to rank within a few weeks.
Tip: Before spending on ads, check the cart to checkout stage of your conversion breakdown; if most shoppers leave there, more traffic will only multiply the losses.
Tags: shopify, ecommerce, indexing, conversion, fix
Fix
WordPress is a sound base for SEO, but a new or migrated site often ships with hidden pages, unreadable addresses, thin archives and a slow theme. This card walks through the checks and changes, in order, that get a WordPress site or blog properly crawled and ranked.
Check the visibility box first
Under Settings, Reading, the option to discourage search engines puts a noindex instruction on every page. It is often left on after a site moves from staging. Untick it, save, and clear any page or CDN cache so Google stops seeing the old tag.
Use readable permalinks
Plain addresses such as ?p=123 tell searchers nothing. Pick Post name under Settings, Permalinks. WordPress sends old plain links on to the new addresses by itself, but moving between two readable structures on an older site needs a full set of 301 redirects.
Pick a single SEO plugin
Core WordPress writes a basic title and sitemap. A plugin adds fields for custom titles and descriptions, archive controls and business details. Choose one and run its wizard. Two plugins together tend to print duplicate tags in the head of every page.
Rewrite titles and descriptions
Begin with the homepage, the main service pages and the posts that already get visits. Lead each title with the phrase people search for and keep it near 55 characters. Give each description a clear promise of what the reader will find, at around 155 characters.
Thin out low-value archives
Tag pages holding one post, date archives and author archives on a one-person blog add little. Set them to noindex in the plugin or merge rarely used tags. Keep category pages indexed and give each a short introduction so it works as a topic hub.
Submit one sitemap
Find out whether the live sitemap is the plugin index or the core wp-sitemap.xml, open it to make sure it lists real posts and pages, then submit that address in Search Console and delete any stale sitemap entries that fail.
Link posts together and speed up
Point related posts at a hub page and at each other with descriptive anchors, so nothing relies on deep blog pagination. Then add full-page caching, serve WebP or AVIF images, drop unused plugins and stop lazy-loading the main image at the top.
Worked example
A bakery blog moved from a staging subdomain and had eleven pages indexed after three months. WP-CLI showed blog_public set to 0, permalinks were plain and there were 340 tag pages for 90 posts. Turning visibility on, switching to Post name, noindexing tags and swapping a five-image slider for one hero took an afternoon, and indexed pages rose to 96 within six weeks.
Commands to check it worked
Run curl -s on a post and grep for the title, robots, canonical and description lines. Use wp option get blog_public (it should print 1) and wp option get permalink_structure. Then inspect the homepage in Search Console and confirm Test live URL says the page can be indexed.
Tip: After any migration or theme change, fetch one post with curl and look for noindex before you do anything else; it takes ten seconds and catches the most costly WordPress mistake.
Tags: wordpress, site-audit, permalinks, sitemaps, fix
Fix
The main WordPress SEO plugins write much the same tags into your pages, so the choice is about extras and ease of use. This card shows what they change, how to pick one, which settings to check and how to make sure two plugins are not printing duplicate tags.
Know what the plugin controls
Underneath the scores, an SEO plugin writes the title, meta description, canonical and robots tags, a sitemap index, a JSON-LD graph and social preview tags. The traffic-light analysis is only a writing prompt and is never seen by Google.
Compare the free versions
Yoast, Rank Math and All in One SEO all cover the core tags for free. Rank Math includes a redirect manager and per-page schema choice without charge, while Yoast and AIOSEO keep redirects for paid tiers. Feature lists change, so check them on the day you choose.
Match the plugin to the site
A small business site or new blog does well with Yoast or AIOSEO defaults. Pick Rank Math if you will use its modules. Developers may prefer a minimal option. If one is already set up and the site ranks, staying put is usually the right call.
Run the set-up wizard
Tell the plugin whether the site stands for a business or a person, add the name and logo, choose which content types appear in search, and accept any offer to import data from a previous plugin.
Set archives and attachments
Leave posts, pages, products and categories indexable. Mark tag, date and single-author archives noindex, and send attachment pages to the file or parent post so they do not exist as thin pages.
Remove the second plugin
If another SEO plugin or a theme SEO option is still on, check a sample of pages carried over correctly, then deactivate it and purge caches. Keep the old plugin inactive for a few weeks in case you need to import again.
Submit the new sitemap
Yoast and Rank Math publish /sitemap_index.xml, while AIOSEO uses /sitemap.xml. Submit the live one in Search Console and delete any sitemap left over from an earlier plugin or from core WordPress.
Worked example
A shop moved from AIOSEO to Rank Math but left the old plugin active. Product pages carried two description tags and two schema graphs, and Search Console held two sitemaps, one still listing noindexed tags. Deactivating AIOSEO, purging the cache and dropping the old sitemap cleared the canonical warnings within a month.
Commands to check it worked
Fetch a post with curl and count the matches for <title, name="description" and rel="canonical": each should be 1. Grep for yoast-schema-graph, rank-math-schema and aioseo-schema to see which plugin writes schema. Run wp plugin list --status=active to spot any leftover SEO plugin.
Tip: Before switching plugins, export a list of your top pages with their current titles and descriptions; comparing that list after the import is the quickest way to prove nothing was lost.
Tags: wordpress, seo-plugin, yoast, rank-math, fix
Fix
Yoast reports error parsing the response to JSON when its background REST API request gets something other than clean JSON back. This card shows how to see that reply, trace it to PHP output, a firewall, caching or limits, and rebuild Yoast's data afterwards.
Understand the message
Yoast builds and updates its indexables through requests to /wp-json/yoast/v1/. When a reply carries text, HTML or nothing at all instead of JSON, the parse fails. The plugin reporting the problem is usually not the one causing it.
Look at the reply itself
In Chrome DevTools, open Network, filter by yoast and repeat the action. The failing request's status, content type and response body tell you whether to look at PHP, a security rule, a redirect or the server.
Test the REST API with curl
Request /wp-json/ and /wp-json/yoast/v1 and print the status and content type. Healthy output is 200 application/json. Tools, Site Health runs a similar test and names a REST API failure if there is one.
Log notices, do not show them
Set WP_DEBUG and WP_DEBUG_LOG to true and WP_DEBUG_DISPLAY to false in wp-config.php. Notices stop leaking into replies, and debug.log names the file behind any warning raised during the failed run.
Clear stray output
A blank line before the opening PHP tag, a closing tag at the end of functions.php, or a byte order mark adds characters ahead of the JSON. The same fault causes the XML declaration error on Yoast sitemaps.
Unblock the right rule
For 401 or 403 replies, check the security plugin log, the ModSecurity audit log on Plesk and Cloudflare Security Events. Except the one rule hitting wp-json, or allow logged-in administrators, instead of switching protection off.
Fix caching, addresses and limits
Keep /wp-json/ and /wp-admin/ out of every cache. Make the two address fields under Settings, General identical. For 500 or empty replies, raise PHP memory to 256M and execution time to 120 seconds.
Worked example
After a new security plugin went on, Yoast's optimisation failed at 4 per cent. DevTools showed a 403 HTML page, yet curl on /wp-json/ returned clean JSON. The plugin's rate limit on REST calls was tripping on the burst of indexing requests; raising it for administrators let the run finish.
Rebuild and confirm
Once replies are clean, rerun the optimisation in Yoast SEO, Tools, or run wp yoast index on the server, adding --reindex if earlier attempts left gaps. Back up first, then check the editor panel loads and posts save normally.
Tip: Read the failing response before changing any setting; the status code and first line of the reply usually name the culprit within a minute.
Tags: yoast, wordpress, rest-api, json, fix
Fix
SEO in Next.js comes down to sending complete HTML with the right tags and status code on the first request. This card covers how rendering choices affect crawling, the Metadata API and next/head, sitemaps, robots, JSON-LD and the curl checks that prove it.
Know what counts
Crawlers judge the raw HTML, status code, links and speed of each URL. Google renders JavaScript later in a queue, and many other bots, including most AI crawlers, never do, so content must be present in the first response.
Fetch content on the server
Server Components, static generation and getServerSideProps all put content in the HTML. A Client Component is still server-rendered; the problem is loading data in useEffect, which leaves only a skeleton for crawlers.
Set layout defaults
In the root layout export metadata with metadataBase, a default title, a title template and a description. metadataBase makes canonical and image addresses absolute, so they never point at localhost.
Give each route its own tags
Use generateMetadata in dynamic routes to return a unique title, a description and alternates.canonical from the clean path. In the Pages Router the same tags go inside next/head, or a wrapper such as next-seo.
Return honest status codes
Call notFound() for missing records before any loading boundary starts streaming, because the status cannot change after that. Permanent redirects in next.config.js send 308, which search engines handle like a 301.
Add sitemap.ts and robots.ts
These files in the app folder generate /sitemap.xml and /robots.txt. Build the sitemap from the same data as the pages, leave out redirected or noindexed URLs and submit it in Search Console.
Render JSON-LD and real links
Output structured data in a script tag of type application/ld+json, escaping the less-than sign. Navigate with next/link so pages are joined by real anchors, and give the hero next/image priority loading.
Worked example
A bakery shop rebuilt in Next.js had most products stuck as crawled but not indexed, all sharing one title. Its product page fetched data in useEffect, so curl found no product name. Moving the fetch into the server page, adding generateMetadata, notFound() and sitemap.ts brought the names and canonicals into the HTML, and indexing climbed over the next month.
Commands to check it worked
Run curl -s on a page and grep for the heading text, title, canonical and robots tags. Run curl -I on a made-up address and expect 404, and on a moved one and expect 308. Then use URL Inspection with Test live URL to view the HTML Google renders.
Tip: Test a production build, not the development server: next dev renders differently and can hide problems that only appear once the site is built and deployed.
Tags: nextjs, javascript, metadata, rendering, fix