<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Justice Manyika</title>
    <description>The latest articles on DEV Community by Justice Manyika (@justicemanyika).</description>
    <link>https://dev.to/justicemanyika</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4237920%2F77ececf0-e198-4c80-93b0-faa9547fc7c2.jpg</url>
      <title>DEV Community: Justice Manyika</title>
      <link>https://dev.to/justicemanyika</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/justicemanyika"/>
    <language>en</language>
    <item>
      <title>336 mirrored pages appeared on my store overnight - diagnosis and rollback</title>
      <dc:creator>Justice Manyika</dc:creator>
      <pubDate>Tue, 10 Nov 2026 22:05:48 +0000</pubDate>
      <link>https://dev.to/justicemanyika/336-mirrored-pages-appeared-on-my-store-overnight-diagnosis-and-rollback-1ieh</link>
      <guid>https://dev.to/justicemanyika/336-mirrored-pages-appeared-on-my-store-overnight-diagnosis-and-rollback-1ieh</guid>
      <description>&lt;p&gt;I opened my WooCommerce store one morning and it was in Spanish. All of it. Three hundred and thirty-six translated product pages that I had never written, indexed under my domain, with my content and someone else's links.&lt;/p&gt;

&lt;p&gt;This is what that looked like under the hood and how I rolled it back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;Search Console started reporting a spike in indexed URLs for the domain. Not a few. Hundreds of new paths like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/es/producto/some-product-name/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pages were real. They returned 200. They carried my product titles machine-translated, my images hotlinked, and at the bottom, links to a domain I had never heard of.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a site gets cloned
&lt;/h2&gt;

&lt;p&gt;Three mechanisms cover almost every case, and they look identical from the outside:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An affiliate or content plugin with a translation feature.&lt;/strong&gt; It registers a rewrite that serves translated copies of your own pages, usually under a language prefix, and appends its own tracking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A scraper with a fast index.&lt;/strong&gt; A third party mirrors your pages on their server. Your HTML is not on your site at all; what you see in Search Console is their domain, not yours. Check the URL prefix before panicking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A compromised theme or plugin file.&lt;/strong&gt; Rare, but it produces the same rewrite rules.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Mine was the first: an installed plugin quietly added a multilingual rewrite and my permalinks were being regenerated with a prefix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 60-second diagnosis
&lt;/h2&gt;

&lt;p&gt;Ask the site itself which rules are answering those requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp rewrite list --format=csv --path=/var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And ask WordPress whether the prefix corresponds to a real post type, taxonomy or language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp post list --post_type=page --fields=ID,post_name --path=/var/www/html &lt;span class="o"&gt;|&lt;/span&gt; grep -i &lt;span class="s2"&gt;"es$"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the URLs return 200 but there is no corresponding content, the route is being generated, not stored. That tells you to look at plugins and rewrite rules rather than at your database.&lt;/p&gt;

&lt;p&gt;A faster check that requires no shell access: request one of the mirrored URLs with a cache-busting query, and look at the response headers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -sI &lt;span class="s2"&gt;"https://example.com/es/producto/some-product/"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; grep -i -E &lt;span class="s2"&gt;"x-|server|location"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A plugin-generated route usually still emits your caching headers, because the response is generated by your own stack. A scraper's copy does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling it back
&lt;/h2&gt;

