Check in this order: is the drop real (analytics versus Search Console), did a deploy break indexing (noindex, robots.txt, canonical, status codes), does Search Console show a new Page indexing or manual-action message, is Google reporting an incident or update, and only then which queries and pages actually lost clicks. Most causes are ruled in or out within thirty minutes.

Key takeaways

  • Confirm the drop is real first. A broken analytics tag looks exactly like lost traffic. Compare against Search Console clicks, which do not depend on your tag.
  • Look at what you shipped before looking at Google. A stray noindex, a robots.txt change or a canonical pointing at staging explains a sudden, sitewide drop far more often than an algorithm change does.
  • Search Console tells you about the loud causes. Manual actions and security issues have their own reports; a spike in excluded pages shows up in Page indexing.
  • Google publishes a status page. Ranking and indexing incidents and core updates are listed there, and Google says a core update can take around two weeks to roll out, so a drop mid-rollout is not final.
  • Segment before concluding. One page, one query group, one country or one device losing clicks is a different problem from everything falling together.

1. Is the drop real? (2 minutes)

Open Search Console, Performance, and compare the last 7 days to the previous 7. If clicks are flat there but analytics fell, your tracking broke, not your rankings. Also check the shape: a drop that starts at an exact date and stays flat points to something that changed on that date, such as a deploy. A gradual slide points elsewhere, usually toward content or competition.

2. What did you ship? (10 minutes)

Look at deploys from the day before the drop. The failures that hide whole sites from Google are boring:

  • A noindex meta tag or an X-Robots-Tag: noindex header left over from staging.
  • A robots.txt that now disallows the site, or a robots.txt that errors (Google documents that a 5xx on robots.txt can pause crawling).
  • Canonical tags pointing at another host or a staging domain.
  • Key pages returning 404, 500 or a redirect loop.

Checking by eye across a few hundred URLs is slow, so I use a script. It reads a list of URLs and reports each problem it finds, using Google's own robots.txt rules for Googlebot.

The checker script

Put your top landing pages (from the Performance report) in a text file, one URL per line:

import re, sys, urllib.request, urllib.robotparser, urllib.parse, urllib.error

UA = 'launch-check/1.0'

def fetch(url):
    req = urllib.request.Request(url, headers={'User-Agent': UA})
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            return r.status, r.geturl(), r.headers, r.read().decode('utf-8', 'replace')
    except urllib.error.HTTPError as e:
        return e.code, url, e.headers, ''

robots = {}
def allowed(url):
    root = '{0.scheme}://{0.netloc}'.format(urllib.parse.urlparse(url))
    if root not in robots:
        rp = urllib.robotparser.RobotFileParser()
        code, _, _, body = fetch(root + '/robots.txt')
        # 4xx (no file) means allow all; 5xx means treat as unreachable, so this script treats both as allow-all
        rp.parse(body.splitlines() if code == 200 else [])
        robots[root] = rp
    return robots[root].can_fetch('Googlebot', url)

for url in (l.strip() for l in open(sys.argv[1]) if l.strip() and not l.startswith('#')):
    status, final, headers, html = fetch(url)
    issues = []
    if status != 200:
        issues.append('returns %s' % status)
    if 'noindex' in (headers.get('X-Robots-Tag') or '').lower():
        issues.append('X-Robots-Tag header says noindex')
    for m in re.finditer(r'<meta[^>]+name=["\'](?:robots|googlebot)["\'][^>]*content=["\']([^"\']*)', html, re.I):
        if 'noindex' in m.group(1).lower():
            issues.append('meta robots says noindex')
    c = re.search(r'<link[^>]+rel=["\']canonical["\'][^>]*href=["\']([^"\']+)', html, re.I)
    if not c:
        issues.append('no canonical')
    elif urllib.parse.urljoin(final, c.group(1)).rstrip('/') != final.rstrip('/'):
        issues.append('canonical points to %s' % urllib.parse.urljoin(final, c.group(1)))
    if not re.search(r'<title>\s*\S', html, re.I):
        issues.append('empty or missing title')
    if not allowed(final):
        issues.append('blocked for Googlebot by robots.txt')
    print(('FIX  ' if issues else 'ok   ') + url)
    for i in issues:
        print('       - ' + i)
Tested against a local server with one healthy page and five deliberately broken ones.
Planted failureResultStatus
Healthy pageokMeasured
noindex in a meta tagFlaggedMeasured
noindex in an X-Robots-Tag headerFlaggedMeasured
URL disallowed in robots.txtFlaggedMeasured
Canonical pointing at another hostFlaggedMeasured
404 page with no title or canonicalFlagged (three issues)Measured

Run on this site's live URLs it reported something real: pages whose canonical points at an .html address that redirects, which is the exact conflict covered in Google picked a different canonical. It also reported one false alarm of my own making, a mistyped URL that returned 404, which shows why it is worth reading the output before reacting. An early version also flagged every page as blocked, because Python's robots parser treats a 403 on robots.txt as "disallow everything"; the script now fetches robots.txt itself.

3. Search Console messages (5 minutes)

Check Security & Manual actions for both reports, then Pages (Page indexing) for a jump in "Excluded" or "Not indexed" reasons. If the count of indexed pages fell on the same date as the traffic, you have found the shape of the problem. The reasons it lists are explained in Discovered, currently not indexed and Crawled, currently not indexed.

4. Is Google reporting something? (3 minutes)

Google keeps a Search Status Dashboard listing ranking and indexing incidents and update rollouts with start and end dates. If your drop coincides with an open core update, Google's guidance is to wait until the rollout finishes before judging, then compare performance to before the update. Nothing in that guidance promises a quick recovery or a specific fix, so be wary of anyone who does.

5. Which queries and pages lost clicks? (10 minutes)

In Performance, compare the two periods and sort by click difference. Then ask: are the losses concentrated on a handful of pages, one folder, one query type, one device? Concentrated losses point at a page-level or template problem. A uniform drop across everything, with impressions falling too, points at indexing or site-level trust. If impressions held but clicks fell, the pages still rank and something changed in how they appear, which is the territory of low-CTR pages and title rewrites.

What this does not cover

This is a triage order, not a diagnosis. It narrows the cause; it does not prove it. A core update can reduce traffic with no technical fault on your side, and the Search Status Dashboard and Search Console cannot tell you which content was judged weaker. The script checks four technical signals on the URLs you give it and nothing else; it does not render JavaScript, so a noindex injected by script will not be caught. The live run is a snapshot of one day.

Badri Dutta

Software engineer · 8 years technical SEO

Written after building the checker for this site's own launches and watching it flag a real canonical conflict on the live pages. It lists what I would look at, in the order I would look at it.

Full background →