<?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: Sandeep Singh</title>
    <description>The latest articles on DEV Community by Sandeep Singh (@sandeepsingh_uchik).</description>
    <link>https://dev.to/sandeepsingh_uchik</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%2F4192364%2F8c1c4592-3fa5-47df-865c-f4a21525e309.png</url>
      <title>DEV Community: Sandeep Singh</title>
      <link>https://dev.to/sandeepsingh_uchik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sandeepsingh_uchik"/>
    <language>en</language>
    <item>
      <title>Free Tools to Check Redirects, Broken Links, Mixed Content and Meta Tags (and When to Automate It)</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Tue, 20 Oct 2026 02:40:21 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/free-tools-to-check-redirects-broken-links-mixed-content-and-meta-tags-and-when-to-automate-it-j3b</link>
      <guid>https://dev.to/sandeepsingh_uchik/free-tools-to-check-redirects-broken-links-mixed-content-and-meta-tags-and-when-to-automate-it-j3b</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I build &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt;, a website monitoring tool. The free checkers in this article are ours. I've tried to make the manual methods useful whether or not you ever use them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most websites don't break in one dramatic moment. They break a little at a time. A product gets renamed and its old URL starts redirecting through three hops. A partner blog you linked to two years ago goes offline. Someone pastes an &lt;code&gt;http://&lt;/code&gt; image into a page template. A CMS plugin update quietly drops your meta descriptions.&lt;/p&gt;

&lt;p&gt;None of these will page you at 3 a.m. They just sit there, costing you clicks, trust, and sometimes sales, until someone notices.&lt;/p&gt;

&lt;p&gt;This guide covers four of the most common of these problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Redirect problems (chains, loops, wrong status codes)&lt;/li&gt;
&lt;li&gt;Broken links (internal and external)&lt;/li&gt;
&lt;li&gt;Mixed content on HTTPS pages&lt;/li&gt;
&lt;li&gt;Missing or bad SEO meta tags&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For each one, I'll explain what it is, how it hurts you, how to check it by hand with nothing but a browser and a terminal, and which free tool will do the same check faster. At the end, I'll talk about when doing this by hand stops making sense.&lt;/p&gt;

&lt;p&gt;All four free tools live at &lt;a href="https://uchik.com/tools?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;uchik.com/tools&lt;/a&gt;. None of them need an account.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Redirect problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What it is
&lt;/h3&gt;

&lt;p&gt;A redirect tells the browser (and search engines) "this page has moved, go here instead." That's healthy. The problems start when redirects pile up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redirect chains&lt;/strong&gt;: URL A → B → C → D. Each hop is another round trip before the visitor sees anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirect loops&lt;/strong&gt;: A → B → A. The browser gives up with "too many redirects" and the page never loads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong redirect type&lt;/strong&gt;: a temporary &lt;code&gt;302&lt;/code&gt; used for a permanent move, when a &lt;code&gt;301&lt;/code&gt; (or &lt;code&gt;308&lt;/code&gt;) is what you mean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed hops&lt;/strong&gt;: &lt;code&gt;http://example.com&lt;/code&gt; → &lt;code&gt;https://example.com&lt;/code&gt; → &lt;code&gt;https://www.example.com&lt;/code&gt; → &lt;code&gt;https://www.example.com/&lt;/code&gt;. Four URLs for one homepage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chains usually build up over time: a site moves to HTTPS, then to &lt;code&gt;www&lt;/code&gt;, then changes its URL structure, and each change adds a rule on top of the old ones instead of updating them.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it hurts rankings and sales
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed.&lt;/strong&gt; Each hop adds latency before the first byte of the real page arrives, which hurts most on mobile networks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawling.&lt;/strong&gt; Google says Googlebot follows a limited number of redirect hops in a chain. Long chains waste crawl effort and can stop a crawler before it reaches the final page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signals.&lt;/strong&gt; Internal links that point at redirecting URLs make search engines do extra work to figure out which URL is canonical. Linking straight to the final URL is cleaner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lost visitors.&lt;/strong&gt; A loop is a fully broken page. If it's a checkout or a product page, that's lost revenue, plain and simple.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wrote a store-specific version of this in &lt;a href="https://dev.to/sandeepsingh_uchik/redirect-chains-in-shopify-after-changing-product-handles-3j5d"&gt;Redirect Chains in Shopify After Changing Product Handles&lt;/a&gt; if you run on Shopify.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check it manually
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; will show you every hop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sIL&lt;/span&gt; http://example.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"^(HTTP|location)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-I&lt;/code&gt; asks for headers only, &lt;code&gt;-L&lt;/code&gt; follows redirects, and the &lt;code&gt;grep&lt;/code&gt; keeps just the status lines and &lt;code&gt;Location&lt;/code&gt; headers. You'll see something like:&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="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt; &lt;span class="ne"&gt;Moved Permanently&lt;/span&gt;
&lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://example.com/&lt;/span&gt;
&lt;span class="s"&gt;HTTP/2 301&lt;/span&gt;
&lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://www.example.com/&lt;/span&gt;
&lt;span class="s"&gt;HTTP/2 200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a two-hop chain. Ideally, &lt;code&gt;http://example.com&lt;/code&gt; would go straight to &lt;code&gt;https://www.example.com/&lt;/code&gt; in one hop.&lt;/p&gt;

&lt;p&gt;In a browser, open DevTools → &lt;strong&gt;Network&lt;/strong&gt;, tick &lt;strong&gt;Preserve log&lt;/strong&gt;, and load the URL. Each redirect shows up as its own row with a 3xx status.&lt;/p&gt;

&lt;p&gt;Things to look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More than one hop for any URL you control.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;302&lt;/code&gt; or &lt;code&gt;307&lt;/code&gt; where the move is actually permanent.&lt;/li&gt;
&lt;li&gt;Any URL that appears twice in the list (a loop).&lt;/li&gt;
&lt;li&gt;Internal links, sitemaps, or canonical tags that point at a URL which redirects.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The free tool
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://uchik.com/tools/redirect-checker?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik Redirect Checker&lt;/a&gt; does the &lt;code&gt;curl&lt;/code&gt; trace for you. Paste a URL and it lists every hop with its status code (301, 302, 307, 308), flags loops and HTTP → HTTPS hops, and shows the response headers and timing for each step.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Broken links
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What it is
&lt;/h3&gt;

&lt;p&gt;A broken link points to a page that no longer works: usually a &lt;code&gt;404 Not Found&lt;/code&gt; or &lt;code&gt;410 Gone&lt;/code&gt;, sometimes a &lt;code&gt;5xx&lt;/code&gt; server error, a DNS failure, or a timeout.&lt;/p&gt;

&lt;p&gt;There are two kinds, and they behave differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broken internal links&lt;/strong&gt;: links between your own pages. You caused these, usually by deleting, renaming, or moving a page without updating links to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken external links&lt;/strong&gt;: links from your site to someone else's. You didn't cause these, but they still show up on your page. Sites shut down, restructure, or move behind paywalls.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How it hurts rankings and sales
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visitors hit a dead end.&lt;/strong&gt; Someone who clicks "See sizing guide" and gets a 404 is less likely to keep shopping. On a store, a broken link to a product or the checkout is a direct path to a lost sale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawlers hit a dead end.&lt;/strong&gt; Internal links are how search engines find and understand your pages. A broken internal link is a dead branch: link value and discovery stop there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust.&lt;/strong&gt; An article full of dead references looks abandoned, even if the content is still good.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want to be careful here: a few broken outbound links won't tank your rankings on their own, and Google has said 404s are a normal part of the web. The real cost is to the people using your site, plus the wasted internal linking. I went into the ranking question in more detail in &lt;a href="https://dev.to/sandeepsingh_uchik/broken-links-and-core-web-vitals-what-actually-affects-rankings-fpa"&gt;Broken Links and Core Web Vitals: What Actually Affects Rankings&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check it manually
&lt;/h3&gt;

&lt;p&gt;For a single page, pull every link and check its status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/some-page"&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oE&lt;/span&gt; &lt;span class="s1"&gt;'href="https?://[^"]+"'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'s/href="([^"]+)"/\1/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nb"&gt;link&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
      &lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 10 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$link&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$link&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;done&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-vE&lt;/span&gt; &lt;span class="s2"&gt;"^(200|204)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This only catches absolute links (relative links like &lt;code&gt;/about&lt;/code&gt; need to be resolved against the page URL first), and some sites return &lt;code&gt;403&lt;/code&gt; to &lt;code&gt;curl&lt;/code&gt; even when the page works fine in a browser. Treat &lt;code&gt;403&lt;/code&gt; and &lt;code&gt;429&lt;/code&gt; results as "check by hand," not "broken."&lt;/p&gt;

&lt;p&gt;For a whole site, a crawler is the realistic option. &lt;code&gt;wget&lt;/code&gt; can do a rough pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget &lt;span class="nt"&gt;--spider&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; 3 &lt;span class="nt"&gt;-nd&lt;/span&gt; &lt;span class="nt"&gt;-nv&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; crawl.log https://example.com
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-B1&lt;/span&gt; &lt;span class="s2"&gt;"broken link"&lt;/span&gt; crawl.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other places to look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Search Console → Pages&lt;/strong&gt;: lists URLs Google found that return 404, and often where it found them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your server or CDN logs&lt;/strong&gt;: filter for &lt;code&gt;404&lt;/code&gt; responses and look at the referrer to see which page holds the bad link.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The free tool
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://uchik.com/tools/broken-link-checker?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik Broken Link Checker&lt;/a&gt; takes a page URL, finds the links on it, and checks each one, so you can see which are dead without writing a script. It's a quick way to clean up a single important page: your homepage, a top blog post, or a product page that gets a lot of traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Mixed content
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What it is
&lt;/h3&gt;

&lt;p&gt;Mixed content happens when a page loaded over &lt;code&gt;https://&lt;/code&gt; pulls in some resources over plain &lt;code&gt;http://&lt;/code&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://example.com/images/hero.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://cdn.example.com/widget.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browsers treat the two kinds differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Active mixed content&lt;/strong&gt; (scripts, stylesheets, iframes, fetch/XHR requests) is blocked outright by modern browsers, because an attacker on the network could change it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Passive mixed content&lt;/strong&gt; (images, audio, video) is often upgraded to HTTPS automatically by modern browsers. If the HTTPS version doesn't exist, the resource fails to load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It nearly always comes from one of three places: old content written before the site moved to HTTPS, hard-coded URLs in a theme or template, or third-party embeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it hurts rankings and sales
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broken pages.&lt;/strong&gt; A blocked script can mean a broken menu, a missing "Add to cart" button, or a payment widget that never loads. A blocked stylesheet can leave a page unstyled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing images.&lt;/strong&gt; An auto-upgraded image that doesn't exist on HTTPS just disappears, leaving gaps on product and blog pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust warnings.&lt;/strong&gt; Browsers may drop the "secure" indicator in the address bar or show a warning, which is the last thing you want on a checkout page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTPS as a signal.&lt;/strong&gt; Google has used HTTPS as a lightweight ranking signal for years. A page that's only partly secure isn't getting the full benefit of the move.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're on WordPress, &lt;a href="https://dev.to/sandeepsingh_uchik/https-and-mixed-content-on-wordpress-after-installing-ssl-2njl"&gt;HTTPS and Mixed Content on WordPress After Installing SSL&lt;/a&gt; walks through the database and plugin fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check it manually
&lt;/h3&gt;

&lt;p&gt;Open the page, open DevTools, and check the &lt;strong&gt;Console&lt;/strong&gt;. Browsers log a "Mixed Content" warning or error for each insecure resource, with its URL. The &lt;strong&gt;Security&lt;/strong&gt; panel in Chromium browsers will also tell you whether the page is fully secure.&lt;/p&gt;

&lt;p&gt;From the command line, you can grep the HTML for insecure resource URLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://example.com/ &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oiE&lt;/span&gt; &lt;span class="s1"&gt;'(src|href|action)="http://[^"]+"'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will also list ordinary &lt;code&gt;&amp;lt;a href="http://..."&amp;gt;&lt;/code&gt; links to other sites. Those are &lt;em&gt;not&lt;/em&gt; mixed content, since clicking a link just goes to a new page. Focus on &lt;code&gt;src&lt;/code&gt; attributes, stylesheets (&lt;code&gt;&amp;lt;link rel="stylesheet" href=...&amp;gt;&lt;/code&gt;), and form &lt;code&gt;action&lt;/code&gt; URLs. Also note that this only sees the raw HTML. Resources added later by JavaScript, or referenced inside CSS files (&lt;code&gt;url(http://...)&lt;/code&gt;), only show up in the browser console.&lt;/p&gt;

&lt;p&gt;The long-term fix is to use &lt;code&gt;https://&lt;/code&gt; URLs (or root-relative ones like &lt;code&gt;/images/hero.jpg&lt;/code&gt;) in your content and templates. As a safety net, the &lt;code&gt;Content-Security-Policy: upgrade-insecure-requests&lt;/code&gt; header tells browsers to upgrade insecure requests on your pages automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  The free tool
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://uchik.com/tools/mixed-content-checker?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik Mixed Content Checker&lt;/a&gt; loads an HTTPS page and lists the resources it requests over plain HTTP, so you can find the template or post that needs fixing without combing through the console.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Missing or bad SEO meta tags
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What it is
&lt;/h3&gt;