&lt;p&gt;The order matters, because each step changes what the next one sees:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Disable the plugin generating the rewrite.&lt;/strong&gt; Do this first. Removing URLs while the route still exists means they reappear on the next request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flush rewrite rules.&lt;/strong&gt; In WP-CLI, &lt;code&gt;wp rewrite flush --hard&lt;/code&gt; regenerates permalinks without the prefix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purge the page cache.&lt;/strong&gt; Otherwise your cache keeps serving the translated HTML from stale entries and you will think the rollback failed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify with curl, not a browser.&lt;/strong&gt; Browsers hold their own cache. Request ten of the mirrored URLs and confirm 404 or 410.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask Google to remove them.&lt;/strong&gt; In Search Console, the Removals tool takes a prefix and drops those URLs from results temporarily while the 404s propagate.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; u &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$(&lt;/span&gt;cat mirrored_urls.txt&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s "&lt;/span&gt; &lt;span class="s2"&gt;"$(curl -s -o /dev/null -w '%{http_code}' -A 'Mozilla/5.0' "&lt;/span&gt;&lt;span class="nv"&gt;$u&lt;/span&gt;&lt;span class="s2"&gt;")"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"$u"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want a clean column of 404s or 410s with no 200s left.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would check weekly
&lt;/h2&gt;

&lt;p&gt;A clone does not announce itself. What surfaces it is a change in indexed URL count for a prefix you do not recognise, so watch that number instead of your total. In my case the count went from my real catalogue to roughly double it within a week.&lt;/p&gt;

&lt;p&gt;Also check the referrer and outbound link report. Mirrors almost always carry a footer link to the operator's domain, and that is the fastest fingerprint available.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable part
&lt;/h2&gt;

&lt;p&gt;Nothing was hacked in the sense of a stolen password. A feature I installed did exactly what it was configured to do, and the configuration was mine, months earlier, forgotten. The rollback took twenty minutes. Finding it took a week, mostly because I kept checking whether the site was broken for visitors, and it never was.&lt;/p&gt;

&lt;p&gt;Originally published on my diary: &lt;a href="https://www.vitaladge.site/2026/10/the-night-site-spoke-spanish-waking-up.html" rel="noopener noreferrer"&gt;The Night the Site Spoke Spanish: Waking Up to 336 Mirrors I Never Built&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>The dead sitemap: how one July click kept a site invisible for three months</title>
      <dc:creator>Justice Manyika</dc:creator>
      <pubDate>Tue, 10 Nov 2026 22:00:21 +0000</pubDate>
      <link>https://dev.to/justicemanyika/the-dead-sitemap-how-one-july-click-kept-a-site-invisible-for-three-months-34lg</link>
      <guid>https://dev.to/justicemanyika/the-dead-sitemap-how-one-july-click-kept-a-site-invisible-for-three-months-34lg</guid>
      <description>&lt;p&gt;Search Console accepted my sitemap, listed it as Success, and told me nothing for three months. That combination is possible, and it cost me every new post I published from July to October.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Success" actually means
&lt;/h2&gt;

&lt;p&gt;A submitted sitemap shows Success when Google has fetched the file. It says nothing about whether the URLs inside it were queued, crawled or indexed. Mine read Success with 0 discovered pages for weeks. I looked at the word Success and moved on.&lt;/p&gt;

&lt;p&gt;The number that mattered was the discovered count next to it. Zero meant the file was fetched and then produced nothing usable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The original mistake
&lt;/h2&gt;

&lt;p&gt;In July I did something that seemed harmless: I edited the sitemap through a plugin's URL field and saved a value with a stray whitespace character at the end. The sitemap endpoint answered with a 200 and a body that was not valid XML.&lt;/p&gt;

&lt;p&gt;Diagnosis in one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -s https://example.com/sitemap.xml &lt;span class="o"&gt;|&lt;/span&gt; head -3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Valid output begins with &lt;code&gt;&amp;lt;?xml&lt;/code&gt;. Mine began with a blank line before the declaration, which is fatal: XML declarations must be the very first bytes in the document. Browsers do not care. Parsers do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the UI cannot tell you this
&lt;/h2&gt;

&lt;p&gt;Search Console has no concept of "your sitemap is well-formed but semantically wrong". It fetched something, got a 200, and marked the row Success. The pages inside were never read because the document never parsed.&lt;/p&gt;

&lt;p&gt;The read that finally surfaced it was the per-URL inspection: every new post showed "URL is unknown to Google", and the discovery column said sitemap. Unknown, from a sitemap listed as Success.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix, and the verification that matters
&lt;/h2&gt;

&lt;p&gt;Remove whatever is generating the file and serve a real one, then verify the bytes rather than the dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -s https://example.com/sitemap.xml \
  &lt;span class="o"&gt;|&lt;/span&gt; python3 -c &lt;span class="s2"&gt;"import sys,xml.dom.minidom as m; m.parseString(sys.stdin.read()); print('valid xml')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then count the URLs and compare against your post count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -s https://example.com/sitemap.xml &lt;span class="o"&gt;|&lt;/span&gt; grep -o &lt;span class="s1"&gt;'&amp;lt;loc&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; wc -l
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If those two numbers disagree, the sitemap is not doing its job regardless of what the dashboard says.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed afterwards
&lt;/h2&gt;

&lt;p&gt;After serving a valid file, the sitemap report moved from 0 discovered to 62 discovered pages within a day, and new posts started appearing in the per-URL inspection as "Discovered" rather than unknown.&lt;/p&gt;

&lt;p&gt;It did not fix indexing on its own. Crawl stats still showed ~1 request per day, and pages sat in the queue. But it moved the problem from "Google cannot read my list" to "Google has my list and is choosing slowly", which is a solvable problem. Before that, nothing downstream could work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson I keep relearning
&lt;/h2&gt;

&lt;p&gt;Dashboards report their own success criteria, not yours. Success on a sitemap row means "fetched". Success on a page row means "indexed". They are different claims and they are one click apart.&lt;/p&gt;

&lt;p&gt;Verify the artefact, not the status. Especially the boring ones.&lt;/p&gt;

&lt;p&gt;Originally published on my diary: &lt;a href="https://www.vitaladge.site/2026/10/the-dead-sitemap-how-one-july-click.html" rel="noopener noreferrer"&gt;The Dead Sitemap: How One July Click Kept My Site Invisible for Months&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Twelve of Sixty-One - the three measurements that showed why my posts were invisible</title>
      <dc:creator>Justice Manyika</dc:creator>
      <pubDate>Tue, 10 Nov 2026 21:47:13 +0000</pubDate>
      <link>https://dev.to/justicemanyika/twelve-of-sixty-one-the-three-measurements-that-showed-why-my-posts-were-invisible-529i</link>
      <guid>https://dev.to/justicemanyika/twelve-of-sixty-one-the-three-measurements-that-showed-why-my-posts-were-invisible-529i</guid>
      <description>&lt;p&gt;I published 61 posts before I checked how many of them Google actually showed to anyone. The answer was twelve. Not because the writing was bad, but because the site had one inbound link and no path between its own pages.&lt;/p&gt;

&lt;p&gt;This is how I found that out, and the three measurements that made the problem obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Count what search shows, not what you published
&lt;/h2&gt;

&lt;p&gt;The only number that matters is the one a stranger can see. Search your own domain in Google with the &lt;code&gt;site:&lt;/code&gt; operator and count the results, including page two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;site:example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare it to your post count. Mine was 12 of 61. Search Console's Pages report said 7 indexed, which was even worse, and it was three weeks stale. Two different numbers, both far below 61, and neither was in my dashboard where I could trip over it.&lt;/p&gt;

&lt;p&gt;Measure it the same way every week. Same query, same browser, count the posts only, not the homepage or the About page. A number you take by hand once a week beats a report you read once a month.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Read crawl stats before writing another post
&lt;/h2&gt;

&lt;p&gt;Search Console, then Settings, then Crawl stats. Look at two lines: total requests, and the purpose breakdown.&lt;/p&gt;

&lt;p&gt;Mine read 87 requests over 90 days, about one per day, and 100% &lt;code&gt;Refresh&lt;/code&gt; with zero &lt;code&gt;Discovery&lt;/code&gt;. That is the whole diagnosis in one screen. Google was coming back to check pages it already knew about, and never once coming to look for new ones.&lt;/p&gt;

&lt;p&gt;If your crawl purpose is all refresh, publishing more does not help. The crawler is not reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Check whether anything links to the page
&lt;/h2&gt;

&lt;p&gt;For any URL that is not indexed, inspect it and look at "Referring page". Mine said "None detected" for the page I cared about. Nothing on the site linked to it and nothing outside did either.&lt;/p&gt;

&lt;p&gt;A page with no inbound link is a page with no path to it. Internal links count. If you have 60 posts and no links between them, you have 60 orphan pages sharing one homepage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed, in order
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Internal links from indexed pages to unindexed ones.&lt;/strong&gt; I added a block to every post Google already had, linking the three newest entries. This costs nothing and it is the only lever entirely within your control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One specific detail per page that cannot be copied.&lt;/strong&gt; For me it is numbers: the crawl counts, the redirect counts, the dates. Anyone can write advice. Almost nobody writes down that their validation sat at zero processed URLs for ten days while the interface said Started.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fewer index requests.&lt;/strong&gt; I had requested around 60 URLs over three weeks with no movement. Requesting is not a strategy; it is a queue position on a crawl budget you have not earned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;External links, which I had been avoiding.&lt;/strong&gt; One inbound domain is not a distribution problem you can write your way out of. This is the uncomfortable part and it stays uncomfortable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest summary
&lt;/h2&gt;

&lt;p&gt;Your site can be technically perfect and still invisible. Mine was: 200s everywhere, clean canonicals, a fresh sitemap, sub-400ms responses. None of that makes Google curious. Curiosity comes from other sites pointing at you, and from pages that are worth the crawl they cost.&lt;/p&gt;

&lt;p&gt;Fix the mechanics first, because they are cheap. Then accept that the rest is reputation, and reputation is not a settings page.&lt;/p&gt;

&lt;p&gt;Originally published on my diary: &lt;a href="https://www.vitaladge.site/2026/11/twelve-of-sixty-one-count-i-kept.html" rel="noopener noreferrer"&gt;Twelve of Sixty-One: The Count I Kept Avoiding&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Fixing "Redirect error" in Search Console: Blogger mobile 302s behind Cloudflare</title>
      <dc:creator>Justice Manyika</dc:creator>
      <pubDate>Tue, 10 Nov 2026 21:41:02 +0000</pubDate>
      <link>https://dev.to/justicemanyika/fixing-redirect-error-in-search-console-blogger-mobile-302s-behind-cloudflare-2d4i</link>
      <guid>https://dev.to/justicemanyika/fixing-redirect-error-in-search-console-blogger-mobile-302s-behind-cloudflare-2d4i</guid>
      <description>&lt;p&gt;My site had 18 URLs flagged as "Redirect error" in Search Console, and the validation sat on "Started" for weeks without moving. The cause was not a broken redirect I had created on purpose. It was Blogger's automatic mobile redirect meeting Google's smartphone crawler.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the label means
&lt;/h2&gt;

&lt;p&gt;"Redirect error" does not mean "this URL redirects". It means Google's last visit to that URL ended somewhere unexpected, or followed a chain it did not want to follow. The report shows the date of that visit, which matters: if you fixed the issue afterwards, the row is a historical record, not a current fault.&lt;/p&gt;

&lt;p&gt;That distinction cost me a week. I was validating a fix that had never been crawled, and the validation stayed pending because there was nothing new to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to reproduce it in a terminal
&lt;/h2&gt;

&lt;p&gt;Send a mobile user agent to the URL and look at the status line and Location header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -sI -A &lt;span class="s2"&gt;"Mozilla/5.0 (Linux; Android 13) Chrome/125 Mobile"&lt;/span&gt; \
  https://example.com/some-post.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my Blogger site this returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;
&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//www.example.com/some-post.html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow that and the second hop was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;302&lt;/span&gt;
&lt;span class="na"&gt;location: /some-post.html?m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things were wrong at once. The apex to www redirect happened on every request because the origin was reached over the naked domain, and Blogger then answered with a 302 to its &lt;code&gt;?m=1&lt;/code&gt; mobile variant. A crawler sees a redirect chain ending on a URL it did not ask for, and it records friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than it looks
&lt;/h2&gt;

&lt;p&gt;The redirect itself is not fatal. What matters is the cost. Every crawler visit spent on a three-hop chain is a visit not spent discovering new content. On a small site with roughly one crawl request per day, friction is not an annoyance, it is the whole budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix without touching the origin
&lt;/h2&gt;

&lt;p&gt;Blogger's current UI no longer exposes a mobile template setting, and the origin will keep answering with &lt;code&gt;?m=1&lt;/code&gt; for mobile user agents. If you run Cloudflare in front, you can normalise the request before it reaches origin:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Redirect &lt;code&gt;http://&lt;/code&gt; and naked-domain requests to &lt;code&gt;https://www.&lt;/code&gt; in a single hop.&lt;/li&gt;
&lt;li&gt;Rewrite crawler user agents to the desktop agent before origin fetch, so origin answers 200 with no mobile redirect.&lt;/li&gt;
&lt;li&gt;Catch any residual 3xx whose Location contains &lt;code&gt;m=1&lt;/code&gt; and turn it into a 301 to the canonical.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;CRAWLERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;Googlebot&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nc"&gt;AdsBot&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nc"&gt;Mediapartners&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nc"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;InspectionTool&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;bingbot&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nc"&gt;PetalBot&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nc"&gt;Applebot&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;DESKTOP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125 Safari/537.36"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nb"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;"http:"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https:"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"www.example.com"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ua&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user-agent"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CRAWLERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DESKTOP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"m=1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verify after deploying
&lt;/h2&gt;

&lt;p&gt;Run the same curl against four user agents: smartphone Googlebot, AdsBot-Google, Mediapartners-Google and bingbot. All four should return 200 with no Location header. Then confirm a real mobile browser still gets the normal behaviour, because you do not want to break the site for humans to please a crawler.&lt;/p&gt;

&lt;p&gt;The numbers moved for me: redirect errors fell from 18 to 15 in the next Search Console refresh, and indexed pages went from 7 to 14 over the following weeks. The redirect was not the only reason, but it was the one defect entirely in my control.&lt;/p&gt;

&lt;p&gt;Originally published on my diary: &lt;a href="https://www.vitaladge.site/2026/11/google-search-console-says-redirect.html" rel="noopener noreferrer"&gt;Google Search Console Says Redirect Error. Here Is What It Actually Means.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>What "Crawled, currently not indexed" actually means, and what I changed</title>
      <dc:creator>Justice Manyika</dc:creator>
      <pubDate>Tue, 10 Nov 2026 21:39:29 +0000</pubDate>
      <link>https://dev.to/justicemanyika/what-crawled-currently-not-indexed-actually-means-and-what-i-changed-2kb9</link>
      <guid>https://dev.to/justicemanyika/what-crawled-currently-not-indexed-actually-means-and-what-i-changed-2kb9</guid>
      <description>&lt;p&gt;I spent three weeks submitting URLs to Google Search Console and watching my indexed count go from twelve posts to eleven. That was the moment I stopped treating submission as a strategy and started reading the numbers properly.&lt;/p&gt;

&lt;p&gt;This is what the labels actually mean, and what I did about the one page that mattered.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four "not indexed" reasons are not the same thing
&lt;/h2&gt;

&lt;p&gt;Search Console lists rejection reasons side by side, which makes them look interchangeable. They are not.&lt;/p&gt;

&lt;p&gt;On my site, 56 pages were outside the index, broken down as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discovered, currently not indexed: 36.&lt;/strong&gt; Google knows the URL exists and has not decided to spend a crawl on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirect error: 18.&lt;/strong&gt; Every one of these came from mobile Googlebot hitting a 302 to &lt;code&gt;?m=1&lt;/code&gt; before my Cloudflare fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawled, currently not indexed: 1.&lt;/strong&gt; Google fetched this page, read it, and declined to include it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alternative page with proper canonical tag: 1.&lt;/strong&gt; Correct behaviour, nothing to fix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three of those are access questions. Exactly one is a judgment about the content.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Crawled, currently not indexed" is a quality verdict
&lt;/h2&gt;

&lt;p&gt;The label means the crawler fetched the page successfully and then decided it did not add enough to what the index already holds. It is not a penalty. The word "currently" is doing real work: the decision is revisited.&lt;/p&gt;

&lt;p&gt;My one page with this label was &lt;code&gt;/p/start-here.html&lt;/code&gt;. The URL inspection panel showed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Last crawl: 28 October, 06:24&lt;/li&gt;
&lt;li&gt;Referring page: &lt;strong&gt;None detected&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Discovery: sitemap (with a "temporary processing error" line that turned out to be a per-URL artefact, not a sitemap failure)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing on my site linked to that page and nothing outside it did either. A short "start here" page with no inbound links, no outbound links and generic advice was competing against a million pages saying the same thing more thoroughly. It lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that explains the whole stall
&lt;/h2&gt;

&lt;p&gt;Open Search Console, then Settings, then Crawl stats. Two lines matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requests per day.&lt;/strong&gt; Mine: roughly 87 over 90 days, about one a day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purpose.&lt;/strong&gt; Mine: 100% &lt;code&gt;Refresh&lt;/code&gt;. Zero &lt;code&gt;Discovery&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Refresh means Google came back to check something it already knew about. Discovery means it came looking for something new. In three months it never once came looking.&lt;/p&gt;

&lt;p&gt;That single fact explains why 36 pages sat in "Discovered" and why index requests changed nothing. A crawler that only refreshes does not read new URLs, no matter what you submit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually changed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Internal links from pages Google already has.&lt;/strong&gt; I added a block linking the three newest posts to all eleven posts that were already indexed. If nothing links to a page, the crawler has no path to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Made the dismissed page specific.&lt;/strong&gt; It now names what the site is, when it started, how many entries exist, what has failed so far, and the numbers. It links out to the entries that explain each failure. Specificity is the part a stranger cannot copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Stopped the submission grind.&lt;/strong&gt; Around sixty URLs requested over three weeks produced no measurable movement. I paused the daily batch rather than keep spending twenty-five minutes a day on something inert.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Fixed the actual redirect bug.&lt;/strong&gt; The mobile redirect was turning crawler hits into 302s. That was a real defect and it is worth checking before blaming content.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest conclusion
&lt;/h2&gt;

&lt;p&gt;A page can be perfectly reachable and still be rejected. When that happens the fix is in the page, not in the plumbing. And if crawl stats show zero discovery requests, the problem is bigger than any single page: the domain has not earned the crawler's curiosity yet, and that comes from other sites pointing at it.&lt;/p&gt;

&lt;p&gt;Originally published on my diary: &lt;a href="https://www.vitaladge.site/2026/11/crawled-currently-not-indexed-what-it.html" rel="noopener noreferrer"&gt;Crawled, Currently Not Indexed: What It Means and What I Actually Did&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
