<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Riichi Mirza]]></title><description><![CDATA[Riichi Mirza]]></description><link>https://riichimirza.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 30 Oct 2026 15:55:12 GMT</lastBuildDate><atom:link href="https://riichimirza.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Pagination & Archive SEO Checklist for WordPress (2026): page/2, Author and Date Archives]]></title><description><![CDATA[A checklist for small WordPress sites to cut crawl waste from paginated lists, author archives and date archives, without breaking discovery of your posts.]]></description><link>https://riichimirza.hashnode.dev/pagination-archive-seo-checklist-for-wordpress-2026-page-2-author-and-date-archives</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/pagination-archive-seo-checklist-for-wordpress-2026-page-2-author-and-date-archives</guid><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><category><![CDATA[webdev]]></category><category><![CDATA[WordPress]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 23:23:15 GMT</pubDate><content:encoded><![CDATA[<p>A checklist for small WordPress sites to cut crawl waste from paginated lists, author archives and date archives, without breaking discovery of your posts.</p>
<h2>1. Know the 2026 baseline</h2>
<ul>
<li><input disabled type="checkbox" /> Accept that <code>rel="next/prev"</code> is not used by Google for indexing (since 2019). Optional, harmless, not a lever.</li>
<li><input disabled type="checkbox" /> Focus instead on crawlable <code>&lt;a href&gt;</code> pagination links and correct canonicals.</li>
<li><input disabled type="checkbox" /> Remember: crawl waste on small sites is about clarity, not penalties.</li>
</ul>
<h2>2. Paginated lists (/page/2/+)</h2>
<ul>
<li><input disabled type="checkbox" /> Page 2+ has a <strong>self-referencing canonical</strong>, not one pointing to page 1.</li>
<li><input disabled type="checkbox" /> Pagination links are plain HTML links (infinite scroll also exposes <code>/page/N/</code> URLs).</li>
<li><input disabled type="checkbox" /> Posts per page set to 12–20 to reduce depth.</li>
<li><input disabled type="checkbox" /> Oldest important posts are reachable within ~3–4 clicks via hubs or categories.</li>
<li><input disabled type="checkbox" /> If you noindex page 2+, every post is still linked from somewhere indexable and in the sitemap.</li>
</ul>
<p>Refresher on canonical logic: <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">How to fix duplicate content with canonical tags</a>.</p>
<h2>3. View All vs paginated</h2>
<ul>
<li><input disabled type="checkbox" /> Use a View All page only if it loads fast (good LCP/INP on mobile).</li>
<li><input disabled type="checkbox" /> If using View All as canonical, paginated pages canonical to it consistently.</li>
<li><input disabled type="checkbox" /> Otherwise keep paginated pages self-canonical.</li>
</ul>
<h2>4. Author archives</h2>
<ul>
<li><input disabled type="checkbox" /> Single author: noindex or disable <code>/author/name/</code> (it duplicates <code>/blog/</code>).</li>
<li><input disabled type="checkbox" /> Multi author: keep indexable only with real bio, photo, expertise and profile links.</li>
<li><input disabled type="checkbox" /> No <code>/author/admin/</code> slug exposed; rename the user nicename/display name.</li>
<li><input disabled type="checkbox" /> Author archives excluded from the sitemap if noindexed.</li>
</ul>
<h2>5. Date archives</h2>
<ul>
<li><input disabled type="checkbox" /> Noindex or disable year/month/day archives unless chronology is the content.</li>
<li><input disabled type="checkbox" /> Remove "Archives" date widgets from sidebar/footer.</li>
<li><input disabled type="checkbox" /> Exclude date archives from the XML sitemap.</li>
</ul>
<h2>6. Parameter page= URLs</h2>
<ul>
<li><input disabled type="checkbox" /> Search for <code>?page=</code>, <code>?paged=</code>, <code>?orderby=</code> variants in GSC and crawl tools.</li>
<li><input disabled type="checkbox" /> Canonical them to clean <code>/page/N/</code> or base URLs.</li>
<li><input disabled type="checkbox" /> Do not robots.txt-block URLs you want Google to read canonicals on.</li>
<li><input disabled type="checkbox" /> Share clean links externally; a <a href="https://riichimirza.com/tools/url-shortener/">free URL shortener</a> helps for campaigns.</li>
</ul>
<h2>7. Infinite scroll / Load more</h2>
<ul>
<li><input disabled type="checkbox" /> Every JS-loaded chunk also exists at a real <code>/page/N/</code> URL.</li>
<li><input disabled type="checkbox" /> A plain HTML link to the next page is present in source.</li>
<li><input disabled type="checkbox" /> Test with JavaScript disabled: older posts still reachable.</li>
<li><input disabled type="checkbox" /> URL updates (History API) as users scroll, so shared links land in the right place.</li>
</ul>
<h2>8. Titles on paginated pages</h2>
<ul>
<li><input disabled type="checkbox" /> Plugin appends "Page N of X" to titles of page 2+.</li>
<li><input disabled type="checkbox" /> No hand-written unique meta for each list page; a pattern is enough.</li>
<li><input disabled type="checkbox" /> Category pagination follows the same rules as blog pagination.</li>
</ul>
<h2>9. Plugin settings</h2>
<ul>
<li><input disabled type="checkbox" /> Yoast: Advanced &gt; Author/Date archives set to not show in search results.</li>
<li><input disabled type="checkbox" /> Rank Math: Titles &amp; Meta &gt; Authors / Misc Pages set to noindex.</li>
<li><input disabled type="checkbox" /> SEOPress: Titles &amp; Metas &gt; Archives &gt; noindex author/date.</li>
<li><input disabled type="checkbox" /> Sitemap index rechecked after the change.</li>
</ul>
<h2>10. Verify in Search Console</h2>
<ul>
<li><input disabled type="checkbox" /> Pages report: check "Duplicate without user-selected canonical" for archive URLs.</li>
<li><input disabled type="checkbox" /> URL Inspection on <code>/page/2/</code> and an author URL: confirm Google-selected canonical.</li>
<li><input disabled type="checkbox" /> Re-check after 2–4 weeks.</li>
</ul>
<h2>Common mistakes to avoid</h2>
<ul>
<li><strong>Canonical everything to the homepage.</strong> Some themes or old plugins set a site-wide canonical. Every archive then claims to be the homepage, and Google ignores the hint. Check source on three different URL types.</li>
<li><strong>Noindex plus robots.txt block.</strong> If you block a URL in robots.txt, Google can't see the noindex tag, so the URL may stay indexed as a bare link. Pick noindex and let it be crawled.</li>
<li><strong>Disabling archives without redirects.</strong> If author archives were indexed and linked externally, a 301 to the About page preserves value; a sudden 404 wastes it.</li>
<li><strong>Forgetting the sitemap.</strong> A noindexed URL listed in the sitemap sends mixed signals. Regenerate after changes.</li>
<li><strong>Fixing it once.</strong> New themes and plugins reintroduce archive widgets and parameter links. Re-run this checklist after any theme switch.</li>
</ul>
<h2>Why this still matters in 2026</h2>
<p>Google is good at ignoring junk URLs on its own. But on a small site, every crawl of <code>/2022/07/</code> or <code>/author/admin/page/4/</code> is a crawl that didn't refresh a post you just updated. Tidy archives also make Search Console reports readable, so real problems stand out.</p>
<h2>Quick decision table</h2>
<table>
<thead>
<tr>
<th>URL type</th>
<th>Small single-author site</th>
<th>Multi-author / news</th>
</tr>
</thead>
<tbody><tr>
<td>/page/2/+</td>
<td>Index, self-canonical</td>
<td>Index, self-canonical</td>
</tr>
<tr>
<td>/author/x/</td>
<td>Noindex or disable</td>
<td>Index with real bio</td>
</tr>
<tr>
<td>/2024/03/</td>
<td>Noindex</td>
<td>Index if chronological</td>
</tr>
<tr>
<td>?page=N</td>
<td>Canonical to clean URL</td>
<td>Same</td>
</tr>
</tbody></table>
<p>More WordPress technical SEO notes on the <a href="https://riichimirza.com/blog/">blog</a>; free utilities on the <a href="https://riichimirza.com/tools/">tools page</a>.</p>
]]></content:encoded></item><item><title><![CDATA[The 40-Minute Breadcrumb and Thin Archive Checklist for Small WordPress Sites]]></title><description><![CDATA[You don't need an audit tool to clean up breadcrumbs and taxonomy archives on a small WordPress site. You need 40 minutes, Google Search Console, the Rich…]]></description><link>https://riichimirza.hashnode.dev/the-40-minute-breadcrumb-and-thin-archive-checklist-for-small-wordpress-sites</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/the-40-minute-breadcrumb-and-thin-archive-checklist-for-small-wordpress-sites</guid><category><![CDATA[webdev]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 23:14:02 GMT</pubDate><content:encoded><![CDATA[<p>You don't need an audit tool to clean up breadcrumbs and taxonomy archives on a small WordPress site. You need 40 minutes, Google Search Console, the Rich Results Test, and your SEO plugin's settings. Here's the order I work in, with a time box for each step.</p>
<p>Why bother in 2026? Google still uses BreadcrumbList to understand site structure and to show the path in results. Thin tag and category archives are still one of the biggest sources of wasted crawl on small blogs. Neither is glamorous, and fixing both usually takes one sitting.</p>
<h2>Minutes 0–5: count your breadcrumb sources</h2>
<p>Open three URLs: a post, a page, and a category archive. On each one:</p>
<ul>
<li>View the source and search for <code>BreadcrumbList</code>.</li>
<li>Run the URL through the Rich Results Test.</li>
</ul>
<p><strong>Pass:</strong> exactly one BreadcrumbList per URL.
<strong>Fail:</strong> two or more (theme microdata plus Rank Math/Yoast JSON-LD plus WooCommerce, for example). Keep the SEO plugin's version and turn off the theme's breadcrumbs in the Customizer or theme options.</p>
<h2>Minutes 5–12: match the visible trail to the markup</h2>
<p>For the same three URLs, write down the crumbs you can see and compare them with the <code>itemListElement</code> list in the markup.</p>
<ul>
<li><input disabled type="checkbox" /> Same number of positions</li>
<li><input disabled type="checkbox" /> Same labels (no slugs like "blog-2" in the schema)</li>
<li><input disabled type="checkbox" /> Same category in the middle crumb</li>
</ul>
<p>If the middle crumb differs, it's almost always because no primary category is set. Set one on your top 10 posts now and note the rest as a follow-up. If you still have an "Uncategorized" category, rename it to something real.</p>
<h2>Minutes 12–17: check every crumb URL</h2>
<p>Click every link in the trail.</p>
<ul>
<li><input disabled type="checkbox" /> Each one returns 200 (no redirect hop)</li>
<li><input disabled type="checkbox" /> Each one uses your canonical host and protocol</li>
<li><input disabled type="checkbox" /> None of them is noindexed</li>
</ul>
<p>A crumb pointing to a noindexed or redirecting archive is a mixed signal. Fix the link, or make that level a real hub. If you're not sure which version of a URL is the "real" one, my guide to <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">fixing duplicate content with canonical tags</a> walks through it.</p>
<h2>Minutes 17–27: triage tags and categories</h2>
<p>In GSC, go to Performance → Pages and filter by <code>/tag/</code>, then by <code>/category/</code>. Then open Indexing → Pages → "Crawled – currently not indexed".</p>
<p>For each term, decide:</p>
<table>
<thead>
<tr>
<th>Situation</th>
<th>Action</th>
</tr>
</thead>
<tbody><tr>
<td>Tag archive, any size</td>
<td>Noindex, follow (default for small sites)</td>
</tr>
<tr>
<td>Category with 6+ related posts and real search intent</td>
<td>Keep indexed; add a 100–200 word intro</td>
</tr>
<tr>
<td>Category with 1–5 posts</td>
<td>Merge into a stronger category; 301 the old archive</td>
</tr>
<tr>
<td>Term with 0 posts</td>
<td>Delete</td>
</tr>
<tr>
<td>Archive outranking its own post for a query</td>
<td>Noindex the archive or strengthen internal links to the post</td>
</tr>
</tbody></table>
<p>Plugin paths: Rank Math → Titles &amp; Meta → Tags/Categories; Yoast → Settings → Taxonomies.</p>
<h2>Minutes 27–32: other archive types</h2>
<ul>
<li><input disabled type="checkbox" /> Date archives off or noindexed</li>
<li><input disabled type="checkbox" /> Author archives noindexed on single-author sites</li>
<li><input disabled type="checkbox" /> Post-format archives off</li>
<li><input disabled type="checkbox" /> Media attachment pages redirect to the file or the parent post (both plugins have a toggle)</li>
</ul>
<h2>Minutes 32–36: pagination</h2>
<p>Open <code>/category/your-main-category/page/2/</code>.</p>
<ul>
<li><input disabled type="checkbox" /> Canonical points to itself, <strong>not</strong> to page 1</li>
<li><input disabled type="checkbox" /> Not noindexed if the category is indexable</li>
<li><input disabled type="checkbox" /> Normal links to page 3 and back to page 1</li>
</ul>
<p>If a category goes past 8–10 pages, link to the older posts directly from a hub page or split the category into subtopics.</p>
<h2>Minutes 36–40: sitemap and links</h2>
<ul>
<li><input disabled type="checkbox" /> Noindexed taxonomies don't appear in the sitemap index</li>
<li><input disabled type="checkbox" /> Merged categories have 301s and no internal links still point at the old URL</li>
<li><input disabled type="checkbox" /> Links you share in newsletters or socials use clean URLs. I use a <a href="https://riichimirza.com/tools/url-shortener/">free URL shortener</a> so tracking parameters don't create more URL variants.</li>
</ul>
<p>Request indexing for one fixed post and one category hub, then come back in two weeks to see whether the "Crawled – currently not indexed" count for taxonomy URLs has gone down.</p>
<h2>Three mistakes to avoid while you're doing this</h2>
<ul>
<li><strong>Noindexing every category at once.</strong> Good categories are useful hubs. Only noindex the thin ones, and merge where you can.</li>
<li><strong>Using <code>nofollow</code> on archives.</strong> Use noindex, follow, so crawlers can still reach the posts listed on the archive.</li>
<li><strong>Deleting terms without redirects.</strong> If an archive URL has backlinks or impressions, 301 it to the closest category.</li>
</ul>
<h2>Save this checklist</h2>
<ol>
<li>One BreadcrumbList per URL</li>
<li>Visible trail = markup</li>
<li>Primary category set; no "Uncategorized"</li>
<li>Crumb URLs are 200, canonical, and indexable</li>
<li>Tags noindexed, follow kept</li>
<li>Thin categories merged with 301s</li>
<li>Date, author, and format archives handled</li>
<li>Pagination canonicalises to itself</li>
<li>Sitemap matches your index decisions</li>
</ol>
<p>More short checklists are on <a href="https://riichimirza.com/blog/">my blog</a>, and free utilities are on the <a href="https://riichimirza.com/tools/">tools page</a>.</p>
]]></content:encoded></item><item><title><![CDATA[The 40-Minute FAQ / HowTo / LocalBusiness Schema Checklist for Small WordPress Sites]]></title><description><![CDATA[Rank Math or Yoast says FAQ schema is “on.” Rich Results Test still complains. The footer phone does not match JSON-LD. On small WordPress sites the fix is…]]></description><link>https://riichimirza.hashnode.dev/the-40-minute-faq-howto-localbusiness-schema-checklist-for-small-wordpress-sites</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/the-40-minute-faq-howto-localbusiness-schema-checklist-for-small-wordpress-sites</guid><category><![CDATA[SEO]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[technical seo]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 23:03:36 GMT</pubDate><content:encoded><![CDATA[<p>Rank Math or Yoast says FAQ schema is “on.” Rich Results Test still complains. The footer phone does not match JSON-LD. On small WordPress sites the fix is usually <strong>schema hygiene on money URLs</strong>, not another Schema.org pack. This checklist covers <strong>FAQPage spam from FAQ blocks</strong>, <strong>duplicate FAQ on every page</strong>, <strong>HowTo on non-howto pages</strong>, <strong>LocalBusiness NAP mismatches</strong>, <strong>missing required properties</strong>, and <strong>testing with Rich Results Test</strong>. About <strong>40 minutes</strong>. Distinct from title/meta and image/OG work.</p>
<p>2026 reality check: Google does not show FAQ or HowTo rich results for ordinary sites anymore. Clean markup anyway (spammy/invisible FAQ still looks bad to validators and other consumers). Put real eligibility energy into <strong>accurate LocalBusiness</strong>.</p>
<p>Forty minutes is enough for Home, top services/locations, and one real FAQ URL. It is not enough to invent a Schema.org strategy deck — and you should not.</p>
<h2>Before you start (4 minutes)</h2>
<p>Write five lines: money URLs; whether Rank Math, Yoast, or both write schema (keep <strong>one</strong>); any dedicated FAQ page; Google Business Profile name/phone/address; known Rich Results / GSC structured-data warnings.</p>
<p>If the same FAQ copy lives on overlapping URLs, settle ownership with <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">canonical tags</a> before polishing JSON-LD on a duplicate.</p>
<h2>Part 1 — Inventory schema types (8 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> View source or plugin schema preview on Home, 3 money URLs, 1 blog post, FAQ page (if any)</li>
<li><input disabled type="checkbox" /> Note every <code>FAQPage</code>, <code>HowTo</code>, <code>LocalBusiness</code> / <code>Organization</code> emission</li>
<li><input disabled type="checkbox" /> Flag <strong>two plugins</strong> both writing the same type</li>
<li><input disabled type="checkbox" /> Flag FAQ JSON-LD on pages with <strong>no visible Q&amp;A</strong></li>
<li><input disabled type="checkbox" /> Flag the <strong>same FAQ questions</strong> repeating across URLs</li>
<li><input disabled type="checkbox" /> Flag HowTo on marketing/pricing pages that are not procedures</li>
</ul>
<p><strong>Pass:</strong> a short list of URLs with one concrete schema problem each.<br /><strong>Fail:</strong> enabling more schema types while invisible FAQ still ships sitewide.</p>
<h2>Part 2 — FAQ blocks without spam (8 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> FAQ markup only where users can <strong>read</strong> the questions and answers</li>
<li><input disabled type="checkbox" /> Remove Rank Math / Yoast / theme FAQ blocks used only to inject JSON-LD</li>
<li><input disabled type="checkbox" /> Kill sitewide/footer/reusable-block FAQ that duplicates the same <code>FAQPage</code> everywhere</li>
<li><input disabled type="checkbox" /> One FAQ section per topic URL — not Home + every service + every post</li>
<li><input disabled type="checkbox" /> Questions sound like real customer questions, not keyword stacks</li>
</ul>
<p><strong>Prefer:</strong> visible accordion or static Q&amp;A on a dedicated FAQ or a single money page.<br /><strong>Avoid:</strong> hidden FAQ “for SEO” and global patterns that stamp identical FAQPage nodes.</p>
<p><strong>Pass:</strong> FAQ schema appears only where FAQ content is primary or clearly visible.<br /><strong>Fail:</strong> “FAQ rich results” toggles still on after Google retired ordinary FAQ stars.</p>
<h2>Part 3 — HowTo only for real procedures (6 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> HowTo only on instructional URLs (ordered steps a human can follow)</li>
<li><input disabled type="checkbox" /> Each step has real text (not empty <code>HowToStep</code> / slogan-only steps)</li>
<li><input disabled type="checkbox" /> Disable auto-HowTo on numbered marketing lists</li>
<li><input disabled type="checkbox" /> No sitewide HowTo “just in case”</li>
</ul>
<p><strong>Pass:</strong> HowTo graph matches a genuine tutorial.<br /><strong>Fail:</strong> “How we work” sales page wrapped as HowTo.</p>
<h2>Part 4 — LocalBusiness NAP (8 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Single LocalBusiness (or clear Organization + location) — not theme + SEO plugin + directory plugin racing</li>
<li><input disabled type="checkbox" /> <code>name</code>, address, <code>telephone</code>, <code>url</code> match footer <strong>and</strong> GBP</li>
<li><input disabled type="checkbox" /> <code>url</code> is the stable homepage / location URL — not a campaign short link (<a href="https://riichimirza.com/tools/url-shortener/">URL shortener</a> is fine for ads; keep it out of LocalBusiness <code>url</code>)</li>
<li><input disabled type="checkbox" /> Update schema when the phone or address changes</li>
<li><input disabled type="checkbox" /> Drop conflicting second NAP blocks</li>
</ul>
<p>Other utilities: <a href="https://riichimirza.com/tools/">tools</a>.</p>
<p><strong>Pass:</strong> one NAP story humans and markup both tell.<br /><strong>Fail:</strong> prettier <code>sameAs</code> list while the phone in JSON-LD is two years old.</p>
<h2>Part 5 — Required properties + Rich Results Test (6 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Fill basics you can prove; skip vanity properties you cannot support</li>
<li><input disabled type="checkbox" /> Run Rich Results Test on each money URL you changed</li>
<li><input disabled type="checkbox" /> Treat FAQ/HowTo detections as <strong>hygiene</strong> signals in 2026 — remove junk; do not expect FAQ stars</li>
<li><input disabled type="checkbox" /> Fix LocalBusiness / Organization errors until clean</li>
<li><input disabled type="checkbox" /> Re-test after plugin or theme updates</li>
</ul>
<p><strong>Pass:</strong> money URLs validate for the types you intentionally keep; spam types gone.<br /><strong>Fail:</strong> “we added more schema” while NAP still mismatches GBP.</p>
<h2>Done criteria</h2>
<p>You are done when invisible/duplicate FAQPage is gone, HowTo exists only on real procedures, LocalBusiness NAP matches footer + GBP, required basics are filled, and Rich Results Test is clean for the types you still use. Leave a full Schema.org redesign for later.</p>
<h2>Closing</h2>
<p>FAQ / HowTo / LocalBusiness hygiene fails from <strong>spammy FAQ blocks</strong>, <strong>duplicate FAQ everywhere</strong>, <strong>HowTo on non-howto pages</strong>, <strong>NAP mismatches</strong>, and <strong>missing required properties</strong> — not from missing an enterprise knowledge graph. Mark up what users see; keep LocalBusiness honest; stop paying plugin toggles for rich results Google already retired.</p>
<p>More practical notes: <a href="https://riichimirza.com/blog/">blog</a>.</p>
<p><em>— Riichi Mirza</em></p>
]]></content:encoded></item><item><title><![CDATA[The 40-Minute Image SEO and Open Graph Checklist for Small WordPress Sites]]></title><description><![CDATA[Someone shares your post and Slack shows the theme screenshot. Google Images ignores your heroes because every alt is keyword salad. GSC is quiet; social…]]></description><link>https://riichimirza.hashnode.dev/the-40-minute-image-seo-and-open-graph-checklist-for-small-wordpress-sites</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/the-40-minute-image-seo-and-open-graph-checklist-for-small-wordpress-sites</guid><category><![CDATA[webdev]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 22:51:03 GMT</pubDate><content:encoded><![CDATA[<p>Someone shares your post and Slack shows the theme screenshot. Google Images ignores your heroes because every alt is keyword salad. GSC is quiet; social CTR is not. On small WordPress sites the fix is usually <strong>honest media + real Open Graph on the money URLs</strong>, not another AI-alt plugin. This checklist covers <strong>image SEO and OG hygiene</strong>: alt myths, missing dimensions, oversized heroes (SEO/discoverability first — not a full CWV redo), OG title/image mismatches, theme-screenshot defaults, filename vs alt, and lazy-load pitfalls above the fold. About <strong>40 minutes</strong>.</p>
<p>Forty minutes is enough to fix Home and the URLs that earn leads. It is not enough to rename every 2019 Media Library dump — and you should not start there.</p>
<h2>Before you start (4 minutes)</h2>
<p>Write five lines: money URLs (Home, top services/posts); SEO plugin in use (one only); whether a sitewide default OG image exists; known bad shares (theme screenshot, logo-only cards); hero strategy (page builder background vs <code>&lt;img&gt;</code>).</p>
<p>Settle which URL owns overlapping content with <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">canonical tags</a> before you polish OG on a duplicate.</p>
<h2>Part 1 — Inventory media + OG (8 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Sample: Home, 5 money URLs, 3 posts with featured images</li>
<li><input disabled type="checkbox" /> View source / plugin preview: <code>og:title</code>, <code>og:description</code>, <code>og:image</code>, <code>og:url</code></li>
<li><input disabled type="checkbox" /> Flag <strong>theme screenshot</strong> or generic logo as <code>og:image</code></li>
<li><input disabled type="checkbox" /> Flag <strong>OG title</strong> that still says Home/tagline on a deep URL</li>
<li><input disabled type="checkbox" /> Flag heroes <strong>without</strong> width/height (or reserved aspect-ratio)</li>
<li><input disabled type="checkbox" /> Flag heroes <strong>with</strong> <code>loading="lazy"</code> above the fold</li>
<li><input disabled type="checkbox" /> Confirm only <strong>one</strong> SEO plugin writes OG tags</li>
</ul>
<p><strong>Pass:</strong> list of URLs with a concrete media/OG problem each.<br /><strong>Fail:</strong> generating AI alts sitewide while <code>/pricing/</code> still shares as <code>screenshot.png</code>.</p>
<h2>Part 2 — Alt text without stuffing (7 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Content images: one honest sentence of what the image shows</li>
<li><input disabled type="checkbox" /> Kill keyword stacks (<code>plumber plumber services plumber…</code>)</li>
<li><input disabled type="checkbox" /> Decorative icons/spacers: empty <code>alt=""</code> when they add no information</li>
<li><input disabled type="checkbox" /> Reused files: alt matches <strong>this</strong> page’s role, not a global dump</li>
<li><input disabled type="checkbox" /> Captions/nearby copy carry extra context — not the alt alone</li>
</ul>
<p><strong>Prefer:</strong> <code>Technician replacing a water-heater anode rod in a utility closet</code><br /><strong>Avoid:</strong> primary keyword ×5 with no visual description.</p>
<p><strong>Pass:</strong> alts a screen reader user would thank you for.<br /><strong>Fail:</strong> “SEO alts” nobody would say aloud.</p>
<h2>Part 3 — Dimensions, weight, filenames (7 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Heroes match layout width (often ≤1200–1600 CSS px) — no 4K dump in a half column</li>
<li><input disabled type="checkbox" /> File weight sensible (heroes often well under ~200–300 KB when quality allows)</li>
<li><input disabled type="checkbox" /> Modern format where easy (WebP/AVIF + fallback) — one optimizer, not five</li>
<li><input disabled type="checkbox" /> <code>width</code> + <code>height</code> set (or CSS aspect-ratio with reserved space)</li>
<li><input disabled type="checkbox" /> Future uploads named before Media Library (<code>service-outcome-city.jpg</code>, not <code>DSC_01992.JPG</code>)</li>
</ul>
<p>Light LCP note only: oversized above-fold heroes are often the LCP element — shrinking them helps CWV as a side effect. This checklist still prioritizes <strong>discoverability and previews</strong>.</p>
<p><strong>Pass:</strong> money heroes sized, weighted, dimensioned.<br /><strong>Fail:</strong> another compression plugin while the hero stays a 5 MB PNG.</p>
<h2>Part 4 — Open Graph package (8 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Sitewide <strong>default</strong> OG image set (brand-safe, ~1200×630)</li>
<li><input disabled type="checkbox" /> Money URLs override with relevant featured/social image</li>
<li><input disabled type="checkbox" /> <code>og:title</code> matches page intent (not leftover “Home”)</li>
<li><input disabled type="checkbox" /> <code>og:description</code> is page-specific where it matters — not only the tagline</li>
<li><input disabled type="checkbox" /> Absolute <strong>HTTPS</strong> image URLs after CDN/offload</li>
<li><input disabled type="checkbox" /> Re-scrape with Facebook/LinkedIn debuggers on URLs you changed</li>
</ul>
<p>Campaign tracking can use a <a href="https://riichimirza.com/tools/url-shortener/">URL shortener</a>; the landing page still needs correct <code>og:*</code>. Other utilities: <a href="https://riichimirza.com/tools/">tools</a>.</p>
<p><strong>Pass:</strong> fresh scrapes show the card you designed.<br /><strong>Fail:</strong> “we updated the plugin” while Slack still shows last month’s screenshot.</p>
<h2>Part 5 — Lazy-load + builder gotchas (6 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Above-fold hero: <strong>not</strong> lazy; consider <code>fetchpriority="high"</code></li>
<li><input disabled type="checkbox" /> Below-fold galleries: lazy is fine</li>
<li><input disabled type="checkbox" /> Srcset so mobile is not handed the desktop master</li>
<li><input disabled type="checkbox" /> Page-builder “background images”: if they carry meaning, use <code>&lt;img&gt;</code> + alt or accessible text nearby</li>
<li><input disabled type="checkbox" /> One SEO plugin only; no duplicate <code>og:image</code> race in <code>&lt;head&gt;</code></li>
</ul>
<p><strong>Pass:</strong> hero paints without waiting on a lazy placeholder.<br /><strong>Fail:</strong> sitewide lazy that includes the LCP image.</p>
<h2>Done criteria</h2>
<p>You are done when money URLs have honest alts, dimensioned/sensible heroes, real OG images (not theme screenshots), matching OG titles, and no above-fold lazy on the primary image. Leave historical Media Library renames for a later batch.</p>
<h2>Closing</h2>
<p>Image SEO and Open Graph fail from <strong>stuffed alts</strong>, <strong>missing dimensions</strong>, <strong>heavy heroes</strong>, <strong>mismatched OG</strong>, <strong>screenshot defaults</strong>, <strong>useless filenames</strong>, and <strong>lazy heroes</strong> — not from missing an enterprise DAM. Label media like you label titles: clear, honest, unique where it matters.</p>
<p>More practical notes: <a href="https://riichimirza.com/blog/">blog</a>.</p>
<p><em>— Riichi Mirza</em></p>
]]></content:encoded></item><item><title><![CDATA[The 40-Minute Title Tag and Meta Description Checklist for Small WordPress Sites]]></title><description><![CDATA[GSC shows duplicate titles, truncated blue links, or thin snippets — and the default advice is "install another SEO plugin." On small WordPress sites the…]]></description><link>https://riichimirza.hashnode.dev/the-40-minute-title-tag-and-meta-description-checklist-for-small-wordpress-sites</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/the-40-minute-title-tag-and-meta-description-checklist-for-small-wordpress-sites</guid><category><![CDATA[webdev]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 22:41:43 GMT</pubDate><content:encoded><![CDATA[<p>GSC shows duplicate titles, truncated blue links, or thin snippets — and the default advice is "install another SEO plugin." On small WordPress sites the fix is usually <strong>better labels on the money URLs</strong>, not a fifth title template. This checklist covers <strong>title tags and meta descriptions</strong>: keyword stuffing myths, SERP truncation, duplicate archive titles, blank/auto metas, and writing for click + match without clickbait. Not Core Web Vitals. Not soft-404. About <strong>40 minutes</strong>.</p>
<p>Forty minutes is enough to fix the URLs that earn leads. It is not enough to rewrite every tag archive from 2019 — and you should not. Prioritize money templates, then stop.</p>
<h2>Before you start (4 minutes)</h2>
<p>Write five lines: money URLs (Home, top services/posts, booking/contact if indexed); SEO plugin in use (one only); whether archives are indexed; known GSC HTML issues (duplicate/missing titles or descriptions); brand name length for truncation math.</p>
<p>Settle which URL owns overlapping content with <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">canonical tags</a> before you polish a duplicate’s title.</p>
<h2>Part 1 — Inventory titles (8 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Crawl or sample: Home, 5 money URLs, 3 blog posts, 2 category/tag archives, one paginated URL</li>
<li><input disabled type="checkbox" /> Flag <strong>duplicates</strong> (same <code>&lt;title&gt;</code> on different URLs)</li>
<li><input disabled type="checkbox" /> Flag <strong>stuffing</strong> (primary keyword repeated, city×3, "best best best")</li>
<li><input disabled type="checkbox" /> Flag <strong>truncation risk</strong> (unique benefit after a long brand/slogan prefix)</li>
<li><input disabled type="checkbox" /> Flag <strong>Untitled / Auto Draft / Just another WordPress site</strong></li>
<li><input disabled type="checkbox" /> Confirm only <strong>one</strong> SEO plugin writes titles</li>
</ul>
<p><strong>Pass:</strong> list of URLs with a concrete title problem each.<br /><strong>Fail:</strong> rewriting random tags while <code>/pricing/</code> still says "Home."</p>
<h2>Part 2 — Rewrite money URL titles (10 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> One clear primary phrase <strong>near the front</strong></li>
<li><input disabled type="checkbox" /> Brand <strong>at the end</strong> unless brand is the query</li>
<li><input disabled type="checkbox" /> Short separator (<code>|</code> or <code>–</code>); no triple stacks</li>
<li><input disabled type="checkbox" /> Specificity: city, outcome, format, or audience — one differentiator</li>
<li><input disabled type="checkbox" /> Preview ~50–60 characters / mobile cut; read the truncated string aloud</li>
<li><input disabled type="checkbox" /> Align H1 with the same document intent (not identical string required)</li>
</ul>
<p><strong>Prefer:</strong> <code>Same-day leak repair in Austin | Acme Plumbing</code><br /><strong>Avoid:</strong> <code>Plumber plumbing services plumber Austin plumber | Acme Plumbing Plumber Austin</code></p>
<p><strong>Pass:</strong> each money URL unique, front-loaded, human.<br /><strong>Fail:</strong> longer title "for ranking" that truncates to nonsense.</p>
<h2>Part 3 — Metas that earn the click (8 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Custom meta on every money URL (blank = Google improvises, often badly)</li>
<li><input disabled type="checkbox" /> Problem → promise → qualifier in 1–2 sentences</li>
<li><input disabled type="checkbox" /> Planning range ~140–160 characters; accept rewrites</li>
<li><input disabled type="checkbox" /> Kill "Welcome to our website" and first-paragraph waffle autos</li>
<li><input disabled type="checkbox" /> If plugin uses excerpt, make excerpt intentional</li>
<li><input disabled type="checkbox" /> No stuffing; natural entity/benefit language</li>
</ul>
<p><strong>Pass:</strong> honest snippet candidates on conversion URLs.<br /><strong>Fail:</strong> identical meta pasted across ten pages.</p>
<p>Campaign tracking can use a <a href="https://riichimirza.com/tools/url-shortener/">URL shortener</a>; the landing page still needs Search-facing title/meta that match the promise. Other utilities: <a href="https://riichimirza.com/tools/">tools</a>.</p>
<h2>Part 4 — Archives, templates, pagination (6 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Category/tag/author titles distinct from cornerstone posts</li>
<li><input disabled type="checkbox" /> Homepage SEO title set explicitly (not only the tagline leftovers)</li>
<li><input disabled type="checkbox" /> CPT/product templates: spot-check one URL per type</li>
<li><input disabled type="checkbox" /> Pagination: page 2+ not identical to page 1 (or noindex per your policy)</li>
<li><input disabled type="checkbox" /> Remove sitewide useless prefixes themes inject before post titles</li>
</ul>
<p><strong>Pass:</strong> archives labeled as archives; posts labeled as posts.<br /><strong>Fail:</strong> every archive titled like the first article in the loop.</p>
<h2>Part 5 — Click + match hygiene (4 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> No fake urgency on evergreen service pages</li>
<li><input disabled type="checkbox" /> No "free tool / secret" claims the page does not deliver</li>
<li><input disabled type="checkbox" /> Title and on-page H1 describe the same document</li>
<li><input disabled type="checkbox" /> After edits, spot-check live <code>&lt;title&gt;</code> and meta in View Source (cache!)</li>
<li><input disabled type="checkbox" /> Request indexing sparingly; snippets can lag</li>
</ul>
<p><strong>Pass:</strong> higher chance of qualified clicks.<br /><strong>Fail:</strong> CTR bait that bounces in ten seconds.</p>
<h2>Quick decision table</h2>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Likely cause</th>
<th>Prefer</th>
</tr>
</thead>
<tbody><tr>
<td>GSC duplicate titles</td>
<td>Template/archive clones</td>
<td>Unique patterns; noindex thin archives if policy says so</td>
</tr>
<tr>
<td>Title cuts mid-benefit</td>
<td>Benefit after long prefix</td>
<td>Front-load unique value; brand last</td>
</tr>
<tr>
<td>Spammy SERP look</td>
<td>Keyword stuffing</td>
<td>One primary phrase; synonyms in body</td>
</tr>
<tr>
<td>Weird snippet</td>
<td>Blank/auto meta</td>
<td>Custom meta; intentional excerpt</td>
</tr>
<tr>
<td>High impressions, low CTR</td>
<td>Vague or truncated title</td>
<td>Specificity + mobile preview</td>
</tr>
<tr>
<td>High CTR, high bounce</td>
<td>Clickbait mismatch</td>
<td>Align title/H1/body promise</td>
</tr>
</tbody></table>
<h2>Myths this checklist refuses</h2>
<ul>
<li>Stuffing the keyword three times "for SEO"  </li>
<li>Longer titles always rank better  </li>
<li>One sitewide title string for every archive  </li>
<li>Leaving metas blank because "Google rewrites anyway"  </li>
<li>Clickbait that the page cannot keep  </li>
<li>Treating Open Graph titles as a substitute for Search <code>&lt;title&gt;</code> (fix Search first; reuse good copy for social later)  </li>
<li>Running Rank Math <em>and</em> Yoast (or any second title writer) "just in case"</li>
</ul>
<h2>What "done" looks like</h2>
<ol>
<li>Money URLs have unique, front-loaded titles  </li>
<li>No stuffing; brand placement intentional  </li>
<li>Blank metas filled on conversion URLs  </li>
<li>Archives not clones of posts  </li>
<li>Truncation checked on mobile-width preview  </li>
<li>H1 and title intent aligned  </li>
<li>One SEO plugin owns the output</li>
</ol>
<p>That will not invent rankings overnight. It stops treating titles and metas as afterthoughts or keyword dumpsters.</p>
<p>Rerun after theme/SEO-plugin changes, site migrations, new CPT templates, or a new GSC duplicate-title spike.</p>
<p>More practical technical notes: <a href="https://riichimirza.com/blog/">blog</a>.</p>
<p><em>Riichi Mirza — SEO freelancer focused on small WordPress and service sites.</em></p>
]]></content:encoded></item><item><title><![CDATA[The 45-Minute Core Web Vitals Checklist for Small WordPress Sites]]></title><description><![CDATA[GSC Core Web Vitals is red (or PSI mobile is ugly) and the default advice is "install another optimizer." On small WordPress sites the fix is usually less…]]></description><link>https://riichimirza.hashnode.dev/the-45-minute-core-web-vitals-checklist-for-small-wordpress-sites</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/the-45-minute-core-web-vitals-checklist-for-small-wordpress-sites</guid><category><![CDATA[webdev]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 22:15:51 GMT</pubDate><content:encoded><![CDATA[<p>GSC Core Web Vitals is red (or PSI mobile is ugly) and the default advice is "install another optimizer." On small WordPress sites the fix is usually <strong>less front-end weight</strong>. This checklist covers <strong>LCP / INP / CLS</strong> in practice — plugin bloat, image weight, render-blocking, host vs theme. Not HTTPS. Not soft-404. About <strong>45 minutes</strong>.</p>
<h2>Before you start (4 minutes)</h2>
<p>Write five lines: money URL (not only Home); failing <strong>mobile</strong> metric (LCP/INP/CLS); host + CDN; theme/builder; front-end injectors already installed (cache/optimize/chat/slider/tags).</p>
<p>Measure that same URL before/after — settle which URL you tune the way you settle <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">canonical tags</a> when duplicates confuse crawlers.</p>
<h2>Part 1 — Read the failure (6 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> GSC → Experience → Core Web Vitals (or CrUX): note <strong>mobile</strong> status for origin / URL group</li>
<li><input disabled type="checkbox" /> PSI or Lighthouse on the <strong>money URL</strong> (mobile); screenshot the LCP element</li>
<li><input disabled type="checkbox" /> Confirm LCP is image vs text/block</li>
<li><input disabled type="checkbox" /> Note TTFB vs "unused JS/CSS" noise; CLS culprits (images, fonts, banners); long tasks if INP or lab TBT is bad</li>
</ul>
<p><strong>Pass:</strong> known metric + element/template.<br /><strong>Fail:</strong> optimizing a random blog post while <code>/pricing/</code> converts.</p>
<h2>Part 2 — Plugin and pixel bloat (10 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> List plugins that print CSS/JS on the front end</li>
<li><input disabled type="checkbox" /> On staging, remove <strong>one</strong> unused class (chat <strong>or</strong> slider <strong>or</strong> unused builder widgets)</li>
<li><input disabled type="checkbox" /> One caching approach your host documents; kill duplicate minify/lazyload</li>
<li><input disabled type="checkbox" /> Defer non-essential tags off money templates if possible</li>
<li><input disabled type="checkbox" /> Re-run mobile PSI after the subtraction</li>
</ul>
<p><strong>Pass:</strong> fewer injectors; improvement without a new plugin.<br /><strong>Fail:</strong> a fifth "AI speed" plugin. Subtraction first — stacking often worsens INP.</p>
<h2>Part 3 — Images and LCP (10 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> If LCP is an image: weight sane for mobile (resize before upload)</li>
<li><input disabled type="checkbox" /> WebP/AVIF where the stack supports them</li>
<li><input disabled type="checkbox" /> Width/height or aspect-ratio (CLS)</li>
<li><input disabled type="checkbox" /> LCP image <strong>not</strong> lazy-loaded; below-fold can be</li>
<li><input disabled type="checkbox" /> <code>fetchpriority="high"</code> on the true hero only</li>
<li><input disabled type="checkbox" /> No carousel competing with LCP on money pages</li>
</ul>
<p><strong>Pass:</strong> LCP image sized, prioritized, dimensioned.<br /><strong>Fail:</strong> 3MB hero lazy-loaded.</p>
<p>Campaign links can use a <a href="https://riichimirza.com/tools/url-shortener/">URL shortener</a>; that is not image hygiene. Other utilities: <a href="https://riichimirza.com/tools/">tools</a>.</p>
<h2>Part 4 — Render-blocking + fonts (8 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Count head CSS/JS on the money URL</li>
<li><input disabled type="checkbox" /> Delay/remove non-critical plugin assets on that template</li>
<li><input disabled type="checkbox" /> Do <strong>not</strong> blank-defer every stylesheet (layout/CLS risk)</li>
<li><input disabled type="checkbox" /> Fonts: fewer weights; <code>font-display</code>; skip icon fonts for three icons</li>
<li><input disabled type="checkbox" /> Sync third-party head scripts: move/delay/delete after testing menus/forms</li>
</ul>
<p><strong>Pass:</strong> first paint not waiting on unused builder CSS.<br /><strong>Fail:</strong> "eliminate all CSS" and a broken fold.</p>
<h2>Part 5 — Host vs theme + CLS (7 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Empty staging still slow TTFB? → host/plan/CDN</li>
<li><input disabled type="checkbox" /> TTFB fine until assets stripped? → theme/builder/plugins</li>
<li><input disabled type="checkbox" /> CLS: image dimensions; banners reserve space; embeds have height</li>
<li><input disabled type="checkbox" /> Re-check mobile lab; <strong>field</strong> CWV can lag days/weeks</li>
<li><input disabled type="checkbox" /> Document removals so the next plugin binge does not undo them</li>
</ul>
<p><strong>Pass:</strong> host vs theme called; obvious CLS shoves gone.<br /><strong>Fail:</strong> upgraded hosting while ten pixels still load.</p>
<h2>Quick decision table</h2>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Likely cause</th>
<th>Prefer</th>
</tr>
</thead>
<tbody><tr>
<td>Slow LCP, huge hero</td>
<td>Image weight / wrong priority</td>
<td>Resize, prioritize, don't lazy LCP</td>
</tr>
<tr>
<td>Slow LCP, light images</td>
<td>CSS/font/TTFB</td>
<td>Cut head CSS, fonts; check host</td>
</tr>
<tr>
<td>High INP / janky taps</td>
<td>Main-thread JS (chat, builders, tags)</td>
<td>Remove/defer injectors</td>
</tr>
<tr>
<td>High CLS</td>
<td>Images/fonts/banners without space</td>
<td>Dimensions, font-display, reserve space</td>
</tr>
<tr>
<td>Everything slow, empty theme still slow</td>
<td>Host / PHP / DNS</td>
<td>Host plan or caching at origin</td>
</tr>
<tr>
<td>Green desktop PSI, red mobile field</td>
<td>Wrong target</td>
<td>Optimize mobile field / money URL</td>
</tr>
</tbody></table>
<p>Lab tools help you triage. Field data (CrUX / GSC) is what Search-oriented CWV use. Do not ship a vanity desktop 95 and ignore mobile LCP on the URL that makes money.</p>
<h2>Myths this checklist refuses</h2>
<ul>
<li>Another optimizer plugin before subtraction  </li>
<li>WebP-as-magic when LCP is text/CSS/TTFB  </li>
<li>Deleting all CSS because Lighthouse said "render-blocking"  </li>
<li>Blaming only the host (or only the theme) without an empty-template test  </li>
<li>Treating desktop PSI as Core Web Vitals "done"</li>
</ul>
<h2>What "done" looks like</h2>
<ol>
<li>Known failing metric + money URL  </li>
<li>One injector class removed  </li>
<li>LCP element treated correctly  </li>
<li>No duplicate lazyload/minify wars  </li>
<li>Host vs theme called honestly  </li>
<li>Obvious CLS fixed  </li>
<li>No new optimizer until subtraction is measured</li>
</ol>
<p>That will not invent rankings overnight. It stops treating CWV as a plugin-store category.</p>
<p>Rerun after theme/builder changes, new chat/tags, homepage redesign, host migration, or a new red GSC URL group.</p>
<p>More practical technical notes: <a href="https://riichimirza.com/blog/">blog</a>.</p>
<p><em>Riichi Mirza — SEO freelancer focused on small WordPress and service sites.</em></p>
]]></content:encoded></item><item><title><![CDATA[The 40-Minute HTTPS + Mixed Content Checklist for Small WordPress Sites]]></title><description><![CDATA[SSL is on. The padlock is still angry. On small WordPress sites that usually means HTTP leftovers, mixed content, or an insecure form — not a soft-404…]]></description><link>https://riichimirza.hashnode.dev/the-40-minute-https-mixed-content-checklist-for-small-wordpress-sites</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/the-40-minute-https-mixed-content-checklist-for-small-wordpress-sites</guid><category><![CDATA[technical seo]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[SEO]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 22:02:52 GMT</pubDate><content:encoded><![CDATA[<p>SSL is on. The padlock is still angry. On small WordPress sites that usually means <strong>HTTP leftovers</strong>, <strong>mixed content</strong>, or an <strong>insecure form</strong> — not a soft-404. This checklist is not a redirect-chain audit. Redirects show up only as <strong>one clean http→https hop</strong>. About 40 minutes.</p>
<h2>Before you start (3 minutes)</h2>
<p>Write five lines:</p>
<ul>
<li>Final host (HTTPS, www or apex — pick one)</li>
<li>Certificate status (valid, covers that host)</li>
<li>SEO / SSL plugin in use (Rank Math, Really Simple SSL, host panel, none)</li>
<li>Top 5 money URLs that must look secure</li>
<li>Whether staging still serves HTTP-only tools on the same brand host</li>
</ul>
<p>Judge everything below against that list. Scheme/host confusion is the same class of problem as duplicate URLs — settle the intended HTTPS URL the way you settle <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">canonical tags</a>.</p>
<h2>Part 1 — Scheme hop + WP addresses (7 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> <code>http://YOUR-DOMAIN/</code> → <strong>one</strong> 301/302 to <code>https://</code> on the intended host (no multi-hop theater)</li>
<li><input disabled type="checkbox" /> Apex vs www: one choice; HTTPS works on the chosen host</li>
<li><input disabled type="checkbox" /> WP Admin → Settings → General: WordPress Address and Site Address both <code>https://</code></li>
<li><input disabled type="checkbox" /> No admin redirect loop when opening <code>/wp-admin/</code> over HTTPS</li>
<li><input disabled type="checkbox" /> Certificate name matches the host users actually land on</li>
</ul>
<p><strong>Pass:</strong> single scheme hop; General settings HTTPS; admin reachable on HTTPS.<br /><strong>Fail:</strong> HTTP still serves content, or <code>siteurl</code>/<code>home</code> still HTTP.</p>
<h2>Part 2 — HTTP leftovers in content and options (10 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> View-source Home: no same-host <code>http://</code> in <code>img</code>, <code>script</code>, <code>link</code>, <code>iframe</code></li>
<li><input disabled type="checkbox" /> View-source each money URL: same check</li>
<li><input disabled type="checkbox" /> Search content / widgets for hard-coded <code>http://yourdomain</code></li>
<li><input disabled type="checkbox" /> Customizer CSS: no absolute HTTP asset URLs</li>
<li><input disabled type="checkbox" /> Plugin-stored full URLs (sliders, logos, OG) upgraded</li>
<li><input disabled type="checkbox" /> Dry-run DB replace <code>http://yourdomain</code> → <code>https://</code> if needed (never blind-replace all <code>http</code>)</li>
</ul>
<p><strong>Pass:</strong> money HTML is HTTPS-clean for first-party assets.<br /><strong>Fail:</strong> hard-coded HTTP images or scripts on money pages.</p>
<h2>Part 3 — Mixed content: active vs passive (8 minutes)</h2>
<p>Open DevTools → Console + Security on Home and one money URL.</p>
<ul>
<li><input disabled type="checkbox" /> <strong>Active</strong> mixed content (scripts, stylesheets, XHR, insecure iframes): <strong>zero</strong> blocked items</li>
<li><input disabled type="checkbox" /> <strong>Passive</strong> mixed content (images/video/audio over HTTP): none, or listed and scheduled to fix</li>
<li><input disabled type="checkbox" /> Third-party widgets/fonts/maps: HTTPS endpoints only</li>
<li><input disabled type="checkbox" /> Abandoned slider / analytics snippets on <code>http://</code> removed or replaced</li>
<li><input disabled type="checkbox" /> Service worker / cache purged if it still maps old HTTP assets</li>
</ul>
<p><strong>Pass:</strong> no active mixed content; passive cleared or rare and documented.<br /><strong>Fail:</strong> menu/JS broken because a script was blocked as mixed active content.</p>
<p>Active breaks the page; passive mostly breaks trust. Fix active first.</p>
<h2>Part 4 — Insecure forms + admin myths (7 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Contact / newsletter / login / checkout forms: <code>action</code> is HTTPS (or empty/same-origin over HTTPS)</li>
<li><input disabled type="checkbox" /> AJAX to <code>admin-ajax.php</code> or REST over HTTPS</li>
<li><input disabled type="checkbox" /> Test submit once; Network tab shows no HTTP posts</li>
<li><input disabled type="checkbox" /> <code>/wp-admin/</code> used over HTTPS (do <strong>not</strong> "fix" SSL by forcing admin back to HTTP)</li>
<li><input disabled type="checkbox" /> If admin looks broken: Console mixed content + <code>siteurl</code> + proxy <code>X-Forwarded-Proto</code> — not "HTTPS is unsafe for admin"</li>
</ul>
<p><strong>Pass:</strong> forms and admin stay on HTTPS without mixed active errors.<br /><strong>Fail:</strong> Chrome "Not secure" on a form page, or admin intentionally left on HTTP.</p>
<h2>Part 5 — HSTS lightly + cleanup (5 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> HTTPS solid everywhere you need <strong>before</strong> HSTS</li>
<li><input disabled type="checkbox" /> Enable HSTS at host/CDN with a modest <code>max-age</code> first</li>
<li><input disabled type="checkbox" /> <code>includeSubDomains</code> only if every real subdomain is ready</li>
<li><input disabled type="checkbox" /> Skip preload until you are sure you will not need HTTP on that host</li>
<li><input disabled type="checkbox" /> Sitemap + internal links use HTTPS; GSC samples of leftover HTTP URLs noted</li>
<li><input disabled type="checkbox" /> External shares can use a <a href="https://riichimirza.com/tools/url-shortener/">URL shortener</a>; on-site paths stay HTTPS. Other free utilities: <a href="https://riichimirza.com/tools/">tools</a></li>
</ul>
<p>HSTS does not repair mixed content inside a page — it only trains browsers to skip HTTP next time.</p>
<h2>Quick decision table</h2>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Likely cause</th>
<th>Prefer</th>
</tr>
</thead>
<tbody><tr>
<td>Padlock warning, images HTTP</td>
<td>Passive mixed / hard-coded content</td>
<td>HTTPS rewrite those URLs</td>
</tr>
<tr>
<td>JS dead, Console mixed blocked</td>
<td>Active mixed script/CSS/XHR</td>
<td>Upgrade or remove the HTTP endpoint</td>
</tr>
<tr>
<td>"Not secure" on form page</td>
<td>Form action or iframe over HTTP</td>
<td>HTTPS action / embed</td>
</tr>
<tr>
<td>Admin loop after SSL</td>
<td>Wrong <code>siteurl</code> or proxy proto</td>
<td>Fix URLs + forwarded proto; keep HTTPS</td>
</tr>
<tr>
<td>Users still hit HTTP</td>
<td>No scheme redirect or bookmark</td>
<td>Single http→https hop (+ HSTS later)</td>
</tr>
</tbody></table>
<h2>What "done" looks like</h2>
<ol>
<li>One clean http→https hop to the intended host  </li>
<li><code>siteurl</code> / <code>home</code> HTTPS  </li>
<li>No active mixed content on money URLs  </li>
<li>Forms post over HTTPS  </li>
<li>Admin used over HTTPS  </li>
<li>HSTS optional seatbelt after the above</li>
</ol>
<p>That will not invent rankings. It stops Chrome and users from treating a "secured" WordPress site as half-migrated.</p>
<h2>Repeat triggers</h2>
<p>Rerun after: host/CDN changes, theme swaps, plugin installs that inject scripts, content imports, staging→prod URL replace, or any new "Not secure" screenshot from a client.</p>
<p>More practical technical notes: <a href="https://riichimirza.com/blog/">blog</a>.</p>
<p><em>Riichi Mirza — SEO freelancer focused on small WordPress and service sites.</em></p>
]]></content:encoded></item><item><title><![CDATA[The 35-Minute robots.txt + noindex Audit for Small WordPress Sites]]></title><description><![CDATA[Indexation advice online jumps straight to sitemaps or "request indexing." On small WordPress sites, start with whether Google may fetch the HTML and…]]></description><link>https://riichimirza.hashnode.dev/the-35-minute-robots-txt-noindex-audit-for-small-wordpress-sites</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/the-35-minute-robots-txt-noindex-audit-for-small-wordpress-sites</guid><category><![CDATA[webdev]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 21:50:18 GMT</pubDate><content:encoded><![CDATA[<p>Indexation advice online jumps straight to sitemaps or "request indexing." On small WordPress sites, start with whether Google may <strong>fetch</strong> the HTML and whether you are still whispering <strong>noindex</strong>. This checklist is not a soft-404 pass and not a full sitemap hygiene rewrite — it is robots + index directives only, finishable in about 35 minutes.</p>
<h2>Before you start (3 minutes)</h2>
<p>Write four lines:</p>
<ul>
<li>Production host (HTTPS, www or not)</li>
<li>Whether a staging copy still exists</li>
<li>SEO plugin in use (Rank Math / Yoast / other / none)</li>
<li>Top 5 money URLs that must be indexable</li>
</ul>
<p>Everything below is judged against that list.</p>
<h2>Part 1 — Live robots.txt (8 minutes)</h2>
<p>Open <code>https://YOUR-DOMAIN/robots.txt</code> in a private window (and <code>curl</code> if you can).</p>
<ul>
<li><input disabled type="checkbox" /> File loads on <strong>production</strong> (not a parked/staging host)</li>
<li><input disabled type="checkbox" /> No <code>Disallow: /</code> or other whole-site block for <code>User-agent: *</code></li>
<li><input disabled type="checkbox" /> <code>/wp-admin/</code> disallow is fine; <code>Allow: /wp-admin/admin-ajax.php</code> is the usual companion</li>
<li><input disabled type="checkbox" /> No Disallow hitting <code>/wp-content/</code>, <code>/wp-includes/</code>, theme paths, <code>*.css</code>, or <code>*.js</code> unless you can defend it</li>
<li><input disabled type="checkbox" /> <code>Sitemap:</code> line points at the production HTTPS sitemap index</li>
<li><input disabled type="checkbox" /> No leftover staging hostnames in Sitemap lines</li>
<li><input disabled type="checkbox" /> Security / "optimization" plugins have not appended mystery Disallows</li>
</ul>
<p><strong>Pass:</strong> boring file; admin blocked; assets fetchable; sitemap URL correct.<br /><strong>Fail:</strong> sitewide or asset Disallows, or Sitemap pointing at staging.</p>
<p>Skip inventing huge <code>Allow:</code> lists if nothing relevant was disallowed. The CSS/JS myth is "Google can't render unless I Allow everything"; the real failure is an accidental Disallow on assets or <code>/</code>.</p>
<h2>Part 2 — Sitewide discourage + headers (5 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> WP Admin → Settings → Reading → <strong>Discourage search engines</strong> is <strong>unchecked</strong> on production</li>
<li><input disabled type="checkbox" /> View-source on Home: no <code>noindex</code> in meta robots</li>
<li><input disabled type="checkbox" /> View-source on each of your 5 money URLs: indexable (or intentional exception documented)</li>
<li><input disabled type="checkbox" /> Response headers on Home: no sitewide <code>X-Robots-Tag: noindex</code> (check hosting/CDN/staging vhost leftovers)</li>
<li><input disabled type="checkbox" /> Coming-soon / maintenance plugins fully removed or confirmed not injecting robots tags</li>
</ul>
<p><strong>Pass:</strong> money URLs fetchable and indexable.<br /><strong>Fail:</strong> discourage still on, or headers/meta still noindexing the host.</p>
<h2>Part 3 — Intentional noindex vs accidents (8 minutes)</h2>
<p>In your SEO plugin (and theme if it hardcodes robots):</p>
<ul>
<li><input disabled type="checkbox" /> List post types / archives set to noindex (search, authors, tags, thin categories, thank-you)</li>
<li><input disabled type="checkbox" /> Confirm each is intentional</li>
<li><input disabled type="checkbox" /> Flag any money page, pillar, or hub accidentally noindexed</li>
<li><input disabled type="checkbox" /> "Noindex" used as soft-delete → either unpublish/redirect or restore index + improve content</li>
<li><input disabled type="checkbox" /> When near-duplicate URLs remain, settle which one should be indexable with clear <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">canonical tags</a> — do not noindex <em>and</em> canonicalize in conflicting ways without a plan</li>
</ul>
<p><strong>Pass:</strong> only utility/thin URLs are noindex.<br /><strong>Fail:</strong> a revenue URL is noindex "from staging" or "temporarily."</p>
<h2>Part 4 — noindex vs sitemap conflicts (6 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Export or open sitemap children for posts/pages (or sample 20 URLs)</li>
<li><input disabled type="checkbox" /> Spot-check: any listed URL returns noindex in HTML/headers?</li>
<li><input disabled type="checkbox" /> Remove noindex URLs from the sitemap <strong>or</strong> remove noindex if they should rank</li>
<li><input disabled type="checkbox" /> Thank-you / cart / account: noindex + <strong>out</strong> of sitemap</li>
<li><input disabled type="checkbox" /> GSC Pages: sample <strong>Excluded by 'noindex' tag</strong> and <strong>Blocked by robots.txt</strong> — match to fixes above</li>
</ul>
<p>Do not "fix" blocked URLs with mass Request indexing while Disallow/noindex still fire.</p>
<h2>Part 5 — Staging leftovers (5 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Staging subdomain: still passworded or Disallow — and <strong>not</strong> linked from production</li>
<li><input disabled type="checkbox" /> Production HTML has zero hrefs to staging hosts</li>
<li><input disabled type="checkbox" /> Old GSC staging property: sitemaps removed or property ignored</li>
<li><input disabled type="checkbox" /> External campaign links use production (a <a href="https://riichimirza.com/tools/url-shortener/">URL shortener</a> is fine for shares; keep on-site paths clean). Other free utilities: <a href="https://riichimirza.com/tools/">tools</a></li>
</ul>
<h2>Quick decision table (keep beside the checklist)</h2>
<table>
<thead>
<tr>
<th>Goal</th>
<th>Prefer</th>
<th>Avoid</th>
</tr>
</thead>
<tbody><tr>
<td>Hide thank-you / cart</td>
<td><code>noindex</code> (+ keep out of sitemap)</td>
<td><code>Disallow</code> alone (may still show URL-only results; crawler never sees noindex)</td>
</tr>
<tr>
<td>Block wp-admin</td>
<td><code>Disallow: /wp-admin/</code></td>
<td>Disallowing all of <code>/wp-content/</code></td>
</tr>
<tr>
<td>Stop thin tag archives ranking</td>
<td>noindex those archives</td>
<td>Sitewide <code>Disallow: /</code></td>
</tr>
<tr>
<td>Staging stay private</td>
<td>HTTP auth or IP allowlist</td>
<td>Only robots Disallow, then forgetting it on prod</td>
</tr>
<tr>
<td>Temporary redesign hide</td>
<td>Maintenance with clear off-switch <strong>or</strong> staging host</td>
<td>Editing robots on production and "remembering later"</td>
</tr>
</tbody></table>
<p>If Google Search Console says <strong>Blocked by robots.txt</strong>, free the path (or accept URL-only listing). If it says <strong>Excluded by noindex</strong>, either remove the directive or stop asking for indexation via sitemap and links.</p>
<h2>What "done" looks like</h2>
<ol>
<li>Boring production robots.txt  </li>
<li>Discourage off; no sitewide X-Robots noindex  </li>
<li>Money URLs indexable  </li>
<li>noindex reserved for utility/thin URLs  </li>
<li>Sitemap does not list noindex URLs  </li>
<li>Staging cannot leak signals onto production</li>
</ol>
<p>That will not invent demand. It stops your own stack from hiding the pages you claim matter.</p>
<h2>Repeat triggers</h2>
<p>Rerun after: migrations, host/CDN changes, security plugin installs, coming-soon toggles, SEO plugin resets, or when GSC fills with Blocked-by-robots / Excluded-by-noindex on URLs you care about.</p>
<p>More practical technical notes: <a href="https://riichimirza.com/blog/">blog</a>.</p>
<p><em>Riichi Mirza — SEO freelancer focused on small WordPress and service sites.</em></p>
]]></content:encoded></item><item><title><![CDATA[The 40-Minute Internal Link Audit for Small WordPress Sites]]></title><description><![CDATA[Internal linking advice online is either "add more links" or a full sitewide graph analysis. Small WordPress sites need a middle path: a checklist you can…]]></description><link>https://riichimirza.hashnode.dev/the-40-minute-internal-link-audit-for-small-wordpress-sites</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/the-40-minute-internal-link-audit-for-small-wordpress-sites</guid><category><![CDATA[webdev]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[technical seo]]></category><category><![CDATA[SEO]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 21:34:07 GMT</pubDate><content:encoded><![CDATA[<p>Internal linking advice online is either "add more links" or a full sitewide graph analysis. Small WordPress sites need a middle path: a checklist you can finish in about 40 minutes, fix the same day, and repeat after big content pushes.</p>
<p>This pass is <strong>not</strong> a sitemap hygiene rewrite (that is a separate triage). Focus: hubs, orphans, nav/footer bloat, and whether in-content anchors actually endorse the pages you care about.</p>
<h2>Before you start (3 minutes)</h2>
<p>Write four lines:</p>
<ul>
<li>Your top 5 money URLs (services, tools, or posts that should rank)</li>
<li>Your intended hubs (category landings / pillar pages — even if imperfect)</li>
<li>Preferred URL shape (HTTPS, www or not, trailing slash)</li>
<li>Where links live today (Appearance → Menus, footer block, widgets)</li>
</ul>
<p>Everything below is judged against that list.</p>
<h2>Part 1 — Map hubs and money pages (8 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> List hubs that should organize clusters (aim for 2–5 on a small site)</li>
<li><input disabled type="checkbox" /> Open each hub: does it link to its spokes with context, or only show a bare post loop?</li>
<li><input disabled type="checkbox" /> Confirm each money URL appears in <strong>at least one</strong> of: primary nav, a hub, or recent in-content links</li>
<li><input disabled type="checkbox" /> Flag money URLs that only exist in the sitemap or footer</li>
</ul>
<p><strong>Pass:</strong> every money URL has a human-visible path from Home within 2–3 clicks.<br /><strong>Fail:</strong> a key page is only reachable via search or direct URL.</p>
<p>Add missing hub → spoke links before you touch plugins. When near-duplicates confuse which URL should receive the links, settle canonicals first — <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">how to fix duplicate content with canonical tags</a>.</p>
<h2>Part 2 — Orphan spot-check (12 minutes)</h2>
<p>You do not need a perfect crawl on day one.</p>
<ul>
<li><input disabled type="checkbox" /> Export or open your post/page list (or sitemap children for posts + pages)</li>
<li><input disabled type="checkbox" /> Crawl with Screaming Frog free tier <strong>or</strong> manually check: Home, hubs, last 20 posts, primary menu destinations</li>
<li><input disabled type="checkbox" /> Pick 15 published URLs you thought mattered. Count inlinks from that crawl</li>
<li><input disabled type="checkbox" /> Mark <strong>0 inlinks</strong> → orphan candidates (ignore thank-you, cart, account, pure legal)</li>
</ul>
<table>
<thead>
<tr>
<th>Finding</th>
<th>Fix same day</th>
</tr>
</thead>
<tbody><tr>
<td>Strong orphan</td>
<td>1–3 contextual links from hub + related posts</td>
</tr>
<tr>
<td>Thin orphan</td>
<td>Merge / redirect / unpublish — do not "rescue" junk</td>
</tr>
<tr>
<td>Money orphan</td>
<td>Home or hub + two in-content mentions</td>
</tr>
<tr>
<td>Intentional unlink</td>
<td>Leave it; remove from sitemap if it should stay quiet</td>
</tr>
</tbody></table>
<p>Do <strong>not</strong> dump orphans into the footer as a batch fix.</p>
<h2>Part 3 — Nav and footer bloat (7 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Count top-level primary nav items. Target ~5–7 for a brochure/small content site</li>
<li><input disabled type="checkbox" /> Count footer links. If you are over ~15–20 mixed URLs, trim</li>
<li><input disabled type="checkbox" /> Remove city/service clones nobody updates</li>
<li><input disabled type="checkbox" /> Ensure footer does not merely mirror the entire sitemap</li>
<li><input disabled type="checkbox" /> Check reusable blocks / old theme footer patterns — menus alone may be clean while a block still bloats</li>
</ul>
<p><strong>Pass:</strong> a new visitor can name your three most important pages from the header alone.<br /><strong>Fail:</strong> every URL looks equally "important."</p>
<h2>Part 4 — In-content anchors (7 minutes)</h2>
<p>Open your last 5 published posts/pages:</p>
<ul>
<li><input disabled type="checkbox" /> Each links to at least one <strong>older</strong> relevant URL (not only "next post")</li>
<li><input disabled type="checkbox" /> Each links up to its hub when a hub exists</li>
<li><input disabled type="checkbox" /> Anchors are readable phrases, not "click here" or stuffed exact matches</li>
<li><input disabled type="checkbox" /> Links use the final canonical URL (no http→https hop left in hrefs)</li>
<li><input disabled type="checkbox" /> No auto-linker firehose (first mention of every keyword sitewide)</li>
</ul>
<p>Campaign/UTM variants stay out of on-site HTML. Keep share links tidy with something like a <a href="https://riichimirza.com/tools/url-shortener/">URL shortener</a> for external campaigns; internal hrefs stay clean. Other free utilities: <a href="https://riichimirza.com/tools/">tools</a>.</p>
<h2>Part 5 — GSC sanity without busywork (3 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Pages → sample 5–10 <strong>Discovered - currently not indexed</strong> URLs</li>
<li><input disabled type="checkbox" /> Ask: orphan? thin? wrong host/canonical? only in sitemap?</li>
<li><input disabled type="checkbox" /> Fix links or substance on the ones you still want — skip mass "Request indexing"</li>
</ul>
<h2>What "done" looks like</h2>
<p>In one sitting you should have:</p>
<ol>
<li>A short list of hubs and money URLs  </li>
<li>Orphans either linked, merged, or consciously dropped  </li>
<li>Leaner nav/footer  </li>
<li>Recent content that points backward and upward  </li>
<li>A tiny follow-up list (not a 200-row spreadsheet)</li>
</ol>
<p>That will not invent demand for thin pages. It stops your own site from hiding the URLs you claim matter.</p>
<h2>Repeat triggers</h2>
<p>Rerun after: publishing sprees, theme/footer rebuilds, migrations, merging categories, or when Discovered-not-indexed piles up on URLs you thought were well linked.</p>
<p>More practical technical notes: <a href="https://riichimirza.com/blog/">blog</a>.</p>
<p><em>Riichi Mirza — SEO freelancer focused on small WordPress and service sites.</em></p>
]]></content:encoded></item><item><title><![CDATA[XML Sitemap Hygiene Checklist for Small WordPress Sites (45 Minutes)]]></title><description><![CDATA[Indexation advice online is either "just submit a sitemap" or a 20-tab enterprise crawl. Small WordPress sites need a middle path: a checklist you can…]]></description><link>https://riichimirza.hashnode.dev/xml-sitemap-hygiene-checklist-for-small-wordpress-sites-45-minutes</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/xml-sitemap-hygiene-checklist-for-small-wordpress-sites-45-minutes</guid><category><![CDATA[webdev]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 21:04:07 GMT</pubDate><content:encoded><![CDATA[<p>Indexation advice online is either "just submit a sitemap" or a 20-tab enterprise crawl. Small WordPress sites need a middle path: a checklist you can finish in about 45 minutes, fix the same day, and repeat after migrations or plugin swaps.</p>
<p>This is the sitemap hygiene pass I run on freelance audits. It is <strong>not</strong> a soft-404 rewrite and not a full crawl-budget deep dive. Focus: what is in the XML list, whether <code>lastmod</code> is honest, whether orphans are hiding in plain sight, and how that lines up with GSC "Discovered - currently not indexed."</p>
<h2>Before you start (2 minutes)</h2>
<p>Write one line:</p>
<ul>
<li>Sitemap index URL you <em>intend</em> to submit (e.g. <code>https://example.com/sitemap_index.xml</code> or <code>/wp-sitemap.xml</code>)</li>
<li>Preferred host format (HTTPS, www or not, trailing slash rule)</li>
</ul>
<p>Everything below is judged against that.</p>
<h2>Part 1 — Confirm the live file (5 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Open the sitemap index in a browser / <code>curl -sI</code>. Expect <strong>200</strong>, <code>Content-Type</code> XML (or text/xml).</li>
<li><input disabled type="checkbox" /> List child sitemaps (posts, pages, categories…). Note counts.</li>
<li><input disabled type="checkbox" /> In GSC → Sitemaps: only <strong>one</strong> primary index is submitted. Remove obsolete Yoast/Rank Math/core duplicates.</li>
<li><input disabled type="checkbox" /> Purge CDN/cache if the XML still shows deleted URLs after a cleanup.</li>
</ul>
<p><strong>Pass:</strong> one clean index, fetchable, matches the plugin you actually use.<br /><strong>Fail:</strong> 404 sitemap, or three competing submissions.</p>
<h2>Part 2 — Spot-check URL quality (15 minutes)</h2>
<p>Pick 15 URLs across posts, pages, and one taxonomy sitemap:</p>
<ul>
<li><input disabled type="checkbox" /> Each returns <strong>200</strong> (not 301 into the list entry itself)</li>
<li><input disabled type="checkbox" /> URL is the <strong>canonical</strong> host/path you want indexed</li>
<li><input disabled type="checkbox" /> Raw HTML has <strong>no</strong> <code>noindex</code> if the URL is in the sitemap</li>
<li><input disabled type="checkbox" /> Canonical tag (if present) points to <strong>this</strong> URL, not a sibling</li>
</ul>
<p><strong>Fail patterns &amp; fixes:</strong></p>
<table>
<thead>
<tr>
<th>Finding</th>
<th>Fix</th>
</tr>
</thead>
<tbody><tr>
<td>Redirecting URL listed</td>
<td>Remove; list only the destination</td>
</tr>
<tr>
<td><code>noindex</code> page listed</td>
<td>Exclude post type/taxonomy in plugin, or noindex off if it should rank</td>
</tr>
<tr>
<td>Wrong host / http</td>
<td>Fix site URL + regenerate sitemap</td>
</tr>
<tr>
<td>Attachment / thank-you / cart</td>
<td>Exclude those types</td>
</tr>
</tbody></table>
<p>When duplicates keep confusing GSC, pair this pass with proper canonicals — <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">how to fix duplicate content with canonical tags</a>.</p>
<h2>Part 3 — lastmod honesty (5 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Open a post you <strong>did not</strong> edit this month. Does <code>lastmod</code> show today anyway?</li>
<li><input disabled type="checkbox" /> Edit one staging/test copy (or a low-risk draft you publish briefly) and confirm <code>lastmod</code> updates only then.</li>
<li><input disabled type="checkbox" /> Disable any "auto bump all dates on regenerate" behavior in the SEO plugin.</li>
</ul>
<p><strong>Pass:</strong> <code>lastmod</code> moves when content moves.<br /><strong>Fail:</strong> sitewide "updated today." Prefer older accurate dates over fake freshness.</p>
<h2>Part 4 — Orphans vs endorsement (10 minutes)</h2>
<p>Sitemaps discover. Internal links endorse.</p>
<ul>
<li><input disabled type="checkbox" /> Export or copy sitemap URLs (or sample the largest child sitemap).</li>
<li><input disabled type="checkbox" /> Crawl or manually check: is each money URL linked from nav, a hub, or at least one relevant post?</li>
<li><input disabled type="checkbox" /> Mark <strong>in sitemap, not linked</strong> → add 1–2 contextual internal links <strong>or</strong> remove from sitemap until the page is worth endorsing.</li>
<li><input disabled type="checkbox" /> Mark <strong>linked money URL, missing from sitemap</strong> → include it (if indexable).</li>
</ul>
<p>Do not put campaign parameter variants into the XML list. Keep share links clean (a <a href="https://riichimirza.com/tools/url-shortener/">URL shortener</a> is fine for campaigns); sitemap stays on canonicals. Other free utilities: <a href="https://riichimirza.com/tools/">tools</a>.</p>
<h2>Part 5 — GSC triage without busywork (8 minutes)</h2>
<ul>
<li><input disabled type="checkbox" /> Sitemaps report: note discovered vs indexed — a gap is normal; a never-moving pile of thin URLs is a content problem.</li>
<li><input disabled type="checkbox" /> Pages → <strong>Discovered - currently not indexed</strong>: sample 10 URLs.<ul>
<li>Should they be in the sitemap at all?</li>
<li>Are they orphans?</li>
<li>Near-duplicate of a stronger URL?</li>
</ul>
</li>
<li><input disabled type="checkbox" /> Fix list + links + substance on the worst samples. Skip mass "Request indexing."</li>
<li><input disabled type="checkbox" /> Disable weak taxonomies (often tags/authors) from the sitemap if those archives are empty doorways.</li>
</ul>
<h2>What "done" looks like</h2>
<p>In one sitting you should have:</p>
<ol>
<li>A single submitted sitemap index  </li>
<li>Only indexable 200 canonicals in the children  </li>
<li>Honest <code>lastmod</code>  </li>
<li>No obvious orphans among pages you care about  </li>
<li>A short list of Discovered-not-indexed URLs with a next action (link, improve, merge, or delist)</li>
</ol>
<p>That will not magic-index junk. It stops the sitemap from arguing against your own quality signals.</p>
<h2>Quick reference</h2>
<p>Keep this sticky note next to GSC: <strong>sitemap = discovery list, not an indexing switch.</strong> Clean URLs in, honest <code>lastmod</code>, real internal links, one submitted index. Everything else is editorial.</p>
<h2>Repeat triggers</h2>
<p>Rerun this checklist after: HTTPS/www cutovers, permalink changes, SEO plugin swaps, bulk imports, or when Discovered-not-indexed spikes without a content push.</p>
<p>For more practical technical notes, see the <a href="https://riichimirza.com/blog/">blog</a>.</p>
<p><em>Riichi Mirza — SEO freelancer focused on small WordPress and service sites.</em></p>
]]></content:encoded></item><item><title><![CDATA[The 30-Minute Redirect Audit for Small Sites: A Checklist That Actually Gets Finished]]></title><description><![CDATA[Redirect advice tends to be either "just use 301s" or a 40-page enterprise migration playbook. Small sites need something in between: a checklist you can…]]></description><link>https://riichimirza.hashnode.dev/the-30-minute-redirect-audit-for-small-sites-a-checklist-that-actually-gets-finished</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/the-30-minute-redirect-audit-for-small-sites-a-checklist-that-actually-gets-finished</guid><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><category><![CDATA[webdev]]></category><category><![CDATA[WordPress]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 20:41:41 GMT</pubDate><content:encoded><![CDATA[<p>Redirect advice tends to be either "just use 301s" or a 40-page enterprise migration playbook. Small sites need something in between: a checklist you can run in half an hour, fix the same day, and repeat every few months.</p>
<p>This is the one I use on freelance audits for sites under ~1,000 URLs. No paid tools required.</p>
<h2>Before you start: write down the "final URL format"</h2>
<p>Answer four questions in one line of notes:</p>
<ul>
<li>HTTPS? (yes — always)</li>
<li>www or non-www?</li>
<li>Trailing slash or not?</li>
<li>Lowercase only?</li>
</ul>
<p>Example: <code>https://www.example.com/page-name/</code>. Every check below compares against this.</p>
<h2>Part 1 — Host-level checks (5 minutes)</h2>
<p>Test the four entry variants of your homepage:</p>
<pre><code class="language-bash">for u in http://example.com http://www.example.com https://example.com https://www.example.com; do
  echo "== $u"; curl -sIL "$u" | grep -iE "^(HTTP|location)"
done
</code></pre>
<p><strong>Pass:</strong> every variant reaches the final homepage in <strong>one</strong> 301 (or is already it).
<strong>Fail:</strong> any variant shows two or more <code>Location</code> lines.</p>
<p>Common cause: separate HTTPS and www rules in different layers (host panel + <code>.htaccess</code> + plugin). Fix by consolidating into a single rule and disabling the duplicates.</p>
<ul>
<li><input disabled type="checkbox" /> All four variants resolve in ≤1 hop</li>
<li><input disabled type="checkbox" /> All redirects are 301/308, not 302/307</li>
<li><input disabled type="checkbox" /> No meta-refresh or JavaScript redirects on the homepage</li>
</ul>
<h2>Part 2 — Crawl for chains and loops (10 minutes)</h2>
<p>Crawl the site (Screaming Frog free tier, Sitebulb trial, or any crawler with a redirect report). Export:</p>
<ul>
<li><p>Redirect chains</p>
</li>
<li><p>Redirect loops</p>
</li>
<li><p>Internal links pointing to 3xx URLs</p>
</li>
<li><p><input disabled type="checkbox" /> Zero loops (fix these first — they break pages outright)</p>
</li>
<li><p><input disabled type="checkbox" /> Zero chains of 2+ hops</p>
</li>
<li><p><input disabled type="checkbox" /> Count of internal links to redirected URLs (goal: 0)</p>
</li>
</ul>
<h2>Part 3 — Redirect rule inventory (5 minutes)</h2>
<p>List every place redirects can live:</p>
<table>
<thead>
<tr>
<th>Source</th>
<th>Where to look</th>
</tr>
</thead>
<tbody><tr>
<td>Server config</td>
<td><code>.htaccess</code>, nginx <code>server</code> blocks</td>
</tr>
<tr>
<td>CMS plugin</td>
<td>Redirection, Rank Math, Yoast Premium</td>
</tr>
<tr>
<td>Host / CDN</td>
<td>Cloudflare page/redirect rules, host "force HTTPS"</td>
</tr>
<tr>
<td>CMS core</td>
<td>WordPress auto-redirect for changed slugs</td>
</tr>
</tbody></table>
<ul>
<li><input disabled type="checkbox" /> Each redirect is defined in <strong>one</strong> layer only</li>
<li><input disabled type="checkbox" /> No rule whose target is itself redirected (A→B where B→C)</li>
<li><input disabled type="checkbox" /> Old temporary (302) rules reviewed — permanent moves switched to 301</li>
</ul>
<h2>Part 4 — The homepage-dump check (5 minutes)</h2>
<p>Filter your redirect list for rules whose target is <code>/</code>.</p>
<p>For each one, ask: <em>was this URL genuinely the homepage?</em> (<code>/index.html</code>, <code>/home</code>, old domain root — yes. A deleted blog post or product — no.)</p>
<p>Google commonly treats unrelated page → homepage redirects as <strong>soft 404s</strong>, so they don't preserve value and they clutter Search Console.</p>
<ul>
<li><input disabled type="checkbox" /> Each non-homepage URL redirects to a relevant page or closest parent (e.g. <code>/blog/</code>)</li>
<li><input disabled type="checkbox" /> Truly retired content with no equivalent returns 404 or 410</li>
<li><input disabled type="checkbox" /> Search Console "Soft 404" list cross-checked against these rules</li>
</ul>
<h2>Part 5 — Signal consistency (5 minutes)</h2>
<p>Redirects are one of three signals that should agree on the final URL:</p>
<ul>
<li><input disabled type="checkbox" /> XML sitemap lists only final, 200-status URLs</li>
<li><input disabled type="checkbox" /> Canonical tags point to the final URL (not an http or non-slash variant)</li>
<li><input disabled type="checkbox" /> hreflang / Open Graph <code>og:url</code> (if used) match too</li>
</ul>
<p>If canonicals are messy, fix them in the same pass — I cover the full process in <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">How to fix duplicate content with canonical tags</a>.</p>
<h2>Bonus — Off-site redirects you control</h2>
<ul>
<li><input disabled type="checkbox" /> Short links point to final URLs (not old slugs)</li>
<li><input disabled type="checkbox" /> QR codes on print material resolve in one hop</li>
<li><input disabled type="checkbox" /> Social profile and directory links updated after URL changes</li>
</ul>
<p>Short links and QR codes are the sneakiest chains because they live on paper or in old posts. Use a shortener where destinations are editable — e.g. a simple <a href="https://riichimirza.com/tools/url-shortener/">URL shortener</a> — so a URL change is a 10-second fix instead of a reprint.</p>
<h2>Fix order (when the audit finds problems)</h2>
<ol>
<li><strong>Loops</strong> — broken pages, fix immediately.</li>
<li><strong>Host-level chains</strong> — affect every URL on the site.</li>
<li><strong>Homepage dumps</strong> — remap or 404/410.</li>
<li><strong>Rule-to-rule chains</strong> — flatten so each source points to the final URL.</li>
<li><strong>Internal links</strong> — update menus, footers, in-content links to final URLs.</li>
<li><strong>Sitemap/canonicals</strong> — align with final URLs.</li>
<li><strong>Re-crawl</strong> — confirm zeros.</li>
</ol>
<h2>How often to run it</h2>
<ul>
<li>After any redesign, migration, HTTPS/host change or plugin swap</li>
<li>After bulk deleting or merging content</li>
<li>Otherwise, quarterly</li>
</ul>
<h2>Why bother on a small site?</h2>
<p>Because unlike most SEO work, this one has a finish line. You can get to zero chains, zero loops and zero internal links to redirects — and stay there with a 30-minute check every quarter. Faster pages, cleaner Search Console, fewer mystery soft 404s.</p>
<p>More small-site technical SEO checklists on the <a href="https://riichimirza.com/blog/">Riichi Mirza blog</a>.</p>
]]></content:encoded></item><item><title><![CDATA[A canonical tag audit checklist for small sites (with the fixes)]]></title><description><![CDATA[15 checks I run before trusting any rel=canonical]]></description><link>https://riichimirza.hashnode.dev/a-canonical-tag-audit-checklist-for-small-sites-with-the-fixes</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/a-canonical-tag-audit-checklist-for-small-sites-with-the-fixes</guid><category><![CDATA[webdev]]></category><category><![CDATA[Google]]></category><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 20:26:50 GMT</pubDate><content:encoded><![CDATA[<p>When a client tells me "Google isn't indexing my pages," canonical tags are on my shortlist of suspects, right after robots rules and soft 404s. They fail silently: no error, no warning, just pages sitting in Search Console under "Alternate page with proper canonical tag."</p>
<p>This is the checklist I actually run. It's built for small sites (under a few hundred URLs) where you can check things by hand, without an enterprise crawler. Every check comes with what "pass" looks like and the fix if it fails.</p>
<hr />
<h2>Part 1: Is the tag there and well-formed?</h2>
<p><strong>☐ 1. Exactly one canonical in the raw HTML <code>&lt;head&gt;</code>.</strong>
View source (Ctrl+U), not DevTools — you want what the server sends.
<em>Fail:</em> zero, or two from a theme and a plugin. <em>Fix:</em> turn off one source. Usually keep the SEO plugin's and remove the theme's.</p>
<p><strong>☐ 2. Absolute URL with the correct protocol and host.</strong>
<code>https://example.com/page/</code>, not <code>/page/</code> or <code>http://</code>.
<em>Fix:</em> set the site URL correctly in your CMS / framework config.</p>
<p><strong>☐ 3. No staging or dev hostnames.</strong>
<code>grep -r "staging\." ./dist</code> or search the live source.
<em>Fix:</em> make canonical base URL an environment variable and check it on deploy.</p>
<p><strong>☐ 4. Not injected only by JavaScript.</strong>
If the canonical only appears after render, Google may process it late or not at all.
<em>Fix:</em> render it server-side or at build time.</p>
<p><strong>☐ 5. Not contradicted by an HTTP header.</strong>
<code>curl -sI https://example.com/page/ | grep -i link</code>
<em>Fail:</em> a <code>Link: &lt;...&gt;; rel="canonical"</code> header that points somewhere different from the HTML tag.</p>
<hr />
<h2>Part 2: Does the target make sense?</h2>
<p><strong>☐ 6. Indexable pages self-canonicalise.</strong>
Spot-check 10 pages across types: home, blog post, category, tool page, contact.
<em>Fail:</em> inner pages pointing to the homepage. This is the single most damaging canonical bug I find.</p>
<p><strong>☐ 7. The target returns 200.</strong>
<code>curl -sI &lt;canonical-url&gt; | head -1</code>
<em>Fail:</em> 301, 404, or 5xx. <em>Fix:</em> point the canonical at the final destination URL.</p>
<p><strong>☐ 8. The target is not noindexed.</strong>
Canonical says "index that one"; noindex on that one says "don't." Google will pick its own answer.</p>
<p><strong>☐ 9. The target self-canonicalises (no chains).</strong>
A → B → C chains get ignored. Every canonical should land on a URL that canonicalises to itself.</p>
<p><strong>☐ 10. Paginated pages self-canonicalise.</strong>
<code>/blog/page/2/</code> should canonicalise to <code>/blog/page/2/</code>, not <code>/blog/</code>.</p>
<hr />
<h2>Part 3: Do your other signals agree?</h2>
<p>Canonicals are a hint. Google checks whether the rest of your site backs them up.</p>
<p><strong>☐ 11. Host/protocol/slash variants redirect.</strong></p>
<pre><code class="language-bash">for u in http://example.com/ http://www.example.com/ https://www.example.com/ https://example.com; do
  echo "$u -&gt; $(curl -s -o /dev/null -w '%{http_code} %{redirect_url}' $u)"
done
</code></pre>
<p><em>Pass:</em> everything 301s to one version. If variants return 200, you need redirects, not more canonicals.</p>
<p><strong>☐ 12. Sitemap lists only canonical URLs.</strong>
No parameter URLs, no redirected URLs, no noindexed URLs.</p>
<p><strong>☐ 13. Internal links use canonical URLs.</strong>
Nav, footer, and in-content links should match canonical format exactly — same slash, same host, no tracking params. Tracking parameters belong on external campaign links (for example, links you shorten or turn into QR codes for print), not inside your own navigation.</p>
<p><strong>☐ 14. hreflang (if used) points to canonical URLs.</strong>
Small multilingual sites often reference non-canonical variants in hreflang, which cancels both.</p>
<hr />
<h2>Part 4: What does Google actually think?</h2>
<p><strong>☐ 15. Compare declared vs selected canonical in Search Console.</strong></p>
<ul>
<li><strong>Pages report:</strong> open "Duplicate, Google chose different canonical than user" and "Alternate page with proper canonical tag."</li>
<li><strong>URL Inspection:</strong> for your top 10 pages, note "User-declared canonical" vs "Google-selected canonical."</li>
</ul>
<p>How to read it:</p>
<table>
<thead>
<tr>
<th>What you see</th>
<th>Usually means</th>
</tr>
</thead>
<tbody><tr>
<td>Alternate page with proper canonical — and they're param/filter URLs</td>
<td>Working as intended. Ignore.</td>
</tr>
<tr>
<td>Alternate page with proper canonical — and they're real content pages</td>
<td>Bad canonical (often check 6). Fix urgently.</td>
</tr>
<tr>
<td>Google chose different canonical</td>
<td>Signals disagree. Recheck 11–13.</td>
</tr>
<tr>
<td>Duplicate without user-selected canonical</td>
<td>Missing canonical. Add self-canonicals.</td>
</tr>
</tbody></table>
<hr />
<h2>A simple log template</h2>
<p>I keep a tiny sheet per audit:</p>
<table>
<thead>
<tr>
<th>URL</th>
<th>Declared canonical</th>
<th>Google canonical</th>
<th>Status code</th>
<th>Indexable?</th>
<th>Action</th>
</tr>
</thead>
</table>
<p>Ten rows is usually enough on a small site to see the pattern. One root cause (a theme setting, a deploy variable, a missing redirect) tends to explain most rows.</p>
<h2>After fixing</h2>
<ol>
<li>Request indexing for the 3–5 most important affected pages in URL Inspection.</li>
<li>Re-submit the sitemap.</li>
<li>Recheck the Pages report in 2–4 weeks. Canonical changes are processed on recrawl, so don't expect it to update overnight.</li>
</ol>
<h2>Summary</h2>
<p>A healthy canonical setup on a small site is boring: one absolute self-canonical per indexable page, parameter variants pointing to clean URLs, redirects handling host and protocol, and a sitemap and internal links that agree. Run these 15 checks once, fix the root cause, and you'll rarely think about canonicals again.</p>
<p>I share more checklists like this from freelance audits on <a href="https://riichimirza.com/how-to-fix-duplicate-content-with-canonical-tags/">my blog</a>, and keep a few <a href="https://riichimirza.com/tools/">free SEO-adjacent tools</a> on the site as well.</p>
<p><em>— Riichi Mirza</em></p>
]]></content:encoded></item><item><title><![CDATA[Does Crawl Budget Matter Under 500 Pages? A Practical Triage]]></title><description><![CDATA[Short answer: usually not. Longer answer: the number of pages you publish is not the number Googlebot sees. A 300-page site with faceted filters, session…]]></description><link>https://riichimirza.hashnode.dev/does-crawl-budget-matter-under-500-pages-a-practical-triage</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/does-crawl-budget-matter-under-500-pages-a-practical-triage</guid><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Googlebot ]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 19:51:04 GMT</pubDate><content:encoded><![CDATA[<p>Short answer: usually not. Longer answer: the <em>number</em> of pages you publish is not the number Googlebot sees. A 300-page site with faceted filters, session parameters, and a client-rendered router can expose tens of thousands of crawlable URLs. That's when "small site" stops being small.</p>
<p>Rather than debating theory, I use a three-bucket triage: <strong>Ignore</strong>, <strong>Watch</strong>, or <strong>Fix now</strong>. Here's how to sort your site in about 20 minutes.</p>
<h2>The one ratio that decides everything</h2>
<p>Before any tooling, estimate this:</p>
<blockquote>
<p><strong>Discoverable URLs ÷ URLs you want indexed</strong></p>
</blockquote>
<ul>
<li>Close to <strong>1:1</strong> → crawl budget is irrelevant to you.</li>
<li>Around <strong>3:1 to 10:1</strong> → you have leaks worth watching.</li>
<li><strong>Above 10:1</strong> → Googlebot is spending its time on junk, and new or updated pages will be slow to get picked up.</li>
</ul>
<p>You can approximate "discoverable" by crawling your site with any desktop crawler (with JavaScript rendering on) and comparing its total to the URL count in your sitemap.</p>
<h2>The triage table</h2>
<table>
<thead>
<tr>
<th>Signal</th>
<th>Ignore</th>
<th>Watch</th>
<th>Fix now</th>
</tr>
</thead>
<tbody><tr>
<td>Discoverable : wanted ratio</td>
<td>≤ 2:1</td>
<td>2:1 – 10:1</td>
<td>&gt; 10:1</td>
</tr>
<tr>
<td>New pages indexed within</td>
<td>days</td>
<td>1–3 weeks</td>
<td>a month or never</td>
</tr>
<tr>
<td>"Discovered – currently not indexed" in GSC</td>
<td>a handful</td>
<td>growing slowly</td>
<td>hundreds or rising fast</td>
</tr>
<tr>
<td>Avg. server response (Crawl Stats)</td>
<td>&lt; 500 ms</td>
<td>500 ms – 1 s</td>
<td>&gt; 1 s or 5xx spikes</td>
</tr>
<tr>
<td>Parameter URLs in logs</td>
<td>rare</td>
<td>some</td>
<td>dominate Googlebot hits</td>
</tr>
</tbody></table>
<p>If every row lands in <strong>Ignore</strong>, stop reading and go write content. Seriously. Most sites under 500 pages belong here.</p>
<p>If you have one or two <strong>Watch</strong> rows, put a monthly check on the calendar. Any <strong>Fix now</strong> row means indexing is already being hurt, whatever you call it.</p>
<h2>Where small sites actually leak</h2>
<h3>1. Query parameters</h3>
<p>The most common culprit. Sort, filter, tracking, and pagination parameters multiply URLs combinatorially.</p>
<table>
<thead>
<tr>
<th>Parameter type</th>
<th>Example</th>
<th>Typical action</th>
</tr>
</thead>
<tbody><tr>
<td>Tracking</td>
<td><code>?utm_source=x</code></td>
<td>Canonical to clean URL; never link internally with it</td>
</tr>
<tr>
<td>Sort</td>
<td><code>?sort=price_asc</code></td>
<td>Canonical to default; consider <code>robots.txt</code> disallow</td>
</tr>
<tr>
<td>Filter (low value)</td>
<td><code>?color=red&amp;size=m</code></td>
<td>Disallow or <code>noindex</code>; don't render as crawlable <code>&lt;a href&gt;</code></td>
</tr>
<tr>
<td>Filter (real demand)</td>
<td><code>/shoes/red/</code></td>
<td>Promote to a clean, static path and add to sitemap</td>
</tr>
<tr>
<td>Session / state</td>
<td><code>?sid=abc123</code></td>
<td>Remove from URLs entirely</td>
</tr>
</tbody></table>
<p>Remember: a canonical tag is a hint, and Googlebot still has to fetch the URL to read it. If the problem is volume, <code>robots.txt</code> stops the fetch; canonicals only consolidate signals afterwards. Don't combine a <code>robots.txt</code> block with a <code>noindex</code> you need Google to see.</p>
<h3>2. SPA routing quirks</h3>
<p>Single-page apps create crawl problems that static sites don't have:</p>
<ul>
<li><strong>Soft 404s.</strong> Your router renders a "Not found" component but the server returns <code>200</code>. Every typo'd or stale URL looks like a real page. Fix: return a real 404 status from the server or edge for unknown routes, or at minimum add <code>&lt;meta name="robots" content="noindex"&gt;</code> on the not-found view.</li>
<li><strong>Hash vs history routing.</strong> <code>/#/pricing</code> is a single URL to Google. If you want those views indexed, use history-API routes with server-rendered or prerendered HTML.</li>
<li><strong>Links that aren't links.</strong> <code>onClick={() =&gt; navigate('/x')}</code> on a <code>&lt;div&gt;</code> isn't followed. Use real <code>&lt;a href&gt;</code> elements for anything you want crawled, and <em>avoid</em> them for state you don't.</li>
<li><strong>Infinite client-side states.</strong> Search boxes, date pickers and calendars that push every state into the URL can create endless crawl paths. Keep transient UI state out of crawlable links.</li>
<li><strong>Rendering cost.</strong> JS-heavy pages require a render pass. Prerendering or SSR for key routes removes a dependency on Google's render queue.</li>
</ul>
<h3>3. Slow or flaky servers</h3>
<p>Crawl capacity drops when your host is slow or throws errors. A cheap shared host that spikes to 3 seconds or returns intermittent 5xx under load tells Googlebot to back off. Caching HTML at the CDN often fixes this in an afternoon.</p>
<h3>4. Redirect chains and stale internal links</h3>
<p>Each hop is an extra fetch. After migrations, update internal links to point directly at final URLs and keep chains to one hop.</p>
<h2>A 20-minute check, in order</h2>
<table>
<thead>
<tr>
<th>Step</th>
<th>Where</th>
<th>What you're looking for</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Sitemap</td>
<td>Count of URLs you actually want indexed</td>
</tr>
<tr>
<td>2</td>
<td>Site crawl (JS on)</td>
<td>Total discoverable URLs → compute ratio</td>
</tr>
<tr>
<td>3</td>
<td>GSC → Pages</td>
<td>Size of "Discovered/Crawled – not indexed"</td>
</tr>
<tr>
<td>4</td>
<td>GSC → Crawl Stats</td>
<td>Response time, status code mix, purpose (refresh vs discovery)</td>
</tr>
<tr>
<td>5</td>
<td>Server logs</td>
<td>Which paths and parameters Googlebot hits most</td>
</tr>
<tr>
<td>6</td>
<td>Random 404 test</td>
<td><code>curl -I https://site.com/does-not-exist</code> returns <code>404</code>, not <code>200</code></td>
</tr>
</tbody></table>
<p>Step 6 takes ten seconds and catches a surprising number of SPA deployments.</p>
<p>If you'd rather not assemble this by hand, I keep a set of free checkers at <a href="https://riichimirza.com/tools/">riichimirza.com/tools</a> that cover several of these steps.</p>
<h2>What "fix now" looks like in practice</h2>
<p>Prioritize by impact per hour:</p>
<ol>
<li><strong>Return correct status codes</strong> (404/410 for gone, 301 for moved). Cheapest, biggest win on SPAs.</li>
<li><strong>Stop generating crawlable parameter links</strong> in templates and components.</li>
<li><strong>Disallow pure-junk parameter patterns</strong> in <code>robots.txt</code> (sort orders, session IDs, internal search results).</li>
<li><strong>Clean the sitemap</strong> so it only lists canonical, indexable, 200-status URLs.</li>
<li><strong>Speed up HTML delivery</strong> via caching or SSR for important routes.</li>
<li><strong>Re-check in four weeks</strong>: the ratio and the "not indexed" counts should both fall.</li>
</ol>
<h2>The honest conclusion</h2>
<p>For a typical brochure site, blog, or portfolio under 500 pages, crawl budget is not a thing to optimize. It's a symptom you'd only notice if something else is broken. The triage above is really a test for <em>those</em> breakages: URL explosion, soft 404s, and slow servers. Fix them and "crawl budget" disappears as a concern on its own.</p>
<p>I write more about practical technical SEO for developers on the <a href="https://riichimirza.com/blog/">blog</a>.</p>
<hr />
<p><em>Written by Riichi Mirza.</em></p>
]]></content:encoded></item><item><title><![CDATA[Soft 404s in Google Search Console: How I Triage Them Without Guessing]]></title><description><![CDATA[A soft 404 is a disagreement. Your server says a page exists. Google looked at it and decided it doesn't. Search Console reports that disagreement, but it…]]></description><link>https://riichimirza.hashnode.dev/soft-404s-in-google-search-console-how-i-triage-them-without-guessing</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/soft-404s-in-google-search-console-how-i-triage-them-without-guessing</guid><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Google Search Console]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 19:39:40 GMT</pubDate><content:encoded><![CDATA[<p>A soft 404 is a disagreement. Your server says a page exists. Google looked at it and decided it doesn't. Search Console reports that disagreement, but it doesn't tell you which side is right.</p>
<p>That's why guessing is risky here. If you "fix" a real page by returning a 404, you lose a URL that should rank. If you leave a dead page returning <code>200</code>, you waste crawl budget and fill the index with junk. So before I change anything, I sort each URL into a bucket. This is the order I work in, written for developers who own the stack.</p>
<h2>Step 1: Separate the two signals</h2>
<p>Google weighs two things for every URL:</p>
<ol>
<li><strong>The HTTP status code</strong>: what your server says.</li>
<li><strong>The content signals</strong>: what the rendered page looks like. That means word count, a template with an empty main area, phrases like "not found" or "no results", and near-duplicate boilerplate.</li>
</ol>
<p>A soft 404 means signal 1 says "fine" and signal 2 says "empty." Your job is to work out which signal is lying.</p>
<p>I export the Soft 404 URLs from <strong>Pages → Not indexed → Soft 404</strong> and put each one into one of three buckets:</p>
<table>
<thead>
<tr>
<th>Bucket</th>
<th>Status should be</th>
<th>Content is</th>
<th>Action</th>
</tr>
</thead>
<tbody><tr>
<td>A. Really gone</td>
<td>404 / 410</td>
<td>Error template</td>
<td>Fix the status code</td>
</tr>
<tr>
<td>B. Real but thin</td>
<td>200</td>
<td>Sparse or empty</td>
<td>Improve the content, or noindex it</td>
</tr>
<tr>
<td>C. Moved</td>
<td>301</td>
<td>Old URL</td>
<td>Redirect to the closest match</td>
</tr>
</tbody></table>
<p>Most of the work is Bucket A, and the most common cause of Bucket A is a single-page app.</p>
<h2>Step 2: Check whether your SPA lies about missing routes</h2>
<p>Here's the classic setup. React, Vue or Svelte with client-side routing, served by a catch-all rule that sends <code>index.html</code> for every path. A visitor hits <code>/products/does-not-exist</code>. The server sends back <code>200</code> with the app shell. JavaScript boots, the router finds nothing, and it renders a nice "Page not found" component.</p>
<p>To a person, that's a 404. To Google, it's a <code>200</code> page whose rendered content says "not found." That's the textbook soft 404.</p>
<p>You can confirm it in about ten seconds. Request a path that can't exist:</p>
<pre><code class="language-bash">curl -s -o /dev/null -w "%{http_code}\n" https://example.com/zzz-definitely-missing-123
</code></pre>
<p>If that prints <code>200</code>, every typo, deleted product and stale backlink on your site is a soft 404 waiting to happen. Then use <strong>URL Inspection → Test live URL → View tested page</strong> on a reported URL. If the screenshot shows your not-found component, the content signal is right and the status code is the problem.</p>
<h3>Fixing it in the client alone isn't enough</h3>
<p>Some guides say to add <code>&lt;meta name="robots" content="noindex"&gt;</code> when the router misses. That keeps the page out of the index, but the status is still <code>200</code>, so the URL can keep showing up in reports. The fix that works reliably is to let the <strong>server</strong> know which routes exist.</p>
<h2>Step 3: Return real 404s from the server</h2>
<h3>Node / Express</h3>
<p>If your routes are known (static paths or a slug lookup), check them before falling back to the shell:</p>
<pre><code class="language-js">import express from "express";
import path from "node:path";

const app = express();
const dist = path.resolve("dist");
const knownStatic = new Set(["/", "/about", "/pricing", "/blog"]);

app.use(express.static(dist, { index: false }));

app.get("*", async (req, res) =&gt; {
  const isKnown =
    knownStatic.has(req.path) ||
    (req.path.startsWith("/products/") &amp;&amp;
      (await productExists(req.path.split("/")[2])));

  res.status(isKnown ? 200 : 404).sendFile(path.join(dist, "index.html"));
});

app.listen(3000);
</code></pre>
<p>The shell still loads for the missing route, so your not-found component still shows. The difference is that the status code now matches what the page says. If you use SSR (Next.js, Nuxt, Remix, SvelteKit), use the framework's built-in not-found mechanism, such as <code>notFound()</code> or throwing a 404 from the loader, instead of rendering an empty state.</p>
<h3>nginx</h3>
<p>For a static SPA where you can't look up every slug, at least stop the catch-all from swallowing obvious junk such as missing assets and old file extensions:</p>
<pre><code class="language-nginx">location ~* \.(js|css|png|jpg|svg|woff2?|php|asp)$ {
    try_files $uri =404;
}

location / {
    try_files $uri $uri/ /index.html;
}

error_page 404 /404.html;
</code></pre>
<p>For paths that are gone for good, an explicit <code>return 410;</code> in a <code>location</code> block tells Google the page is intentionally gone. In my experience, Google drops those faster than plain 404s.</p>
<h2>Step 4: Handle Bucket B (real but thin)</h2>
<p>Not every soft 404 is a routing bug. The repeat offenders I see are:</p>
<ul>
<li><strong>Empty search or filter pages</strong>, such as <code>/shop?color=teal</code> with zero results</li>
<li><strong>Category pages with no items yet</strong></li>
<li><strong>Out-of-stock product pages</strong> stripped down to a title and a "notify me" button</li>
<li><strong>User profile pages</strong> with no activity</li>
</ul>
<p>For each one, decide whether it deserves to rank. If it does, add real content: descriptions, related items, specs. If it doesn't, return a 404 when it's truly empty, or add <code>noindex</code>, and stop linking to it internally. Search result pages generally shouldn't be indexable at all.</p>
<h2>Step 5: Clean up your sitemap</h2>
<p>This is the step people skip, and it's often why soft 404s keep coming back. If your sitemap lists URLs that return 404, redirect, or render empty, you're asking Google to crawl problems.</p>
<p>A quick audit in Node:</p>
<pre><code class="language-js">const xml = await (await fetch("https://example.com/sitemap.xml")).text();
const urls = [...xml.matchAll(/&lt;loc&gt;(.*?)&lt;\/loc&gt;/g)].map(m =&gt; m[1]);

for (const url of urls) {
  const r = await fetch(url, { redirect: "manual" });
  if (r.status !== 200) console.log(r.status, url);
}
</code></pre>
<p>My rules for sitemaps:</p>
<ul>
<li>List only canonical URLs that return <code>200</code>.</li>
<li>No redirects, no noindexed pages, no parameter variations.</li>
<li>Build the sitemap from the same data source as your routes, so a deleted product leaves both at the same time.</li>
<li>Keep <code>&lt;lastmod&gt;</code> honest. Don't stamp every URL with today's date.</li>
</ul>
<h2>Step 6: Validate, then wait</h2>
<p>Once you've deployed the fix, run the curl check again, run URL Inspection's live test on a few reported URLs, and then click <strong>Validate Fix</strong> in Search Console. Validation can take a couple of weeks. Bucket A URLs should move from "Soft 404" to "Not found (404)". That's the result you want: the report now matches what's really on your site.</p>
<p>If you'd like quick utilities for status checks and URL work, I keep a set at <a href="https://riichimirza.com/tools/">riichimirza.com/tools</a>. I write more technical SEO teardowns like this one on the <a href="https://riichimirza.com/blog/">blog</a>.</p>
<h2>TL;DR</h2>
<ul>
<li>A soft 404 means the status code and the content disagree. Work out which one is wrong.</li>
<li>Test a nonsense URL. If it returns <code>200</code>, your SPA catch-all is the cause.</li>
<li>Fix status codes on the server (Express, SSR not-found handling, nginx). Client-side noindex is a patch, not a fix.</li>
<li>Thin pages either get real content or get removed from the index.</li>
<li>Your sitemap should list only live, canonical <code>200</code> URLs.</li>
</ul>
<hr />
<p><em>Written by Riichi Mirza</em></p>
]]></content:encoded></item><item><title><![CDATA[9 QR Code Print Mistakes That Quietly Burn Your Marketing Budget]]></title><description><![CDATA[A QR code looks like the cheapest part of any print job. It's a small square in the corner of a flyer, menu, or box. But when it fails, it takes the whole…]]></description><link>https://riichimirza.hashnode.dev/9-qr-code-print-mistakes-that-quietly-burn-your-marketing-budget</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/9-qr-code-print-mistakes-that-quietly-burn-your-marketing-budget</guid><category><![CDATA[qr code]]></category><category><![CDATA[print]]></category><category><![CDATA[webdev]]></category><category><![CDATA[SEO]]></category><category><![CDATA[marketing]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Mon, 26 Oct 2026 18:51:53 GMT</pubDate><content:encoded><![CDATA[<p>A QR code looks like the cheapest part of any print job. It's a small square in the corner of a flyer, menu, or box. But when it fails, it takes the whole campaign down with it. Ten thousand brochures with a code nobody can scan don't just waste paper. They waste the design hours, the distribution costs, and every lead you were hoping to get.</p>
<p>Most of these failures come from a short list of avoidable mistakes. Here they are, with what to do about each, so your next print run pays off.</p>
<hr />
<h2>1. Printing a static code that points to a URL you'll change later</h2>
<p>This one costs more than any other. A static QR code bakes the destination URL into the pattern. If that page moves, gets renamed during a site redesign, or the campaign ends and the URL redirects to a 404, every printed piece becomes useless.</p>
<p><strong>Fix it:</strong></p>
<ul>
<li>Point printed codes at a short, stable URL you control, not a deep link that might change.</li>
<li>Use a redirect layer so you can update the destination without reprinting. A <a href="https://riichimirza.com/tools/url-shortener/">URL shortener</a> or a redirect on your own domain both work.</li>
<li>Before you approve the proof, ask yourself: "Will this exact URL still work in two years?"</li>
</ul>
<h2>2. Making the code too small</h2>
<p>Designers like to shrink QR codes so they don't spoil the layout. Phones are less forgiving. A code that scans fine on a monitor can fail at 1.5 cm on a business card, especially if the camera is older or the light is poor.</p>
<p><strong>Fix it:</strong></p>
<ul>
<li>As a rule of thumb, keep the code at least <strong>2 × 2 cm (about 0.8 in)</strong> for handheld items.</li>
<li>Scale up with distance. A simple guide is that the scanning distance divided by 10 gives you the minimum code width. A poster read from 2 meters needs a code around 20 cm wide.</li>
<li>Longer URLs make denser patterns, and dense patterns need more size. Shorter links help here too.</li>
</ul>
<h2>3. Cutting into the quiet zone</h2>
<p>The blank margin around a QR code (the "quiet zone") isn't wasted space. Scanners use it to find where the code begins. Crowd it with text, borders, or background patterns and scan rates drop.</p>
<p><strong>Fix it:</strong></p>
<ul>
<li>Leave a clear margin of at least <strong>four modules</strong> (four of the code's small squares) on every side.</li>
<li>Don't put the code right up against a trim line, fold, or busy photo.</li>
</ul>
<h2>4. Low contrast or inverted colors</h2>
<p>Brand teams often want the code in brand colors. That's fine within limits. Light gray on white, dark blue on black, and gold foil on cream all look elegant and scan poorly. Inverted codes (light modules on a dark background) still confuse some scanner apps.</p>
<p><strong>Fix it:</strong></p>
<ul>
<li>Keep the modules dark and the background light, with strong contrast.</li>
<li>Test colored versions on several phones before you commit.</li>
<li>Be careful with metallic inks, spot varnish, and glossy laminates. Glare can wipe out a code under shop lighting.</li>
</ul>
<h2>5. Exporting a low-resolution image</h2>
<p>A code pulled from a screenshot or saved as a small JPEG will blur or pick up compression artifacts once it's printed. Soft module edges make scanning less reliable, and on large formats the problem is obvious.</p>
<p><strong>Fix it:</strong></p>
<ul>
<li>Export as <strong>SVG or high-resolution PNG</strong>. Vector formats are best for print.</li>
<li>Don't stretch a code in your layout software without locking its proportions.</li>
<li>Generate a fresh, clean file for each campaign rather than reusing an old asset. A free <a href="https://riichimirza.com/tools/qr-code-generator/">QR code generator</a> that exports SVG makes this quick.</li>
</ul>
<h2>6. Overdoing logos and customization</h2>
<p>A logo in the middle of the code can work, because QR codes have built-in error correction. But error correction has limits. A big logo, rounded "artistic" modules, and altered corner markers can push a code past what scanners can recover.</p>
<p><strong>Fix it:</strong></p>
<ul>
<li>Keep center logos small, roughly under 20–25% of the code's area.</li>
<li>Use a higher error-correction level (Q or H) if you add a logo.</li>
<li>Never change the three large corner squares (the finder patterns).</li>
</ul>
<h2>7. Sending scanners to a page that isn't mobile-ready</h2>
<p>The code scans perfectly, and then the visitor lands on a slow desktop page, a pop-up, or a generic homepage that has nothing to do with the offer they just saw. You paid for the scan and lost the visitor in the next three seconds.</p>
<p><strong>Fix it:</strong></p>
<ul>
<li>Send every code to a dedicated, mobile-first landing page that matches the printed offer.</li>
<li>Keep the page light: compressed images, minimal scripts, and a clear call to action above the fold.</li>
<li>Make sure the page loads over HTTPS. Some phones warn people about insecure links.</li>
</ul>
<h2>8. No tracking, so no way to prove ROI</h2>
<p>If every QR code across your flyers, packaging, and storefront points to the same plain URL, you can't tell which placement worked. Next quarter's print budget ends up based on guesswork.</p>
<p><strong>Fix it:</strong></p>
<ul>
<li>Add UTM parameters to each destination (for example <code>utm_source=flyer&amp;utm_medium=print&amp;utm_campaign=spring-sale</code>).</li>
<li>Use a different code or link for each placement you want to compare.</li>
<li>Check your analytics after a week. A placement with no scans is a placement you can drop.</li>
</ul>
<h2>9. Skipping the real-world test</h2>
<p>This is the mistake behind most of the others. Teams check the code on screen, approve the proof, and send it to print. The first real test happens when a customer tries it, and by then the print run is finished.</p>
<p><strong>Fix it:</strong> run this checklist on a <strong>printed proof</strong>, not a monitor:</p>
<ul>
<li><input disabled type="checkbox" /> Scan with at least one iPhone and one Android phone</li>
<li><input disabled type="checkbox" /> Try the native camera app, not just a dedicated scanner app</li>
<li><input disabled type="checkbox" /> Test at the real viewing distance and under the real lighting</li>
<li><input disabled type="checkbox" /> Confirm the destination loads quickly on mobile data, not just office Wi-Fi</li>
<li><input disabled type="checkbox" /> Check that UTM tags appear in your analytics</li>
<li><input disabled type="checkbox" /> Check that a call to action sits next to the code ("Scan for 10% off", not just a bare square)</li>
</ul>
<hr />
<h2>Quick pre-print checklist</h2>
<p>Before any file goes to the printer, confirm:</p>
<ol>
<li><strong>Stable destination:</strong> a short URL or redirect you control</li>
<li><strong>Size:</strong> at least 2 cm for handheld items, scaled up for distance</li>
<li><strong>Quiet zone:</strong> four modules of clear space on all sides</li>
<li><strong>Contrast:</strong> dark modules on a light background, no glare-prone finishes</li>
<li><strong>File format:</strong> SVG or high-resolution PNG</li>
<li><strong>Customization:</strong> a small logo, high error correction, and untouched corner markers</li>
<li><strong>Landing page:</strong> mobile-first and matching the offer</li>
<li><strong>Tracking:</strong> UTM parameters for each placement</li>
<li><strong>Tested:</strong> scanned on a physical proof with multiple devices</li>
</ol>
<p>A QR code is only worth the money if people can scan it, land somewhere useful, and you can measure the result. Getting these nine details right takes about fifteen minutes. A reprint takes weeks and costs far more.</p>
<hr />
<p><em>Riichi Mirza builds free web tools at riichimirza.com.</em></p>
]]></content:encoded></item><item><title><![CDATA[Do URL shorteners hurt SEO? What actually happens with 301s, 302s, and link equity]]></title><description><![CDATA[Short answer: reputable URL shorteners that use 301 redirects do not meaningfully hurt SEO. The link equity largely passes to the destination URL. Use…]]></description><link>https://riichimirza.hashnode.dev/do-url-shorteners-hurt-seo-what-actually-happens-with-301s-302s-and-link-equity</link><guid isPermaLink="true">https://riichimirza.hashnode.dev/do-url-shorteners-hurt-seo-what-actually-happens-with-301s-302s-and-link-equity</guid><category><![CDATA[SEO]]></category><category><![CDATA[technical seo]]></category><category><![CDATA[marketing]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Riichi Mirza]]></dc:creator><pubDate>Sun, 25 Oct 2026 16:56:16 GMT</pubDate><content:encoded><![CDATA[<p>Short answer: reputable URL shorteners that use <strong>301 redirects</strong> do not meaningfully hurt SEO. The link equity largely passes to the destination URL.</p>
<h3>What matters</h3>
<ol>
<li><strong>301 vs 302</strong> — Permanent (301) redirects pass equity; temporary (302) and JS-only redirects are weaker or opaque to crawlers.</li>
<li><strong>Interstitials / ads</strong> — Skip shorteners that show interstitial pages before the destination.</li>
<li><strong>On-site links</strong> — Never replace your own internal links with short URLs. Link directly on your site.</li>
<li><strong>Tracking</strong> — Short links shine for print, SMS, and social where click analytics matter.</li>
<li><strong>Brand trust</strong> — A branded short domain usually gets better CTR than a random opaque slug.</li>
</ol>
<h3>Practical takeaway</h3>
<p>Use shorteners for <strong>distribution and measurement</strong>, not as a ranking hack. If you need a free shortener for campaigns, I keep one at <a href="https://riichimirza.com/tools/url-shortener/">https://riichimirza.com/tools/url-shortener/</a>.</p>
]]></content:encoded></item></channel></rss>