&lt;p&gt;Meta tags are the bits of the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; that tell search engines and social platforms what a page is. The ones worth checking on every page:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The clickable headline in search results and the browser tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;meta name="description"&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Often used as the snippet under the title in search results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;link rel="canonical"&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tells search engines which URL is the "real" version of the page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;meta name="robots"&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Can tell search engines not to index the page (&lt;code&gt;noindex&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;meta name="viewport"&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Needed for a page to display properly on mobile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open Graph (&lt;code&gt;og:title&lt;/code&gt;, &lt;code&gt;og:description&lt;/code&gt;, &lt;code&gt;og:image&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Controls the preview card when the link is shared on social and chat apps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Common problems: the tag is missing, every page has the same title, the title is the template default ("Home" or "Product"), the canonical points to the wrong page, or a &lt;code&gt;noindex&lt;/code&gt; left over from a staging site made it to production.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it hurts rankings and sales
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A stray &lt;code&gt;noindex&lt;/code&gt;&lt;/strong&gt; can take a page, or a whole site, out of search results. This is the one that really matters, and it's surprisingly common after a launch or a redesign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A wrong canonical&lt;/strong&gt; can make search engines index a different URL than the one you want, or ignore the page as a duplicate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak or duplicate titles and descriptions&lt;/strong&gt; make your result less attractive and less clearly relevant. Search engines may rewrite them, but you lose control over what people see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing Open Graph tags&lt;/strong&gt; mean shared links show up with no image or the wrong text, which makes them less likely to be clicked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a full checklist with recommended formats, see &lt;a href="https://dev.to/sandeepsingh_uchik/the-seo-meta-tags-checklist-title-description-canonical-and-open-graph-1hfl"&gt;The SEO Meta Tags Checklist: Title, Description, Canonical and Open Graph&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check it manually
&lt;/h3&gt;

&lt;p&gt;In a browser, use &lt;strong&gt;View Page Source&lt;/strong&gt; (not DevTools' Elements panel, which shows the page after JavaScript runs) and search for &lt;code&gt;&amp;lt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;canonical&lt;/code&gt;, and &lt;code&gt;robots&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://example.com/ &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oiE&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;title&amp;gt;[^&amp;lt;]*&amp;lt;/title&amp;gt;|&amp;lt;meta[^&amp;gt;]+(name|property)="(description|robots|viewport|og:[a-z]+)"[^&amp;gt;]*&amp;gt;|&amp;lt;link[^&amp;gt;]+rel="canonical"[^&amp;gt;]*&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also check the &lt;code&gt;X-Robots-Tag&lt;/code&gt; HTTP header, which can apply &lt;code&gt;noindex&lt;/code&gt; without anything showing up in the HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://example.com/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; x-robots-tag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Google Search Console, the &lt;strong&gt;URL Inspection&lt;/strong&gt; tool shows whether a page can be indexed and which canonical Google picked, which may not be the one you declared.&lt;/p&gt;

&lt;h3&gt;
  
  
  The free tool
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://uchik.com/tools/meta-tag-checker?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik Meta Tag Checker&lt;/a&gt; fetches a page and shows its title, description, canonical, robots directives, and social tags in one view, and flags the ones that are missing. It's the quickest way to confirm a fresh deploy didn't ship with a staging &lt;code&gt;noindex&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A quick manual routine
&lt;/h2&gt;

&lt;p&gt;If you're doing this by hand, here's a simple routine that covers the basics for a small site:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Homepage and top 5 pages&lt;/strong&gt;: run each through the meta tag checker. Confirm there's no &lt;code&gt;noindex&lt;/code&gt; and that the canonical points to itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your main domain variants&lt;/strong&gt;: run &lt;code&gt;http://&lt;/code&gt;, &lt;code&gt;https://&lt;/code&gt;, &lt;code&gt;www&lt;/code&gt; and non-&lt;code&gt;www&lt;/code&gt; through the redirect checker. Each should reach the final URL in one hop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your money pages&lt;/strong&gt; (pricing, product, checkout, signup, contact): run each through the broken link checker and the mixed content checker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After any migration, theme change, or plugin update&lt;/strong&gt;: repeat all of the above.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That takes maybe 20–30 minutes for a handful of pages, and it'll catch the worst problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to automate it
&lt;/h2&gt;

&lt;p&gt;The manual routine works, but it has two weaknesses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, it's a snapshot.&lt;/strong&gt; Your site keeps changing after you check it. External links rot on their own schedule. A teammate edits a page. An app update changes a template. The check you did last Tuesday doesn't tell you about the product page that went down on Thursday.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, it doesn't scale.&lt;/strong&gt; Checking five pages by hand is fine. Checking every page on a 300-product store, or across ten client sites, every week, is the kind of job that quietly stops getting done.&lt;/p&gt;

&lt;p&gt;A rough rule of thumb I'd suggest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check by hand&lt;/strong&gt; if you have a small, mostly static site that you rarely change. Run the routine after every significant change and a few times a year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automate&lt;/strong&gt; if you run a store, publish often, have more than a few dozen pages, manage sites for clients, or if a broken page directly costs you money. I wrote more about how often to check what in &lt;a href="https://dev.to/sandeepsingh_uchik/automating-website-monitoring-what-to-check-daily-vs-weekly-vs-monthly-bd3"&gt;Automating Website Monitoring: What to Check Daily vs Weekly vs Monthly&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the problem we built &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; to solve. It runs seven checks on your site automatically every day:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Broken internal links&lt;/li&gt;
&lt;li&gt;Broken external links&lt;/li&gt;
&lt;li&gt;Missing images&lt;/li&gt;
&lt;li&gt;Dead checkout and product pages&lt;/li&gt;
&lt;li&gt;Mixed-content warnings&lt;/li&gt;
&lt;li&gt;Redirect problems&lt;/li&gt;
&lt;li&gt;Missing SEO tags&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead of remembering to run a routine, you get a report that tells you what broke and where. You can see what that looks like in the &lt;a href="https://uchik.com/sample-report?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sample report&lt;/a&gt;, or read &lt;a href="https://uchik.com/how-it-works?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;how it works&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Whether you automate with Uchik, another tool, or your own scripts built from the commands above, the point is the same: these problems are cheap to fix when you catch them early and expensive when your customers find them first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Quick manual check&lt;/th&gt;
&lt;th&gt;Free tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redirect chains and loops&lt;/td&gt;
&lt;td&gt;&lt;code&gt;curl -sIL URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://uchik.com/tools/redirect-checker?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Redirect Checker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broken links&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wget --spider&lt;/code&gt; or a link-check loop&lt;/td&gt;
&lt;td&gt;&lt;a href="https://uchik.com/tools/broken-link-checker?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Broken Link Checker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed content&lt;/td&gt;
&lt;td&gt;DevTools Console&lt;/td&gt;
&lt;td&gt;&lt;a href="https://uchik.com/tools/mixed-content-checker?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Mixed Content Checker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meta tags&lt;/td&gt;
&lt;td&gt;View Source + &lt;code&gt;curl -sI&lt;/code&gt; for &lt;code&gt;X-Robots-Tag&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://uchik.com/tools/meta-tag-checker?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Meta Tag Checker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All the free tools are at &lt;a href="https://uchik.com/tools?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;uchik.com/tools&lt;/a&gt;. If you'd rather have all of this checked every day without thinking about it, you can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Got a manual check I missed? Tell me in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>A Technical SEO Checklist for Launching a New Website</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 19:46:28 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/a-technical-seo-checklist-for-launching-a-new-website-235</link>
      <guid>https://dev.to/sandeepsingh_uchik/a-technical-seo-checklist-for-launching-a-new-website-235</guid>
      <description>&lt;p&gt;Launch day is when small configuration mistakes do the most damage. A &lt;code&gt;noindex&lt;/code&gt; left over from staging, a &lt;code&gt;robots.txt&lt;/code&gt; copied from the dev server, a 404 page that returns &lt;code&gt;200&lt;/code&gt;, or a domain switch with no redirects can each keep a good site out of search for weeks, and none of them is visible to someone just looking at the homepage.&lt;/p&gt;

&lt;p&gt;This is the checklist I run for a new site launch, split into before launch, launch day, and the first month after. Each item links to the documentation behind it, mostly Google Search Central, so you can check the reasoning rather than take my word for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before launch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Decide your canonical hostname
&lt;/h3&gt;

&lt;p&gt;Pick one version: &lt;code&gt;https://www.example.com&lt;/code&gt; or &lt;code&gt;https://example.com&lt;/code&gt;. Every other variant (&lt;code&gt;http://&lt;/code&gt;, the other &lt;code&gt;www&lt;/code&gt; choice, trailing-slash differences) should 301 to it in one hop.&lt;/p&gt;

&lt;p&gt;Google calls redirects and &lt;code&gt;rel="canonical"&lt;/code&gt; strong canonicalization signals and sitemap inclusion a weak one (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls" rel="noopener noreferrer"&gt;Google: Consolidate duplicate URLs&lt;/a&gt;). Use redirects for hostname variants and a self-referencing canonical on each page.&lt;/p&gt;

&lt;p&gt;Test all four combinations:&lt;br&gt;
&lt;/p&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;http://example.com http://www.example.com https://example.com https://www.example.com&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"== &lt;/span&gt;&lt;span class="nv"&gt;$u&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  curl &lt;span class="nt"&gt;-sIL&lt;/span&gt; &lt;span class="s2"&gt;"&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'^(HTTP/|location:)'&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each should end at the same final URL with no more than one redirect.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. HTTPS everywhere
&lt;/h3&gt;

&lt;p&gt;Install the certificate, redirect HTTP to HTTPS, and check for mixed content: anything on the page loaded over &lt;code&gt;http://&lt;/code&gt;. MDN's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content" rel="noopener noreferrer"&gt;mixed content guide&lt;/a&gt; explains what browsers upgrade and what they block. Google lists being "served in a secure fashion" among page experience considerations (&lt;a href="https://developers.google.com/search/docs/appearance/page-experience" rel="noopener noreferrer"&gt;Google: Page experience&lt;/a&gt;). Certificates also expire; Let's Encrypt certificates last 90 days, so make sure auto-renewal works before you rely on it. If you're on WordPress, my post on &lt;a href="https://dev.to/sandeepsingh_uchik/https-and-mixed-content-on-wordpress-after-installing-ssl-2njl"&gt;HTTPS and Mixed Content on WordPress After Installing SSL&lt;/a&gt; has the platform-specific steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Real 404s
&lt;/h3&gt;

&lt;p&gt;Request a URL that doesn't exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}\n'&lt;/span&gt; https://example.com/this-page-does-not-exist-123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It must return &lt;code&gt;404&lt;/code&gt; (or &lt;code&gt;410&lt;/code&gt;), not &lt;code&gt;200&lt;/code&gt;. Google says that if a page's content suggests an error, "an empty page or an error message," Search Console will report a soft 404 (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/http-network-errors" rel="noopener noreferrer"&gt;Google: HTTP status codes&lt;/a&gt;). Single-page apps and some themes get this wrong by default. Also don't redirect every missing URL to the homepage. Google treats those as soft 404s too (&lt;a href="https://support.google.com/webmasters/answer/2445990" rel="noopener noreferrer"&gt;Google Search Console Help&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Titles and meta descriptions
&lt;/h3&gt;

&lt;p&gt;Every indexable page needs a unique, descriptive &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;. Google's &lt;a href="https://developers.google.com/search/docs/appearance/title-link" rel="noopener noreferrer"&gt;title link guidance&lt;/a&gt; asks for unique, concise titles and notes Google may rewrite them. Write a meta description for key pages. Google may use it for the snippet but isn't required to. Add Open Graph tags so shares look right. I covered all of these in detail in &lt;a href="https://dev.to/sandeepsingh_uchik/the-seo-meta-tags-checklist-title-description-canonical-and-open-graph-1hfl"&gt;The SEO Meta Tags Checklist: Title, Description, Canonical and Open Graph&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Crawlable links and navigation
&lt;/h3&gt;

&lt;p&gt;Google follows links in &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; elements. Navigation built from click handlers on &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s or &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;s may not be crawled (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/links-crawlable" rel="noopener noreferrer"&gt;Google: Link best practices&lt;/a&gt;). Check that every important page is reachable through normal links from the homepage, and that anchor text describes the destination.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Core Web Vitals baseline
&lt;/h3&gt;

&lt;p&gt;Run PageSpeed Insights or Lighthouse on your main templates (home, article/product, category). The "good" thresholds are LCP 2.5 seconds or less, INP 200 ms or less and CLS 0.1 or less, measured at the 75th percentile of page loads (&lt;a href="https://web.dev/articles/vitals" rel="noopener noreferrer"&gt;web.dev: Web Vitals&lt;/a&gt;). Before launch you only have lab data. Search Console's Core Web Vitals report uses real-user field data and fills in once you have traffic. Common launch-time fixes are image dimensions, not lazy-loading the hero image, and trimming third-party scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. If you're replacing an old site: map redirects
&lt;/h3&gt;

&lt;p&gt;This is the most expensive item to skip. Export every URL from the old site (sitemap, crawl, analytics, and Search Console's top pages), map each one to its closest new equivalent, and 301 it. Google's &lt;a href="https://developers.google.com/search/docs/crawling-indexing/site-move-with-url-changes" rel="noopener noreferrer"&gt;site move guide&lt;/a&gt; recommends keeping redirects for at least a year, and its Change of Address tool is only for moves to a new domain. My full recovery process is in &lt;a href="https://dev.to/sandeepsingh_uchik/404-errors-after-a-site-migration-or-redesign-a-recovery-checklist-1lf0"&gt;404 Errors After a Site Migration or Redesign: A Recovery Checklist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launch day
&lt;/h2&gt;

&lt;h3&gt;
  
  
  8. Remove the staging &lt;code&gt;noindex&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The classic launch bug. Staging sites are usually kept out of search with &lt;code&gt;&amp;lt;meta name="robots" content="noindex"&amp;gt;&lt;/code&gt;, an &lt;code&gt;X-Robots-Tag: noindex&lt;/code&gt; header, or WordPress's "Discourage search engines" setting, and the flag survives the launch.&lt;/p&gt;

&lt;p&gt;Check both places:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://example.com/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; x-robots-tag
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://example.com/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-io&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;meta[^&amp;gt;]*robots[^&amp;gt;]*&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check a few inner templates too. A noindex can live in one template only.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Fix &lt;code&gt;robots.txt&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A staging &lt;code&gt;Disallow: /&lt;/code&gt; copied to production blocks crawling of everything. Two things from Google's &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots/intro" rel="noopener noreferrer"&gt;robots.txt introduction&lt;/a&gt; are worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;robots.txt manages crawling and "is not a mechanism for keeping a web page out of Google."&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;noindex&lt;/code&gt; to work, the page "must not be blocked by a robots.txt file," because a blocked crawler "will never see the noindex rule" (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/block-indexing" rel="noopener noreferrer"&gt;Google: Block indexing&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So don't combine them on the same URL. For pages that shouldn't be indexed, allow crawling and use &lt;code&gt;noindex&lt;/code&gt;. Use &lt;code&gt;Disallow&lt;/code&gt; for things like infinite filter URLs or internal search results that waste crawl activity.&lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;Sitemap:&lt;/code&gt; line pointing to your sitemap.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Sitemap
&lt;/h3&gt;

&lt;p&gt;Generate an XML sitemap containing only canonical, indexable URLs that return &lt;code&gt;200&lt;/code&gt;. No redirects, no noindexed pages, no staging hostnames. Many platforms do this automatically (WordPress has served &lt;code&gt;/wp-sitemap.xml&lt;/code&gt; since 5.5). Google says small, well-linked sites of about 500 pages or fewer may not need one (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview" rel="noopener noreferrer"&gt;Google: Sitemaps overview&lt;/a&gt;), but a sitemap is cheap and it gives Search Console a list to report against.&lt;/p&gt;

&lt;p&gt;Quick check that every sitemap URL returns 200:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://example.com/sitemap.xml | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oE&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;loc&amp;gt;[^&amp;lt;]+'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/&amp;lt;loc&amp;gt;//'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; u&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$u&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$u&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^200'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No output means every URL listed returns &lt;code&gt;200&lt;/code&gt; directly. (For a sitemap index, run it on each child sitemap.)&lt;/p&gt;

&lt;h3&gt;
  
  
  11. Search Console
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://support.google.com/webmasters/answer/9008080" rel="noopener noreferrer"&gt;Verify the site&lt;/a&gt;, preferably as a Domain property so it covers every protocol and subdomain. Submit the sitemap. Use the &lt;a href="https://support.google.com/webmasters/answer/9012289" rel="noopener noreferrer"&gt;URL Inspection tool&lt;/a&gt; on the homepage and a few key pages. Its live test shows what Google can fetch and render, and you can request indexing. Add teammates with the right permission level rather than sharing a login. Bing Webmaster Tools is worth setting up at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Analytics
&lt;/h3&gt;

&lt;p&gt;Confirm your analytics tag fires on every template, including thank-you and checkout pages, and that staging traffic is excluded. It's not an SEO ranking factor, but you'll want data from day one to judge everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first month
&lt;/h2&gt;

&lt;h3&gt;
  
  
  13. Watch Page indexing
&lt;/h3&gt;

&lt;p&gt;In Search Console's Page indexing report, look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Excluded by 'noindex' tag&lt;/strong&gt; on pages you want indexed (a leftover from step 8)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocked by robots.txt&lt;/strong&gt; on pages you want indexed (step 9)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not found (404)&lt;/strong&gt;, usually old URLs missing a redirect (step 7) or broken internal links&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft 404&lt;/strong&gt; (step 3)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate, Google chose different canonical than user&lt;/strong&gt;, which suggests mixed signals between redirects, canonicals and internal links (step 1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New sites take time to be crawled and indexed, so don't panic over a slow first week. Look for patterns, not individual URLs.&lt;/p&gt;

&lt;h3&gt;
  
  
  14. Crawl your own site
&lt;/h3&gt;

&lt;p&gt;Run a full crawl after launch. &lt;strong&gt;Screaming Frog SEO Spider&lt;/strong&gt; is free up to 500 URLs (&lt;a href="https://www.screamingfrog.co.uk/seo-spider/" rel="noopener noreferrer"&gt;limits&lt;/a&gt;). Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal links to 404s or to redirected URLs (update them to the final URL)&lt;/li&gt;
&lt;li&gt;Redirect chains, especially left over from the old site&lt;/li&gt;
&lt;li&gt;Missing or duplicate titles and descriptions&lt;/li&gt;
&lt;li&gt;Broken images&lt;/li&gt;
&lt;li&gt;Pages with no internal links pointing to them&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  15. Re-test after every deploy
&lt;/h3&gt;

&lt;p&gt;Launch isn't the only risky moment. Every deploy can bring back a noindex, break a template's canonical, or remove a page. Put the checks from steps 1, 3, 8 and 10 into your deploy pipeline or a scheduled job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ongoing monitoring
&lt;/h2&gt;

&lt;p&gt;Steps 13 to 15 are where launches usually slip: they need repeating, and nobody owns them once launch week is over.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Uchik, which automates the recurring part. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; crawls a site daily and emails a report of broken links, missing images, redirect chains and loops, mixed content and pages that can't be reached. The &lt;a href="https://uchik.com/how-it-works?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;how it works&lt;/a&gt; page explains the crawl, the &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; lists the checks, and the &lt;a href="https://uchik.com/sample-report?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sample report&lt;/a&gt; shows the output. Honest limits: it isn't a replacement for Search Console's indexing data; it reads server-delivered HTML, so links added only by JavaScript may be missed; its redirect-chain warning targets chains longer than 3 hops, so use the curl loop above to enforce one-hop redirects; and the free plan covers one site. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before launch&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] One canonical hostname; all variants 301 to it in one hop&lt;/li&gt;
&lt;li&gt;[ ] Self-referencing canonicals on every indexable page&lt;/li&gt;
&lt;li&gt;[ ] HTTPS everywhere, no mixed content, auto-renewal confirmed&lt;/li&gt;
&lt;li&gt;[ ] Missing URLs return 404/410, not 200 or a homepage redirect&lt;/li&gt;
&lt;li&gt;[ ] Unique titles; descriptions on key pages; Open Graph tags&lt;/li&gt;
&lt;li&gt;[ ] Navigation uses real &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; links&lt;/li&gt;
&lt;li&gt;[ ] Core Web Vitals lab baseline on main templates&lt;/li&gt;
&lt;li&gt;[ ] Old-site URL inventory mapped to 301s (if replacing a site)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Launch day&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] No &lt;code&gt;noindex&lt;/code&gt; meta tag or &lt;code&gt;X-Robots-Tag&lt;/code&gt; on live templates&lt;/li&gt;
&lt;li&gt;[ ] robots.txt has no staging &lt;code&gt;Disallow: /&lt;/code&gt;; includes &lt;code&gt;Sitemap:&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Sitemap lists only 200, canonical, indexable URLs&lt;/li&gt;
&lt;li&gt;[ ] Search Console verified, sitemap submitted, key URLs inspected&lt;/li&gt;
&lt;li&gt;[ ] Analytics firing; staging excluded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;First month and beyond&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Page indexing report reviewed weekly&lt;/li&gt;
&lt;li&gt;[ ] Full crawl: broken links, redirects, titles, images, orphan pages&lt;/li&gt;
&lt;li&gt;[ ] Launch checks automated for every deploy&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>devops</category>
    </item>
    <item>
      <title>Image SEO for Blogs: Finding Missing, Broken and Unindexable Images</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 19:46:12 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/image-seo-for-blogs-finding-missing-broken-and-unindexable-images-304j</link>
      <guid>https://dev.to/sandeepsingh_uchik/image-seo-for-blogs-finding-missing-broken-and-unindexable-images-304j</guid>
      <description>&lt;p&gt;Most blog posts lean on images: screenshots, diagrams, charts, product photos, hero images for social shares. And most blogs carry some broken ones that nobody has noticed. The author's browser cached the image years ago, the post looks fine to them, and a reader sees an empty box with a tiny broken-image icon.&lt;/p&gt;

&lt;p&gt;Broken images are a quality problem first: a tutorial with missing screenshots stops being a tutorial. They are also an image SEO problem, because an image that returns a 404, or that crawlers can't find in the first place, can't show up in Google Images, Discover or the thumbnails beside text results.&lt;/p&gt;

&lt;p&gt;This guide covers why blog images break, how to find every broken or missing image on a site, and the image SEO basics that Google documents, with sources for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why blog images break
&lt;/h2&gt;

&lt;p&gt;In rough order of how often I see them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deleted media.&lt;/strong&gt; Someone cleans up the media library and removes files still used in old posts. In WordPress, deleting a file from the library doesn't edit the posts that embed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain or HTTPS moves.&lt;/strong&gt; Images hard-coded as &lt;code&gt;http://old-domain.com/wp-content/uploads/...&lt;/code&gt; break, or get blocked as mixed content, after a move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hotlinked images.&lt;/strong&gt; Images embedded straight from another site disappear when that site reorganises, deletes them, or blocks hotlinking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN or storage changes.&lt;/strong&gt; Moving uploads to S3, a CDN or a new image service changes URLs. Old posts keep the old URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expired third-party embeds.&lt;/strong&gt; Screenshot services, badge generators and chart widgets shut down or change URL schemes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case and filename mismatches.&lt;/strong&gt; &lt;code&gt;Screenshot.PNG&lt;/code&gt; works on one server and 404s on a case-sensitive one after a migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken &lt;code&gt;srcset&lt;/code&gt; entries.&lt;/strong&gt; The &lt;code&gt;src&lt;/code&gt; works, but one of the resized versions listed in &lt;code&gt;srcset&lt;/code&gt; is missing, so only some devices see a broken image. These are the hardest to notice by eye.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1: Find every broken image on the blog
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Crawl the site and check image URLs
&lt;/h3&gt;

&lt;p&gt;Checking pages one by one doesn't scale. A crawler collects every &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; URL across the site and requests each one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Screaming Frog SEO Spider&lt;/strong&gt; (free up to 500 URLs, &lt;a href="https://www.screamingfrog.co.uk/seo-spider/" rel="noopener noreferrer"&gt;limits&lt;/a&gt;) lists images under the &lt;strong&gt;Images&lt;/strong&gt; tab and response codes for every resource. Filter for &lt;strong&gt;Client Error (4xx)&lt;/strong&gt; and use &lt;strong&gt;Inlinks&lt;/strong&gt; to see which posts embed each broken image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Search Console&lt;/strong&gt; won't list broken images directly, but its Page indexing and Core Web Vitals reports help with the related problems below.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A quick script for a single post
&lt;/h3&gt;

&lt;p&gt;For spot checks, this pulls the &lt;code&gt;src&lt;/code&gt; and every &lt;code&gt;srcset&lt;/code&gt; candidate from a page and checks each one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&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;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;

&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&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="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;img-check/1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&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="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;(?:img|source)\b[^&amp;gt;]*&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\ssrc=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;([^&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]+)&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\ssrcset=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;([^&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]+)&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urljoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&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="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&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="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HEAD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;img-check/1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with &lt;code&gt;python3 imgcheck.py https://yourblog.example/some-post/&lt;/code&gt;. Some servers reject &lt;code&gt;HEAD&lt;/code&gt; requests; if you get a &lt;code&gt;405&lt;/code&gt;, retry that URL with &lt;code&gt;GET&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check for mixed content images
&lt;/h3&gt;

&lt;p&gt;On an HTTPS page, an image loaded over &lt;code&gt;http://&lt;/code&gt; is mixed content. MDN explains that browsers now try to upgrade "upgradable" content such as images to HTTPS, but if the image isn't available over HTTPS the upgrade fails and the image doesn't load. MDN also notes that images in &lt;code&gt;srcset&lt;/code&gt; or &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; aren't upgraded and are blocked instead (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content" rel="noopener noreferrer"&gt;MDN: Mixed content&lt;/a&gt;). Search your post content for &lt;code&gt;src="http://&lt;/code&gt; and &lt;code&gt;srcset="http://&lt;/code&gt;. I covered the WordPress side of this in &lt;a href="https://dev.to/sandeepsingh_uchik/https-and-mixed-content-on-wordpress-after-installing-ssl-2njl"&gt;HTTPS and Mixed Content on WordPress After Installing SSL&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Fix them
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File deleted, original available&lt;/td&gt;
&lt;td&gt;Re-upload to the same path, or update the post to the new path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File gone, no copy&lt;/td&gt;
&lt;td&gt;Check the Wayback Machine for an archived copy; otherwise replace or remove the image and adjust the text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Old domain or &lt;code&gt;http://&lt;/code&gt; URLs&lt;/td&gt;
&lt;td&gt;Search-replace in the database (on WordPress, WP-CLI &lt;code&gt;search-replace&lt;/code&gt; handles serialized data safely)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hotlinked image gone&lt;/td&gt;
&lt;td&gt;Host your own copy if licensing allows, or replace it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing &lt;code&gt;srcset&lt;/code&gt; sizes&lt;/td&gt;
&lt;td&gt;Regenerate thumbnails or resized versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Case mismatch&lt;/td&gt;
&lt;td&gt;Rename the file or fix the reference so they match exactly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then re-crawl. Fixing images by hand is error-prone, and the re-crawl is what proves the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Make images indexable
&lt;/h2&gt;

&lt;p&gt;A working image can still be invisible to search. Google's &lt;a href="https://developers.google.com/search/docs/appearance/google-images" rel="noopener noreferrer"&gt;image SEO best practices&lt;/a&gt; set out the basics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use real &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements
&lt;/h3&gt;

&lt;p&gt;Google says it "can find images in &lt;code&gt;src&lt;/code&gt; attribute of &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element (even when it's a child of other elements, such as the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element)" and that it "doesn't index CSS images." A hero image set as a CSS &lt;code&gt;background-image&lt;/code&gt; won't be indexed. If the image matters for search, use an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Always include a &lt;code&gt;src&lt;/code&gt; fallback
&lt;/h3&gt;

&lt;p&gt;Google notes that "some browsers and crawlers don't understand" &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;, and recommends you "always specify a fallback URL using the &lt;code&gt;src&lt;/code&gt; attribute." A &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element should contain an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; with a &lt;code&gt;src&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use supported formats
&lt;/h3&gt;

&lt;p&gt;Google Search supports BMP, GIF, JPEG, PNG, WebP, SVG and AVIF in &lt;code&gt;&amp;lt;img src&amp;gt;&lt;/code&gt;, and suggests filename extensions match the file type (same source).&lt;/p&gt;

&lt;h3&gt;
  
  
  Consider an image sitemap
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/search/docs/crawling-indexing/sitemaps/image-sitemaps" rel="noopener noreferrer"&gt;Image sitemaps&lt;/a&gt; tell Google about images it might not otherwise find, "such as images your site reaches with JavaScript code." Each &lt;code&gt;&amp;lt;url&amp;gt;&lt;/code&gt; can list up to 1,000 &lt;code&gt;&amp;lt;image:image&amp;gt;&lt;/code&gt; entries, and image URLs can be on other domains, such as a CDN.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Alt text, filenames and context
&lt;/h2&gt;

&lt;p&gt;Google is explicit: "The most important attribute when it comes to providing more metadata for an image is the alt text," which "also improves accessibility for people who can't see images." It warns against keyword stuffing in &lt;code&gt;alt&lt;/code&gt; attributes, which "may cause your site to be seen as spam" (&lt;a href="https://developers.google.com/search/docs/appearance/google-images" rel="noopener noreferrer"&gt;Google&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Practical rules for blog images:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Describe what's in the image&lt;/strong&gt; in a short sentence. For a screenshot: what it shows and why it's there ("WordPress Site Health screen with the HTTPS status warning").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decorative images&lt;/strong&gt; can use an empty &lt;code&gt;alt=""&lt;/code&gt; so screen readers skip them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image links:&lt;/strong&gt; Google notes alt text acts as anchor text when an image is a link, so describe the destination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filenames:&lt;/strong&gt; Google says filenames give "very light clues." &lt;code&gt;wordpress-site-health-https.png&lt;/code&gt; beats &lt;code&gt;IMG00023.PNG&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Placement:&lt;/strong&gt; put images near the text they illustrate. Google uses page content and captions to understand them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same image, same URL:&lt;/strong&gt; if an image appears on many pages, reference it with one consistent URL so it can be cached and reused.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A crawl export of &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags with missing &lt;code&gt;alt&lt;/code&gt; attributes, filtered to content images, gives you a manageable to-do list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Images and Core Web Vitals
&lt;/h2&gt;

&lt;p&gt;Images affect two of the three Core Web Vitals.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLS: always set dimensions
&lt;/h3&gt;

&lt;p&gt;web.dev lists "images without dimensions" first among the most common causes of poor Cumulative Layout Shift (&lt;a href="https://web.dev/articles/optimize-cls" rel="noopener noreferrer"&gt;web.dev: Optimize CLS&lt;/a&gt;). Set &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes, or use CSS &lt;code&gt;aspect-ratio&lt;/code&gt;, so the browser reserves space before the image loads.&lt;/p&gt;

&lt;h3&gt;
  
  
  LCP: don't lazy-load the hero
&lt;/h3&gt;

&lt;p&gt;web.dev's lazy-loading guide warns: "Don't lazy-load images that are likely to be in-viewport when the page loads, especially LCP images" (&lt;a href="https://web.dev/articles/browser-level-image-lazy-loading" rel="noopener noreferrer"&gt;web.dev&lt;/a&gt;). WordPress has added &lt;code&gt;loading="lazy"&lt;/code&gt; automatically since 5.5, and only to images with both width and height attributes (&lt;a href="https://make.wordpress.org/core/2020/07/14/lazy-loading-images-in-5-5/" rel="noopener noreferrer"&gt;Make WordPress Core&lt;/a&gt;), so check that your theme isn't lazy-loading the featured image at the top of each post.&lt;/p&gt;

&lt;p&gt;To be fair about scale: a broken image below the fold is a user-experience bug, not a ranking crisis. I go into what affects rankings and what doesn't in &lt;a href="https://dev.to/sandeepsingh_uchik/broken-links-and-core-web-vitals-what-actually-affects-rankings-fpa"&gt;Broken Links and Core Web Vitals: What Actually Affects Rankings&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Social preview images
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;og:image&lt;/code&gt; meta tag sets the image used when a post is shared, and Google lists it among the ways to suggest a preferred image for a page. Google advises choosing an image that is "relevant and representative of the page" and avoiding generic images such as a site logo. When &lt;code&gt;og:image&lt;/code&gt; points at a deleted file, every share of that post shows a blank card. Include &lt;code&gt;og:image&lt;/code&gt; URLs in your broken-image checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it from coming back
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before deleting media,&lt;/strong&gt; check whether it's still used. On WordPress, media plugins or a database search for the filename will tell you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After any migration, CDN change or HTTPS switch,&lt;/strong&gt; re-crawl images specifically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid hotlinking&lt;/strong&gt; images you don't control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor continuously.&lt;/strong&gt; Images break between audits, and the person who notices is usually a reader.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disclosure: I build Uchik. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; crawls a site daily and includes broken images alongside broken links and redirect problems in its emailed report, with the page where each was found. &lt;a href="https://uchik.com/how-it-works?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;How it works&lt;/a&gt; and the &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; explain the checks. Limits, also covered in the &lt;a href="https://uchik.com/faq?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt;: it reads server-delivered HTML, so images injected only by JavaScript may be missed; it doesn't judge alt text quality; and the free plan covers one site. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;. Screaming Frog's free version and Search Console are both good options too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Full crawl with image response codes; 4xx images fixed or removed&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; sources checked, not just &lt;code&gt;src&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] No &lt;code&gt;http://&lt;/code&gt; image URLs on HTTPS pages&lt;/li&gt;
&lt;li&gt;[ ] Important images use &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; (not CSS backgrounds), with a &lt;code&gt;src&lt;/code&gt; fallback&lt;/li&gt;
&lt;li&gt;[ ] Supported formats; extensions match file types&lt;/li&gt;
&lt;li&gt;[ ] Image sitemap considered for JavaScript-loaded images&lt;/li&gt;
&lt;li&gt;[ ] Descriptive alt text on content images; empty alt on decorative ones&lt;/li&gt;
&lt;li&gt;[ ] Descriptive filenames&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;width&lt;/code&gt;/&lt;code&gt;height&lt;/code&gt; or &lt;code&gt;aspect-ratio&lt;/code&gt; on every image&lt;/li&gt;
&lt;li&gt;[ ] Hero/LCP image not lazy-loaded&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;og:image&lt;/code&gt; URLs return 200&lt;/li&gt;
&lt;li&gt;[ ] Re-crawl after migrations, CDN moves and media clean-ups&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>wordpress</category>
      <category>performance</category>
    </item>
    <item>
      <title>Newsletter Link Rot on Substack, Ghost and beehiiv: Archive Checks That Work</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 19:32:55 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/newsletter-link-rot-on-substack-ghost-and-beehiiv-archive-checks-that-work-3n0p</link>
      <guid>https://dev.to/sandeepsingh_uchik/newsletter-link-rot-on-substack-ghost-and-beehiiv-archive-checks-that-work-3n0p</guid>
      <description>&lt;p&gt;Every newsletter issue is sent once and read for years. The email lands in inboxes on Tuesday. The web version sits in your archive, gets indexed, gets shared, and becomes the thing new readers browse before subscribing.&lt;/p&gt;

&lt;p&gt;The links in those archive pages quietly decay. Sources move, tools shut down, sponsors rebrand, tweets disappear, and your own old URLs change when you switch platforms or add a custom domain. Pew Research Center found that 38% of webpages that existed in 2013 were no longer accessible a decade later, and that even of pages collected in 2021, about one in five were gone two years on (&lt;a href="https://www.pewresearch.org/data-labs/2024/05/17/when-online-content-disappears/" rel="noopener noreferrer"&gt;Pew Research Center, 2024&lt;/a&gt;). A newsletter that links to a dozen sources an issue builds up dead links fast.&lt;/p&gt;

&lt;p&gt;This guide is practical and platform-specific: what to check before you send, how to audit an archive on Substack, Ghost or beehiiv, the custom domain and migration traps, and a routine that fits around a weekly publishing schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: The pre-send check (two minutes per issue)
&lt;/h2&gt;

&lt;p&gt;The cheapest dead link to fix is one that never ships. Before every send:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open the web preview and click every link.&lt;/strong&gt; Yes, every one, especially the sponsor link and your main source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for redirects to homepages.&lt;/strong&gt; If a source link lands on the site's homepage instead of the article, the article has probably moved or been removed. Find the current URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strip tracking junk&lt;/strong&gt; from links you copied (&lt;code&gt;?utm_...&lt;/code&gt;, &lt;code&gt;?ref=...&lt;/code&gt;, session IDs). They make URLs longer and more fragile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer stable sources:&lt;/strong&gt; original research, official docs and the publisher's own page over syndicated copies, screenshots of screenshots, or social posts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save key sources to the Wayback Machine.&lt;/strong&gt; Submitting a URL to web.archive.org/save before you publish means a snapshot exists if the page later disappears.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For longer issues, a quick command-line pass over the links on the web preview catches typos you'd miss by eye:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; &lt;span class="s2"&gt;"https://yournewsletter.example/p/issue-slug"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oE&lt;/span&gt; &lt;span class="s1"&gt;'href="https?://[^"]+"'&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="nt"&gt;-f2&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; u&lt;span class="p"&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="s1"&gt;'%s %s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 15 &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$u&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$u&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
   &lt;span class="k"&gt;done&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything that isn't &lt;code&gt;200&lt;/code&gt; deserves a look. Some sites return &lt;code&gt;403&lt;/code&gt; to scripts but work in a browser, so treat those as "check by hand", not "broken".&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Find every archive URL with the sitemap
&lt;/h2&gt;

&lt;p&gt;You can't audit an archive you can't list. Fortunately, the major platforms publish sitemaps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Substack&lt;/strong&gt; publications serve a sitemap at &lt;code&gt;/sitemap.xml&lt;/code&gt; listing the archive, about page and individual posts under &lt;code&gt;/p/…&lt;/code&gt;. You can see this on Substack's own publication at &lt;code&gt;on.substack.com/sitemap.xml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ghost&lt;/strong&gt; "automatically creates and updates an XML sitemap for your website" (&lt;a href="https://ghost.org/help/seo/" rel="noopener noreferrer"&gt;Ghost: SEO in Ghost&lt;/a&gt;), at &lt;code&gt;/sitemap.xml&lt;/code&gt;, which links to separate sitemaps for posts, pages, tags and authors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;beehiiv&lt;/strong&gt; and other platforms generally provide a sitemap too. Check &lt;code&gt;yourdomain/sitemap.xml&lt;/code&gt;, or look for a sitemap reference in &lt;code&gt;/robots.txt&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Extract the post URLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://yournewsletter.example/sitemap.xml | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oE&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;loc&amp;gt;[^&amp;lt;]+'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/&amp;lt;loc&amp;gt;//'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; archive.txt
&lt;span class="c"&gt;# For a Ghost sitemap index, repeat for each child sitemap listed (e.g. sitemap-posts.xml)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have the complete list of issue pages to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Audit the archive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Check every outbound link
&lt;/h3&gt;

&lt;p&gt;Feed &lt;code&gt;archive.txt&lt;/code&gt; into a crawler in list mode. &lt;strong&gt;Screaming Frog SEO Spider&lt;/strong&gt; does this with &lt;strong&gt;Mode → List&lt;/strong&gt; and is free up to 500 URLs (&lt;a href="https://www.screamingfrog.co.uk/seo-spider/" rel="noopener noreferrer"&gt;limits&lt;/a&gt;), which covers years of weekly issues. Configure it to check external links, then filter outbound links by &lt;strong&gt;4xx/5xx&lt;/strong&gt; and use the &lt;strong&gt;Inlinks&lt;/strong&gt; pane to see which issue each dead link is in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prioritise
&lt;/h3&gt;

&lt;p&gt;Fix in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Issues that still get traffic.&lt;/strong&gt; Check your platform's post stats or analytics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issues linked from your welcome email, about page or "start here" page.&lt;/strong&gt; New subscribers read these first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sponsor links.&lt;/strong&gt; A sponsor paid for a working link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The main source of each issue,&lt;/strong&gt; before passing mentions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Fix
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Moved:&lt;/strong&gt; update to the new URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gone but archived:&lt;/strong&gt; link to the Wayback Machine snapshot and say it's an archived copy. The &lt;a href="https://archive.org/help/wayback_api.php" rel="noopener noreferrer"&gt;Wayback availability API&lt;/a&gt; can find the closest snapshot for a URL and date, which helps for bulk fixes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gone, no archive:&lt;/strong&gt; replace with an equivalent source or remove the link and adjust the sentence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edit the &lt;strong&gt;web version&lt;/strong&gt; of each issue; the emails already delivered can't be changed. Keep a short changelog of what you fixed and when, so a later audit doesn't re-check the same dead sources and you can spot which sites rot most often.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: Your own URLs, the links you can actually control
&lt;/h2&gt;

&lt;p&gt;The most damaging rot in a newsletter archive is often self-inflicted: your own URLs changing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a custom domain
&lt;/h3&gt;

&lt;p&gt;Moving from &lt;code&gt;yourname.substack.com&lt;/code&gt; to your own domain is a common step. Substack says that when you add a custom domain, "all previous links using the Substack subdomain will automatically redirect to your new custom domain" (&lt;a href="https://on.substack.com/p/substack-bulletin-7" rel="noopener noreferrer"&gt;Substack: Bulletin&lt;/a&gt;). Its help centre recommends hosting on a subdomain such as &lt;code&gt;www.&lt;/code&gt; with the root domain redirected to it (&lt;a href="https://support.substack.com/hc/en-us/articles/360051222571-How-do-I-set-up-my-custom-domain-on-Substack" rel="noopener noreferrer"&gt;Substack Help: custom domains&lt;/a&gt;). After the switch, test a handful of old subdomain links yourself, including from old emails, and confirm they land on the right post in one redirect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Changing platforms
&lt;/h3&gt;

&lt;p&gt;Platform moves usually change URL patterns. Ghost's redirect tutorial includes specific rules for handling the &lt;code&gt;/p/&lt;/code&gt; post paths used by Substack and beehiiv when migrating to Ghost, and explains that redirects are managed by uploading a redirects file in Ghost's settings (&lt;a href="https://ghost.org/tutorials/implementing-redirects/" rel="noopener noreferrer"&gt;Ghost: Implementing redirects&lt;/a&gt;). Whatever the direction of your move:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Before migrating,&lt;/strong&gt; save the full old sitemap (&lt;code&gt;archive.txt&lt;/code&gt; above). It's your old-URL inventory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map&lt;/strong&gt; old patterns to new ones (often a single rule per pattern).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After migrating,&lt;/strong&gt; run the old list through a status check. Every old URL should 301 to its new home in one hop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update internal links&lt;/strong&gt; in recent issues and your about/welcome pages so they don't depend on redirects.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wrote a fuller recovery process for site moves in &lt;a href="https://dev.to/sandeepsingh_uchik/404-errors-after-a-site-migration-or-redesign-a-recovery-checklist-1lf0"&gt;404 Errors After a Site Migration or Redesign: A Recovery Checklist&lt;/a&gt;. It applies just as much to newsletter archives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links between your own issues
&lt;/h3&gt;

&lt;p&gt;"As I wrote last month…" links point at your own archive. Use the canonical URL of the post (custom domain, not the old subdomain) so they never rely on a redirect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links in email bodies
&lt;/h3&gt;

&lt;p&gt;Most email platforms rewrite links for click tracking through their own domain. That works while the account is active. If you change platforms or tracking domains, links in emails already sitting in inboxes can stop resolving, which is one more reason the web archive, which you &lt;em&gt;can&lt;/em&gt; fix, matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5: The quiet failure mode, reference rot
&lt;/h2&gt;

&lt;p&gt;Some links still "work" but no longer show what you linked to: the page is now a custom 404 returning 200, a redirect to a homepage, or an updated article that no longer says what you quoted. A Harvard study of legal citations found that more than 70% of URLs in the journals studied, and 50% of URLs in U.S. Supreme Court opinions, no longer produced the information originally cited (&lt;a href="https://harvardlawreview.org/forum/vol-127/perma-scoping-and-addressing-the-problem-of-link-and-reference-rot-in-legal-citations/" rel="noopener noreferrer"&gt;Zittrain, Albert &amp;amp; Lessig, Harvard Law Review Forum, 2014&lt;/a&gt;). Status-code checkers can't catch this. For your most important sources, open them by hand once in a while, and keep Wayback snapshots of anything you quote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 6: A routine for busy writers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Every issue&lt;/td&gt;
&lt;td&gt;Pre-send click-through; save key sources to Wayback&lt;/td&gt;
&lt;td&gt;2–5 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;Check the last month's issues plus your welcome/about pages&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quarterly&lt;/td&gt;
&lt;td&gt;Full archive crawl from the sitemap; fix top-traffic issues first&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After any domain or platform change&lt;/td&gt;
&lt;td&gt;Old-URL list check; fix redirects&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ongoing&lt;/td&gt;
&lt;td&gt;Automated daily check of the archive site&lt;/td&gt;
&lt;td&gt;0 min once set up&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the bigger picture of what to check daily, weekly and monthly on any site, see &lt;a href="https://dev.to/sandeepsingh_uchik/automating-website-monitoring-what-to-check-daily-vs-weekly-vs-monthly-bd3"&gt;Automating Website Monitoring: What to Check Daily vs Weekly vs Monthly&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the archive check
&lt;/h2&gt;

&lt;p&gt;The quarterly crawl is the step that usually slips, because it's never urgent until a reader or sponsor points out a dead link.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Uchik, which can run that check daily for a newsletter's web archive. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; crawls the site every day and emails a report of broken outbound and internal links, broken images and redirect problems, showing the page where each one was found. The &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; lists the checks and the &lt;a href="https://uchik.com/sample-report?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sample report&lt;/a&gt; shows what a report looks like. Limits worth knowing, also covered in the &lt;a href="https://uchik.com/faq?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt;: it checks the public web archive, not the emails in inboxes; it detects link rot, not reference rot; and it reads server-delivered HTML, so links added only by JavaScript may be missed; and the free plan covers one site. Google Search Console is still worth connecting for your archive's indexing and 404 reports. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Pre-send click-through for every issue&lt;/li&gt;
&lt;li&gt;[ ] Welcome email and "start here" page links checked monthly&lt;/li&gt;
&lt;li&gt;[ ] Key sources saved to the Wayback Machine at publish time&lt;/li&gt;
&lt;li&gt;[ ] Archive URL list pulled from the sitemap&lt;/li&gt;
&lt;li&gt;[ ] Quarterly outbound link check; top-traffic issues and sponsor links fixed first&lt;/li&gt;
&lt;li&gt;[ ] Dead links updated, pointed to archived copies, or removed&lt;/li&gt;
&lt;li&gt;[ ] Custom domain redirects tested from old subdomain links&lt;/li&gt;
&lt;li&gt;[ ] Platform migration: old-URL inventory, mapping, one-hop redirects, re-check&lt;/li&gt;
&lt;li&gt;[ ] Internal "as I wrote" links use canonical URLs&lt;/li&gt;
&lt;li&gt;[ ] Important sources spot-checked for reference rot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your archive is the best case for subscribing. Keeping its links alive keeps that case strong.&lt;/p&gt;

</description>
      <category>newsletter</category>
      <category>writing</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Redirect Chains in Shopify After Changing Product Handles</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 19:32:14 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/redirect-chains-in-shopify-after-changing-product-handles-3j5d</link>
      <guid>https://dev.to/sandeepsingh_uchik/redirect-chains-in-shopify-after-changing-product-handles-3j5d</guid>
      <description>&lt;p&gt;Shopify makes changing a product URL easy. Edit the URL handle, leave the redirect box ticked, save. The old address now forwards to the new one, and nothing appears broken.&lt;/p&gt;

&lt;p&gt;Do that a few times over a product's life (a rename for SEO, a rebrand, a seasonal tweak, a typo fix) and something quieter happens. The redirects start pointing at other redirects. A shopper clicking a two-year-old Pinterest pin or email link goes from handle A to handle B to handle C before seeing the product. That's a &lt;strong&gt;redirect chain&lt;/strong&gt;, and Shopify stores accumulate them without anyone noticing.&lt;/p&gt;

&lt;p&gt;This guide explains how chains form in Shopify specifically, what they cost, and a safe process to find and flatten them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How product handles and redirects work in Shopify
&lt;/h2&gt;

&lt;p&gt;Every product has a &lt;strong&gt;handle&lt;/strong&gt;, the last part of its URL: &lt;code&gt;/products/trail-runner-4&lt;/code&gt;. Shopify generates it from the product title when you create the product. You can edit it in the product's &lt;strong&gt;Search engine listing&lt;/strong&gt; section.&lt;/p&gt;

&lt;p&gt;When you change a handle in the admin, Shopify offers a &lt;strong&gt;"Create a URL redirect"&lt;/strong&gt; checkbox so the old URL forwards to the new one, as described in a &lt;a href="https://community.shopify.com/t/getting-wrong-product-name-in-detail-page-url/112136" rel="noopener noreferrer"&gt;Shopify Community answer&lt;/a&gt;. The redirect appears in &lt;strong&gt;Content → Menus → View URL redirects&lt;/strong&gt;, alongside any you create manually (&lt;a href="https://help.shopify.com/en/manual/online-store/menus-and-links/url-redirect" rel="noopener noreferrer"&gt;Shopify Help: URL redirects&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Shopify's own product help includes a caution worth repeating: you can edit the handle, "but don't edit it too often or the product might not display in search engine results" (&lt;a href="https://help.shopify.com/en/manual/products/add-update-products" rel="noopener noreferrer"&gt;Shopify Help: Adding and updating products&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Three rules from Shopify's redirect documentation explain most of the surprises store owners run into:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Redirects only work from broken URLs.&lt;/strong&gt; "You can redirect only from broken URLs… If the URL still loads a valid webpage, then the URL redirect won't work."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reactivating content deletes redirects.&lt;/strong&gt; If a hidden collection is made visible again, "the URL redirect is automatically deleted because redirects only work for URLs that return 404 errors". Shopify notes the same behaviour applies to products set to Draft or removed from sales channels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;301s are cached.&lt;/strong&gt; "301 redirects are cached by browsers and search engines. If you remove a redirect after customers have accessed it, then the redirect might continue to work in their browsers until the cache expires."&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How a chain forms
&lt;/h2&gt;

&lt;p&gt;Here's a typical product history:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Redirects in admin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jan&lt;/td&gt;
&lt;td&gt;Created as &lt;code&gt;/products/trail-shoe&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr&lt;/td&gt;
&lt;td&gt;Renamed for SEO → &lt;code&gt;/products/waterproof-trail-running-shoe&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;trail-shoe&lt;/code&gt; → &lt;code&gt;waterproof-trail-running-shoe&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep&lt;/td&gt;
&lt;td&gt;New version, renamed → &lt;code&gt;/products/trail-runner-4&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;waterproof-trail-running-shoe&lt;/code&gt; → &lt;code&gt;trail-runner-4&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now anyone following an old link to &lt;code&gt;/products/trail-shoe&lt;/code&gt; goes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/products/trail-shoe  →301→  /products/waterproof-trail-running-shoe  →301→  /products/trail-runner-4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two hops. Add a domain change (&lt;code&gt;http://&lt;/code&gt; → &lt;code&gt;https://&lt;/code&gt;, or the &lt;code&gt;myshopify.com&lt;/code&gt; domain → your custom domain) or a &lt;code&gt;www&lt;/code&gt; redirect in front, and it's three or four.&lt;/p&gt;

&lt;p&gt;Other common ways chains appear in Shopify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Imported redirects from a previous platform&lt;/strong&gt; that point at URLs you later changed again&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirect apps&lt;/strong&gt; layering their own rules on top of the native redirect list&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collection handle changes&lt;/strong&gt; where internal links, menus or old redirects still use the older handle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old campaign redirects&lt;/strong&gt; (&lt;code&gt;/spring-sale&lt;/code&gt; → a collection) whose target collection was later renamed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why chains matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Speed
&lt;/h3&gt;

&lt;p&gt;Each redirect is another round trip before the page can load. Lighthouse notes that a redirect "can delay the loading of the resource by hundreds of milliseconds" and fails a page that has two or more redirects (&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/redirects" rel="noopener noreferrer"&gt;Lighthouse: Avoid multiple page redirects&lt;/a&gt;). web.dev's LCP guide adds that a common cause of slow Time to First Byte "for an otherwise fast site is visitors arriving through multiple redirects, such as from advertisements or shortened links" (&lt;a href="https://web.dev/articles/optimize-lcp" rel="noopener noreferrer"&gt;Optimize LCP&lt;/a&gt;). Ads, emails and social posts are exactly where old product URLs live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fragility
&lt;/h3&gt;

&lt;p&gt;Every link in a chain is a point of failure. If someone deletes or edits the middle redirect while tidying up, or the middle URL becomes a live page again (Shopify then deletes that redirect, per the rule above), the chain breaks. Old links then land on a 404 or the wrong page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search signals
&lt;/h3&gt;

&lt;p&gt;Google follows redirects, and its documentation describes permanent redirects as a strong signal that the target should be canonical (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/301-redirects" rel="noopener noreferrer"&gt;Redirects and Google Search&lt;/a&gt;). Google can follow several hops, but there's no benefit in making it. Shorter paths are simpler for crawlers and leave fewer chances for something to go wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Export your redirect list
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Content → Menus → View URL redirects&lt;/strong&gt;, use &lt;strong&gt;Export&lt;/strong&gt; to download your redirects as CSV (&lt;a href="https://help.shopify.com/en/manual/online-store/menus-and-links/url-redirect" rel="noopener noreferrer"&gt;Shopify Help&lt;/a&gt;). You'll get two columns: &lt;em&gt;Redirect from&lt;/em&gt; and &lt;em&gt;Redirect to&lt;/em&gt;. Save a copy before making changes; that's your rollback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Find chains in the export
&lt;/h2&gt;

&lt;p&gt;A chain exists wherever a &lt;em&gt;Redirect to&lt;/em&gt; value also appears as a &lt;em&gt;Redirect from&lt;/em&gt; value. A short script finds them and calculates each URL's final destination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redirects.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="c1"&gt;# Adjust these names to match your export's header row
&lt;/span&gt;&lt;span class="n"&gt;src_col&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst_col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Redirect from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Redirect to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;src_col&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dst_col&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;          &lt;span class="c1"&gt;# loop
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;m&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;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOOP:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CHAIN (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; hops): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This covers your native redirect list. It won't see redirects from apps or domain-level rules, so verify live URLs too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Verify against the live store
&lt;/h2&gt;

&lt;p&gt;Check what visitors actually experience, including domain and protocol hops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; url&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{num_redirects} hops  %{http_code}  %{url_effective}  &amp;lt;- &lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; old-urls.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build &lt;code&gt;old-urls.txt&lt;/code&gt; from the &lt;em&gt;Redirect from&lt;/em&gt; paths (with your domain added), plus a few old links from emails, ads and social posts. Anything with more than one hop, or ending in a 404, needs attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Flatten the chains
&lt;/h2&gt;

&lt;p&gt;For each chain, edit the &lt;strong&gt;first&lt;/strong&gt; redirect (and every intermediate one) so it points straight at the &lt;strong&gt;final&lt;/strong&gt; URL:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Redirect from&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/products/trail-shoe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/products/waterproof-trail-running-shoe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/products/trail-runner-4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/products/waterproof-trail-running-shoe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/products/trail-runner-4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/products/trail-runner-4&lt;/code&gt; (unchanged)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep the intermediate redirects. Other links may point at the middle URL, and it still needs to resolve, just in one hop. Shopify lets you edit redirects one at a time or in bulk, and import a corrected CSV (&lt;a href="https://help.shopify.com/en/manual/online-store/menus-and-links/url-redirect" rel="noopener noreferrer"&gt;Shopify Help&lt;/a&gt;). For bulk changes, test the import on a handful of rows first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Loops&lt;/strong&gt; (A → B → A) are urgent: those URLs fail outright. Decide the correct final page and point both at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Update your own links
&lt;/h2&gt;

&lt;p&gt;Redirects exist for links you &lt;em&gt;can't&lt;/em&gt; change. For links you &lt;em&gt;can&lt;/em&gt;, point straight at the current URL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigation menus and footer links&lt;/li&gt;
&lt;li&gt;Homepage sections, banners and featured product blocks&lt;/li&gt;
&lt;li&gt;Collection descriptions and blog posts&lt;/li&gt;
&lt;li&gt;Email templates, automations and saved campaign links&lt;/li&gt;
&lt;li&gt;Ads, social profile links and anything in your own control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A crawl of your storefront will list internal links that pass through redirects. Fix those at the source and only old external links will ever touch a redirect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Change handles less often, and more deliberately
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decide the handle at launch.&lt;/strong&gt; Short, descriptive, stable, without years or version numbers unless the product genuinely is versioned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't rename for small SEO tweaks.&lt;/strong&gt; Shopify's own warning about editing handles too often applies. Improve the page title and description instead; those don't change the URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When you must rename,&lt;/strong&gt; tick the redirect box, then immediately check whether the &lt;em&gt;old&lt;/em&gt; handle was itself the target of an earlier redirect, and repoint that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Successor products:&lt;/strong&gt; for a genuinely new model, consider a new product with its own URL, and redirect the old product to it only when the old one is retired. I covered those decisions in &lt;a href="https://dev.to/sandeepsingh_uchik/product-page-404s-and-out-of-stock-urls-an-ecommerce-guide-5h"&gt;Product Page 404s and Out-of-Stock URLs: An Ecommerce Guide&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Recheck regularly
&lt;/h2&gt;

&lt;p&gt;Chains come back as the catalogue evolves. A quarterly export-and-script check, plus a crawl after any bulk catalogue change, keeps them under control. For the broader Shopify link clean-up, including broken links and missing images, see &lt;a href="https://dev.to/sandeepsingh_uchik/how-to-find-and-fix-broken-links-on-a-shopify-store-33ll"&gt;How to Find and Fix Broken Links on a Shopify Store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Uchik, which can automate the crawling part. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; checks your store every day with read-only GET requests (it never adds to cart or places orders) and emails a report of broken links, missing images and redirect problems, each tied to the page and HTML element where the link appears. &lt;a href="https://uchik.com/how-it-works?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;How it works&lt;/a&gt; explains how it follows redirects one hop at a time and records each hop; the &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; lists the checks and the &lt;a href="https://uchik.com/sample-report?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sample report&lt;/a&gt; shows a redirect finding. Two honest limits: its redirect-chain warning is aimed at longer chains (more than three hops), so the export-and-script check above is still the best way to catch two-hop chains in your redirect list. It also reads server-delivered HTML, so links added only by JavaScript or apps may be missed. It isn't a Shopify app; it checks your public pages from outside. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Redirect list exported and backed up&lt;/li&gt;
&lt;li&gt;[ ] Chains and loops found with a script&lt;/li&gt;
&lt;li&gt;[ ] Old URLs (including emails, ads, social) tested live for hop count&lt;/li&gt;
&lt;li&gt;[ ] Every redirect points straight at the final URL; intermediates kept&lt;/li&gt;
&lt;li&gt;[ ] Loops fixed first&lt;/li&gt;
&lt;li&gt;[ ] Menus, banners, collections, blog posts and email templates link to current URLs&lt;/li&gt;
&lt;li&gt;[ ] Handle changes rare and deliberate; redirect box ticked and earlier redirects repointed&lt;/li&gt;
&lt;li&gt;[ ] Quarterly recheck and a crawl after bulk catalogue changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One redirect is good housekeeping. Three in a row is housekeeping that needs doing again.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>HTTPS and Mixed Content on WordPress After Installing SSL</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 19:22:40 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/https-and-mixed-content-on-wordpress-after-installing-ssl-2njl</link>
      <guid>https://dev.to/sandeepsingh_uchik/https-and-mixed-content-on-wordpress-after-installing-ssl-2njl</guid>
      <description>&lt;p&gt;You installed the SSL certificate. Your host says it's active. You visit the site and... the padlock is missing, or the browser warns that parts of the page aren't secure, or some images have vanished, or the slider stopped working. Welcome to &lt;strong&gt;mixed content&lt;/strong&gt;, the most common problem after moving a WordPress site to HTTPS.&lt;/p&gt;

&lt;p&gt;It matters beyond looks. Browsers block some insecure resources outright, so features break. Visitors see warnings at exactly the moment you want them to trust you (contact forms, checkouts, sign-ups). And Google's page experience guidance asks whether "your pages are served in a secure fashion" (&lt;a href="https://developers.google.com/search/docs/appearance/page-experience" rel="noopener noreferrer"&gt;Understanding page experience&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This guide covers switching WordPress to HTTPS properly, finding every source of mixed content, and making sure it doesn't creep back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What mixed content is, and why some of it breaks your site
&lt;/h2&gt;

&lt;p&gt;Mixed content means an HTTPS page loads a sub-resource (image, script, stylesheet, iframe, font) over plain HTTP. MDN explains that browsers now split it into two categories (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content" rel="noopener noreferrer"&gt;MDN: Mixed content&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upgradable content:&lt;/strong&gt; browsers "should automatically upgrade requests" from HTTP to HTTPS. This includes &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; with a &lt;code&gt;src&lt;/code&gt; attribute, CSS images like &lt;code&gt;background-image&lt;/code&gt;, and audio and video. If the file exists at the HTTPS address it loads; if not, you get a missing image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blockable content:&lt;/strong&gt; everything else, including scripts, stylesheets, iframes and fonts, which browsers block.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two details catch WordPress sites out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images loaded through &lt;code&gt;srcset&lt;/code&gt; or &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; are &lt;strong&gt;not&lt;/strong&gt; in the upgradable list, according to MDN. WordPress uses &lt;code&gt;srcset&lt;/code&gt; for responsive images, so old &lt;code&gt;http://&lt;/code&gt; image URLs in &lt;code&gt;srcset&lt;/code&gt; can break even when the plain &lt;code&gt;src&lt;/code&gt; would have been upgraded.&lt;/li&gt;
&lt;li&gt;Resources requested from an &lt;strong&gt;IP address&lt;/strong&gt; rather than a hostname aren't upgraded either.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So mixed content produces three kinds of symptoms: a missing padlock or warning, missing images, and broken features where a script, stylesheet or embed was blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Make sure HTTPS itself works
&lt;/h2&gt;

&lt;p&gt;Before touching WordPress settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;code&gt;https://yourdomain.com&lt;/code&gt; directly. The certificate should be valid for both the &lt;code&gt;www&lt;/code&gt; and non-&lt;code&gt;www&lt;/code&gt; names you use.&lt;/li&gt;
&lt;li&gt;Check the certificate's expiry and that renewal is automatic. Let's Encrypt certificates, which many hosts use, last 90 days by default and are meant to renew automatically (&lt;a href="https://letsencrypt.org/docs/faq/" rel="noopener noreferrer"&gt;Let's Encrypt FAQ&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;If you're behind Cloudflare or another proxy, check its SSL mode. A proxy in "Flexible" mode talking to the origin over HTTP while WordPress forces HTTPS is a classic cause of redirect loops.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Switch WordPress's own URLs to HTTPS
&lt;/h2&gt;

&lt;p&gt;WordPress stores two URLs in &lt;strong&gt;Settings → General&lt;/strong&gt;: "WordPress Address (URL)" and "Site Address (URL)". Both need to start with &lt;code&gt;https://&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since WordPress 5.7, Site Health can do this for you. WordPress detects whether the environment supports HTTPS and, if so, shows a button in the HTTPS section of &lt;strong&gt;Tools → Site Health&lt;/strong&gt; that "switches the site from HTTP to HTTPS with a single click" (&lt;a href="https://make.wordpress.org/core/2021/02/22/improved-https-detection-and-migration-in-wordpress-5-7/" rel="noopener noreferrer"&gt;Make WordPress Core: HTTPS in WordPress 5.7&lt;/a&gt;). The underlying function also verifies the change worked and reverts it automatically if WordPress doesn't then recognise the site as HTTPS.&lt;/p&gt;

&lt;p&gt;The same release added on-the-fly rewriting of insecure references to your own site's URL in content. That rewriting only applies when certain conditions are met: the site uses HTTPS, the Site Address and WordPress Address use the same domain, and an internal migration flag was set because the URLs were switched on a site with existing content. It's a big help, but it rewrites output rather than fixing the stored data. It also doesn't cover every place a URL can hide: theme files, CSS, third-party embeds, and plugins that build their own output. The core announcement itself notes that advanced configurations aren't supported by the one-click migration.&lt;/p&gt;

&lt;p&gt;To force HTTPS in the dashboard, WordPress's documentation describes setting &lt;code&gt;FORCE_SSL_ADMIN&lt;/code&gt; in &lt;code&gt;wp-config.php&lt;/code&gt; (&lt;a href="https://developer.wordpress.org/advanced-administration/security/https/" rel="noopener noreferrer"&gt;WordPress: HTTPS&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'FORCE_SSL_ADMIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Redirect all HTTP traffic to HTTPS
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;http://&lt;/code&gt; URL should 301-redirect to its &lt;code&gt;https://&lt;/code&gt; equivalent in &lt;strong&gt;one hop&lt;/strong&gt;, with no chain through &lt;code&gt;http://www&lt;/code&gt; → &lt;code&gt;https://www&lt;/code&gt; → &lt;code&gt;https://&lt;/code&gt;. Many hosts offer a "force HTTPS" toggle; otherwise use a server rule. Check with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sIL&lt;/span&gt; http://example.com/some-page/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"^(HTTP|location)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want exactly one &lt;code&gt;301&lt;/code&gt; and then a &lt;code&gt;200&lt;/code&gt;. Google's site move documentation notes that an HTTP-to-HTTPS move doesn't need the Change of Address tool, but the redirects themselves are essential (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/site-move-with-url-changes" rel="noopener noreferrer"&gt;Site moves with URL changes&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Fix the stored URLs in the database
&lt;/h2&gt;

&lt;p&gt;Old posts, pages, widgets, menus, custom fields and plugin settings often contain hard-coded &lt;code&gt;http://yourdomain.com/...&lt;/code&gt; links and image URLs. Fixing them in the database makes the fix permanent rather than relying on output rewriting.&lt;/p&gt;

&lt;p&gt;With WP-CLI (take a backup first):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp db &lt;span class="nb"&gt;export &lt;/span&gt;before-https.sql
wp search-replace &lt;span class="s1"&gt;'http://example.com'&lt;/span&gt; &lt;span class="s1"&gt;'https://example.com'&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;span class="c"&gt;# check the counts, then run without --dry-run&lt;/span&gt;
wp search-replace &lt;span class="s1"&gt;'http://example.com'&lt;/span&gt; &lt;span class="s1"&gt;'https://example.com'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WP-CLI's documentation notes that search-replace "intelligently handles PHP serialized data", which is why it's safer than raw SQL for WordPress options and widgets (&lt;a href="https://developer.wordpress.org/cli/commands/search-replace/" rel="noopener noreferrer"&gt;WP-CLI: search-replace&lt;/a&gt;). Repeat for &lt;code&gt;http://www.example.com&lt;/code&gt; if your old content used it. Many hosts and migration plugins offer an equivalent tool if you don't have shell access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Hunt down what's left
&lt;/h2&gt;

&lt;p&gt;After the database fix, remaining mixed content usually comes from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Theme files:&lt;/strong&gt; hard-coded &lt;code&gt;http://&lt;/code&gt; in &lt;code&gt;header.php&lt;/code&gt;, &lt;code&gt;footer.php&lt;/code&gt; or template parts. Search the theme folder for &lt;code&gt;http://&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS files:&lt;/strong&gt; &lt;code&gt;background-image: url(http://...)&lt;/code&gt; in theme or custom CSS, including the Customizer's "Additional CSS".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page builder data:&lt;/strong&gt; some builders store CSS or URLs in their own tables or files; regenerate their CSS after the database change if they offer that option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third-party embeds:&lt;/strong&gt; old video, map, form or widget embed codes using &lt;code&gt;http://&lt;/code&gt;. Replace them with the provider's current HTTPS embed code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External images:&lt;/strong&gt; images hotlinked from other sites over HTTP. If the other site doesn't support HTTPS, host the image yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin settings:&lt;/strong&gt; logo URLs, tracking scripts and custom code fields in plugins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard-coded IP addresses:&lt;/strong&gt; these aren't upgraded by browsers at all.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How to find them
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser developer tools:&lt;/strong&gt; open the Console on a page; browsers log mixed content warnings and blocked requests with the exact URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View source and search&lt;/strong&gt; for &lt;code&gt;http://&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawl the site:&lt;/strong&gt; a crawler that reports insecure resources finds mixed content across all pages, not just the ones you thought to check. Screaming Frog's free version (up to 500 URLs; &lt;a href="https://www.screamingfrog.co.uk/seo-spider/" rel="noopener noreferrer"&gt;limits&lt;/a&gt;) can flag HTTP links and insecure resources found on HTTPS pages, with the page each one appears on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  WordPress-specific gotchas
&lt;/h2&gt;

&lt;p&gt;A few situations trip people up even after following the steps above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Greyed-out URL fields.&lt;/strong&gt; If "WordPress Address" and "Site Address" can't be edited in Settings, they're probably defined as &lt;code&gt;WP_HOME&lt;/code&gt; and &lt;code&gt;WP_SITEURL&lt;/code&gt; constants in &lt;code&gt;wp-config.php&lt;/code&gt;. Change them there instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching.&lt;/strong&gt; Page caches, CDN caches and minified CSS bundles can keep serving the old &lt;code&gt;http://&lt;/code&gt; versions for hours or days. Clear every cache layer after the switch, including any your host runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multisite.&lt;/strong&gt; Each site in a network has its own URLs, and the one-click migration doesn't support every advanced configuration. Plan the change per site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email and form links.&lt;/strong&gt; Transactional email templates and form confirmation messages often contain hard-coded links. Search plugin settings, not just posts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 6: Lock it in (optional, carefully)
&lt;/h2&gt;

&lt;p&gt;Two headers can stop regressions, but both need care:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Content-Security-Policy: upgrade-insecure-requests&lt;/code&gt;&lt;/strong&gt; tells browsers to request all of the page's HTTP sub-resources over HTTPS (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/upgrade-insecure-requests" rel="noopener noreferrer"&gt;MDN: upgrade-insecure-requests&lt;/a&gt;). It's a useful safety net, but it hides problems rather than fixing them, and a resource with no HTTPS version still fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Strict-Transport-Security&lt;/code&gt; (HSTS)&lt;/strong&gt; tells browsers to only use HTTPS for your domain for a period set by &lt;code&gt;max-age&lt;/code&gt; (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security" rel="noopener noreferrer"&gt;MDN: Strict-Transport-Security&lt;/a&gt;). Start with a short &lt;code&gt;max-age&lt;/code&gt; and only extend it, or add &lt;code&gt;includeSubDomains&lt;/code&gt; or &lt;code&gt;preload&lt;/code&gt;, once you're sure every subdomain supports HTTPS. Mistakes here are hard to undo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Re-check, then keep checking
&lt;/h2&gt;

&lt;p&gt;After the switch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crawl the site again; there should be no HTTP resources on HTTPS pages and no internal links to &lt;code&gt;http://&lt;/code&gt; URLs.&lt;/li&gt;
&lt;li&gt;Check canonical tags, &lt;code&gt;og:image&lt;/code&gt; and sitemap URLs now use &lt;code&gt;https://&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Look at Search Console over the following weeks for crawl errors or indexing drops.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mixed content tends to come back: a new embed pasted into a post, a plugin that stores an &lt;code&gt;http://&lt;/code&gt; logo URL, an old page restored from a revision. That's why a recurring check matters more than a one-time cleanup. I covered the store-owner version of this, including broken images, in &lt;a href="https://dev.to/sandeepsingh_uchik/mixed-content-and-missing-images-a-practical-fix-guide-for-store-owners-5g90"&gt;Mixed Content and Missing Images: A Practical Fix Guide for Store Owners&lt;/a&gt;. SSL renewal is one of the yearly items in &lt;a href="https://dev.to/sandeepsingh_uchik/the-website-maintenance-checklist-for-small-business-websites-5eg0"&gt;The Website Maintenance Checklist for Small Business Websites&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Uchik, which can handle the "keep checking" part. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; crawls your site every day and emails a report that includes HTTPS mixed content alongside broken links, missing images and redirect chains, each tied to the page and HTML element to fix. The &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; lists the checks, &lt;a href="https://uchik.com/how-it-works?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;How it works&lt;/a&gt; explains the crawl, and the &lt;a href="https://uchik.com/sample-report?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sample report&lt;/a&gt; shows a mixed-content finding. It reads server-delivered HTML, so resources added only by JavaScript may be missed; the browser console is still the final check for those. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Certificate valid for all hostnames; auto-renewal confirmed&lt;/li&gt;
&lt;li&gt;[ ] Proxy/CDN SSL mode compatible (no Flexible-mode loops)&lt;/li&gt;
&lt;li&gt;[ ] WordPress Address and Site Address on &lt;code&gt;https://&lt;/code&gt; (Site Health one-click or Settings)&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;FORCE_SSL_ADMIN&lt;/code&gt; set&lt;/li&gt;
&lt;li&gt;[ ] Single-hop 301 from every HTTP URL to HTTPS&lt;/li&gt;
&lt;li&gt;[ ] Database search-replace done with backup and dry run&lt;/li&gt;
&lt;li&gt;[ ] Theme files, CSS, page builder data, embeds, plugin settings checked&lt;/li&gt;
&lt;li&gt;[ ] No &lt;code&gt;srcset&lt;/code&gt; or IP-address resources over HTTP&lt;/li&gt;
&lt;li&gt;[ ] Canonicals, &lt;code&gt;og:image&lt;/code&gt;, sitemap on HTTPS&lt;/li&gt;
&lt;li&gt;[ ] Optional: &lt;code&gt;upgrade-insecure-requests&lt;/code&gt;; HSTS with a short &lt;code&gt;max-age&lt;/code&gt; first&lt;/li&gt;
&lt;li&gt;[ ] Recurring crawl for mixed content and HTTP links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The certificate is the easy part. Making every image, script and link on the site follow it is what earns the padlock.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>https</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The SEO Meta Tags Checklist: Title, Description, Canonical and Open Graph</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 19:22:25 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/the-seo-meta-tags-checklist-title-description-canonical-and-open-graph-1hfl</link>
      <guid>https://dev.to/sandeepsingh_uchik/the-seo-meta-tags-checklist-title-description-canonical-and-open-graph-1hfl</guid>
      <description>&lt;p&gt;The &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of a web page is easy to forget because visitors never see it. But it controls the headline people see in Google, the preview text under it, which URL search engines treat as the "real" version of a page, whether the page is indexed at all, and what appears when someone shares the link on social media or in a chat app.&lt;/p&gt;

&lt;p&gt;When these tags are wrong, nothing visibly breaks. The page loads fine. You just get fewer clicks from search results, odd previews when people share your work, or pages that quietly drop out of the index. That's why they deserve a checklist and a regular audit.&lt;/p&gt;

&lt;p&gt;This guide covers the tags that matter, what Google's documentation says about each, the common mistakes, and the tags you can stop worrying about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The minimum viable &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here's a sensible baseline for a typical content or product page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Trail Runner 4 Waterproof Running Shoe | Example Outdoor&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Lightweight waterproof trail shoe with a grippy outsole. Free returns within 30 days. See sizes and colours."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/products/trail-runner-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Trail Runner 4 Waterproof Running Shoe"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"product"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/images/trail-runner-4-og.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:url"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/products/trail-runner-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Lightweight waterproof trail shoe with a grippy outsole."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the detail for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;: the most important tag here
&lt;/h2&gt;

&lt;p&gt;The title element is usually the source of the clickable headline in Google results (Google calls it the "title link"). Google's guidance (&lt;a href="https://developers.google.com/search/docs/appearance/title-link" rel="noopener noreferrer"&gt;Title links&lt;/a&gt;) boils down to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every page should have one.&lt;/strong&gt; "Make sure every page on your site has a title specified in the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; element."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Descriptive and concise.&lt;/strong&gt; Avoid vague titles like "Home" or "Profile", and avoid "unnecessarily long or verbose text".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique per page.&lt;/strong&gt; Google warns against "repeated or boilerplate text", giving the example that titling every page of a commerce site "Cheap products for sale" makes pages impossible to tell apart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No keyword stuffing.&lt;/strong&gt; "Foobar, foo bar, foobars, foo bars" style titles "can make your results look spammy".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brand concisely.&lt;/strong&gt; A short brand name at the start or end, separated by a delimiter, is fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Google may rewrite the title link it shows if it thinks another text on the page describes the page better, so a well-written, accurate title is your best way to influence it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistakes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every page using the site name as its title (template default never changed)&lt;/li&gt;
&lt;li&gt;Duplicate titles across paginated, filtered or variant URLs&lt;/li&gt;
&lt;li&gt;Titles that describe the old content after a page was rewritten&lt;/li&gt;
&lt;li&gt;Missing titles on pages generated by plugins or apps&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Meta description: your pitch, not a ranking factor
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google's snippet documentation is precise about this tag: snippets are "primarily created from the page content itself. However, Google sometimes uses the meta description HTML element if it might give users a more accurate description of the page" (&lt;a href="https://developers.google.com/search/docs/appearance/snippet" rel="noopener noreferrer"&gt;Snippets&lt;/a&gt;). So it influences the text under your result rather than your ranking.&lt;/p&gt;

&lt;p&gt;Google's guidance for good descriptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unique per page&lt;/strong&gt;, at least for the homepage and important pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Informative&lt;/strong&gt;, not a list of keywords. Google gives a "bad" example of a keyword list and a "better" one that explains what the page offers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No fixed length limit:&lt;/strong&gt; "There's no limit on how long a meta description can be, but the snippet is truncated in Google Search results as needed, typically to fit the device width."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generated descriptions are fine for large sites if they use page-specific data, such as a product's key attributes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistakes:&lt;/strong&gt; the same description on every page; descriptions left from a template ("Just another WordPress site"); descriptions that promise something the page doesn't deliver.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Canonical: telling search engines which URL is the real one
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/products/trail-runner-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same content is often reachable at several URLs: with and without &lt;code&gt;www&lt;/code&gt;, with tracking parameters, through filtered category paths, or as variants. The canonical tag says which one you prefer.&lt;/p&gt;

&lt;p&gt;Google's documentation lists the ways to indicate a canonical "in order of how strongly they can influence canonicalization": redirects are "a strong signal", &lt;code&gt;rel="canonical"&lt;/code&gt; link annotations are "a strong signal", and sitemap inclusion is "a weak signal". It notes these methods "can stack", becoming more effective when combined (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls" rel="noopener noreferrer"&gt;Consolidate duplicate URLs&lt;/a&gt;). A canonical is a strong hint, not a command; Google may choose a different URL if other signals disagree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rules of thumb:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use absolute URLs with the correct protocol and host.&lt;/li&gt;
&lt;li&gt;Point to a URL that returns 200, not a redirect or a 404.&lt;/li&gt;
&lt;li&gt;Self-reference on canonical pages (each page names itself).&lt;/li&gt;
&lt;li&gt;Keep canonicals, internal links, redirects and sitemaps consistent. If they disagree, you're sending mixed signals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common mistakes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every page canonicalised to the homepage (a template bug that can effectively hide your whole site)&lt;/li&gt;
&lt;li&gt;Canonicals pointing at the staging domain after launch&lt;/li&gt;
&lt;li&gt;Canonicals to &lt;code&gt;http://&lt;/code&gt; after moving to HTTPS&lt;/li&gt;
&lt;li&gt;Canonicals pointing at URLs that now 404 or redirect&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Robots meta: indexing on or off
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"robots"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"noindex"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You only need this tag when you want to &lt;em&gt;restrict&lt;/em&gt; something, because the default is to index and follow. Google's documentation says that when it sees &lt;code&gt;noindex&lt;/code&gt;, it will "drop that page entirely from Google Search results, regardless of whether other sites link to it", and it notes that the page must not be blocked by robots.txt or Google can't see the rule at all (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/block-indexing" rel="noopener noreferrer"&gt;Block Search indexing with noindex&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit for:&lt;/strong&gt; &lt;code&gt;noindex&lt;/code&gt; left on from staging, or applied by a plugin setting to templates you actually want indexed (tag pages, products, blog posts).&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Viewport: required for mobile
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google describes this tag as telling "the browser how to render a page on a mobile device", adding that its presence "indicates to Google that the page is mobile friendly" (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/special-tags" rel="noopener noreferrer"&gt;Meta tags Google supports&lt;/a&gt;). Most modern themes include it; check custom templates and landing page builders.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Open Graph: how your links look when shared
&lt;/h2&gt;

&lt;p&gt;Open Graph tags control the title, image and description shown when a page is shared on many social networks and messaging apps. The &lt;a href="https://ogp.me/" rel="noopener noreferrer"&gt;Open Graph protocol&lt;/a&gt; lists four required properties for every page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;og:title&lt;/code&gt;: the title as it should appear&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;og:type&lt;/code&gt;: for example &lt;code&gt;website&lt;/code&gt;, &lt;code&gt;article&lt;/code&gt;, or other types&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;og:image&lt;/code&gt;: an image URL representing the page&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;og:url&lt;/code&gt;: the canonical URL, used as the page's permanent ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;og:description&lt;/code&gt; is optional but commonly used. Some platforms also read their own tags (for example, &lt;code&gt;twitter:card&lt;/code&gt; for X); check each platform's documentation for current image size recommendations.&lt;/p&gt;

&lt;p&gt;Open Graph tags aren't a Google ranking signal, but they affect click-through from shares, which is often a large source of traffic for blogs, newsletters and stores.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistakes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broken &lt;code&gt;og:image&lt;/code&gt;&lt;/strong&gt;: the image was deleted or moved, so shares show a blank box&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;og:image&lt;/code&gt; using a relative URL or &lt;code&gt;http://&lt;/code&gt; on an HTTPS site&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;og:url&lt;/code&gt; different from the canonical&lt;/li&gt;
&lt;li&gt;The same default image and title on every page&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Tags you can drop or ignore
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;&amp;lt;meta name="keywords"&amp;gt;&lt;/code&gt;&lt;/strong&gt;: Google says it "is not used by Google Search, and it has no effect on indexing and ranking at all" (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/special-tags" rel="noopener noreferrer"&gt;Meta tags Google supports&lt;/a&gt;). Harmless, but don't spend time on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meta refresh redirects&lt;/strong&gt;: Google recommends a server-side 301 redirect instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate tags&lt;/strong&gt;: two titles, two descriptions or two canonicals (often from a theme and a plugin both adding them) send conflicting signals. Remove the duplicate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to audit meta tags across a site
&lt;/h2&gt;

&lt;p&gt;Checking one page is easy: view source or use your browser's developer tools. Checking a whole site needs a crawler.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Screaming Frog SEO Spider&lt;/strong&gt; (free up to 500 URLs; &lt;a href="https://www.screamingfrog.co.uk/seo-spider/" rel="noopener noreferrer"&gt;limits&lt;/a&gt;) has dedicated tabs for Page Titles, Meta Description and Canonicals, with filters for missing, duplicate and multiple tags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Search Console&lt;/strong&gt;: URL Inspection shows the canonical Google selected versus the one you declared, which is the best way to spot canonical conflicts. The Page indexing report lists pages excluded by &lt;code&gt;noindex&lt;/code&gt; and pages marked "Duplicate, Google chose different canonical than user".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick one-page check from the terminal:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://example.com/page | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;title|name="description"|rel="canonical"|name="robots"|property="og:'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Work through issues in this order: accidental &lt;code&gt;noindex&lt;/code&gt; → missing or duplicate titles → wrong canonicals → missing descriptions on key pages → broken &lt;code&gt;og:image&lt;/code&gt; → everything else.&lt;/p&gt;

&lt;p&gt;For small personal sites and portfolios, &lt;a href="https://dev.to/sandeepsingh_uchik/seo-health-basics-for-personal-websites-and-portfolios-54hp"&gt;SEO Health Basics for Personal Websites and Portfolios&lt;/a&gt; covers these checks alongside indexing and link health.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping it right over time
&lt;/h2&gt;

&lt;p&gt;Meta tags break silently over time: a new template ships without a canonical, a plugin update changes how descriptions are generated, a page is rewritten but keeps its old title, a CDN change breaks every &lt;code&gt;og:image&lt;/code&gt;. A one-off audit is a good start; a recurring check is what keeps it right. The &lt;a href="https://dev.to/sandeepsingh_uchik/the-website-maintenance-checklist-for-small-business-websites-5eg0"&gt;Website Maintenance Checklist for Small Business Websites&lt;/a&gt; puts this review in the yearly content pass, but on sites that change often, more frequent automated checks are worth it.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Uchik, which includes basic on-page checks in its daily crawl. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; emails a report that flags missing titles, meta descriptions, H1s and canonical tags, alongside broken links, missing images, redirect chains and mixed content, each tied to the exact page. The &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; lists every check and &lt;a href="https://uchik.com/how-it-works?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;How it works&lt;/a&gt; explains the crawl. It doesn't validate Open Graph tags or compare declared and Google-selected canonicals (use Search Console's URL Inspection for that), and it reads server-delivered HTML, so tags injected only by JavaScript may be missed. The &lt;a href="https://uchik.com/faq?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt; covers other limits. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;&amp;lt;meta charset&amp;gt;&lt;/code&gt; and viewport present&lt;/li&gt;
&lt;li&gt;[ ] Unique, descriptive, concise &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; on every page; no boilerplate&lt;/li&gt;
&lt;li&gt;[ ] Unique, informative meta description on homepage and key pages&lt;/li&gt;
&lt;li&gt;[ ] Self-referencing absolute canonical; points to a 200 URL&lt;/li&gt;
&lt;li&gt;[ ] Canonicals, redirects, internal links and sitemap agree&lt;/li&gt;
&lt;li&gt;[ ] No stray &lt;code&gt;noindex&lt;/code&gt;; &lt;code&gt;noindex&lt;/code&gt; pages not blocked in robots.txt&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;og:title&lt;/code&gt;, &lt;code&gt;og:type&lt;/code&gt;, &lt;code&gt;og:image&lt;/code&gt;, &lt;code&gt;og:url&lt;/code&gt; on shareable pages; &lt;code&gt;og:image&lt;/code&gt; loads over HTTPS&lt;/li&gt;
&lt;li&gt;[ ] No duplicate title/description/canonical tags from theme + plugin&lt;/li&gt;
&lt;li&gt;[ ] Meta keywords ignored; meta refresh replaced with 301s&lt;/li&gt;
&lt;li&gt;[ ] Recurring audit scheduled or automated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ten minutes in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; can decide whether people click, share and find the page in the first place.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>html</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Product Page 404s and Out-of-Stock URLs: An Ecommerce Guide</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 19:16:19 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/product-page-404s-and-out-of-stock-urls-an-ecommerce-guide-5h</link>
      <guid>https://dev.to/sandeepsingh_uchik/product-page-404s-and-out-of-stock-urls-an-ecommerce-guide-5h</guid>
      <description>&lt;p&gt;Ecommerce catalogues change constantly. Products sell out, come back, get replaced by a new version, change names, move collections or disappear for good. Every one of those changes affects a URL, and every URL might be linked from somewhere: your own homepage banner, a collection page, a blog post, an email campaign, a supplier's site, a Pinterest pin, a Google Shopping listing, or a customer's bookmark.&lt;/p&gt;

&lt;p&gt;Handle those changes badly and you get two kinds of loss:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shoppers hit dead ends.&lt;/strong&gt; Someone clicks "Shop the look" in last month's email and lands on a "Page not found". That's a lost sale, and they may not come back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search visibility leaks away.&lt;/strong&gt; Product pages that ranked drop out of the index, and links pointing at them stop helping you.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's a practical decision guide for each situation, based on what Google has actually said, and a routine to catch broken product links before your customers do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four product URL situations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Temporarily out of stock (it's coming back)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Keep the page live, returning 200, and say clearly that it's out of stock.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Show the status prominently with an expected restock date if you know it.&lt;/li&gt;
&lt;li&gt;Offer a "notify me when available" option and related in-stock alternatives.&lt;/li&gt;
&lt;li&gt;Keep the product in your structured data, with availability set to out of stock. Google's Product structured data supports &lt;code&gt;https://schema.org/OutOfStock&lt;/code&gt; and related values such as &lt;code&gt;PreOrder&lt;/code&gt; and &lt;code&gt;BackOrder&lt;/code&gt; (&lt;a href="https://developers.google.com/search/docs/appearance/structured-data/product-snippet" rel="noopener noreferrer"&gt;Product snippet structured data&lt;/a&gt;). Most ecommerce platforms update this automatically from inventory. Check yours does.&lt;/li&gt;
&lt;li&gt;If you use Merchant Center, keep the feed's availability attribute in sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Google's John Mueller has said that sites can handle temporarily unavailable products in different ways (marking them out of stock, using a 404, or adding &lt;code&gt;noindex&lt;/code&gt;) and that "all of those can work for search", including the approach of keeping the page as a 200 marked out of stock and making it a 404 later if the product doesn't return (&lt;a href="https://www.youtube.com/watch?v=fGP1bl_HLu0" rel="noopener noreferrer"&gt;Google SEO office hours, June 18, 2021&lt;/a&gt;). For most stores, keeping the page live is the most customer-friendly option, because people who arrive can sign up for a restock alert instead of hitting an error.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Permanently discontinued, with no direct replacement
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Let the URL return a real 404 or 410, and remove internal links to it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google's Search Console help is direct: "If you have permanently deleted content without intending to replace it with newer, related content, let the old URL return a 404 or 410." It adds that Google currently treats 410 the same as 404 (&lt;a href="https://support.google.com/webmasters/answer/2445990" rel="noopener noreferrer"&gt;404 errors&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Make the 404 page useful: search, top categories and bestsellers. You can return a 404 status code while showing whatever helpful content you like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small, high-effort catalogues are an exception.&lt;/strong&gt; In a video on Google's official Search Central (formerly Google Webmasters) channel, Google suggested that for a handful of handmade, high-effort products it can be worth keeping the page and showing related items, while for larger catalogues with hundreds or thousands of products, returning a 404 for truly gone products is reasonable (&lt;a href="https://www.youtube.com/watch?v=9tz7Eexwp_A" rel="noopener noreferrer"&gt;What should sites do with pages for products that are no longer available?&lt;/a&gt;). If you keep the page, make it obvious the item is no longer available and show alternatives prominently.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Replaced by a new version or a close equivalent
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;301-redirect the old URL to the replacement product.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Trail Runner 3" replaced by "Trail Runner 4" is a genuine successor, so a permanent redirect sends shoppers and search signals to the right place. Google's redirect documentation describes permanent redirects (301, 308) as a signal that the target should be shown in search results (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/301-redirects" rel="noopener noreferrer"&gt;Redirects and Google Search&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Redirect only to something a shopper looking for the old product would actually want. Redirecting to a &lt;em&gt;related category&lt;/em&gt; can be reasonable when the category is clearly the right next step. Redirecting all discontinued products to the homepage is not.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. URL changed (same product, new handle or slug)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Always 301 the old URL to the new one, in a single hop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the most avoidable product 404. It happens when someone edits a product title and the platform regenerates the URL handle, or during a migration when URL patterns change. Most platforms have a redirect tool: Shopify's is under &lt;strong&gt;Content → Menus → View URL redirects&lt;/strong&gt;, and redirects "start working immediately" (&lt;a href="https://help.shopify.com/en/manual/online-store/menus-and-links/url-redirect" rel="noopener noreferrer"&gt;Shopify Help: URL redirects&lt;/a&gt;). Get in the habit of checking for a redirect every time a product URL changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake to avoid: homepage catch-alls
&lt;/h2&gt;

&lt;p&gt;Some stores, or apps installed on them, redirect every missing product URL to the homepage. It looks tidy but it's a known problem. Google's help page says that "redirecting users to another page, such as the homepage, instead of returning a 404" creates &lt;strong&gt;soft 404s&lt;/strong&gt;, which "can be confusing to both users and search engines" (&lt;a href="https://support.google.com/webmasters/answer/2445990" rel="noopener noreferrer"&gt;404 errors&lt;/a&gt;). A shopper who clicked a specific product and lands on your homepage has no idea what happened.&lt;/p&gt;

&lt;p&gt;If you have a "redirect all 404s" setting or app, review it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where broken product links hide
&lt;/h2&gt;

&lt;p&gt;The product page is only half the story. The links &lt;em&gt;pointing to&lt;/em&gt; products break just as often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Homepage banners and featured products&lt;/strong&gt; pointing at items that sold out and were archived&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collection and category pages&lt;/strong&gt; with manually added links&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blog posts and buying guides&lt;/strong&gt; ("our 10 favourite…") written months ago&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation menus&lt;/strong&gt; with hand-picked product links&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email campaigns and automations&lt;/strong&gt;, such as abandoned-cart, win-back and welcome sequences, with hard-coded product URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Social posts, pins and influencer links&lt;/strong&gt; you can't edit, but whose targets you can redirect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product images&lt;/strong&gt; referencing deleted files, leaving broken images on otherwise working pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fix your own links at the source. For links you can't edit (old emails, social posts, other sites), the redirect decisions above are what protect you.&lt;/p&gt;

&lt;h2&gt;
  
  
  A routine that catches problems early
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Daily: automated crawl
&lt;/h3&gt;

&lt;p&gt;Crawl the storefront daily for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal links returning 404, especially from the homepage, menus and collections&lt;/li&gt;
&lt;li&gt;Missing product and banner images&lt;/li&gt;
&lt;li&gt;Redirect chains (old handle → older handle → current)&lt;/li&gt;
&lt;li&gt;Links to cart and checkout URLs that fail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use read-only crawling only. A crawler that clicks "Add to cart" or submits forms on a live store creates junk carts and orders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Weekly: catalogue changes review
&lt;/h3&gt;

&lt;p&gt;Before or after any batch of catalogue changes (seasonal range swaps, discontinued lines, renamed products):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Export the list of product URLs being removed or changed.&lt;/li&gt;
&lt;li&gt;Decide for each: keep live (out of stock), 404, or 301 to a successor.&lt;/li&gt;
&lt;li&gt;Add the redirects.&lt;/li&gt;
&lt;li&gt;Search your own content (blog, menus, banners, email templates) for links to those URLs and update them.&lt;/li&gt;
&lt;li&gt;Re-check the list after the change: each URL should be a single-hop 301, a deliberate 404, or a live page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A quick list check from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; url&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code} %{num_redirects} %{url_effective}  &amp;lt;- &lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; changed-products.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Monthly: Search Console
&lt;/h3&gt;

&lt;p&gt;Check &lt;strong&gt;Indexing → Pages&lt;/strong&gt; for spikes in "Not found (404)" and "Soft 404". A soft 404 spike after installing a redirect app is a strong hint that it's sending missing products to the homepage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Images: the quieter version of the same problem
&lt;/h2&gt;

&lt;p&gt;Missing images are the product-page equivalent of broken links: the page loads but looks broken, and shoppers trust it less. They usually come from deleted media files, CDN path changes in migrations, or theme changes that reference old image sizes. I covered finding and fixing them, including mixed-content images after an HTTPS change, in &lt;a href="https://dev.to/sandeepsingh_uchik/mixed-content-and-missing-images-a-practical-fix-guide-for-store-owners-5g90"&gt;Mixed Content and Missing Images: A Practical Fix Guide for Store Owners&lt;/a&gt;. For the Shopify-specific steps to find and fix broken links, see &lt;a href="https://dev.to/sandeepsingh_uchik/how-to-find-and-fix-broken-links-on-a-shopify-store-33ll"&gt;How to Find and Fix Broken Links on a Shopify Store&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Status code&lt;/th&gt;
&lt;th&gt;Internal links&lt;/th&gt;
&lt;th&gt;Structured data / feed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Out of stock, returning&lt;/td&gt;
&lt;td&gt;200 (live page)&lt;/td&gt;
&lt;td&gt;Keep; optionally de-emphasise&lt;/td&gt;
&lt;td&gt;Availability = out of stock / backorder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discontinued, no replacement&lt;/td&gt;
&lt;td&gt;404 or 410&lt;/td&gt;
&lt;td&gt;Remove&lt;/td&gt;
&lt;td&gt;Remove product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replaced by successor&lt;/td&gt;
&lt;td&gt;301 → successor&lt;/td&gt;
&lt;td&gt;Update to successor&lt;/td&gt;
&lt;td&gt;Successor's data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL/handle changed&lt;/td&gt;
&lt;td&gt;301 → new URL (one hop)&lt;/td&gt;
&lt;td&gt;Update to new URL&lt;/td&gt;
&lt;td&gt;Update URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small catalogue, high-effort item gone&lt;/td&gt;
&lt;td&gt;200 with clear "no longer available" + alternatives, or 404&lt;/td&gt;
&lt;td&gt;Remove from listings&lt;/td&gt;
&lt;td&gt;Remove offer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Automating the daily check
&lt;/h2&gt;

&lt;p&gt;The daily crawl is the step that most stores skip, and it's where most of the lost sales come from: a dead link on the homepage can sit there for weeks.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Uchik, which focuses on exactly this. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; crawls your store every day with read-only GET requests (it never adds to cart, submits forms or places orders) and emails a report of broken product, collection, cart and checkout links, missing images and redirect chains, each with the page and HTML element that caused it. The &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; lists the checks, the &lt;a href="https://uchik.com/sample-report?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sample report&lt;/a&gt; includes an ecommerce example, and the &lt;a href="https://uchik.com/faq?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt; covers the limits: links added only by JavaScript, such as some app-injected recommendation widgets, may be missed. It isn't a Shopify or WooCommerce app; it checks your public pages from outside. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Out-of-stock products stay live with clear status and correct structured data&lt;/li&gt;
&lt;li&gt;[ ] Discontinued products with no replacement return 404/410&lt;/li&gt;
&lt;li&gt;[ ] Successor products get a single-hop 301&lt;/li&gt;
&lt;li&gt;[ ] Every handle/slug change gets a redirect&lt;/li&gt;
&lt;li&gt;[ ] No "redirect all 404s to homepage" setting or app&lt;/li&gt;
&lt;li&gt;[ ] Banners, menus, collections, blog posts and email templates updated after catalogue changes&lt;/li&gt;
&lt;li&gt;[ ] Daily read-only crawl for broken product links and images&lt;/li&gt;
&lt;li&gt;[ ] Monthly Search Console check for 404 and soft 404 spikes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every product URL is a promise to someone who saved it, shared it or clicked it in an email. Keeping those promises is cheaper than winning those shoppers back.&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>seo</category>
      <category>shopify</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Broken Link Checking for Agencies Managing Client Sites</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 19:15:45 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/broken-link-checking-for-agencies-managing-client-sites-4an7</link>
      <guid>https://dev.to/sandeepsingh_uchik/broken-link-checking-for-agencies-managing-client-sites-4an7</guid>
      <description>&lt;p&gt;If you look after one website, broken links are a chore. If you look after twenty, they're a process problem.&lt;/p&gt;

&lt;p&gt;Agencies and freelancers who maintain client sites tend to discover broken links the worst possible way: the client finds them first. A "Book a consultation" button that has pointed to a deleted page for three weeks. A product range removed by the client's own team, still linked from the homepage banner. A PDF menu replaced with a new file name. The client emails, slightly annoyed, and asks why they're paying for maintenance.&lt;/p&gt;

&lt;p&gt;The fix isn't working harder. It's having a system where every client site is checked on a schedule, problems are triaged by impact, and the client hears about issues from you, along with the fix, before they find them.&lt;/p&gt;

&lt;p&gt;This guide sets out that system using free or low-cost tools. It's written for small agencies, freelancers and in-house teams who manage several sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why agency sites break more than you'd expect
&lt;/h2&gt;

&lt;p&gt;Client sites have several features that make link rot more likely than on sites you fully control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Many editors.&lt;/strong&gt; The client's staff, their marketing contractor and your team all edit content. Nobody sees the whole picture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third-party dependencies.&lt;/strong&gt; Booking widgets, payment pages, embedded forms, affiliate links and partner sites change without telling anyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seasonal and campaign content.&lt;/strong&gt; Promotions end, landing pages get unpublished, and the links pointing to them stay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform churn.&lt;/strong&gt; Plugin updates, theme switches and migrations between hosts or platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The web itself decays.&lt;/strong&gt; Pew Research Center found that 38% of webpages that existed in 2013 were no longer accessible a decade later, and that 23% of news webpages contained at least one broken link (&lt;a href="https://www.pewresearch.org/data-labs/2024/05/17/when-online-content-disappears/" rel="noopener noreferrer"&gt;Pew Research Center, 2024&lt;/a&gt;). Any client site that links out will lose some of those links over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is your fault, but under a maintenance agreement it's your problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Build a client site inventory
&lt;/h2&gt;

&lt;p&gt;Before you can check sites consistently you need one list that everyone on the team can see. For each client:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary domain and any alternate domains&lt;/td&gt;
&lt;td&gt;So you check the right one and catch redirect issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform (WordPress, Shopify, Webflow, custom…)&lt;/td&gt;
&lt;td&gt;Determines how fixes are made&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Critical paths&lt;/td&gt;
&lt;td&gt;The 3–10 URLs that make money or generate leads: booking, checkout, contact, pricing, key landing pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who edits content&lt;/td&gt;
&lt;td&gt;So you know who to ask when something changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your access level&lt;/td&gt;
&lt;td&gt;CMS, hosting, DNS, Search Console&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance scope&lt;/td&gt;
&lt;td&gt;What you're contracted to fix, and what you only report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reporting contact&lt;/td&gt;
&lt;td&gt;Who receives reports and approves changes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;critical paths&lt;/strong&gt; column matters most. It's what turns "your site has 47 broken links" into "your booking button is broken, and here are 46 minor issues".&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Get the right access, without sharing passwords
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Search Console:&lt;/strong&gt; ask the client to add you as a user rather than sharing their Google login. Search Console supports Owner, Full and Restricted users (&lt;a href="https://support.google.com/webmasters/answer/7687615" rel="noopener noreferrer"&gt;Managing owners, users, and permissions&lt;/a&gt;). Full access is usually enough for monitoring and URL inspection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CMS:&lt;/strong&gt; your own named account with the minimum role you need, not a shared "admin" login. This also gives you an audit trail of who changed what.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform redirect tools:&lt;/strong&gt; make sure your role can create redirects (Shopify URL redirects, a WordPress redirect plugin, server or CDN rules). Otherwise every fix needs a ticket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a client relationship ends, removing your named accounts is clean. Rotating a shared password is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Schedule the checks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Daily: automated crawl of each site
&lt;/h3&gt;

&lt;p&gt;Every site should be crawled automatically, ideally daily, and the results should reach you without anyone remembering to start a scan. Look for a tool or setup that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs on a schedule without someone pressing a button&lt;/li&gt;
&lt;li&gt;Reports &lt;strong&gt;where&lt;/strong&gt; each broken link is (source page and the HTML element) as well as which URL failed. Without that, fixing takes far longer&lt;/li&gt;
&lt;li&gt;Uses read-only requests, so it's safe on live stores and never submits forms or creates orders&lt;/li&gt;
&lt;li&gt;Groups results per site, so each client's issues stay separate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Options range from desktop crawlers you schedule yourself to hosted services. Screaming Frog SEO Spider is the standard desktop crawler and is free up to 500 URLs per crawl (&lt;a href="https://www.screamingfrog.co.uk/seo-spider/" rel="noopener noreferrer"&gt;limits&lt;/a&gt;); scheduling and some integrations need the paid licence. WordPress-only teams sometimes use a plugin such as &lt;a href="https://wordpress.org/plugins/broken-link-checker/" rel="noopener noreferrer"&gt;Broken Link Checker&lt;/a&gt; on each site, though plugins add load to the client's server and differ in how they handle external links.&lt;/p&gt;

&lt;h3&gt;
  
  
  Weekly: critical-path check by a person
&lt;/h3&gt;

&lt;p&gt;Automated crawls catch broken links; they don't confirm a contact form actually delivers email or that a booking widget completes. Once a week, someone should run each client's critical paths by hand, or with a test mode the platform provides, especially after updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monthly: Search Console review
&lt;/h3&gt;

&lt;p&gt;For each client, look at &lt;strong&gt;Indexing → Pages&lt;/strong&gt; for growing "Not found (404)" or "Server error (5xx)" counts, and &lt;strong&gt;Performance&lt;/strong&gt; for sudden drops on key pages. Google's 404 guidance is a useful framing for clients: a URL that doesn't exist and returns 404 is normal, and "redirecting users to another page, such as the homepage, instead of returning a 404" creates soft 404s that confuse users and search engines (&lt;a href="https://support.google.com/webmasters/answer/2445990" rel="noopener noreferrer"&gt;Search Console Help: 404 errors&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Triage by impact, not by count
&lt;/h2&gt;

&lt;p&gt;A raw report with hundreds of rows will overwhelm a client and waste your time. Sort every issue into one of four buckets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P1: Revenue/lead blocker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Broken link or image on a critical path (checkout, booking, contact, pricing, main nav)&lt;/td&gt;
&lt;td&gt;Fix same day; tell the client it's fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P2: Important page broken&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A ranking page, high-traffic post or page with backlinks now returns 404/5xx&lt;/td&gt;
&lt;td&gt;Restore or 301 to the correct replacement within the week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P3: Internal hygiene&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Internal links through redirect chains, broken links in old posts, missing images on low-traffic pages&lt;/td&gt;
&lt;td&gt;Batch-fix monthly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P4: External rot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Outbound links to third-party pages that died&lt;/td&gt;
&lt;td&gt;Batch-fix monthly or quarterly; replace or remove&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Redirect chains belong in P3 rather than being ignored: each hop is an extra request, and Lighthouse notes a redirect can delay loading "by hundreds of milliseconds" (&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/redirects" rel="noopener noreferrer"&gt;Lighthouse: Avoid multiple page redirects&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Agree these definitions with clients up front, ideally in the maintenance agreement, so "we fix P1 same day" is a promise you can keep and report on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Fix at the source, and redirect only when it makes sense
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Internal link broken:&lt;/strong&gt; edit the link where it appears (page, menu, widget, template). Don't paper over it with a redirect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page moved or renamed:&lt;/strong&gt; add a single-hop 301 from the old URL to the new one, so external links and bookmarks still work, and update internal links too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page deliberately removed:&lt;/strong&gt; let it return 404 or 410 and remove internal links to it. Google treats 410 the same as 404 (&lt;a href="https://support.google.com/webmasters/answer/2445990" rel="noopener noreferrer"&gt;Search Console Help&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never redirect everything to the homepage.&lt;/strong&gt; It's the soft 404 pattern Google warns against.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I covered the platform-specific mechanics for one common case in &lt;a href="https://dev.to/sandeepsingh_uchik/how-to-find-and-fix-broken-links-on-a-shopify-store-33ll"&gt;How to Find and Fix Broken Links on a Shopify Store&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Handle the breakages clients cause
&lt;/h2&gt;

&lt;p&gt;A large share of breakage on client sites comes from the client's own edits. That's fine; it's their site. But it needs handling diplomatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Report without blame:&lt;/strong&gt; "On Tuesday the 'Spring offers' page was unpublished. Three pages still link to it, including the homepage banner. We've pointed them to the current offers page." That's a service, not an accusation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teach the pattern once:&lt;/strong&gt; a short note for client editors: "before you delete or rename a page, tell us or add a redirect".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offer a pre-change check:&lt;/strong&gt; for planned redesigns or product range changes, crawl before and after.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Report in the client's language
&lt;/h2&gt;

&lt;p&gt;Clients don't care about status codes. A monthly report that works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Headline:&lt;/strong&gt; "All critical paths working" or "1 critical issue found and fixed on [date]".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What was fixed:&lt;/strong&gt; counts by priority, plus examples of the P1/P2 fixes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What needs a decision:&lt;/strong&gt; e.g., "the supplier's catalogue page we link to is gone; should we remove the link or link to their homepage?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trend:&lt;/strong&gt; issues found vs fixed over time. A falling line is the best evidence your maintenance is working.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For smaller clients, &lt;a href="https://dev.to/sandeepsingh_uchik/the-website-maintenance-checklist-for-small-business-websites-5eg0"&gt;The Website Maintenance Checklist for Small Business Websites&lt;/a&gt; is a plain-English summary you can share so they understand what you're doing and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checking only when something feels wrong.&lt;/strong&gt; Problems sit unnoticed for weeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sending raw crawler exports to clients.&lt;/strong&gt; Triage first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating every 404 as urgent.&lt;/strong&gt; Old, unlinked URLs returning 404 are normal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixing with redirects instead of updating links.&lt;/strong&gt; Chains build up over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting images.&lt;/strong&gt; A missing hero image on a landing page is as visible as a broken link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relying on a plugin on every site&lt;/strong&gt; without checking its load and update status across your portfolio.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where automation fits
&lt;/h2&gt;

&lt;p&gt;The daily crawl is the part that's most worth automating across a portfolio, because it's the part people skip when they're busy with client work.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Uchik, which handles that daily layer. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; crawls a site every day with read-only GET requests (it never submits forms or places orders) and emails a report where each broken link, missing image, redirect chain or mixed-content warning is tied to the page and HTML element that caused it, which makes P1 triage fast. &lt;a href="https://uchik.com/how-it-works?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;How it works&lt;/a&gt; describes the crawl, the &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; lists the checks, and the &lt;a href="https://uchik.com/sample-report?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sample report&lt;/a&gt; shows what a client-ready report looks like. Limits worth knowing: it reads server-delivered HTML, so links added only by JavaScript may be missed, and it doesn't test that forms deliver. The free plan covers one website, so you can try it on one client before deciding. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agency checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Client inventory with critical paths, editors, access and scope&lt;/li&gt;
&lt;li&gt;[ ] Named accounts (CMS, Search Console) instead of shared logins&lt;/li&gt;
&lt;li&gt;[ ] Daily automated crawl per site, with source-page detail&lt;/li&gt;
&lt;li&gt;[ ] Weekly human check of critical paths and forms&lt;/li&gt;
&lt;li&gt;[ ] Monthly Search Console review per client&lt;/li&gt;
&lt;li&gt;[ ] P1–P4 triage rules agreed in the maintenance agreement&lt;/li&gt;
&lt;li&gt;[ ] Fix-at-source policy; single-hop redirects; no homepage catch-alls&lt;/li&gt;
&lt;li&gt;[ ] Monthly client report in plain language&lt;/li&gt;
&lt;li&gt;[ ] Pre/post crawl around client-led changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A broken link the client finds is a complaint. A broken link you found, fixed and reported is the reason they renew.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>agency</category>
      <category>freelance</category>
    </item>
    <item>
      <title>Automating Website Monitoring: What to Check Daily vs Weekly vs Monthly</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 15:59:46 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/automating-website-monitoring-what-to-check-daily-vs-weekly-vs-monthly-bd3</link>
      <guid>https://dev.to/sandeepsingh_uchik/automating-website-monitoring-what-to-check-daily-vs-weekly-vs-monthly-bd3</guid>
      <description>&lt;p&gt;Most websites aren't monitored at all. Or they're "monitored" by whoever happens to notice something odd, which usually means a customer emailing to say the checkout doesn't work, a week after it stopped working.&lt;/p&gt;

&lt;p&gt;The opposite problem is just as real. Teams that set up alerts for everything soon learn to ignore them, and an ignored alert is no better than no alert.&lt;/p&gt;

&lt;p&gt;The fix is a schedule that matches the &lt;strong&gt;frequency of each check&lt;/strong&gt; to two questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;How fast can this break?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How much does it cost while it's broken?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A site going completely down can happen any minute and costs everything while it lasts, so you check it constantly. A domain renewal can only fail once a year and gives you weeks of warning, so you check it monthly. Everything else sits somewhere in between.&lt;/p&gt;

&lt;p&gt;Here's a practical schedule for a small business, blog, store or SaaS marketing site, with what to automate at each level and a few small scripts to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous (every few minutes): is it up?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What:&lt;/strong&gt; Does the homepage, and your most important page (checkout, booking, sign-up, login), return a 200 quickly?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this often:&lt;/strong&gt; An outage is total. Every minute down means every visitor is lost, and it can happen at any time from a host failure, a bad deploy, an expired certificate or a DNS mistake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to automate:&lt;/strong&gt; An uptime monitoring service that requests a URL every few minutes from outside your network and alerts you (email, SMS, chat) when it fails. Many offer free tiers for a handful of URLs. Check that the monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks from outside your hosting provider (a monitor on the same server goes down with it)&lt;/li&gt;
&lt;li&gt;Looks for expected text on the page, not just a status code, because an error page can still return 200&lt;/li&gt;
&lt;li&gt;Requires a couple of consecutive failures before alerting, to avoid false alarms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep this list short: two to five URLs. This level is for "the site is down", not for every page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Daily: is anything broken?
&lt;/h2&gt;

&lt;p&gt;These are problems that don't take the site down but quietly cost visitors and sales. They can appear at any time (after an update, a content edit, a third-party change), and every day they go unnoticed costs you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Broken links and missing images
&lt;/h3&gt;

&lt;p&gt;Pages get edited, products get removed, external sites change, and links break. A daily crawl catches a broken "Buy now" button the day after someone deletes a product, not at the end of the quarter.&lt;/p&gt;

&lt;p&gt;What to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal links returning 4xx/5xx&lt;/li&gt;
&lt;li&gt;Missing images (404s on &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; sources)&lt;/li&gt;
&lt;li&gt;New redirect chains or loops&lt;/li&gt;
&lt;li&gt;Links to key conversion pages (cart, checkout, booking, pricing, contact) still resolving&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I walked through doing this by hand for one platform in &lt;a href="https://dev.to/sandeepsingh_uchik/how-to-find-and-fix-broken-links-on-a-shopify-store-33ll"&gt;How to Find and Fix Broken Links on a Shopify Store&lt;/a&gt;. The daily version is the same checks, automated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate validity
&lt;/h3&gt;

&lt;p&gt;Certificates are meant to renew automatically, but renewals do fail. Let's Encrypt certificates are valid for 90 days by default, and renewal is recommended every 60 days (&lt;a href="https://letsencrypt.org/docs/faq/" rel="noopener noreferrer"&gt;Let's Encrypt FAQ&lt;/a&gt;). A daily check that warns when fewer than about 20 days remain gives you plenty of time to fix a stuck renewal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# cert-check.sh: warn if a certificate expires within N days&lt;/span&gt;
&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?usage:&lt;span class="p"&gt; cert-check.sh example.com [days]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;20&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;cert&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-servername&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-connect&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;:443"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-enddate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cert&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;-f2&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-checkend&lt;/span&gt; &lt;span class="k"&gt;$((&lt;/span&gt;days&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;86400&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cert&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"WARNING: &lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt; certificate expires &lt;/span&gt;&lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"OK: &lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt; certificate valid until &lt;/span&gt;&lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Backups completed
&lt;/h3&gt;

&lt;p&gt;Not "are backups configured", but "did last night's backup finish?" Most backup tools can email on failure. Make sure that's turned on and goes to someone who reads it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key-page smoke test
&lt;/h3&gt;

&lt;p&gt;A tiny script that requests your critical URLs and checks for the expected content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# smoke.sh: each line of urls.txt is "&amp;lt;url&amp;gt; &amp;lt;text that must appear&amp;gt;"&lt;/span&gt;
&lt;span class="nv"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; url expect&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 20 &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n%{http_code}'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$expect&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAIL &lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt; (missing: &lt;/span&gt;&lt;span class="nv"&gt;$expect&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
  &lt;span class="k"&gt;fi
done&lt;/span&gt; &amp;lt; urls.txt
&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$fail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One caution: &lt;strong&gt;don't automate form submissions or test purchases on a live site&lt;/strong&gt; unless your platform provides a proper test mode. You'll fill your CRM with junk, trigger real emails or create real orders. Test forms by hand (weekly, below).&lt;/p&gt;

&lt;h3&gt;
  
  
  Running it on a schedule
&lt;/h3&gt;

&lt;p&gt;Any scheduler works: cron on a server you control, your host's scheduled tasks, or a CI service. With GitHub Actions, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daily-site-checks&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;   &lt;span class="c1"&gt;# 10:00 UTC daily&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash smoke.sh &amp;amp;&amp;amp; bash cert-check.sh example.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub's documentation notes that scheduled workflows run on the default branch, can be delayed during periods of high load (particularly at the start of each hour), and in public repositories are automatically disabled after 60 days without repository activity (&lt;a href="https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows" rel="noopener noreferrer"&gt;Events that trigger workflows&lt;/a&gt;). Pick an odd minute, and don't rely on this alone for time-critical alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly: the human checks
&lt;/h2&gt;

&lt;p&gt;Some things need a person, or are slow-moving enough that weekly is fine.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test every contact path by hand:&lt;/strong&gt; submit the contact form, try the booking flow, tap the phone link on mobile. Contact forms often fail silently after plugin updates or email configuration changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply updates&lt;/strong&gt; (CMS core, plugins, themes, dependencies), then re-run the daily checks and re-test forms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glance at Google Search Console.&lt;/strong&gt; It emails about new critical issues, but a weekly look at &lt;strong&gt;Indexing → Pages&lt;/strong&gt; for rising 404 or server-error counts catches slower trends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the week's daily reports&lt;/strong&gt; and fix anything still open.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Monthly: performance, content and access
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core Web Vitals.&lt;/strong&gt; Search Console's Core Web Vitals report "shows how your pages perform, based on real world usage data" (&lt;a href="https://support.google.com/webmasters/answer/9205520" rel="noopener noreferrer"&gt;Core Web Vitals report&lt;/a&gt;). Because it reflects real visitors over time, it moves slowly, so monthly is the right cadence. Google's targets are LCP within 2.5 s, INP under 200 ms and CLS under 0.1 (&lt;a href="https://developers.google.com/search/docs/appearance/core-web-vitals" rel="noopener noreferrer"&gt;Core Web Vitals&lt;/a&gt;). Use PageSpeed Insights to diagnose any template that's slipping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content accuracy:&lt;/strong&gt; prices, hours, staff, service areas, promotions that should have ended.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outbound links on your most-visited pages&lt;/strong&gt;, opened by hand, to check they still say what you linked them for. A status-code checker can't tell when a page changed content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access review:&lt;/strong&gt; remove accounts for people who no longer need them (CMS, hosting, registrar, analytics).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business listings:&lt;/strong&gt; check that the website URL on your Google Business Profile and other directories still points at a working page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Yearly: renewals and recovery
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain renewal.&lt;/strong&gt; Registrars of generic top-level domains must send at least two expiry reminders, about one month and about one week before expiry, under ICANN's &lt;a href="https://www.icann.org/resources/pages/errp-2013-02-28-en" rel="noopener noreferrer"&gt;Expired Registration Recovery Policy&lt;/a&gt;. That only helps if the contact email is current. Turn on auto-renew and confirm the payment card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restore test:&lt;/strong&gt; restore a backup somewhere safe and confirm the site works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation:&lt;/strong&gt; who owns the domain, hosting, DNS, email and each account, and when each renews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility and SEO review&lt;/strong&gt; of key templates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a non-technical version of this list, see &lt;a href="https://dev.to/sandeepsingh_uchik/the-website-maintenance-checklist-for-small-business-websites-5eg0"&gt;The Website Maintenance Checklist for Small Business Websites&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alerts versus reports
&lt;/h2&gt;

&lt;p&gt;One last design decision makes the difference between monitoring people use and monitoring they mute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alerts&lt;/strong&gt; (immediate, interruptive) are only for things that need action &lt;em&gt;now&lt;/em&gt;: site down, checkout down, certificate expiring within days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reports&lt;/strong&gt; (daily or weekly digests) are for everything else: broken links, missing images, redirect chains, slow templates. Nobody needs a 2 a.m. page about a broken link in a blog post from 2021, but they do need to see it in tomorrow's report.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an alert fires and the right response is "I'll look at it later", it should have been a report.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Uchik, which handles the daily report layer. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; crawls your site once a day with read-only requests (it never submits forms or places orders) and emails a report of broken links, missing images, redirect chains, mixed content and basic SEO gaps, each tied to the exact page and HTML element. &lt;a href="https://uchik.com/how-it-works?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;How it works&lt;/a&gt; explains the crawl, the &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; lists the checks, and the &lt;a href="https://uchik.com/sample-report?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sample report&lt;/a&gt; shows what arrives in your inbox. It isn't an uptime monitor and it doesn't measure Core Web Vitals, so pair it with the continuous and monthly checks above. Links added only by JavaScript may be missed. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The schedule on one page
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cadence&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Automate?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Every few minutes&lt;/td&gt;
&lt;td&gt;Homepage + critical page up, expected text present&lt;/td&gt;
&lt;td&gt;Yes: uptime monitor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;Broken links, missing images, redirects&lt;/td&gt;
&lt;td&gt;Yes: crawler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;Certificate expiry &amp;gt; ~20 days&lt;/td&gt;
&lt;td&gt;Yes: script or monitor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;Backup succeeded&lt;/td&gt;
&lt;td&gt;Yes: backup tool alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;Critical URLs return expected content&lt;/td&gt;
&lt;td&gt;Yes: smoke script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;td&gt;Forms, booking, phone links&lt;/td&gt;
&lt;td&gt;No: test by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;td&gt;Updates applied, then re-tested&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;td&gt;Search Console indexing trends&lt;/td&gt;
&lt;td&gt;Email alerts + quick look&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;Core Web Vitals (field data)&lt;/td&gt;
&lt;td&gt;Report; review by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;Content accuracy, outbound links, access, listings&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yearly&lt;/td&gt;
&lt;td&gt;Domain renewal, restore test, documentation&lt;/td&gt;
&lt;td&gt;Auto-renew + calendar&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Start with the first two rows this week. Together they cover most of what quietly costs small sites visitors and sales.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>monitoring</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>Broken Links and Core Web Vitals: What Actually Affects Rankings</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 15:59:30 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/broken-links-and-core-web-vitals-what-actually-affects-rankings-fpa</link>
      <guid>https://dev.to/sandeepsingh_uchik/broken-links-and-core-web-vitals-what-actually-affects-rankings-fpa</guid>
      <description>&lt;p&gt;Ask in any web forum whether broken links hurt SEO and you'll get confident answers in every direction. "Every 404 is a ranking penalty." "Google doesn't care about 404s at all." "Fix your Core Web Vitals or you'll never rank." "Core Web Vitals don't matter."&lt;/p&gt;

&lt;p&gt;Most of these are partly true and mostly unhelpful. This article sticks to what Google's own documentation says, quotes it directly, and separates three different questions that usually get blurred together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do broken links affect &lt;strong&gt;rankings&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Do Core Web Vitals affect &lt;strong&gt;rankings&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;What do both cost you &lt;strong&gt;regardless of rankings&lt;/strong&gt;?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That third question is the one people skip, and it's often where the real losses are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: What Google says about 404s and broken links
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A 404 on its own is not a penalty
&lt;/h3&gt;

&lt;p&gt;Google's Search Central blog answers this directly: "404 errors are a perfectly normal part of the web", and "If some URLs on your site 404, this fact alone does not hurt you or count against you in Google's search results." It goes further: the fact that some URLs return 404 "does not affect how your site's other URLs (the ones that return 200 (Success) status codes) perform in our search results" (&lt;a href="https://developers.google.com/search/blog/2011/05/do-404s-hurt-my-site" rel="noopener noreferrer"&gt;Do 404 errors hurt my site?&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Google actually &lt;em&gt;prefers&lt;/em&gt; a proper 404 or 410 for content you've removed, rather than a fake success page.&lt;/p&gt;

&lt;h3&gt;
  
  
  But a 404 on a page that matters is a real loss
&lt;/h3&gt;

&lt;p&gt;The same post adds the important caveat: "if some of the pages that 404 are pages you actually care about, you should look into why." And Google's HTTP status documentation explains what happens to an indexed page that starts returning 4xx: "the indexing pipeline removes the URL from the index if it was previously indexed" (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/http-network-errors" rel="noopener noreferrer"&gt;HTTP status codes&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A random old URL returning 404 → normal, no penalty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your ranking product page, service page or popular post&lt;/strong&gt; returning 404 → it drops out of search results. That isn't a "penalty", but the effect on your traffic is the same.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Internal links are how Google finds your pages
&lt;/h3&gt;

&lt;p&gt;Broken &lt;em&gt;internal&lt;/em&gt; links matter for a different reason: discovery. Google's link guidance says links are used to find new pages to crawl, and that "every page you care about should have a link from at least one other page on your site" (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/links-crawlable" rel="noopener noreferrer"&gt;Link best practices&lt;/a&gt;). If the only link to a page is broken, or points through a JavaScript handler instead of a real &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt;, that page is harder for Google to discover.&lt;/p&gt;

&lt;h3&gt;
  
  
  Soft 404s and homepage redirects are worse than honest 404s
&lt;/h3&gt;

&lt;p&gt;Google warns about two patterns: serving a "not found" page with a 200 status, and redirecting "any unknown URLs to their home page instead of returning 404 errors". Both "can have negative effects on our understanding and indexing" of your site (&lt;a href="https://developers.google.com/search/blog/2011/05/do-404s-hurt-my-site" rel="noopener noreferrer"&gt;Do 404 errors hurt my site?&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Broken outbound links
&lt;/h3&gt;

&lt;p&gt;I couldn't find any Google documentation saying broken links to &lt;em&gt;other&lt;/em&gt; sites lower your rankings, so I won't claim they do. What they clearly do is make your content less useful and less trustworthy to readers, which is a good enough reason to fix them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: What Google says about Core Web Vitals
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The metrics and thresholds
&lt;/h3&gt;

&lt;p&gt;Google's documentation defines three Core Web Vitals and the targets for a good experience (&lt;a href="https://developers.google.com/search/docs/appearance/core-web-vitals" rel="noopener noreferrer"&gt;Core Web Vitals&lt;/a&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Measures&lt;/th&gt;
&lt;th&gt;Good&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Largest Contentful Paint (LCP)&lt;/td&gt;
&lt;td&gt;Loading&lt;/td&gt;
&lt;td&gt;within 2.5 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interaction to Next Paint (INP)&lt;/td&gt;
&lt;td&gt;Responsiveness&lt;/td&gt;
&lt;td&gt;less than 200 milliseconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cumulative Layout Shift (CLS)&lt;/td&gt;
&lt;td&gt;Visual stability&lt;/td&gt;
&lt;td&gt;less than 0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;web.dev adds how these are assessed: a good threshold to measure is "the 75th percentile of page loads, segmented across mobile and desktop devices" (&lt;a href="https://web.dev/articles/vitals" rel="noopener noreferrer"&gt;Web Vitals&lt;/a&gt;). In other words, most of your real visitors need a good experience, not just the developer on fast office Wi-Fi.&lt;/p&gt;

&lt;h3&gt;
  
  
  Yes, they're used in ranking, but they aren't decisive
&lt;/h3&gt;

&lt;p&gt;Google's page experience documentation says: "Core Web Vitals are used by our ranking systems." In the same section it adds two important qualifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Getting good results in reports like Search Console's Core Web Vitals report or third-party tools doesn't guarantee that your pages will rank at the top of Google Search results."&lt;/li&gt;
&lt;li&gt;"Google Search always seeks to show the most relevant content, even if the page experience is sub-par. But for many queries, there is lots of helpful content available. Having a great page experience can contribute to success in Search, in such cases."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(&lt;a href="https://developers.google.com/search/docs/appearance/page-experience" rel="noopener noreferrer"&gt;Understanding page experience&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;So the accurate summary is that Core Web Vitals are a real signal that can make a difference when competing pages are similarly relevant. They won't make an unhelpful page rank, and chasing a perfect score isn't worthwhile. Google says there is "no single signal" for page experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lab scores versus field data
&lt;/h3&gt;

&lt;p&gt;Lighthouse and PageSpeed Insights' lab results are diagnostic tools: one simulated load, on one simulated device. Search Console's Core Web Vitals report is different: it "shows how your pages perform, based on real world usage data (sometimes called field data)" (&lt;a href="https://support.google.com/webmasters/answer/9205520" rel="noopener noreferrer"&gt;Core Web Vitals report&lt;/a&gt;). If you want to know how real visitors experience your site, the field data is what counts. Use Lighthouse to find out &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rest of page experience
&lt;/h3&gt;

&lt;p&gt;Google's page experience page lists more than Core Web Vitals. It asks whether pages are "served in a secure fashion", display well on mobile, avoid excessive distracting ads and intrusive interstitials, and make the main content easy to distinguish. That "secure fashion" point is where HTTPS problems like mixed content come in. I covered fixing those in &lt;a href="https://dev.to/sandeepsingh_uchik/mixed-content-and-missing-images-a-practical-fix-guide-for-store-owners-5g90"&gt;Mixed Content and Missing Images: A Practical Fix Guide for Store Owners&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Where broken links and Core Web Vitals overlap
&lt;/h2&gt;

&lt;p&gt;They're usually discussed separately, but several "link health" problems are also performance problems:&lt;/p&gt;

&lt;h3&gt;
  
  
  Redirect chains slow every page load
&lt;/h3&gt;

&lt;p&gt;Every redirect is another network round trip before the page can even start loading. Lighthouse says this "can delay the loading of the resource by hundreds of milliseconds" and fails a page with two or more redirects (&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/redirects" rel="noopener noreferrer"&gt;Avoid multiple page redirects&lt;/a&gt;). web.dev's LCP guide makes the connection explicit: Time to First Byte includes redirect time, a high TTFB "can make achieving a 2.5 second LCP challenging, or even impossible", and "a common cause of a slow TTFB for an otherwise fast site is visitors arriving through multiple redirects" (&lt;a href="https://web.dev/articles/optimize-lcp" rel="noopener noreferrer"&gt;Optimize LCP&lt;/a&gt;). Visitors arriving from old internal links, ads or emails pay that cost. Updating internal links to point directly at final URLs is both a link fix and a speed fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Images without dimensions cause layout shift
&lt;/h3&gt;

&lt;p&gt;web.dev lists "images without dimensions" first among the most common causes of poor CLS (&lt;a href="https://web.dev/articles/optimize-cls" rel="noopener noreferrer"&gt;Optimize CLS&lt;/a&gt;). Broken image references and slow-loading images often come together on older templates, so when you're fixing missing images, add &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes (or CSS &lt;code&gt;aspect-ratio&lt;/code&gt;) at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Broken resources waste requests
&lt;/h3&gt;

&lt;p&gt;A page that requests a missing script, stylesheet, font or image still makes the request and waits for the 404. Removing references to resources that no longer exist is free performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: The costs that have nothing to do with rankings
&lt;/h2&gt;

&lt;p&gt;Even where Google says something "does not hurt you" in search, it can still cost you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A visitor who hits a 404 on a product, booking or sign-up page&lt;/strong&gt; is a lost conversion today, whatever your rankings are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A slow or jumpy page&lt;/strong&gt; frustrates visitors who already found you. Core Web Vitals exist because these metrics reflect real user experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken links in content&lt;/strong&gt; make readers trust your recommendations less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Links from other sites to your dead pages&lt;/strong&gt; stop sending referral visitors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the strongest argument for fixing link problems: not that Google punishes them, but that people do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5: A sensible priority order
&lt;/h2&gt;

&lt;p&gt;Based on all of the above, here's how I'd prioritise:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Important pages returning errors.&lt;/strong&gt; Ranking pages, money pages and pages with backlinks that return 404/5xx. Restore them or 301 them to the correct replacement. &lt;em&gt;(Direct loss of indexing and conversions.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken internal links to live content.&lt;/strong&gt; Fix at the source. &lt;em&gt;(Discovery and user experience.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Homepage-redirect catch-alls and soft 404s.&lt;/strong&gt; Return honest 404s or targeted redirects. &lt;em&gt;(Google explicitly warns about these.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirect chains in internal links and navigation.&lt;/strong&gt; Point links at final URLs. &lt;em&gt;(Speed and crawl efficiency.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core Web Vitals issues in field data,&lt;/strong&gt; starting with templates that affect many pages. &lt;em&gt;(A ranking signal and a user-experience issue.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed content and HTTPS problems.&lt;/strong&gt; &lt;em&gt;(Part of page experience, plus browser warnings.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken outbound links.&lt;/strong&gt; &lt;em&gt;(Trust and usefulness.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old, unlinked URLs returning 404.&lt;/strong&gt; Usually nothing to do. That's normal.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Part 6: Monitoring both
&lt;/h2&gt;

&lt;p&gt;The tools are complementary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search Console:&lt;/strong&gt; Page indexing (404s Google sees), Core Web Vitals (field data), HTTPS report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PageSpeed Insights / Lighthouse:&lt;/strong&gt; diagnose &lt;em&gt;why&lt;/em&gt; a page is slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A crawler&lt;/strong&gt; (Screaming Frog's free tier handles up to 500 URLs; &lt;a href="https://www.screamingfrog.co.uk/seo-spider/" rel="noopener noreferrer"&gt;limits&lt;/a&gt;): every broken link, redirect and missing image, with the page it's on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For personal and small sites, &lt;a href="https://dev.to/sandeepsingh_uchik/seo-health-basics-for-personal-websites-and-portfolios-54hp"&gt;SEO Health Basics for Personal Websites and Portfolios&lt;/a&gt; turns this into a short routine.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Uchik, which covers the crawling side. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; checks your site daily and emails a report of broken links, missing images, redirect chains, mixed content and basic on-page SEO gaps, each tied to the exact page and HTML element. &lt;a href="https://uchik.com/how-it-works?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;How it works&lt;/a&gt; explains the method, and the &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; lists what it checks. It doesn't measure Core Web Vitals; use Search Console and PageSpeed Insights for that. The &lt;a href="https://uchik.com/faq?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt; covers other limits, including that links added only by JavaScript may be missed. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A 404 on its own isn't a ranking penalty, but a 404 on a page that ranked removes it from the index.&lt;/li&gt;
&lt;li&gt;Broken internal links hurt discovery, and homepage catch-all redirects are explicitly discouraged.&lt;/li&gt;
&lt;li&gt;Core Web Vitals are used by ranking systems, but relevance comes first and good scores don't guarantee top rankings.&lt;/li&gt;
&lt;li&gt;Redirect chains and images without dimensions are both link-health and performance problems.&lt;/li&gt;
&lt;li&gt;The biggest cost of broken links is usually visitors, not rankings.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>seo</category>
      <category>webperf</category>
      <category>webdev</category>
      <category>corewebvitals</category>
    </item>
    <item>
      <title>How to Audit Internal Links on a WordPress Site</title>
      <dc:creator>Sandeep Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 15:53:30 +0000</pubDate>
      <link>https://dev.to/sandeepsingh_uchik/how-to-audit-internal-links-on-a-wordpress-site-149b</link>
      <guid>https://dev.to/sandeepsingh_uchik/how-to-audit-internal-links-on-a-wordpress-site-149b</guid>
      <description>&lt;p&gt;Internal links are the roads inside your website. They're how visitors move from a blog post to a product or service page, how they find related content, and how search engines discover and understand your pages. Google's own guidance says it plainly: "Every page you care about should have a link from at least one other page on your site" (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/links-crawlable" rel="noopener noreferrer"&gt;Link best practices&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;WordPress sites are especially prone to internal link problems because they grow over years. Posts get updated, slugs change, categories get reorganised, plugins come and go, themes get swapped, and the site gets moved between hosts or from staging to production. Each of those leaves some links pointing somewhere they shouldn't.&lt;/p&gt;

&lt;p&gt;The result is lost traffic you never see: a reader following "read my full guide" hits a 404 and leaves, a high-converting landing page has no links pointing to it so nobody finds it, and search engines spend their time on redirects instead of your content.&lt;/p&gt;

&lt;p&gt;This guide walks through a full internal link audit using free tools, then shows how to fix problems at the source rather than piling up redirects.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you're looking for
&lt;/h2&gt;

&lt;p&gt;An internal link audit checks for six things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Broken internal links:&lt;/strong&gt; links to pages on your own site that return 404 or another error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirected internal links:&lt;/strong&gt; links that work but go through one or more redirects first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orphan pages:&lt;/strong&gt; published pages that no other page links to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deeply buried pages:&lt;/strong&gt; important pages that take many clicks to reach from the homepage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak anchor text:&lt;/strong&gt; "click here" and "read more" instead of descriptive text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leftover URLs:&lt;/strong&gt; links to your staging domain, &lt;code&gt;http://&lt;/code&gt; versions or old domain.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1: Crawl the site
&lt;/h2&gt;

&lt;p&gt;You need a full list of every internal link: the page it's on, where it points and what status that target returns. Some free options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Screaming Frog SEO Spider&lt;/strong&gt; (free up to 500 URLs; &lt;a href="https://www.screamingfrog.co.uk/seo-spider/" rel="noopener noreferrer"&gt;limits&lt;/a&gt;). Enter your homepage and crawl. Use &lt;strong&gt;Response Codes&lt;/strong&gt; to filter internal URLs by status, and the &lt;strong&gt;Inlinks&lt;/strong&gt; tab at the bottom to see every page that links to a selected URL, with anchor text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Broken Link Checker plugin&lt;/strong&gt; (&lt;a href="https://wordpress.org/plugins/broken-link-checker/" rel="noopener noreferrer"&gt;WordPress.org&lt;/a&gt;) checks links from inside WordPress and lets you edit or unlink from its dashboard. Plugin-based checkers use your server's resources, so check its settings on shared hosting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Search Console's Links report&lt;/strong&gt; shows your most internally linked pages from Google's point of view. It won't show broken links directly, but it's a useful sanity check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A crawler that starts from your homepage only finds pages reachable by links. That's exactly what you want for steps 2–5, and it's why orphan pages need a separate check in Step 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Fix broken internal links
&lt;/h2&gt;

&lt;p&gt;Filter the crawl for internal URLs returning &lt;strong&gt;4xx&lt;/strong&gt; and &lt;strong&gt;5xx&lt;/strong&gt;. For each one, open &lt;strong&gt;Inlinks&lt;/strong&gt; to see which posts or pages contain the bad link.&lt;/p&gt;

&lt;p&gt;Common causes on WordPress:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A post slug was changed.&lt;/strong&gt; WordPress core tries to rescue some of these: its &lt;code&gt;wp_old_slug_redirect()&lt;/code&gt; function attempts to find a post's current slug from past slugs and redirect (&lt;a href="https://developer.wordpress.org/reference/functions/wp_old_slug_redirect/" rel="noopener noreferrer"&gt;WordPress developer reference&lt;/a&gt;). That doesn't cover every case, such as pages, custom post types on some setups, or posts that were deleted and recreated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The permalink structure was changed&lt;/strong&gt; in &lt;strong&gt;Settings → Permalinks&lt;/strong&gt; (&lt;a href="https://wordpress.org/documentation/article/customize-permalinks/" rel="noopener noreferrer"&gt;WordPress docs&lt;/a&gt;). Links hard-coded in old content keep the old format.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A page was deleted or unpublished.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A plugin that created pages was removed:&lt;/strong&gt; shop, events, forums, landing page builders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category or tag base changed,&lt;/strong&gt; breaking every category link in old posts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Typos in manually entered links.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fix the link &lt;strong&gt;in the content&lt;/strong&gt; where it appears: edit the post, menu or widget and point it at the correct current URL. If the destination genuinely no longer exists, either link to the best replacement or remove the link.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Update redirected internal links
&lt;/h2&gt;

&lt;p&gt;Next, filter for internal links returning &lt;strong&gt;3xx&lt;/strong&gt;. These work, but every redirect is an extra request before the page loads. Lighthouse notes each redirect can delay a page "by hundreds of milliseconds" (&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/redirects" rel="noopener noreferrer"&gt;Avoid multiple page redirects&lt;/a&gt;), and chains make it worse.&lt;/p&gt;

&lt;p&gt;Typical WordPress sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Links to &lt;code&gt;http://&lt;/code&gt; versions of your own pages, redirected to &lt;code&gt;https://&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Links to &lt;code&gt;www&lt;/code&gt; when the site lives on the bare domain (or the reverse)&lt;/li&gt;
&lt;li&gt;Links missing the trailing slash, redirected to add it&lt;/li&gt;
&lt;li&gt;Old slugs being rescued by a redirect plugin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Redirects are the right tool for &lt;em&gt;external&lt;/em&gt; links and old bookmarks. For your own internal links, update the link to the final URL so visitors and crawlers get there directly. If you're seeing chains or loops, I covered fixing those in &lt;a href="https://sandeepsingh-uchik.hashnode.dev/redirect-chains-and-loops-what-they-cost-an-ecommerce-site-and-how-to-fix-them" rel="noopener noreferrer"&gt;Redirect Chains and Loops: What They Cost an Ecommerce Site and How to Fix Them&lt;/a&gt;, and &lt;a href="https://dev.to/sandeepsingh_uchik/mixed-content-and-missing-images-a-practical-fix-guide-for-store-owners-5g90"&gt;Mixed Content and Missing Images: A Practical Fix Guide for Store Owners&lt;/a&gt; covers the related &lt;code&gt;http://&lt;/code&gt; asset problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bulk-fixing domain and protocol changes
&lt;/h3&gt;

&lt;p&gt;If thousands of links point at an old domain or &lt;code&gt;http://&lt;/code&gt;, editing posts by hand isn't realistic. WP-CLI's search-replace handles it, including serialized data in options and widgets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Always preview first, and take a database backup&lt;/span&gt;
wp search-replace &lt;span class="s1"&gt;'http://example.com'&lt;/span&gt; &lt;span class="s1"&gt;'https://example.com'&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
wp search-replace &lt;span class="s1"&gt;'https://staging.example.com'&lt;/span&gt; &lt;span class="s1"&gt;'https://example.com'&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove &lt;code&gt;--dry-run&lt;/code&gt; only after checking the counts look right. Many hosts and migration plugins offer a similar search-and-replace tool if you don't have shell access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Find orphan pages
&lt;/h2&gt;

&lt;p&gt;An orphan page is published but not linked from anywhere on the site. A link-following crawler can't find it, and visitors are unlikely to either.&lt;/p&gt;

&lt;p&gt;To find them, compare &lt;strong&gt;what you've published&lt;/strong&gt; with &lt;strong&gt;what the crawler found&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the list of published URLs from your XML sitemap. Since WordPress 5.5, core generates a sitemap index at &lt;code&gt;/wp-sitemap.xml&lt;/code&gt; (&lt;a href="https://make.wordpress.org/core/2020/07/22/new-xml-sitemaps-functionality-in-wordpress-5-5/" rel="noopener noreferrer"&gt;Make WordPress Core&lt;/a&gt;); SEO plugins often replace it with their own, usually at &lt;code&gt;/sitemap_index.xml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Export the list of HTML URLs your crawler found by following links.&lt;/li&gt;
&lt;li&gt;Anything in the sitemap but not in the crawl is an orphan.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A spreadsheet works for this: paste both lists into separate columns and use a lookup to flag sitemap URLs missing from the crawl.&lt;/p&gt;

&lt;p&gt;For each orphan, decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Still valuable?&lt;/strong&gt; Link to it from relevant posts, a category page or a hub page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outdated or thin?&lt;/strong&gt; Update it, merge it into a stronger page (and redirect), or unpublish it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intentionally hidden&lt;/strong&gt; (a thank-you page or a campaign landing page)? Consider whether it should be in the sitemap at all, or whether it should carry &lt;code&gt;noindex&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Check click depth for important pages
&lt;/h2&gt;

&lt;p&gt;Most crawlers report &lt;strong&gt;crawl depth&lt;/strong&gt;: how many clicks from the homepage each page is. Your most important pages (key services, best guides, top products) shouldn't be buried at depth 5 behind pagination.&lt;/p&gt;

&lt;p&gt;Ways to bring them closer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Link to them from the main menu or a footer section&lt;/li&gt;
&lt;li&gt;Create a hub or "start here" page linking to your best content&lt;/li&gt;
&lt;li&gt;Add contextual links from related, popular posts&lt;/li&gt;
&lt;li&gt;Use breadcrumbs (many themes and SEO plugins provide them)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 6: Improve anchor text
&lt;/h2&gt;

&lt;p&gt;Google says good anchor text "is descriptive, reasonably concise, and relevant to the page that it's on and to the page it links to", and that it helps "both people and Google make sense of your site" (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/links-crawlable" rel="noopener noreferrer"&gt;Link best practices&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In the crawl's &lt;strong&gt;Inlinks&lt;/strong&gt; or &lt;strong&gt;All anchor text&lt;/strong&gt; export, look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generic text: "click here", "read more", "this post", "here"&lt;/li&gt;
&lt;li&gt;Empty anchors, such as image links with no alt text&lt;/li&gt;
&lt;li&gt;The same exact keyword phrase used for every link to a page, which reads awkwardly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rewrite them as natural descriptions: "see our guide to [choosing a running shoe]" rather than "[click here] for our guide".&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Check your theme and page builder output
&lt;/h2&gt;

&lt;p&gt;Two WordPress-specific issues to verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Links must be real &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; elements.&lt;/strong&gt; Google says it can generally only crawl links that are &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; elements with an &lt;code&gt;href&lt;/code&gt; attribute (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/links-crawlable" rel="noopener noreferrer"&gt;Link best practices&lt;/a&gt;). Some page builders and theme widgets render buttons or "clickable boxes" with JavaScript &lt;code&gt;onclick&lt;/code&gt; handlers instead. View the source of a few pages and check that menus, buttons and cards are real links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related-posts and widget links.&lt;/strong&gt; These are generated automatically and can point at drafts, trashed posts or attachment pages. Check them in the crawl, too.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 8: Make it a habit
&lt;/h2&gt;

&lt;p&gt;A one-off audit fixes today's problems. WordPress sites keep changing, so links keep breaking. A sustainable routine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;After every significant change&lt;/strong&gt; (theme switch, permalink change, plugin removal, migration): crawl and fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly:&lt;/strong&gt; a quick crawl filtered for 4xx and 3xx internal links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quarterly:&lt;/strong&gt; the orphan-page and anchor-text review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/sandeepsingh_uchik/the-website-maintenance-checklist-for-small-business-websites-5eg0"&gt;The Website Maintenance Checklist for Small Business Websites&lt;/a&gt; puts internal link checks in context alongside backups, updates and renewals.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Uchik, which automates the recurring part. &lt;a href="https://uchik.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;Uchik&lt;/a&gt; crawls your site every day and emails a report of broken internal and external links, missing images, redirect chains and HTTPS problems, each with the source page and the exact HTML element to fix. It runs as an external crawler, so there's no plugin to install or keep updated. The &lt;a href="https://uchik.com/features?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;features page&lt;/a&gt; lists every check, the &lt;a href="https://uchik.com/sample-report?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sample report&lt;/a&gt; shows the output, and the &lt;a href="https://uchik.com/faq?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt; explains its limits, including that links added only by JavaScript may be missed. It doesn't do the orphan-page comparison in Step 4; keep that in your quarterly review. You can &lt;a href="https://uchik.com/signup?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=oct26-1k" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Full crawl from the homepage&lt;/li&gt;
&lt;li&gt;[ ] Broken internal links fixed at the source&lt;/li&gt;
&lt;li&gt;[ ] Redirected internal links updated to final URLs&lt;/li&gt;
&lt;li&gt;[ ] Old domain, staging and &lt;code&gt;http://&lt;/code&gt; links bulk-replaced (with backup and dry run)&lt;/li&gt;
&lt;li&gt;[ ] Sitemap vs crawl compared; orphans linked, merged or removed&lt;/li&gt;
&lt;li&gt;[ ] Important pages within a few clicks of the homepage&lt;/li&gt;
&lt;li&gt;[ ] Generic anchor text rewritten&lt;/li&gt;
&lt;li&gt;[ ] Menus, buttons and cards confirmed as real &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; links&lt;/li&gt;
&lt;li&gt;[ ] Recurring check scheduled or automated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Internal links are the one part of your site's linking you fully control. A few hours of auditing keeps readers and crawlers moving to the pages that matter.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>seo</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
