<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 任宏涛</title>
    <description>The latest articles on DEV Community by 任宏涛 (@_6614b04c8831704fcf9b2).</description>
    <link>https://dev.to/_6614b04c8831704fcf9b2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4107161%2Fb7e8a950-cd58-402f-8301-127c92c91752.png</url>
      <title>DEV Community: 任宏涛</title>
      <link>https://dev.to/_6614b04c8831704fcf9b2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_6614b04c8831704fcf9b2"/>
    <language>en</language>
    <item>
      <title>Nine SEO checks to run before you publish a page</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Wed, 23 Sep 2026 03:35:11 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/nine-seo-checks-to-run-before-you-publish-a-page-3de0</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/nine-seo-checks-to-run-before-you-publish-a-page-3de0</guid>
      <description>&lt;h2&gt;
  
  
  The problem with SEO checklists
&lt;/h2&gt;

&lt;p&gt;Most of them list things you can see. The real problems are the ones you can't.&lt;/p&gt;

&lt;p&gt;A missing canonical, a duplicated &lt;code&gt;h1&lt;/code&gt;, a meta description that gets truncated — none of these break anything. The page renders, the content is good, and you find out weeks later in a report nobody reads closely.&lt;/p&gt;

&lt;p&gt;So this is a list of checks for &lt;em&gt;invisible&lt;/em&gt; failures. The order is deliberate: structural checks first, because fixing structure usually changes the text, and re-running the content checks after a rewrite wastes the first pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Does the page have exactly one &lt;code&gt;h1&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Not zero, not two.&lt;/p&gt;

&lt;p&gt;Zero usually means the title was styled with a class instead of a heading element. Two usually means a template printed the site name as an &lt;code&gt;h1&lt;/code&gt; and the page title as another one.&lt;/p&gt;

&lt;p&gt;Both are invisible in a browser, and both make the page's main subject ambiguous.&lt;/p&gt;

&lt;p&gt;Read the heading outline rather than an issue count. If the &lt;code&gt;h2&lt;/code&gt; list reads like a table of contents for the page, the structure is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Do heading levels descend without skipping?
&lt;/h2&gt;

&lt;p&gt;An &lt;code&gt;h2&lt;/code&gt; followed by an &lt;code&gt;h4&lt;/code&gt; tells a reader a level is missing.&lt;/p&gt;

&lt;p&gt;Sometimes a level genuinely is missing and the outline is still correct — but more often the &lt;code&gt;h4&lt;/code&gt; was chosen for its smaller font size.&lt;/p&gt;

&lt;p&gt;Choose levels for meaning and style them in CSS. Nothing in HTML requires an &lt;code&gt;h2&lt;/code&gt; to be visually larger than an &lt;code&gt;h3&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Is the title tag specific to this page?
&lt;/h2&gt;

&lt;p&gt;Open ten tabs of your site and read the titles.&lt;/p&gt;

&lt;p&gt;If several are identical, those pages compete for the same query and none of them wins clearly. A title should describe what &lt;em&gt;this&lt;/em&gt; page covers, not the category it belongs to.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Will the description survive truncation?
&lt;/h2&gt;

&lt;p&gt;The widely quoted 155-character limit is an average, not a rule. The real constraint is rendered pixel width, and mobile is meaningfully narrower than desktop.&lt;/p&gt;

&lt;p&gt;Put the sentence that must survive in the first ~120 characters and treat the rest as elaboration.&lt;/p&gt;

&lt;p&gt;Preview it rather than counting — measure in pixels, not characters.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Does the canonical point at itself?
&lt;/h2&gt;

&lt;p&gt;A page's canonical should be its own URL, &lt;strong&gt;in the same form the page is served at&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Trailing slashes are the common case. If &lt;code&gt;/tools&lt;/code&gt; redirects to &lt;code&gt;/tools/&lt;/code&gt;, the canonical must say &lt;code&gt;/tools/&lt;/code&gt; — otherwise you're telling Google the real page is a URL that redirects.&lt;/p&gt;

&lt;p&gt;Check index pages specifically. They're the ones most often generated with a canonical that omits the trailing slash.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Is the page in the sitemap, in the right URL form?
&lt;/h2&gt;

&lt;p&gt;Same trailing-slash question. A sitemap entry that redirects is a wasted crawl.&lt;/p&gt;

&lt;p&gt;If your sitemap is generated, generate it from the same value as the canonical. Two independent string constructions will eventually disagree — and nothing will tell you when they do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wrong: two places that must agree, with nothing enforcing it&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canonical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// ← drifts&lt;/span&gt;

&lt;span class="c1"&gt;// Right: one value, used twice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Are the internal links real and useful?
&lt;/h2&gt;

&lt;p&gt;Every new page should link to at least two existing pages, and at least one existing page should link to it.&lt;/p&gt;

&lt;p&gt;A page nothing links to is a page that's hard to find. Internal links are the only navigation signal you fully control.&lt;/p&gt;

&lt;p&gt;Use descriptive anchor text. "Read the guide" tells a reader nothing about where it goes.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Do images have dimensions and alt text?
&lt;/h2&gt;

&lt;p&gt;Set explicit &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; on every image.&lt;/p&gt;

&lt;p&gt;Without them the browser can't reserve space, and the page shifts as images load — the single most common cause of a bad layout-shift score.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/chart.png"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"420"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Response time by region"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alt text describes the image for someone who can't see it. If the image is purely decorative, &lt;code&gt;alt=""&lt;/code&gt; is correct — and better than a description nobody needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Does the structured data describe this page accurately?
&lt;/h2&gt;

&lt;p&gt;If you mark up an &lt;code&gt;Article&lt;/code&gt;, the headline should match the visible title and the dates should be real.&lt;/p&gt;

&lt;p&gt;If you mark up an &lt;code&gt;FAQPage&lt;/code&gt;, those questions must be visible on the page. Markup describing content that isn't there is a violation, not an optimisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is deliberately not on this list
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Keyword density.&lt;/strong&gt; There's no target to hit, and writing to one produces worse text. Run a density check only to see whether the page is actually about the topic you intended — if the top phrases surprise you, that's a content problem, not a density problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Word count.&lt;/strong&gt; Length is a consequence of covering the subject, not a goal. A page that answers the question in 400 words is finished. Padding it to 1200 makes it worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meta keywords.&lt;/strong&gt; Ignored for over a decade.&lt;/p&gt;




&lt;p&gt;The full version of this, with the reasoning behind each item, is on &lt;a href="https://www.serpprism.com/guides/pre-publish-seo-checklist" rel="noopener noreferrer"&gt;SerpPrism&lt;/a&gt;. The tools mentioned above — &lt;a href="https://www.serpprism.com/tools/heading-analyzer" rel="noopener noreferrer"&gt;heading analyzer&lt;/a&gt;, &lt;a href="https://www.serpprism.com/tools/serp-preview" rel="noopener noreferrer"&gt;SERP preview&lt;/a&gt;, &lt;a href="https://www.serpprism.com/tools/robots-txt-tester" rel="noopener noreferrer"&gt;robots.txt tester&lt;/a&gt; — all run client-side, nothing is uploaded.&lt;/p&gt;

&lt;p&gt;Curious whether anyone has a check I've missed. The one that keeps biting me is #5, because the failure only shows up on index pages.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>html</category>
      <category>a11y</category>
    </item>
    <item>
      <title>I checked the Open Graph tags on 62 homepages. Here's what's actually broken.</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Wed, 23 Sep 2026 03:35:02 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/i-checked-the-open-graph-tags-on-62-homepages-heres-whats-actually-broken-53g1</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/i-checked-the-open-graph-tags-on-62-homepages-heres-whats-actually-broken-53g1</guid>
      <description>&lt;p&gt;Open Graph tags don't affect rankings. They decide something narrower and easier to forget: whether your link renders as a card with an image, or as a bare grey link that people scroll past.&lt;/p&gt;

&lt;p&gt;I got curious about what's actually in the wild, so I fetched the homepages of 78 well-known domains and parsed every &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag whose &lt;code&gt;property&lt;/code&gt; or &lt;code&gt;name&lt;/code&gt; started with &lt;code&gt;og:&lt;/code&gt; or &lt;code&gt;twitter:&lt;/code&gt;. 62 came back usable.&lt;/p&gt;

&lt;p&gt;Here's what I found, including one failure mode I had never considered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method, and the two counting rules that changed the numbers
&lt;/h2&gt;

&lt;p&gt;78 domains, 62 usable homepages. Two rules mattered:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An empty or missing &lt;code&gt;content&lt;/code&gt; attribute counts as absent.&lt;/strong&gt; &lt;code&gt;og:title=""&lt;/code&gt; is a tag that exists and says nothing. If you count it as present, you inflate your numbers and — as it turns out — hide the most interesting bug in the dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No JavaScript was executed.&lt;/strong&gt; This is server-rendered HTML only. That's usually fine for social tags (they're server-rendered &lt;em&gt;because&lt;/em&gt; crawlers don't run JS), but it means every percentage below is a floor, not a rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: Open Graph is near-universal. Twitter cards are not.
&lt;/h2&gt;

&lt;p&gt;56 of 62 homepages (90%) had at least one Open Graph tag. But that headline hides how patchy individual fields are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;tag&lt;/th&gt;
&lt;th&gt;count&lt;/th&gt;
&lt;th&gt;%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;54/62&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;53/62&lt;/td&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;49/62&lt;/td&gt;
&lt;td&gt;79%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;48/62&lt;/td&gt;
&lt;td&gt;77%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;48/62&lt;/td&gt;
&lt;td&gt;77%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:site_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;40/62&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:locale&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;22/62&lt;/td&gt;
&lt;td&gt;35%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So about one homepage in four has no shareable image.&lt;/p&gt;

&lt;p&gt;The Twitter side is where the real gap is. Of the 62:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;34 (55%) declared &lt;code&gt;twitter:card&lt;/code&gt; = &lt;code&gt;summary_large_image&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;14 (23%) = &lt;code&gt;summary&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;2 (3%) = &lt;code&gt;app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;12 (19%) declared no &lt;code&gt;twitter:card&lt;/code&gt; at all&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last group is the one to care about. X falls back to Open Graph for title, description and image — but it still needs &lt;code&gt;twitter:card&lt;/code&gt; to know &lt;em&gt;which layout to build&lt;/em&gt;. Without it you get a plain link, no matter how good your Open Graph tags are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: two sites ship an &lt;code&gt;og:image&lt;/code&gt; tag with nothing in it
&lt;/h2&gt;

&lt;p&gt;I expected this to be common. It wasn't: only 2 of the 34 sites declaring &lt;code&gt;summary_large_image&lt;/code&gt; had no usable image. But the two fail differently, and one of them is genuinely sneaky.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;netflix.com&lt;/strong&gt; declares &lt;code&gt;twitter:card = summary_large_image&lt;/code&gt; and &lt;code&gt;twitter:site&lt;/code&gt;, and nothing else. No &lt;code&gt;og:image&lt;/code&gt;, no &lt;code&gt;og:title&lt;/code&gt;, no &lt;code&gt;og:url&lt;/code&gt;. The card type promises a large image that was never supplied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;forbes.com&lt;/strong&gt; does emit the tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;data-next-head=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no &lt;code&gt;content&lt;/code&gt; attribute at all.&lt;/p&gt;

&lt;p&gt;This is the failure mode worth internalising. A &lt;em&gt;missing&lt;/em&gt; tag fails loudly — any validator catches it. A &lt;em&gt;present-but-empty&lt;/em&gt; tag passes every "is it there?" check and renders nothing. If your audit tool asks "does &lt;code&gt;og:image&lt;/code&gt; exist?", Forbes returns yes.&lt;/p&gt;

&lt;p&gt;Its &lt;code&gt;twitter:image&lt;/code&gt; has the same shape, and its &lt;code&gt;og:image:type&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"image/jpeg,image/gif,image/png"&lt;/span&gt; &lt;span class="na"&gt;data-next-head=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A comma-separated list. Not a single MIME type. Not a valid value for that field.&lt;/p&gt;

&lt;p&gt;You can spot this in one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://example.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;meta[^&amp;gt;]*og:image[^&amp;gt;]*&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the tag shows up with no &lt;code&gt;content=&lt;/code&gt;, you've found it.&lt;/p&gt;

&lt;p&gt;I should be candid that I had this exactly backwards: until the day I collected this data, all 25 pages on my own site declared &lt;code&gt;summary_large_image&lt;/code&gt; with no &lt;code&gt;og:image&lt;/code&gt;. The pattern that turns out to be rare in the wild was on every page of my site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: only 23% declare image dimensions
&lt;/h2&gt;

&lt;p&gt;Of the 48 homepages with a usable &lt;code&gt;og:image&lt;/code&gt;, just &lt;strong&gt;11 (23%)&lt;/strong&gt; also declared &lt;code&gt;og:image:width&lt;/code&gt; and &lt;code&gt;og:image:height&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This sounds harmless. It isn't, and the reason is subtle: without dimensions the platform has to download the image before it can lay out the card. If that fetch is slow, rate-limited, or blocked — a CDN that dislikes the crawler's user agent, a region where the image host is slow — &lt;strong&gt;the first share goes out with no image and the second one works.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An intermittent bug like that is far harder to diagnose than a missing tag, because by the time you look, it works.&lt;/p&gt;

&lt;p&gt;The 11 that do declare dimensions: &lt;code&gt;figma.com&lt;/code&gt;, &lt;code&gt;x.com&lt;/code&gt;, &lt;code&gt;docker.com&lt;/code&gt;, &lt;code&gt;developer.mozilla.org&lt;/code&gt;, &lt;code&gt;w3.org&lt;/code&gt;, &lt;code&gt;moz.com&lt;/code&gt;, &lt;code&gt;screamingfrog.co.uk&lt;/code&gt;, &lt;code&gt;yoast.com&lt;/code&gt;, &lt;code&gt;searchenginejournal.com&lt;/code&gt;, &lt;code&gt;harvard.edu&lt;/code&gt;, &lt;code&gt;heroku.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Four of those — Moz, Screaming Frog, Yoast, Search Engine Journal — are SEO vendors or publications. &lt;strong&gt;All four&lt;/strong&gt; SEO sites in the sample declare dimensions, against 23% overall. They're also the same four that showed up as &lt;code&gt;@graph&lt;/code&gt; users when I ran the equivalent survey on JSON-LD. Consistent signal: the sites whose business is this metadata treat it better than average by a wide margin.&lt;/p&gt;

&lt;p&gt;Only 5 of 48 (10%) declared &lt;code&gt;og:image:alt&lt;/code&gt;. Alt text on a social card isn't decorative — it's what a screen reader announces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 4: one in four &lt;code&gt;og:title&lt;/code&gt; disagrees with &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;14 of the 54 homepages with both showed different text in each (26%).&lt;/p&gt;

&lt;p&gt;Most are trivial: &lt;code&gt;github.com&lt;/code&gt; drops a trailing "· GitHub" from the Open Graph version. Some aren't. &lt;code&gt;kubernetes.io&lt;/code&gt; ships:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Kubernetes&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Production-Grade Container Orchestration"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The search result says one thing; the shared link says another. Neither is &lt;em&gt;wrong&lt;/em&gt; — but it means the title you optimised for search isn't the one people see when the page is shared, and those are written for different purposes. If you only check one, check the one that appears in search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 5: the things I expected and didn't find
&lt;/h2&gt;

&lt;p&gt;Across 48 usable images:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1&lt;/strong&gt; relative path (&lt;code&gt;kubernetes.io&lt;/code&gt;, &lt;code&gt;/images/kubernetes-open-graph.png&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt; &lt;code&gt;http://&lt;/code&gt; images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt; sites declaring more than one &lt;code&gt;og:image&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Relative &lt;code&gt;og:image&lt;/code&gt; URLs are the classic warning — scrapers have no base URL to resolve against, so the tag gets dropped. It happened once in 48. Insecure images, not at all.&lt;/p&gt;

&lt;p&gt;This is the third survey in a row where a widely repeated warning turned out to be rare at this sample size. That's not an argument for ignoring the warnings. It's an argument for checking the base rate before you spend a day on the thing you're worried about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check on your own tags
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is the image tag carrying anything?&lt;/strong&gt; View source, look for &lt;code&gt;content=&lt;/code&gt;. Present-but-empty is the check no validator does for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you declare width and height?&lt;/strong&gt; Two lines, kills the "first share has no image" failure mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you declare &lt;code&gt;twitter:card&lt;/code&gt;?&lt;/strong&gt; One in five sites skip it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does &lt;code&gt;og:title&lt;/code&gt; match your title?&lt;/strong&gt; If not, pick which one you meant.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full dataset — including the raw HTML for all 62 domains, so you can check any claim against the markup rather than my summary of it — is on &lt;a href="https://www.serpprism.com/guides/open-graph-in-the-wild" rel="noopener noreferrer"&gt;SerpPrism&lt;/a&gt;. There's also a &lt;a href="https://www.serpprism.com/tools/open-graph-preview" rel="noopener noreferrer"&gt;preview tool&lt;/a&gt; that renders what your tags will actually produce.&lt;/p&gt;

&lt;p&gt;If you find a row that's wrong, the markup settles it — and I'd genuinely like to know.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>html</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Your JSON-LD is duplicated in three places. Here's the fix.</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Wed, 23 Sep 2026 03:34:12 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/your-json-ld-is-duplicated-in-three-places-heres-the-fix-3pf8</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/your-json-ld-is-duplicated-in-three-places-heres-the-fix-3pf8</guid>
      <description>&lt;h2&gt;
  
  
  The pattern almost everyone starts with
&lt;/h2&gt;

&lt;p&gt;You add an &lt;code&gt;Organization&lt;/code&gt; block to the footer. An &lt;code&gt;Article&lt;/code&gt; block to the article template. A &lt;code&gt;BreadcrumbList&lt;/code&gt; to the breadcrumb component. Three separate &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt; blocks, each describing one thing.&lt;/p&gt;

&lt;p&gt;It works — Google reads all of them — and it's where most sites stop.&lt;/p&gt;

&lt;p&gt;The cost isn't visible immediately. It shows up the first time you rename the company, change the logo URL, or move domains. Now you have three places containing the same facts, and nothing enforces that they agree. One gets updated, the others don't, and your structured data now contradicts itself in a way &lt;strong&gt;no error message will tell you about.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;@id&lt;/code&gt; actually does
&lt;/h2&gt;

&lt;p&gt;Every node in JSON-LD can carry an &lt;code&gt;@id&lt;/code&gt; — a stable identifier. A URL is conventional.&lt;/p&gt;

&lt;p&gt;Once a node has an &lt;code&gt;@id&lt;/code&gt;, any other node can &lt;em&gt;refer&lt;/em&gt; to it instead of repeating its properties.&lt;/p&gt;

&lt;p&gt;So instead of the Article containing its own copy of the publisher's name and logo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Article"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publisher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Organization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"logo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ImageObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/logo.png"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…it contains a reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Article"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publisher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/#organization"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full description lives once, in a node other nodes point at. Change the logo in that one place and every reference is correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape that works
&lt;/h2&gt;

&lt;p&gt;One &lt;code&gt;@graph&lt;/code&gt; array in the page head, containing in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Organization&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WebSite&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WebPage&lt;/code&gt; (for the current URL)&lt;/li&gt;
&lt;li&gt;whatever page-specific nodes apply — &lt;code&gt;Article&lt;/code&gt;, &lt;code&gt;FAQPage&lt;/code&gt;, &lt;code&gt;HowTo&lt;/code&gt;, &lt;code&gt;Product&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Organization and WebSite nodes are identical on every page — that's the point. They're declared once per page but defined consistently, and page-specific nodes reference them by &lt;code&gt;@id&lt;/code&gt; rather than restating them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@graph"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Organization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/#organization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WebSite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/#website"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"publisher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/#organization"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WebPage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/blog/post#webpage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/blog/post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isPartOf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/#website"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"datePublished"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dateModified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-02"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Article"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/blog/post#article"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mainEntityOfPage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/blog/post#webpage"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"publisher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.com/#organization"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four nodes, one block, every relationship explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways this goes wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Referencing an &lt;code&gt;@id&lt;/code&gt; that doesn't exist on the page.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the Article says its publisher is &lt;code&gt;#organization&lt;/code&gt; but no node with that &lt;code&gt;@id&lt;/code&gt; is present, the reference resolves to nothing. Google handles this by &lt;em&gt;ignoring the property&lt;/em&gt; — so the symptom isn't an error, it's &lt;strong&gt;silently missing data&lt;/strong&gt;. Always include the node you reference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Reusing the same &lt;code&gt;@id&lt;/code&gt; for different things.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Identifiers must be unique. Two nodes sharing an &lt;code&gt;@id&lt;/code&gt; means you've declared two contradictory definitions of one entity, and the outcome depends on which one a consumer reads first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Relative &lt;code&gt;@id&lt;/code&gt; values.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use absolute URLs. A relative reference like &lt;code&gt;#organization&lt;/code&gt; is interpreted against the page URL, which means the same markup on two domains produces two different identifiers — and any cross-page relationship you were expressing breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dates deserve their own section
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;datePublished&lt;/code&gt; should never change after the page goes live.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dateModified&lt;/code&gt; should change when the content genuinely changes, and not otherwise.&lt;/p&gt;

&lt;p&gt;Incrementing &lt;code&gt;dateModified&lt;/code&gt; on every deploy is a common habit, usually adopted because someone believed freshness helps rankings. It doesn't — and the pattern is trivially detectable: a modified date that advances daily while the words stay identical tells any consumer the field is noise, which means it gets discounted on the pages where it was actually true.&lt;/p&gt;

&lt;p&gt;If you use a build system, wire &lt;code&gt;dateModified&lt;/code&gt; to a &lt;strong&gt;per-page content hash&lt;/strong&gt;, not the build timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// store hash → date in a committed JSON file&lt;/span&gt;
&lt;span class="c1"&gt;// update the date only when the hash changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The field then moves only when the content does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking your markup
&lt;/h2&gt;

&lt;p&gt;Validate with Google's Rich Results Test (for eligibility) and the Schema Markup Validator (for syntax).&lt;/p&gt;

&lt;p&gt;Neither will tell you that your &lt;code&gt;@id&lt;/code&gt; references are dangling — &lt;strong&gt;a dangling reference is not a syntax error.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The check that catches it takes a minute and is manual:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract every &lt;code&gt;@id&lt;/code&gt; &lt;strong&gt;defined&lt;/strong&gt; in the page&lt;/li&gt;
&lt;li&gt;Extract every value used in a &lt;strong&gt;reference&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Confirm every reference appears in the defined list&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On a page with six nodes this is faster by eye than to automate.&lt;/p&gt;




&lt;p&gt;I built a small generator for this shape that validates required properties as you type — &lt;a href="https://www.serpprism.com/tools/schema-markup-generator" rel="noopener noreferrer"&gt;SerpPrism's JSON-LD generator&lt;/a&gt;. It's client-side, nothing is uploaded. There's also a longer version of this post with a few more edge cases on the &lt;a href="https://www.serpprism.com/guides/json-ld-graph-structure" rel="noopener noreferrer"&gt;site itself&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy to hear how other people handle the dateModified problem — it's the one I've seen go wrong most often in production.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>html</category>
    </item>
    <item>
      <title>Why Google Won't Index Your Page: A Checklist</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/why-google-wont-index-your-page-a-checklist-94d</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/why-google-wont-index-your-page-a-checklist-94d</guid>
      <description>&lt;h2&gt;
  
  
  Two problems that look identical and are not
&lt;/h2&gt;

&lt;p&gt;Almost every "my page is not indexed" complaint is one of two things, and they need opposite responses.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;code fault&lt;/strong&gt; is something wrong with the URL or the page. It is fixable today, and once it is fixed the page becomes eligible. A &lt;strong&gt;trust gate&lt;/strong&gt; is Google having fetched the page and decided not to spend index space on it yet. There is nothing to fix, because nothing is broken — and this is where most advice sends people in circles.&lt;/p&gt;

&lt;p&gt;Order matters because the two produce the same symptom in Search Console, and the fix for one does nothing for the other. Re-requesting indexing on a page in a trust gate wastes your one lever. Rewriting the content of a page blocked by a &lt;code&gt;noindex&lt;/code&gt; tag is worse than wasted, because the actual problem is still sitting there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — read the status code, not the page
&lt;/h2&gt;

&lt;p&gt;A page that renders in your browser is not proof of anything. Ask the server instead:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -sS -o /dev/null -w "%{http_code}" https://example.com/page&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What the answer tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;404 or 410&lt;/strong&gt; — the page is not indexed by design. Either the URL is wrong or it was deliberately removed. Nothing else in this guide applies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5xx&lt;/strong&gt; — the server is failing. Google backs off and retries later; it will not index a page it cannot fetch. Fix the server first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;301 or 302&lt;/strong&gt; — you are looking at a different URL from the one you think. Follow the redirect and diagnose the destination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;200&lt;/strong&gt; — good, but keep reading, because 200 is where the real traps live.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trap that hides inside a 200 is the &lt;strong&gt;soft 404&lt;/strong&gt; : the server reports success and the content is an error page. Our &lt;a href="https://www.serpprism.com/guides/robots-txt-in-the-wild" rel="noopener noreferrer"&gt;robots.txt survey&lt;/a&gt; caught exactly this on two of 78 well-known domains — &lt;code&gt;khanacademy.org&lt;/code&gt; and &lt;code&gt;cdc.gov&lt;/code&gt; answered 200 with an HTML error page instead of a robots file. A monitor watching status codes sees nothing wrong and a browser shows a page. If your unindexed URL returns 200 and reads like an error, you have your answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — is it blocked?
&lt;/h2&gt;

&lt;p&gt;Two mechanisms can block indexing, and they are not the same mechanism:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;robots.txt&lt;/strong&gt; — a &lt;code&gt;Disallow&lt;/code&gt; line stops the crawl. Note that it stops the &lt;em&gt;fetch&lt;/em&gt;, not the indexing of a URL Google already knows about from links elsewhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A &lt;code&gt;noindex&lt;/code&gt; meta tag or &lt;code&gt;X-Robots-Tag&lt;/code&gt; header&lt;/strong&gt; — this stops indexing even when the page is crawled normally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One combination costs people weeks: &lt;strong&gt;a robots.txt &lt;code&gt;Disallow&lt;/code&gt; and a &lt;code&gt;noindex&lt;/code&gt; on the same page.&lt;/strong&gt; The disallow prevents Google from fetching the page, so Google never sees the &lt;code&gt;noindex&lt;/code&gt;, so the URL can still be indexed from links pointing at it. You then remove the &lt;code&gt;noindex&lt;/code&gt;, observe no change, and conclude the tag was never the problem. The order to undo it is: remove the &lt;code&gt;Disallow&lt;/code&gt; first, let Google fetch the page, confirm it reads the &lt;code&gt;noindex&lt;/code&gt;, and only then remove that.&lt;/p&gt;

&lt;p&gt;You can test the first of these with our &lt;a href="https://www.serpprism.com/tools/robots-txt-tester" rel="noopener noreferrer"&gt;robots.txt tester&lt;/a&gt;. For the header, one curl is enough: &lt;code&gt;curl -sSI https://example.com/page | grep -i x-robots-tag&lt;/code&gt;. And note that a page with no robots meta at all is normal — &lt;code&gt;index, follow&lt;/code&gt; is the default, so its absence is not a fault.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — can it be discovered?
&lt;/h2&gt;

&lt;p&gt;Google cannot index a URL it never finds. Three ways a page stays invisible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is not in your sitemap.&lt;/strong&gt; Check that the URL appears in &lt;code&gt;sitemap.xml&lt;/code&gt; — and that the URL listed there actually exists. A sitemap full of 404s is a signal of neglect rather than of coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The sitemap is not declared.&lt;/strong&gt; Listing it in robots.txt lets a crawler find it without being told twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing links to it.&lt;/strong&gt; This is the most common cause of a page that is technically perfect and never gets indexed. A page with no internal links is an orphan, and orphans depend entirely on the sitemap being read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the same class of omission our &lt;a href="https://www.serpprism.com/guides/canonical-in-the-wild" rel="noopener noreferrer"&gt;canonical survey&lt;/a&gt; found on 11 of 55 famous homepages: the tag was simply absent. Nobody forgot on purpose. Omission is the default state of a site nobody audits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — is it a duplicate?
&lt;/h2&gt;

&lt;p&gt;If the page is crawlable, discoverable and unblocked, the next question is whether Google treats it as a second copy of something else. The canonical tag is the instruction that decides this, and the mistake that matters is comparing it against the wrong URL.&lt;/p&gt;

&lt;p&gt;In our canonical survey, 43 of 55 homepages redirected at least once before serving content. If you compare your canonical against the URL you typed rather than the URL you were finally served, you will conclude you have a cross-domain canonical problem when you do not. Compare against the final URL after redirects. Then check that the canonical target itself returns 200 — two of the 43 we tested pointed at URLs that did not, and nothing warns you when that happens.&lt;/p&gt;

&lt;p&gt;Three more duplicates worth ruling out: the http and https versions of the page, the &lt;code&gt;www&lt;/code&gt; and non-&lt;code&gt;www&lt;/code&gt; versions, and any tracking parameters that create a second address for the same content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — is the content actually there?
&lt;/h2&gt;

&lt;p&gt;If the page is a single-page app, the HTML Google receives may contain almost nothing. Curl the URL and read the raw source rather than the rendered page. If the body is an empty container and a bundle of script tags, that is what a crawler may be evaluating.&lt;/p&gt;

&lt;p&gt;This is not hypothetical. Two of the homepages in our &lt;a href="https://www.serpprism.com/guides/serp-snippet-in-the-wild" rel="noopener noreferrer"&gt;SERP snippet survey&lt;/a&gt; returned client-rendered shells with no title in the served HTML at all. Google can render JavaScript, but it does so on a second pass, and a page that needs rendering is a page that is slower and less certain to be evaluated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — now it is a trust gate, and there is nothing to fix
&lt;/h2&gt;

&lt;p&gt;If every check above passed, the page is eligible and Google is choosing not to index it yet. Search Console describes this in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discovered — currently not indexed&lt;/strong&gt; : Google knows the URL exists and has not fetched it yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawled — currently not indexed&lt;/strong&gt; : Google fetched it and judged it not worth index space at this point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither is a defect, and neither is a bug you can patch. The usual reasons are thin content, no internal links, a brand-new domain with no authority, or content close enough to pages that already exist that Google does not need another copy of it.&lt;/p&gt;

&lt;p&gt;What actually moves it: content clearly more useful than what is already indexed, internal links from pages that are themselves indexed, genuine external links or mentions, and time. What does not move it: requesting indexing repeatedly, resubmitting the sitemap repeatedly, or rewriting the page for the third time.&lt;/p&gt;

&lt;p&gt;We went through this on our own site. A batch of pages sat in "Crawled — currently not indexed" while every technical check passed: 200 status, self-referencing canonical, index and follow, well over a thousand words each. There was nothing to fix. They drained on their own within days. The useful conclusion is not "wait and hope" — it is that &lt;strong&gt;when all five steps above pass, the correct action is to stop working on that page and go make the rest of the site better.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Request indexing" does, and does not do
&lt;/h2&gt;

&lt;p&gt;It queues a fetch. That is the whole of it. It is worth using on a brand-new URL, or on a page you have just fixed, because it shortens the wait for the next crawl.&lt;/p&gt;

&lt;p&gt;It is not worth using on a page that has already been crawled and judged. Requesting indexing does not override a quality decision, there is a practical limit on how often you can use it, and spending it on a trust gate burns the one lever you have for a genuine fault.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order, in one table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;If it fails&lt;/th&gt;
&lt;th&gt;Fixable by you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Status code&lt;/td&gt;
&lt;td&gt;404, 410, 5xx, or a soft 404&lt;/td&gt;
&lt;td&gt;Yes, today&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;robots.txt, noindex, X-Robots-Tag&lt;/td&gt;
&lt;td&gt;Blocked from crawl or from index&lt;/td&gt;
&lt;td&gt;Yes, today&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Sitemap and internal links&lt;/td&gt;
&lt;td&gt;Undiscoverable, or an orphan&lt;/td&gt;
&lt;td&gt;Yes, today&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Canonical and duplicates&lt;/td&gt;
&lt;td&gt;Consolidated into another URL&lt;/td&gt;
&lt;td&gt;Yes, today&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Content present in the served HTML&lt;/td&gt;
&lt;td&gt;Empty shell as seen by a crawler&lt;/td&gt;
&lt;td&gt;Yes, today&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;All of the above passed&lt;/td&gt;
&lt;td&gt;A trust gate&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No&lt;/strong&gt; — content, links and time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The point of working in this order is that you stop at the first failure, and you stop entirely at step 6. Most people start at step 6 and work backwards, which is why the advice to "just keep improving the content" gets applied to pages that were never crawlable in the first place.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>marketing</category>
    </item>
    <item>
      <title>What 59 Homepages Put in Their SERP Snippet</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/what-59-homepages-put-in-their-serp-snippet-5d6j</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/what-59-homepages-put-in-their-serp-snippet-5d6j</guid>
      <description>&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;On 2026-09-25 we fetched &lt;code&gt;https://&amp;lt;domain&amp;gt;/&lt;/code&gt; for 78 hand-picked domains spanning news, commerce, SaaS, developer tooling, social, education, finance, government and SEO. 62 returned a usable page: 41 over a direct connection and 21 over a local proxy, because a number of these domains are unreachable from our network without one.&lt;/p&gt;

&lt;p&gt;Three of the 62 were not homepages and were dropped. &lt;code&gt;khanacademy.org&lt;/code&gt; answered 200 with a 3 KB Cloudflare interstitial whose entire title is "Client Challenge". &lt;code&gt;paypal.com&lt;/code&gt; returned an 8 KB JavaScript shell with no &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; at all. &lt;code&gt;archive.org&lt;/code&gt; returned a 1.9 KB shell. Counting them would have manufactured findings that do not exist — a 15-character title, two missing titles — so they are excluded and listed in the script's output instead.&lt;/p&gt;

&lt;p&gt;One bug is worth naming, because it changed the headline. Our first pass read &lt;code&gt;python.org&lt;/code&gt; as having no &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;. It has one. The site returns gzip unconditionally, and our fetch was not decompressing, so we were parsing gzip bytes as HTML. Once the fetch decompressed, the title appeared, and the count of homepages without a title went from two to zero. A measurement pipeline that fails quietly produces confident nonsense.&lt;/p&gt;

&lt;p&gt;For each page we took the first &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; and the first &lt;code&gt;&amp;lt;meta name="description"&amp;gt;&lt;/code&gt;, decoded HTML entities, collapsed runs of whitespace and measured in Unicode code points. Skipping the decode step inflates every number: &lt;code&gt;&amp;amp;amp;&lt;/code&gt; counts as five characters instead of one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: every homepage has a title, and the median is 35 characters
&lt;/h2&gt;

&lt;p&gt;59 of 59. Not one homepage in the sample ships without a &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;. The distribution is narrow at the bottom and long at the top: minimum 3 characters, 25th percentile 22, median 35, 75th percentile 54, 90th percentile 61, maximum 116, mean 36.9.&lt;/p&gt;

&lt;p&gt;The familiar rule — keep your title under 60 characters — is true and almost empty. Only 7 of 59 pages (12%) exceed 60, and just 2 exceed 70. The real behaviour is at the opposite end: 21 of 59 (36%) are under 30 characters. Half the sample writes a title shorter than 35 characters.&lt;/p&gt;

&lt;p&gt;If you have been treating 60 as a target to hit, you are aiming at a number almost nobody in this sample reaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: the shorter the title, the bigger the brand
&lt;/h2&gt;

&lt;p&gt;The shortest titles belong to the most famous names in the sample: &lt;code&gt;w3.org&lt;/code&gt; (3 characters, "W3C"), &lt;code&gt;forbes.com&lt;/code&gt; (6, "Forbes"), &lt;code&gt;tumblr.com&lt;/code&gt; (6), &lt;code&gt;twitch.tv&lt;/code&gt; (6), &lt;code&gt;youtube.com&lt;/code&gt; (7), &lt;code&gt;pinterest.com&lt;/code&gt; (9), &lt;code&gt;wikipedia.org&lt;/code&gt; (9), &lt;code&gt;wikimedia.org&lt;/code&gt; (9), &lt;code&gt;kubernetes.io&lt;/code&gt; (10), &lt;code&gt;developer.mozilla.org&lt;/code&gt; (12, "MDN Web Docs").&lt;/p&gt;

&lt;p&gt;The longest belong to sites that still have to explain themselves: &lt;code&gt;bbc.com&lt;/code&gt; at 116 characters, &lt;code&gt;airbnb.com&lt;/code&gt; at 74, &lt;code&gt;atlassian.com&lt;/code&gt; at 70, &lt;code&gt;wise.com&lt;/code&gt; at 68, &lt;code&gt;shopify.com&lt;/code&gt; at 66, &lt;code&gt;github.com&lt;/code&gt; at 61.&lt;/p&gt;

&lt;p&gt;That is not a style difference, it is an equity budget. "Forbes" is a complete title because the reader already knows what Forbes is; the word carries the meaning the other 54 characters would otherwise have to. A site nobody has heard of that ships its name alone has said nothing at all. The 60 characters are not a ceiling to stay under — they are space you have to fill until your name does the work by itself.&lt;/p&gt;

&lt;p&gt;The practical corollary: copying the title format of a famous site means copying a luxury you have not earned yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: nearly everyone writes a meta description, so that advice is stale
&lt;/h2&gt;

&lt;p&gt;57 of 59 homepages (97%) ship a description. Only two do not: &lt;code&gt;linkedin.com&lt;/code&gt; and &lt;code&gt;cdc.gov&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This contradicts a piece of advice still repeated constantly — that most sites forget the description and let Google invent one. On this sample that is simply false. What the last decade of snippet rewriting actually did was make the tag universal rather than optional. The omission rate here is 3%, not the 50% the folklore implies.&lt;/p&gt;

&lt;p&gt;Two caveats. &lt;code&gt;linkedin.com&lt;/code&gt; served a 121 KB JavaScript shell and &lt;code&gt;cdc.gov&lt;/code&gt; a 60 KB document, so a description injected after load would be invisible to this method; both are recorded as "none in the served HTML", which is also what a crawler parsing the initial document sees. And writing a description does not mean Google uses it — this measurement is only about what sites ship, not about which sentence ends up in the snippet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 4: descriptions cluster at 114 to 150, and the 160 guideline holds up
&lt;/h2&gt;

&lt;p&gt;The length distribution: minimum 45, 25th percentile 114, median 131, 75th percentile 150, 90th percentile 184, maximum 559, mean 141.&lt;/p&gt;

&lt;p&gt;That interquartile range — 114 to 150 — is remarkably tight for a number nobody enforces. Practitioners have converged on a working band just under the display limit without being told to.&lt;/p&gt;

&lt;p&gt;12 of the 57 (21%) run past 160 characters. The extremes are instructive: &lt;code&gt;kubernetes.io&lt;/code&gt; ships 559 characters, roughly three and a half times what will ever be shown; &lt;code&gt;stanford.edu&lt;/code&gt; ships 364; &lt;code&gt;trello.com&lt;/code&gt; ships 195. At the other end, four descriptions fall under 70 characters, leaving more than half of the available space empty.&lt;/p&gt;

&lt;p&gt;The practical read: 120 to 155 characters is where this sample lives, and it is also where truncation is least likely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 5: pipe is the most common separator, but no separator is more common
&lt;/h2&gt;

&lt;p&gt;Counting only separators with whitespace on both sides, so that the hyphen inside &lt;code&gt;rust-lang&lt;/code&gt; is not mistaken for one: pipe appears in 16 titles (27%), hyphen in 11 (19%), em dash in 2, and colon, middle dot and slash once each.&lt;/p&gt;

&lt;p&gt;But 27 of 59 (46%) use no separator at all. The pipe is the plurality, not the majority; nearly half the sample writes a name or a sentence and stops.&lt;/p&gt;

&lt;p&gt;A separator is a formatting decision with no ranking consequence. What it does signal is how many ideas you are concatenating. One separator means two ideas, and in most of the titles above the second one is the weaker — the tagline, the region, the boilerplate. The titles with no separator have a single idea, and a single idea is usually the better title.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 6: your title is not one thing
&lt;/h2&gt;

&lt;p&gt;We fetched from a Chinese IP address, and several sites served a regional variant rather than the default. &lt;code&gt;linkedin.com&lt;/code&gt; returned 领英企业服务. &lt;code&gt;stripe.com&lt;/code&gt; returned "Stripe | 金融基础设施，托举营收增长". &lt;code&gt;wise.com&lt;/code&gt; returned "Wise: The international account | Money without borders | Wise China". &lt;code&gt;bloomberg.com&lt;/code&gt; returned "Bloomberg Europe".&lt;/p&gt;

&lt;p&gt;None of those are mistakes. But they mean a title audit performed from one location gives you one slice of a title that changes with geography, and a crawler dispatched from a different location reads a different string. The same applies to the description.&lt;/p&gt;

&lt;p&gt;If you localise titles per region, decide it deliberately and keep it consistent with your &lt;a href="https://www.serpprism.com/guides/hreflang-in-the-wild" rel="noopener noreferrer"&gt;hreflang annotations&lt;/a&gt; and your &lt;a href="https://www.serpprism.com/guides/canonical-in-the-wild" rel="noopener noreferrer"&gt;canonical choice&lt;/a&gt;. If you do not intend to localise, check that your edge is not doing it for you — the way to find out is to fetch your own homepage from more than one country.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Write 50 to 60 characters, not 3.&lt;/strong&gt; The median homepage title in this sample is 35 characters and 36% are under 30 — but those are household names. Unless yours is one, use the space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always write a description.&lt;/strong&gt; 97% of the sample does. It is table stakes now, not an optional extra, and omitting it hands the sentence a searcher reads entirely to Google.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target 120 to 155 characters.&lt;/strong&gt; That is the measured interquartile band, and it sits just inside where truncation begins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not copy a famous site's title.&lt;/strong&gt; Its brevity is backed by recognition you have not earned yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prefer one idea per title.&lt;/strong&gt; 46% of the sample uses no separator. When you do need one, pipe is the safe default at 27% — but check whether the second half is pulling its weight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check what your edge actually serves.&lt;/strong&gt; Fetch your homepage from two or three regions before concluding that your title is what you wrote.&lt;/p&gt;

&lt;p&gt;To see the truncation before you ship it, the &lt;a href="https://www.serpprism.com/tools/serp-preview" rel="noopener noreferrer"&gt;SERP preview tool&lt;/a&gt; on this site renders a title and description at Google's real desktop and mobile widths. The &lt;a href="https://www.serpprism.com/tools/meta-tag-generator" rel="noopener noreferrer"&gt;meta tag generator&lt;/a&gt; emits the title, description and canonical from a single input, so the values cannot drift apart from each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;59 homepages from 78 hand-picked, well-known domains — representative of famous sites, not of the web. A random sample would look different in both directions, and probably longer: small sites write longer titles than W3C does.&lt;/p&gt;

&lt;p&gt;16 of the 78 domains never produced a readable page — 403s, bot checks, timeouts. That loss is not random; it skews toward large consumer and financial sites that fight crawlers, so the sample is biased toward sites that serve their homepage to anyone who asks.&lt;/p&gt;

&lt;p&gt;We read the initial HTML only. A title or description injected by JavaScript after load is invisible here, and one of the two missing descriptions may be exactly that.&lt;/p&gt;

&lt;p&gt;One fetch, from one IP address, on one day. Finding 6 is the direct consequence of that limitation rather than a separate discovery.&lt;/p&gt;

&lt;p&gt;The brand-position analysis we planned did not survive checking and is not reported. Judging brand placement from the domain's first label fails on multi-word brands and on brands spelled differently from their domain: 9 of the 10 titles flagged as containing no brand did contain one — "the Guardian", "Node.js", "Rust", "Screaming Frog", "Search Engine Journal", "Square". We dropped the metric rather than publish a number we could not stand behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The script and the shared domain list ship with this site. &lt;code&gt;node scripts/survey-serp-snippet.mjs&lt;/code&gt; fetches and stores the raw HTML for every domain; &lt;code&gt;--report&lt;/code&gt; re-derives every number from that stored HTML without touching the network; &lt;code&gt;--sample=N&lt;/code&gt; prints raw title and description lines for manual checking; and &lt;code&gt;--outliers&lt;/code&gt; lists every title over 60 characters, every description over 160, and every response excluded as a non-homepage. Our &lt;a href="https://www.serpprism.com/guides/robots-txt-in-the-wild" rel="noopener noreferrer"&gt;robots.txt&lt;/a&gt;, &lt;a href="https://www.serpprism.com/guides/hreflang-in-the-wild" rel="noopener noreferrer"&gt;hreflang&lt;/a&gt; and &lt;a href="https://www.serpprism.com/guides/canonical-in-the-wild" rel="noopener noreferrer"&gt;canonical&lt;/a&gt; surveys cover the same domain list from different angles.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>html</category>
      <category>marketing</category>
    </item>
    <item>
      <title>robots.txt Mistakes That Silently Deindex Pages</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/robotstxt-mistakes-that-silently-deindex-pages-3dfh</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/robotstxt-mistakes-that-silently-deindex-pages-3dfh</guid>
      <description>&lt;h2&gt;
  
  
  1. Disallowing a page you also marked noindex
&lt;/h2&gt;

&lt;p&gt;This is the most damaging one and it looks reasonable. You want a page out of the index, so you add &lt;code&gt;noindex&lt;/code&gt; — and you also disallow it so crawlers stop wasting time. But a crawler that cannot fetch the page can never read the noindex. The URL stays in the index indefinitely, usually with a note saying it was not crawled.&lt;/p&gt;

&lt;p&gt;The fix is counterintuitive: to remove a page using noindex, you must &lt;em&gt;allow&lt;/em&gt; crawling. Let Google fetch it, see the directive, and drop it. Only then disallow, if you still want to.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Blocking CSS and JavaScript
&lt;/h2&gt;

&lt;p&gt;An older habit, and still common in older configs. Google renders pages, and rendering requires your stylesheets and scripts. Block &lt;code&gt;/assets/&lt;/code&gt; or &lt;code&gt;/static/&lt;/code&gt; and Google sees a page with no layout, which it may judge as broken or as a poor mobile experience.&lt;/p&gt;

&lt;p&gt;Check your file for broad disallows on asset directories. If they exist, remove them — there is no upside to blocking Google from seeing your own stylesheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Assuming order decides priority
&lt;/h2&gt;

&lt;p&gt;It does not. Within a user-agent group, the longest matching path wins regardless of line order. So &lt;code&gt;Disallow: /admin/&lt;/code&gt; followed by &lt;code&gt;Allow: /admin/public/&lt;/code&gt; leaves /admin/public/ crawlable. Reordering those lines changes nothing, which is why people conclude the file is being ignored when it is behaving exactly as specified.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Wildcard patterns that over-match
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Disallow: /*.pdf$&lt;/code&gt; blocks PDFs — and if written slightly wrong, blocks far more. A misplaced &lt;code&gt;*&lt;/code&gt; or a missing &lt;code&gt;$&lt;/code&gt; turns a narrow rule into a site-wide block. Because robots.txt produces no warnings, this can run for months before anyone notices the traffic drop.&lt;/p&gt;

&lt;p&gt;Test every rule containing a wildcard against a few real URLs before deploying it. That is what the tester on this site is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Forgetting that staging is one typo away from public
&lt;/h2&gt;

&lt;p&gt;A staging host protected by robots.txt alone is not protected. Any crawler that ignores the file — and there are many — will index it. Duplicate staging content competing with production is a slow, confusing problem to untangle later.&lt;/p&gt;

&lt;p&gt;Put real authentication in front of staging. Keep the robots.txt disallow as well, but understand it is politeness, not a lock.&lt;/p&gt;

&lt;h2&gt;
  
  
  A five-minute check that catches all of these
&lt;/h2&gt;

&lt;p&gt;Fetch your robots.txt and paste it into a tester. Then run five URLs through it: your homepage, a category page, a deep article, one asset from your stylesheet directory, and a URL you believe is blocked. If any of the five gives a verdict you did not expect, you have found the bug — and you have found it before Google did.&lt;/p&gt;

&lt;p&gt;Do this again after any deploy that touches routing, and any time someone new edits the file. The failure mode here is silence: nothing breaks visibly, nothing appears in an error log, and traffic from one section of the site quietly stops arriving. A monthly five-minute check is the entire maintenance cost.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>Meta Description Length: What Gets Truncated</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/meta-description-length-what-gets-truncated-4gg9</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/meta-description-length-what-gets-truncated-4gg9</guid>
      <description>&lt;h2&gt;
  
  
  Where the 155-character rule comes from
&lt;/h2&gt;

&lt;p&gt;It is a working average, not a limit. Someone measured a batch of results, found the cut usually landed near 155 characters, and the number spread because it is easy to remember. What Google actually does is render the description in a specific font at a specific size inside a container of a specific width, then drop whatever does not fit.&lt;/p&gt;

&lt;p&gt;The practical consequence: character count and rendered width disagree constantly. A description of 150 narrow characters fits. A description of 138 characters with several capital M and W does not. If you have ever shipped a snippet that looked correct in your CMS and appeared chopped in search, this is why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two widths that matter
&lt;/h2&gt;

&lt;p&gt;Desktop results render the description at roughly 14px in a container around 600px wide. Mobile is narrower — meaningfully narrower, not marginally. A description tuned exactly to the desktop limit will be cut on a phone, and most of your traffic is on a phone.&lt;/p&gt;

&lt;p&gt;The safe approach is to aim at the mobile limit, not the desktop one. Put the part that must survive in the first 120 characters and treat everything after that as optional elaboration.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Google ignores your description entirely
&lt;/h2&gt;

&lt;p&gt;Often. If Google decides a sentence from your page body matches the query better than the description you wrote, it substitutes that sentence. This is normal behaviour and not a signal that anything is wrong.&lt;/p&gt;

&lt;p&gt;You cannot force it to use your text. What you can do is remove the reason to substitute: write the opening sentence of the page so that it would work as a snippet on its own, and keep the meta description aligned with what the page actually says. When the two agree, there is nothing to gain by swapping.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is not worth doing
&lt;/h2&gt;

&lt;p&gt;Do not stuff the description with keyword variants. Descriptions are not a ranking factor; their entire job is to earn the click. A description written for a machine reads like it was written for a machine, and gets skipped.&lt;/p&gt;

&lt;p&gt;Do not write one description and reuse it across a template. Duplicate descriptions are not penalised, but they make every page in a set look identical in the results, which costs you the clicks that a specific sentence would have earned.&lt;/p&gt;

&lt;h2&gt;
  
  
  A working method
&lt;/h2&gt;

&lt;p&gt;Draft the description last, after the page is written. At that point you know what the page actually delivers, and you can say it in one sentence without guessing. Descriptions written before the page exists tend to describe the page you intended rather than the one you shipped.&lt;/p&gt;

&lt;p&gt;Then put the sentence into a preview and check where it lands. If the important part is past the mobile cut, move it forward rather than trimming the tail — the end of a description is the cheapest thing to lose.&lt;/p&gt;

&lt;p&gt;One more thing worth doing: read the first sentence of the page body next to the description. If they are far apart, Google has a reason to substitute one for the other. Aligning them is the closest thing to control you have over what gets shown.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>html</category>
      <category>marketing</category>
    </item>
    <item>
      <title>What 61 Homepages Actually Ship in JSON-LD</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/what-61-homepages-actually-ship-in-json-ld-1jfc</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/what-61-homepages-actually-ship-in-json-ld-1jfc</guid>
      <description>&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;On 2026-09-23 we fetched the homepage of 78 well-known domains and extracted every &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt; block from the response. 61 returned a usable page — 42 over a direct connection and 19 over a local proxy. Of those 61, 32 contained at least one JSON-LD block.&lt;/p&gt;

&lt;p&gt;One caveat governs everything below, so it belongs at the top rather than the bottom: &lt;strong&gt;this measures what is present in the server-rendered HTML.&lt;/strong&gt; A page that injects its structured data with JavaScript will look empty here even though a browser — and Google, which renders — would see it. Single-page applications are the obvious case. So when this article says a site "ships none", read it as "none in the initial HTML response", which is a different and weaker claim.&lt;/p&gt;

&lt;p&gt;For the sites where we did find markup, that caveat disappears: everything about blocks, &lt;code&gt;@graph&lt;/code&gt;, &lt;code&gt;@id&lt;/code&gt; and node types is measured on markup that was actually present.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: about half ship nothing in the HTML
&lt;/h2&gt;

&lt;p&gt;29 of the 61 homepages contained no JSON-LD block at all. The list includes &lt;code&gt;github.com&lt;/code&gt;, &lt;code&gt;youtube.com&lt;/code&gt;, &lt;code&gt;wikipedia.org&lt;/code&gt;, &lt;code&gt;w3.org&lt;/code&gt;, &lt;code&gt;mozilla.org&lt;/code&gt;, &lt;code&gt;linkedin.com&lt;/code&gt;, &lt;code&gt;x.com&lt;/code&gt;, &lt;code&gt;spotify.com&lt;/code&gt;, &lt;code&gt;npmjs.com&lt;/code&gt;, &lt;code&gt;nodejs.org&lt;/code&gt;, &lt;code&gt;kubernetes.io&lt;/code&gt;, &lt;code&gt;mit.edu&lt;/code&gt;, &lt;code&gt;stanford.edu&lt;/code&gt;, &lt;code&gt;khanacademy.org&lt;/code&gt;, &lt;code&gt;theguardian.com&lt;/code&gt;, &lt;code&gt;slack.com&lt;/code&gt;, &lt;code&gt;notion.so&lt;/code&gt;, &lt;code&gt;trello.com&lt;/code&gt;, &lt;code&gt;tumblr.com&lt;/code&gt;, &lt;code&gt;vimeo.com&lt;/code&gt;, &lt;code&gt;archive.org&lt;/code&gt;, &lt;code&gt;wikimedia.org&lt;/code&gt;, &lt;code&gt;digitalocean.com&lt;/code&gt;, &lt;code&gt;squareup.com&lt;/code&gt;, &lt;code&gt;uber.com&lt;/code&gt;, &lt;code&gt;cdc.gov&lt;/code&gt;, &lt;code&gt;go.dev&lt;/code&gt;, &lt;code&gt;rust-lang.org&lt;/code&gt; and &lt;code&gt;developer.mozilla.org&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Some of those are certainly JavaScript-rendered and will have markup in a browser. But &lt;code&gt;wikipedia.org&lt;/code&gt; is not a JavaScript application, and neither is &lt;code&gt;w3.org&lt;/code&gt; or &lt;code&gt;github.com&lt;/code&gt; — those responses were 119KB, 51KB and 576KB of ordinary server-rendered HTML with no structured data anywhere in them.&lt;/p&gt;

&lt;p&gt;The honest conclusion is narrower than "half the web has no structured data" and more useful than nothing: shipping JSON-LD is a choice that a large share of serious sites have not made, and it is not a precondition for ranking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: 14 sites still ship markup for a retired feature
&lt;/h2&gt;

&lt;p&gt;This was the most surprising result. Google retired the sitelinks search box on &lt;strong&gt;21 November 2024&lt;/strong&gt; — the search field that used to appear under a brand's result. The markup that powered it, a &lt;code&gt;SearchAction&lt;/code&gt; on the &lt;code&gt;WebSite&lt;/code&gt; node, now produces nothing.&lt;/p&gt;

&lt;p&gt;Fourteen of the 32 sites with JSON-LD still carry it: &lt;code&gt;cnn.com&lt;/code&gt;, &lt;code&gt;forbes.com&lt;/code&gt;, &lt;code&gt;walmart.com&lt;/code&gt;, &lt;code&gt;atlassian.com&lt;/code&gt;, &lt;code&gt;zoom.us&lt;/code&gt;, &lt;code&gt;pinterest.com&lt;/code&gt;, &lt;code&gt;docker.com&lt;/code&gt;, &lt;code&gt;python.org&lt;/code&gt;, &lt;code&gt;screamingfrog.co.uk&lt;/code&gt;, &lt;code&gt;yoast.com&lt;/code&gt;, &lt;code&gt;cloudflare.com&lt;/code&gt;, &lt;code&gt;heroku.com&lt;/code&gt;, &lt;code&gt;airbnb.com&lt;/code&gt; and &lt;code&gt;who.int&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two of those are SEO tool vendors. That is the interesting part: this is not a case of amateurs leaving stale markup around. It is a case of markup that was correct when it was written, that nothing broke when it stopped mattering, and that no tool flags because it is still valid schema.org. There is no error, no warning, and no rich result — just a few lines of JSON that no longer do anything.&lt;/p&gt;

&lt;p&gt;It is worth being fair about this: leaving it in place costs nothing measurable. We are not suggesting the markup is harmful. The point is that it is invisible dead weight, and the only way to notice is to know the feature was retired — which is exactly the kind of thing that a site audit will never tell you.&lt;/p&gt;

&lt;p&gt;If you are auditing your own markup, this is the check that pays: &lt;strong&gt;is any of it aimed at a feature that no longer exists?&lt;/strong&gt; The &lt;a href="https://www.serpprism.com/tools/schema-markup-generator" rel="noopener noreferrer"&gt;schema generator&lt;/a&gt; on this site does not emit &lt;code&gt;SearchAction&lt;/code&gt; for that reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: a single block is the norm, and &lt;a class="mentioned-user" href="https://dev.to/graph"&gt;@graph&lt;/a&gt; is the minority
&lt;/h2&gt;

&lt;p&gt;Of the 32 sites with markup, 24 used exactly one block, four used two, three used three, and one used four. So the "many small blocks" pattern that &lt;a href="https://www.serpprism.com/guides/json-ld-graph-structure" rel="noopener noreferrer"&gt;our @graph guide&lt;/a&gt; warns about is not actually the common shape — one block is.&lt;/p&gt;

&lt;p&gt;Within that single block, however, the split matters. Only &lt;strong&gt;13 of the 32 (41%)&lt;/strong&gt; use a &lt;code&gt;@graph&lt;/code&gt; array. The rest put a single node at the top level.&lt;/p&gt;

&lt;p&gt;The 13: &lt;code&gt;gitlab.com&lt;/code&gt;, &lt;code&gt;atlassian.com&lt;/code&gt;, &lt;code&gt;zoom.us&lt;/code&gt;, &lt;code&gt;asana.com&lt;/code&gt;, &lt;code&gt;docker.com&lt;/code&gt;, &lt;code&gt;twitch.tv&lt;/code&gt;, &lt;code&gt;moz.com&lt;/code&gt;, &lt;code&gt;screamingfrog.co.uk&lt;/code&gt;, &lt;code&gt;yoast.com&lt;/code&gt;, &lt;code&gt;searchenginejournal.com&lt;/code&gt;, &lt;code&gt;stripe.com&lt;/code&gt;, &lt;code&gt;netlify.com&lt;/code&gt; and &lt;code&gt;heroku.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Look at that list again. Four of the thirteen are SEO tool vendors or SEO publications — Moz, Screaming Frog, Yoast and Search Engine Journal. When the people who build the tooling converge on a pattern, that is usually worth more than a spec reading, and it is consistent with the argument in the &lt;a class="mentioned-user" href="https://dev.to/graph"&gt;@graph&lt;/a&gt; guide: one connected graph beats several disconnected blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 4: most markup is boilerplate
&lt;/h2&gt;

&lt;p&gt;Twenty of the 32 sites ship only generic node types — &lt;code&gt;Organization&lt;/code&gt;, &lt;code&gt;WebSite&lt;/code&gt;, &lt;code&gt;WebPage&lt;/code&gt;, &lt;code&gt;ContactPoint&lt;/code&gt;, &lt;code&gt;PostalAddress&lt;/code&gt; — with nothing specific to the page being viewed.&lt;/p&gt;

&lt;p&gt;The type frequency across all 32 tells the same story. The most common types were &lt;code&gt;PostalAddress&lt;/code&gt; (33 occurrences), &lt;code&gt;Organization&lt;/code&gt; (25), &lt;code&gt;Place&lt;/code&gt; (24), &lt;code&gt;WebSite&lt;/code&gt; (20) and &lt;code&gt;ImageObject&lt;/code&gt; (20). &lt;code&gt;BreadcrumbList&lt;/code&gt; appeared 6 times. &lt;code&gt;SoftwareApplication&lt;/code&gt; 6 times. &lt;code&gt;VideoObject&lt;/code&gt; 6 times.&lt;/p&gt;

&lt;p&gt;More addresses than web pages. For a homepage that is defensible — the homepage is where the organization describes itself. But it means that on most of these sites, the structured data says "here is a company" and never says "here is what this particular page is about".&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 5: the pattern at full size
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;stripe.com&lt;/code&gt; packs &lt;strong&gt;54 nodes into a single &lt;code&gt;@graph&lt;/code&gt;&lt;/strong&gt; — by a wide margin the largest we found, and more than the next three sites combined.&lt;/p&gt;

&lt;p&gt;That is the &lt;a class="mentioned-user" href="https://dev.to/graph"&gt;@graph&lt;/a&gt; pattern doing what it is for. Fifty-four connected nodes with &lt;code&gt;@id&lt;/code&gt; references resolve to one coherent description of the site rather than fifty-four independent assertions that must agree with each other by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we did not find: almost no dangling references
&lt;/h2&gt;

&lt;p&gt;Our &lt;a href="https://www.serpprism.com/guides/json-ld-graph-structure" rel="noopener noreferrer"&gt;@graph guide&lt;/a&gt; warns that referencing an &lt;code&gt;@id&lt;/code&gt; which is never defined causes the property to be silently ignored. We expected to find this in the wild.&lt;/p&gt;

&lt;p&gt;One site out of 32 — &lt;code&gt;paypal.com&lt;/code&gt;, with a single reference to &lt;code&gt;https://www.paypal.com/c2/home#website&lt;/code&gt; that no node defines.&lt;/p&gt;

&lt;p&gt;Two other things we expected and did not find: &lt;strong&gt;zero&lt;/strong&gt; JSON parse failures across every block we extracted, and no site shipping markup in more than four blocks. Invalid JSON-LD and block sprawl both turn out to be much rarer than the advice would suggest.&lt;/p&gt;

&lt;p&gt;This is the second time in two surveys that a widely repeated warning did not appear at all in a sample of this size. That is not an argument for ignoring the warnings — a dangling reference is still a bug — but it is an argument for checking how often the thing you are worried about actually happens before you spend a week refactoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check on your own markup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is any of it aimed at a retired feature?&lt;/strong&gt; &lt;code&gt;SearchAction&lt;/code&gt; is the one to search for today. Check the markup against the current list of supported rich results rather than against a tutorial you followed in 2023.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it say anything about this page?&lt;/strong&gt; If every page on your site emits the same &lt;code&gt;Organization&lt;/code&gt; and &lt;code&gt;WebSite&lt;/code&gt; nodes and nothing else, the markup is describing your company and not your content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it one connected graph or several disconnected blocks?&lt;/strong&gt; Neither is an error. One is easier to keep consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does every &lt;code&gt;@id&lt;/code&gt; you reference exist on the page?&lt;/strong&gt; Extract the defined &lt;code&gt;@id&lt;/code&gt; values and the referenced ones and compare the lists. It takes a minute by eye on a page with under ten nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;The sample is 61 reachable homepages chosen by hand to span categories — representative of well-known sites, not of the web. Homepages only: article, product and FAQ markup lives on inner pages and is not measured here.&lt;/p&gt;

&lt;p&gt;The server-rendered caveat is the big one. Any JavaScript-injected markup is invisible to this method, so the true rate of sites using structured data is certainly higher than 52%. The findings about &lt;code&gt;@graph&lt;/code&gt;, node types and &lt;code&gt;SearchAction&lt;/code&gt; are drawn only from markup we actually retrieved, so they are unaffected — but the headline number is a floor, not a rate.&lt;/p&gt;

&lt;p&gt;Finally, this is a single-day snapshot of pages that change constantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The extraction script and the domain list ship with this site and run in about two minutes. It writes the extracted JSON-LD for every domain, so each claim above can be checked against the markup itself rather than our summary of it.&lt;/p&gt;

&lt;p&gt;If a row is wrong, the markup settles it. Corrections via the &lt;a href="https://www.serpprism.com/contact" rel="noopener noreferrer"&gt;contact page&lt;/a&gt; — and if you know why the sitelinks search box markup is still so widespread two years after retirement, we would like to hear that too.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>html</category>
    </item>
    <item>
      <title>Redirect Chains in the Wild: 78 Domains</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/redirect-chains-in-the-wild-78-domains-109j</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/redirect-chains-in-the-wild-78-domains-109j</guid>
      <description>&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;On 2026-09-30 we requested four entry URLs for each of the same 78 hand-picked domains our other surveys use — news, commerce, SaaS, developer tooling, social, streaming, reference, SEO, education, finance, cloud, local and government. The four entries were &lt;code&gt;http://domain/&lt;/code&gt;, &lt;code&gt;https://domain/&lt;/code&gt;, &lt;code&gt;http://www.domain/&lt;/code&gt; and &lt;code&gt;https://www.domain/&lt;/code&gt;: 312 chain walks in total.&lt;/p&gt;

&lt;p&gt;We did not use &lt;code&gt;curl -L&lt;/code&gt;. We requested one hop at a time so each hop's status code and &lt;code&gt;Location&lt;/code&gt; could be recorded separately. The reason is a measurement artefact: with &lt;code&gt;-L&lt;/code&gt; and a header dump, a proxy's own &lt;code&gt;CONNECT 200&lt;/code&gt; is written into the header file alongside the real responses, and it is indistinguishable from a real 200. Walking the chain by hand avoids that entirely. Every hop was attempted directly first and then through a local proxy; 220 hops answered on the direct path, 114 needed the proxy, and one never answered at all. We recorded which path each hop used so this can be checked.&lt;/p&gt;

&lt;p&gt;We used a survey user agent that identifies itself. 14 of the 78 domains answered with a bot wall rather than a page — those are reported as unreachable and excluded, not counted as defects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: 62 of 78 reach a page from the worst possible entry
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;http://domain/&lt;/code&gt; is the entry old links arrive on, the one people type, the one a third party cites. &lt;strong&gt;62 of 78 (79.5%) end at a 200.&lt;/strong&gt; Sixteen do not, and nearly all of those are walls rather than failures: 11 answered 403 (&lt;code&gt;nytimes.com&lt;/code&gt;, &lt;code&gt;bloomberg.com&lt;/code&gt;, &lt;code&gt;ebay.com&lt;/code&gt;, &lt;code&gt;etsy.com&lt;/code&gt;, &lt;code&gt;quora.com&lt;/code&gt;, &lt;code&gt;stackoverflow.com&lt;/code&gt;, &lt;code&gt;npmjs.com&lt;/code&gt;, &lt;code&gt;searchengineland.com&lt;/code&gt;, &lt;code&gt;coinbase.com&lt;/code&gt;, &lt;code&gt;doordash.com&lt;/code&gt;, &lt;code&gt;yelp.com&lt;/code&gt;), 2 answered 202 (&lt;code&gt;amazon.com&lt;/code&gt; and &lt;code&gt;booking.com&lt;/code&gt;, both challenge pages), one answered 406 (&lt;code&gt;uber.com&lt;/code&gt;) and 2 answered 503 (&lt;code&gt;bbc.com&lt;/code&gt; and &lt;code&gt;nih.gov&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Four domains answered the bare HTTP entry without redirecting at all. &lt;code&gt;archive.org&lt;/code&gt; returned 200 over plain HTTP. &lt;code&gt;bbc.com&lt;/code&gt; and &lt;code&gt;nih.gov&lt;/code&gt; returned 503 — they refuse HTTP outright, which is a defensible choice. &lt;code&gt;searchengineland.com&lt;/code&gt; returned 403.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: two hops is the most common answer, and 28 of them are avoidable
&lt;/h2&gt;

&lt;p&gt;Hop counts from &lt;code&gt;http://domain/&lt;/code&gt;: &lt;strong&gt;32 take one hop (41.0%), 37 take two (47.4%) and 5 take three (6.4%).&lt;/strong&gt; One hop is not the norm here; two is.&lt;/p&gt;

&lt;p&gt;The shapes explain why. &lt;strong&gt;28 domains go &lt;code&gt;http://domain&lt;/code&gt; → &lt;code&gt;https://domain&lt;/code&gt; → &lt;code&gt;https://www.domain&lt;/code&gt;&lt;/strong&gt; — the scheme upgrade and the host canonicalisation are two separate redirects. &lt;strong&gt;16 collapse both into one hop&lt;/strong&gt; (&lt;code&gt;http://domain&lt;/code&gt; → &lt;code&gt;https://www.domain&lt;/code&gt;). &lt;strong&gt;15 do the scheme only&lt;/strong&gt; and stop at the bare domain, which is their canonical form. So 28 of the 37 two-hop sites spend two round trips doing what the other 16 do in one.&lt;/p&gt;

&lt;p&gt;The usual cause is two layers that do not know about each other: an edge or load balancer that upgrades HTTP to HTTPS, and an application that then adds &lt;code&gt;www&lt;/code&gt;. Neither is wrong alone. Together they turn every first visit into two requests before any content arrives.&lt;/p&gt;

&lt;p&gt;The five three-hop chains, verbatim:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;theguardian.com&lt;/code&gt;: &lt;a href="http://theguardian.com/" rel="noopener noreferrer"&gt;http://theguardian.com/&lt;/a&gt; → &lt;a href="https://theguardian.com/" rel="noopener noreferrer"&gt;https://theguardian.com/&lt;/a&gt; → &lt;a href="https://www.theguardian.com/" rel="noopener noreferrer"&gt;https://www.theguardian.com/&lt;/a&gt; → /international.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;notion.so&lt;/code&gt;: → &lt;a href="https://notion.so/" rel="noopener noreferrer"&gt;https://notion.so/&lt;/a&gt; → &lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;https://www.notion.so/&lt;/a&gt; → &lt;a href="https://www.notion.com/" rel="noopener noreferrer"&gt;https://www.notion.com/&lt;/a&gt; — a domain migration stacked on top of the normalisation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;netflix.com&lt;/code&gt;: → &lt;a href="https://netflix.com/" rel="noopener noreferrer"&gt;https://netflix.com/&lt;/a&gt; → &lt;a href="https://www.netflix.com/" rel="noopener noreferrer"&gt;https://www.netflix.com/&lt;/a&gt; → &lt;a href="https://www.netflix.com/hk-en/" rel="noopener noreferrer"&gt;https://www.netflix.com/hk-en/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;paypal.com&lt;/code&gt;: → &lt;a href="https://paypal.com/" rel="noopener noreferrer"&gt;https://paypal.com/&lt;/a&gt; → &lt;a href="https://www.paypal.com/" rel="noopener noreferrer"&gt;https://www.paypal.com/&lt;/a&gt; → &lt;a href="https://www.paypal.com/cn/home" rel="noopener noreferrer"&gt;https://www.paypal.com/cn/home&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cdc.gov&lt;/code&gt;: → &lt;a href="https://cdc.gov/" rel="noopener noreferrer"&gt;https://cdc.gov/&lt;/a&gt; → &lt;a href="https://www.cdc.gov/" rel="noopener noreferrer"&gt;https://www.cdc.gov/&lt;/a&gt; → /index.html. Three permanent redirects to land on a filename.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: 14 sites use a temporary redirect for something permanent
&lt;/h2&gt;

&lt;p&gt;Across all 312 walks we saw &lt;strong&gt;301 ×245, 302 ×36, 308 ×16 and 307 ×14&lt;/strong&gt;. &lt;strong&gt;14 domains used 302 or 307 somewhere in the chain that ends at their own homepage:&lt;/strong&gt; &lt;code&gt;theguardian.com&lt;/code&gt;, &lt;code&gt;cnn.com&lt;/code&gt;, &lt;code&gt;notion.so&lt;/code&gt;, &lt;code&gt;linkedin.com&lt;/code&gt;, &lt;code&gt;nodejs.org&lt;/code&gt;, &lt;code&gt;go.dev&lt;/code&gt;, &lt;code&gt;developer.mozilla.org&lt;/code&gt;, &lt;code&gt;netflix.com&lt;/code&gt;, &lt;code&gt;mozilla.org&lt;/code&gt;, &lt;code&gt;mit.edu&lt;/code&gt;, &lt;code&gt;stripe.com&lt;/code&gt;, &lt;code&gt;paypal.com&lt;/code&gt;, &lt;code&gt;coinbase.com&lt;/code&gt; and &lt;code&gt;doordash.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Four use it on the very first hop off HTTP: &lt;code&gt;go.dev&lt;/code&gt; (302), &lt;code&gt;mozilla.org&lt;/code&gt; (302), &lt;code&gt;mit.edu&lt;/code&gt; (302) and &lt;code&gt;doordash.com&lt;/code&gt; (302). &lt;code&gt;mit.edu&lt;/code&gt;'s first hop is &lt;code&gt;http://mit.edu/&lt;/code&gt; → &lt;code&gt;http://web.mit.edu/&lt;/code&gt;, a temporary code on a host mapping that has been stable for years.&lt;/p&gt;

&lt;p&gt;This matters because 301 and 308 tell a crawler to consolidate signals onto the target and update its index, while 302 and 307 tell it to keep the original. Reaching for the temporary pair during canonicalisation is a way of asking Google to remember the URL you just said you did not want.&lt;/p&gt;

&lt;p&gt;Two details worth keeping. &lt;code&gt;mozilla.org&lt;/code&gt;'s first hop redirects to &lt;code&gt;https://mozilla.org:443/&lt;/code&gt; — an explicit default port, valid and mildly unusual. &lt;code&gt;pinterest.com&lt;/code&gt; uses 308 twice, and &lt;code&gt;vercel.com&lt;/code&gt; and &lt;code&gt;khanacademy.org&lt;/code&gt; each use a single 308 to upgrade the scheme; 308 is 301's method-preserving twin and is the correct choice here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 4: six sites serve two reachable homepages
&lt;/h2&gt;

&lt;p&gt;56 of 78 converge — every entry that resolves ends at the same URL. &lt;strong&gt;7 do not&lt;/strong&gt; , and one of those is our own artefact, so six are real:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bbc.com&lt;/code&gt;: &lt;code&gt;https://bbc.com&lt;/code&gt; and &lt;code&gt;https://www.bbc.com&lt;/code&gt; both answer 200 and neither redirects to the other. Plain HTTP returns 503, so at least the scheme is settled.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go.dev&lt;/code&gt;: &lt;code&gt;https://go.dev&lt;/code&gt; and &lt;code&gt;https://www.go.dev&lt;/code&gt; both answer 200.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;developer.mozilla.org&lt;/code&gt;: the bare host redirects to &lt;code&gt;/en-US/&lt;/code&gt;, while &lt;code&gt;https://www.developer.mozilla.org&lt;/code&gt; answers 200 at the root.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mit.edu&lt;/code&gt;: &lt;code&gt;http://mit.edu/&lt;/code&gt; ends at &lt;code&gt;https://web.mit.edu/&lt;/code&gt;, but &lt;code&gt;http://www.mit.edu/&lt;/code&gt; ends at &lt;code&gt;https://www.mit.edu/&lt;/code&gt;. Which homepage you get depends on whether you typed &lt;code&gt;www&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;archive.org&lt;/code&gt;: all four entries answer 200. &lt;code&gt;http://archive.org/&lt;/code&gt; is served, and &lt;code&gt;www&lt;/code&gt; redirects back to the bare host with a 302.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;khanacademy.org&lt;/code&gt;: the bare domain upgrades to &lt;code&gt;https://www.khanacademy.org&lt;/code&gt;, but &lt;code&gt;http://www.khanacademy.org&lt;/code&gt; answers 200 in plain HTTP. Of the four combinations, www plus http is the one that was forgotten.&lt;/p&gt;

&lt;p&gt;The seventh, &lt;code&gt;paypal.com&lt;/code&gt;, landed on &lt;code&gt;/cn/home&lt;/code&gt; on one path and &lt;code&gt;/hk/home&lt;/code&gt; on the other. That is a geo redirect and an artefact of us exiting from two IP addresses, not a defect on their side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 5: eight homepages are not at /
&lt;/h2&gt;

&lt;p&gt;Eight domains end their chain on a path other than the root: &lt;code&gt;theguardian.com/international&lt;/code&gt;, &lt;code&gt;nodejs.org/en&lt;/code&gt;, &lt;code&gt;developer.mozilla.org/en-US/&lt;/code&gt;, &lt;code&gt;netflix.com/hk-en/&lt;/code&gt;, &lt;code&gt;stripe.com/cn&lt;/code&gt;, &lt;code&gt;paypal.com/cn/home&lt;/code&gt;, &lt;code&gt;squareup.com/us/en&lt;/code&gt; and &lt;code&gt;cdc.gov/index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Four of those are geography — we fetched from a Chinese IP address, and Stripe, PayPal, Netflix and Square each answered with the edition they serve from there. Those redirects are doing their job; the consequence is that the URL in your sitemap is not the URL a crawler somewhere else receives. One of our &lt;a href="https://www.serpprism.com/guides/sitemap-in-the-wild" rel="noopener noreferrer"&gt;sitemap&lt;/a&gt; findings is the same effect seen from the other end.&lt;/p&gt;

&lt;p&gt;Two are language negotiation (&lt;code&gt;nodejs.org/en&lt;/code&gt;, MDN's &lt;code&gt;/en-US/&lt;/code&gt;). One is an edition router (the Guardian). One, &lt;code&gt;cdc.gov&lt;/code&gt;, redirects its root to &lt;code&gt;/index.html&lt;/code&gt; — the homepage's address is a filename.&lt;/p&gt;

&lt;p&gt;Seven also end on a different host from the one requested: &lt;code&gt;cnn.com&lt;/code&gt; → &lt;code&gt;edition.cnn.com&lt;/code&gt;, &lt;code&gt;gitlab.com&lt;/code&gt; → &lt;code&gt;about.gitlab.com&lt;/code&gt;, &lt;code&gt;notion.so&lt;/code&gt; → &lt;code&gt;www.notion.com&lt;/code&gt;, &lt;code&gt;zoom.us&lt;/code&gt; → &lt;code&gt;www.zoom.com&lt;/code&gt;, &lt;code&gt;spotify.com&lt;/code&gt; → &lt;code&gt;open.spotify.com&lt;/code&gt;, &lt;code&gt;mit.edu&lt;/code&gt; → &lt;code&gt;web.mit.edu&lt;/code&gt; and &lt;code&gt;linkedin.com&lt;/code&gt; → &lt;code&gt;www.linkedin.cn&lt;/code&gt;. Two are domain migrations (Notion, Zoom), one is geography (LinkedIn), and the rest are structural.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 6: HSTS is on half the sample, preload on a third
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;39 of 78&lt;/strong&gt; send &lt;code&gt;Strict-Transport-Security&lt;/code&gt; on their final HTTPS response. &lt;strong&gt;25 of those include &lt;code&gt;preload&lt;/code&gt;&lt;/strong&gt; : Shopify, Atlassian, Slack, Notion, Figma, Asana, Trello, python.org, nodejs.org, go.dev, w3.org, Ahrefs, Semrush, Yoast, Search Engine Journal, Stanford, Coursera, Stripe, PayPal, Square, Vercel, Netlify, Airbnb, cdc.gov and who.int.&lt;/p&gt;

&lt;p&gt;That leaves 39 sending no HSTS at all, which is why the scheme hop has to exist for them: without it, a browser's first request to a typed domain is still an HTTP request. With it — and especially with preload — the browser upgrades before the request leaves, and the first hop disappears for every visit after the first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Count your own hops.&lt;/strong&gt; Run &lt;code&gt;curl -sIL http://yourdomain/&lt;/code&gt; and count the &lt;code&gt;HTTP/&lt;/code&gt; lines. Two is the most common answer in this sample and one is achievable. If you see scheme and host handled separately, move both into a single rule at whichever layer is outermost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use 308 or 301 for canonicalisation, never 302 or 307.&lt;/strong&gt; Fourteen sites here use the temporary pair on a redirect that will not change. 308 preserves the method and is the better default on modern stacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose www or bare and redirect every other form to it.&lt;/strong&gt; The forgotten combination in this sample is almost always www plus http: Khan Academy normalises the bare domain to https plus www but serves http plus www as-is. Test all four entries, not two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you geo-redirect, test from more than one country.&lt;/strong&gt; Four sites here served us an edition based on our IP. The redirect is correct, and the URL in your sitemap is still not the one most of the world sees. It is also part of why our &lt;a href="https://www.serpprism.com/guides/hreflang-in-the-wild" rel="noopener noreferrer"&gt;hreflang&lt;/a&gt; survey found so much variation in what a homepage even is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decide whether a redirect or a canonical tag is the answer before reaching for either.&lt;/strong&gt; They solve different problems: a redirect removes a URL, a canonical tag consolidates duplicates you have to keep serving. Our &lt;a href="https://www.serpprism.com/tools/meta-tag-generator" rel="noopener noreferrer"&gt;meta tag generator&lt;/a&gt; emits the tag for the second case — and if you are unsure which applies, the answer is usually that the redirect is the one you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turn on HSTS last.&lt;/strong&gt; It removes the first hop for repeat visitors and, once preloaded, for first visits too — but preload is deliberately hard to reverse, so only add it after every subdomain serves HTTPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;78 hand-picked well-known domains, not a random sample of the web. Categories are our own manual grouping, several contain five domains, and one site moves a rate by 20 points.&lt;/p&gt;

&lt;p&gt;All requests came from one survey user agent on one day, over two egress IP addresses — one in China and one proxy exit. &lt;strong&gt;14 domains answered with a bot wall&lt;/strong&gt; (403, 406 or 202); those are excluded rather than counted as defects, and a browser-like user agent would have reached more of them. The geographic redirects in this data — Stripe's &lt;code&gt;/cn&lt;/code&gt;, PayPal's &lt;code&gt;/cn/home&lt;/code&gt;, Netflix's &lt;code&gt;/hk-en/&lt;/code&gt;, Square's &lt;code&gt;/us/en&lt;/code&gt;, LinkedIn's &lt;code&gt;.cn&lt;/code&gt; — describe where we were, not what those sites do for everyone. PayPal appearing in the two-version list is an artefact of our two exits, not a finding about PayPal.&lt;/p&gt;

&lt;p&gt;Channel is chosen per hop, so some chains switch egress partway through: 220 hops ran direct, 114 through the proxy. A geo-sensitive site can therefore show a hop that no single visitor would experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We counted hops, not latency.&lt;/strong&gt; A three-hop chain inside one CDN is often cheaper than a single cross-origin redirect, and we did not measure TTFB or connection reuse. Hop count stands in for wasted round trips; it is not a measurement of how slow anything is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We did not check what gets indexed.&lt;/strong&gt; A redirect does not guarantee consolidation, and we did not read the canonical tag at the destination — several of these domains redirect to a URL that then declares something different in our &lt;a href="https://www.serpprism.com/guides/canonical-in-the-wild" rel="noopener noreferrer"&gt;canonical survey&lt;/a&gt;. Whether the homepage that ranks is the one at the end of the chain is a different question from the one answered here.&lt;/p&gt;

&lt;p&gt;We followed up to eight hops and stopped on a loop or an unresolvable &lt;code&gt;Location&lt;/code&gt;. No chain in this sample hit either limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The script and the shared domain list ship with this site. &lt;code&gt;node scripts/survey-redirects.mjs&lt;/code&gt; walks all four entries for all 78 domains and stores every hop; &lt;code&gt;--report&lt;/code&gt; re-derives every number from stored data without touching the network; &lt;code&gt;--evidence&lt;/code&gt; prints the status and &lt;code&gt;Location&lt;/code&gt; of every hop for manual checking, which is how each chain quoted above was verified. Our &lt;a href="https://www.serpprism.com/guides/robots-txt-in-the-wild" rel="noopener noreferrer"&gt;robots.txt&lt;/a&gt;, &lt;a href="https://www.serpprism.com/guides/canonical-in-the-wild" rel="noopener noreferrer"&gt;canonical&lt;/a&gt;, &lt;a href="https://www.serpprism.com/guides/hreflang-in-the-wild" rel="noopener noreferrer"&gt;hreflang&lt;/a&gt;, &lt;a href="https://www.serpprism.com/guides/ai-crawlers-in-the-wild" rel="noopener noreferrer"&gt;AI crawler&lt;/a&gt; and &lt;a href="https://www.serpprism.com/guides/llms-txt-in-the-wild" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; surveys use the same domain list from other angles.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>technicalseo</category>
      <category>http</category>
    </item>
    <item>
      <title>What 60 Sites Actually Do With hreflang</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/what-60-sites-actually-do-with-hreflang-a1f</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/what-60-sites-actually-do-with-hreflang-a1f</guid>
      <description>&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;On 2026-09-24 we fetched the homepage of 78 high-traffic domains and looked for &lt;code&gt;&amp;lt;link rel="alternate" hreflang="..."&amp;gt;&lt;/code&gt; tags in the initial HTML. Of the 78, 60 returned a usable page: 41 over a direct connection and 19 over a local proxy, because some domains are unreachable from our network without one. The other 18 were unreadable — four behind bot protection returning 403 or 418, the rest timing out or returning 404.&lt;/p&gt;

&lt;p&gt;Extraction used a single regex over &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tags, keeping only those whose &lt;code&gt;rel&lt;/code&gt; contains &lt;code&gt;alternate&lt;/code&gt; and whose &lt;code&gt;hreflang&lt;/code&gt; is non-empty. A bare &lt;code&gt;hreflang=""&lt;/code&gt; is treated as absent, because an empty annotation is the same as none and counting it would invent findings that are not there. The script and the domain list ship with this site, so every number below can be re-derived rather than taken on trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: fewer than half use hreflang at all
&lt;/h2&gt;

&lt;p&gt;28 of the 60 sites (46.7%) declare hreflang. The other 32 declare none. That sounds low until you look at who the 32 are: Amazon, LinkedIn, Reddit, GitHub, Wikipedia, the New York Times, Bloomberg — overwhelmingly monolingual or single-locale sites where hreflang would be meaningless. hreflang exists to tell a crawler "this page has equivalents in other languages or regions." A site with one language has nothing to point at, so its absence is correct, not a gap.&lt;/p&gt;

&lt;p&gt;So the coverage number is not a score. It is a reminder that hreflang is a multilingual-site tool, and most of the web's famous names are not multilingual in the way that needs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: when sites do it, they do it thoroughly
&lt;/h2&gt;

&lt;p&gt;The 28 that declare hreflang are not dabbling. 24 of them ship six or more alternate links; the median site in the group declares 15. The outliers are startling: uber.com emits 173, stripe.com 89, mozilla.org 80, linkedin.com 77, shopify.com 71. The average across the 28 is 30.9 annotations per site.&lt;/p&gt;

&lt;p&gt;Two things explain the spread. First, genuinely global products — ride-hailing, payments, browsers — really do serve dozens of locales, and each one is a link. Second, a few sites that are not obviously global still go deep: ahrefs.com and semrush.com, both SEO vendors, declare 14 each, which is consistent with the pattern we have seen across every survey on this site — the companies whose business is this metadata treat it more carefully than average.&lt;/p&gt;

&lt;p&gt;The practical point for a normal site: you do not need 70 annotations. You need one correct set per localized URL, which for most operators is a handful. The big numbers belong to companies with genuinely big locale matrices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: the common miss is x-default
&lt;/h2&gt;

&lt;p&gt;Of the 28 sites that declare hreflang, 9 (32.1%) have no &lt;code&gt;x-default&lt;/code&gt; annotation. The list is not obscure: bbc.com, atlassian.com, notion.so, pinterest.com, docker.com, kubernetes.io, semrush.com, squareup.com, uber.com.&lt;/p&gt;

&lt;p&gt;Google recommends x-default as the fallback for users whose language or region matches none of your specific alternates. Without it, the version an unmatched user sees is undefined, and because hreflang errors are silent, no validator will tell you it is missing. It is recommended, not required — a site without x-default is not penalised — but for sites serving this many locales, leaving it out is a real, if mild, gap. That a third of even the careful sites skip it tells you how easy it is to forget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 4: the format is cleaner than expected
&lt;/h2&gt;

&lt;p&gt;We checked every hreflang value against BCP-47. Zero sites used a relative URL in the &lt;code&gt;href&lt;/code&gt;. The values a naive validator might flag as non-standard — &lt;code&gt;zh-Hans&lt;/code&gt;, &lt;code&gt;zh-Hant-TW&lt;/code&gt;, &lt;code&gt;sco&lt;/code&gt;, &lt;code&gt;ast&lt;/code&gt; — are all valid: the &lt;code&gt;Hans&lt;/code&gt;/&lt;code&gt;Hant&lt;/code&gt; forms are script subtags, and &lt;code&gt;sco&lt;/code&gt; and &lt;code&gt;ast&lt;/code&gt; are legitimate ISO 639-3 language codes used by sites that genuinely serve Scots and Asturian. Nobody in the sample shipped a broken value like "english" or "en_en".&lt;/p&gt;

&lt;p&gt;This is a useful contrast with our other surveys, which did find real defects. hreflang's format is mature enough that the mistakes are about relationships between annotations, not about the annotations themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 5: a sampling caveat, not a defect
&lt;/h2&gt;

&lt;p&gt;Four sites — gitlab.com, notion.so, zoom.us, spotify.com — have their hreflang set living on a different domain or subdomain from the bare homepage we fetched: about.gitlab.com, notion.com, zoom.com, open.spotify.com. Because we sampled the bare homepage, that page shows no same-origin self-reference. In every case the bare domain is effectively an entry point that redirects to the host serving localized content, where the set is complete and reciprocal. We report it as a measurement artifact, not as a broken annotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two errors that actually break hreflang
&lt;/h2&gt;

&lt;p&gt;The format is fine and the coverage question is mostly irrelevant. The failures that matter are about relationships, and both are silent. The first is a missing return tag: if your English page points at the German page, the German page must point back, or Google treats the whole annotation as unconfirmed and ignores it. The second is a missing self-reference: every URL in the set must include a link to itself. A page that lists its siblings but not itself is, to a crawler, a page that never confirmed its own identity.&lt;/p&gt;

&lt;p&gt;Both are invisible. The page renders, the links are present, and the annotation simply does not take effect — which is exactly the state you discover months later when a locale refuses to rank. The fix is to generate the full set, including x-default and the self-link, from one source, so the relationships cannot drift apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to do it without the foot-guns
&lt;/h2&gt;

&lt;p&gt;A correct set has one entry per locale plus &lt;code&gt;x-default&lt;/code&gt;, every entry points at an absolute URL, the page lists itself, and every target links back. Writing that by hand for more than two locales is how the return-tag rule gets broken. Generating it — as the &lt;a href="https://www.serpprism.com/tools/hreflang-generator" rel="noopener noreferrer"&gt;hreflang generator&lt;/a&gt; on this site does, from a single list of language and URL pairs — makes the reciprocal links and the self-reference impossible to forget, because the tool emits them for you.&lt;/p&gt;

&lt;p&gt;If your site is monolingual, the right move is to declare nothing and spend the effort on content. hreflang is for sites that actually have equivalents to offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;60 pages from 78 hand-picked high-traffic domains — representative of well-known sites, not of the web. A random sample would be dominated by monolingual sites and would show lower usage. We parsed only the initial HTML of the homepage, so annotations delivered through HTTP headers, sitemap entries, or injected by client-side JavaScript on a single-page app are not counted. A site whose hreflang lives only in its sitemap would appear here as not using it. Where a specific site is named, the raw HTML we retrieved is the arbiter; corrections are welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The script and the shared domain list ship with this site and store the raw HTML for every domain, so every claim above can be checked against the source rather than against our summary of it.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>html</category>
      <category>programming</category>
    </item>
    <item>
      <title>What 55 Homepages Actually Do With Canonical</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/what-55-homepages-actually-do-with-canonical-198e</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/what-55-homepages-actually-do-with-canonical-198e</guid>
      <description>&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;On 2026-09-24 we fetched &lt;code&gt;https://&amp;lt;domain&amp;gt;/&lt;/code&gt; for 78 well-known domains spanning news, commerce, SaaS, developer tooling, social, education, finance and government. 55 returned a usable page: 34 over a direct connection and 21 over a local proxy, because a number of these domains are unreachable from our network without one.&lt;/p&gt;

&lt;p&gt;The other 23 could not be read. 14 answered with something other than 200 — mostly 403, one 401, one 202, one timeout — and 20 served a bot check or an interstitial instead of a page; the two groups overlap. One is worth naming: &lt;code&gt;amazon.com&lt;/code&gt; answers 200 with a 2 KB page whose entire content is a &lt;code&gt;bm-verify&lt;/code&gt; meta-refresh, not a homepage. Our first pass counted it as a real page and would have recorded Amazon as a site with no canonical. Re-reading the response is what caught it, and it is why every number below was checked against the raw HTML before it was written down.&lt;/p&gt;

&lt;p&gt;For each page we took the &lt;code&gt;&amp;lt;link rel="canonical"&amp;gt;&lt;/code&gt; from the initial HTML, resolved it against the URL we actually landed on after redirects, and compared the two after normalising: lowercased host, &lt;code&gt;www&lt;/code&gt; stripped, trailing slash and &lt;code&gt;index.html&lt;/code&gt; removed. We also captured the HTTP &lt;code&gt;Link&lt;/code&gt; header, counted how many canonical tags each page ships, and recorded whether the tag sits before &lt;code&gt;&amp;lt;/head&amp;gt;&lt;/code&gt;. Then we made a second request to each canonical URL with redirects disabled, to see whether it points at a page that exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: one homepage in five declares nothing
&lt;/h2&gt;

&lt;p&gt;44 of the 55 pages (80%) declare a canonical. The other 11 declare none:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;reddit.com, kubernetes.io, python.org, rust-lang.org, netflix.com, wikipedia.org, mozilla.org, w3.org, archive.org, wikimedia.org, airbnb.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Two of those need an asterisk. &lt;code&gt;reddit.com&lt;/code&gt; and &lt;code&gt;archive.org&lt;/code&gt; returned client-rendered shells — 8.4 KB and 1.9 KB with no content in them — so a canonical injected by JavaScript would be invisible to a method that reads only the initial HTML. We cannot say they have no canonical; we can only say none is served with the document.&lt;/p&gt;

&lt;p&gt;The other nine are server-rendered and full-sized, and the absence is real. Wikipedia, Mozilla, W3C and Python.org are not sites that forgot a tag through carelessness; for a homepage that is the most-linked URL on the domain, the duplicate-selection problem canonical solves is largely solved by the link graph anyway. &lt;code&gt;airbnb.com&lt;/code&gt; is the interesting one: it ships &lt;code&gt;&amp;lt;meta id="english-canonical-url" content=""&amp;gt;&lt;/code&gt;, a canonical slot that arrived empty.&lt;/p&gt;

&lt;p&gt;What the omission costs is narrow but real. Without it, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;/index.html&lt;/code&gt;, &lt;code&gt;/?utm_source=newsletter&lt;/code&gt; and the http variant are four separate URLs that a crawler must choose between. It is one line of HTML to remove the question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: you have to follow the redirect first
&lt;/h2&gt;

&lt;p&gt;43 of the 55 homepages (78%) redirected at least once before serving content, and 35 of the 44 that declare a canonical (80%) did. Some are ordinary www normalisation; several land on a different host entirely:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gitlab.com → about.gitlab.com&lt;/code&gt;, &lt;code&gt;spotify.com → open.spotify.com&lt;/code&gt;, &lt;code&gt;notion.so → notion.com&lt;/code&gt;, &lt;code&gt;zoom.us → zoom.com&lt;/code&gt;, &lt;code&gt;linkedin.com → www.linkedin.cn&lt;/code&gt;, &lt;code&gt;cnn.com → edition.cnn.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the most common way to misread your own canonical. If you compare the tag against the URL you typed rather than the URL you were finally served, every one of those six looks like a cross-domain canonical pointing somewhere suspicious. They are not — they are self-referencing canonicals on the page that actually exists. When you audit, audit against the final URL after redirects, which is what a crawler sees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: six of 44 point anywhere other than themselves
&lt;/h2&gt;

&lt;p&gt;38 of the 44 canonicals (86%) resolve to exactly the URL we were served. The six that do not are the whole story of this survey:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cnn.com&lt;/code&gt; — served &lt;code&gt;edition.cnn.com&lt;/code&gt;, canonical &lt;code&gt;https://www.cnn.com&lt;/code&gt;, which returns 200. This is geo consolidation done deliberately: the regional edition defers to the global URL.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;linkedin.com&lt;/code&gt; — served &lt;code&gt;www.linkedin.cn&lt;/code&gt;, canonical &lt;code&gt;https://business.linkedin.com/zh-cn/zh-cn&lt;/code&gt;, 200. The same pattern on a different host.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tumblr.com&lt;/code&gt; — served &lt;code&gt;/&lt;/code&gt;, canonical &lt;code&gt;https://www.tumblr.com/explore/trending&lt;/code&gt;, 200. The logged-out homepage canonicalises to the trending feed rather than to itself.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;stanford.edu&lt;/code&gt; — served &lt;code&gt;/&lt;/code&gt;, canonical &lt;code&gt;https://www.stanford.edu/home&lt;/code&gt;, which returns &lt;strong&gt;404&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;trello.com&lt;/code&gt; — served &lt;code&gt;/&lt;/code&gt;, canonical &lt;code&gt;https://www.trello.com/home&lt;/code&gt;, which returns &lt;strong&gt;301&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mit.edu&lt;/code&gt; — served &lt;code&gt;web.mit.edu&lt;/code&gt;, canonical &lt;code&gt;https://tlecms.mit.edu/spotlight/zombie-cells&lt;/code&gt;: a different host, and a specific article about zombie cells. We could not reach that URL to check it — a direct request timed out and a proxied request failed the TLS handshake — so we report it as unverified rather than as broken. It has the shape of the classic template bug, where a homepage inherits a canonical slot filled from an article template, but we did not confirm it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 4: two canonical targets are not reachable pages
&lt;/h2&gt;

&lt;p&gt;We managed to test 43 of the 44 canonical URLs. 41 returned 200 (95%). Two did not, and both belong to large, well-resourced sites.&lt;/p&gt;

&lt;p&gt;Stanford's homepage tells a crawler that the preferred version of &lt;code&gt;https://www.stanford.edu/&lt;/code&gt; is a URL that answers 404. Trello's points at &lt;code&gt;/home&lt;/code&gt;, which 301s — and in Trello's case the bare host redirects too, so the canonical points into a redirect chain rather than at a destination.&lt;/p&gt;

&lt;p&gt;Neither is a syntax error, and nothing warns you. The tag is well-formed, the page renders, validators pass. The failure is silent in exactly the way canonical failures always are: a crawler that cannot resolve the canonical behaves as though you had not written one, which returns you to the duplicate-selection problem you wrote the tag to avoid. One command settles it for any URL you own:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -sS -o /dev/null -w "%{http_code}" https://example.com/your-canonical&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The answer must be 200. Anything else is a canonical that does not do its job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 5: the mechanics are cleaner than the reputation suggests
&lt;/h2&gt;

&lt;p&gt;Across 44 canonicals we found &lt;strong&gt;zero&lt;/strong&gt; relative hrefs — every single one used an absolute URL. Zero http/https mismatches. Zero pages shipping two conflicting canonicals. Zero canonicals placed outside &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two near-misses are worth knowing. &lt;code&gt;bbc.com&lt;/code&gt; ships the identical canonical tag twice, a harmless artefact of a framework rendering the head twice. &lt;code&gt;moz.com&lt;/code&gt; is the only site in the sample to send a canonical in the HTTP &lt;code&gt;Link&lt;/code&gt; header — and it also has the tag in its HTML, so the header is redundant rather than load-bearing.&lt;/p&gt;

&lt;p&gt;Read alongside the two broken targets above, this is the useful shape of the result: nobody writes malformed canonicals any more. The failures are not in the syntax, they are in what the tag points at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 6: geo-personalisation is the real design decision
&lt;/h2&gt;

&lt;p&gt;Seven of the 44 canonical URLs carry a language or region segment in the path. Four are region-personalised versions of what we asked for: &lt;code&gt;stripe.com/cn&lt;/code&gt;, &lt;code&gt;coinbase.com/en-ca&lt;/code&gt;, &lt;code&gt;squareup.com/us/en&lt;/code&gt;, &lt;code&gt;uber.com/ca/en&lt;/code&gt;. Because we fetched from a Chinese IP address, those are the variants we were served — and each of them self-canonicalises to the variant it served.&lt;/p&gt;

&lt;p&gt;CNN made the opposite choice: it served us the regional edition and canonicalised back to the global URL. Both are defensible. What matters is that the choice is consistent, that it matches your &lt;a href="https://www.serpprism.com/guides/hreflang-in-the-wild" rel="noopener noreferrer"&gt;hreflang annotations&lt;/a&gt;, and that one URL produces one canonical regardless of who asks. If your canonical varies with the visitor's IP, then a crawler geo-dispatched differently from your users will consolidate a different set of pages than the set your users actually see.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Emit an absolute, self-referencing canonical on every indexable URL.&lt;/strong&gt; One in five famous homepages does not, and the ones that do are not doing anything clever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compare it against the final URL after redirects.&lt;/strong&gt; 80% of the pages here redirected before serving content. Auditing against the URL you typed will manufacture cross-domain errors that do not exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check that the canonical itself returns 200.&lt;/strong&gt; Two of 43 did not. It is one curl command and no validator will do it for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never point a canonical at a redirect.&lt;/strong&gt; Point it at where the redirect lands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick one geo policy and apply it everywhere.&lt;/strong&gt; Consolidate globally like CNN, or self-canonicalise per locale like Stripe — but do not let the answer depend on who is asking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generate the tag rather than hand-writing it.&lt;/strong&gt; Every failure above is a value problem, not a syntax problem, and values are what templates and generators get right. The &lt;a href="https://www.serpprism.com/tools/meta-tag-generator" rel="noopener noreferrer"&gt;meta tag generator&lt;/a&gt; on this site emits the canonical alongside the rest of the head from a single URL, so the value cannot drift away from the page it describes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;55 pages from 78 hand-picked, well-known domains — representative of famous sites, not of the web. A random sample would look different in both directions.&lt;/p&gt;

&lt;p&gt;The 23 unreadable pages are not a random loss. They are disproportionately large consumer and financial sites that fight crawlers — Amazon, GitHub, the New York Times, PayPal, Target, eBay — so the sample skews toward sites that serve their homepage to anyone who asks. That is a real bias, and it probably makes these numbers look tidier than the web as a whole.&lt;/p&gt;

&lt;p&gt;We read only the initial HTML. A canonical delivered through JavaScript, a sitemap entry, or an HTTP header on a site that omits it from HTML is invisible here; two of the eleven "absent" pages were client-rendered shells where that caveat is live rather than theoretical.&lt;/p&gt;

&lt;p&gt;One fetch, from one IP address, on one day. Because that address is in China we were served regional variants of several sites, which is what made Finding 6 visible — and it also means a different vantage point would produce a slightly different set of canonicals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The script and the shared domain list ship with this site. &lt;code&gt;node scripts/survey-canonical.mjs&lt;/code&gt; fetches and stores the raw HTML for every domain; &lt;code&gt;--report&lt;/code&gt; re-derives every number from that stored HTML without touching the network, and &lt;code&gt;--evidence&lt;/code&gt; prints the raw &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag behind each verdict. Our &lt;a href="https://www.serpprism.com/guides/robots-txt-in-the-wild" rel="noopener noreferrer"&gt;first robots.txt survey&lt;/a&gt; covers the same domain list from a different angle.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>html</category>
      <category>programming</category>
    </item>
    <item>
      <title>Sitemaps in the Wild: 55 of 78 Sites</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/sitemaps-in-the-wild-55-of-78-sites-506i</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/sitemaps-in-the-wild-55-of-78-sites-506i</guid>
      <description>&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;On 2026-09-29 we requested &lt;code&gt;https://&amp;lt;domain&amp;gt;/sitemap.xml&lt;/code&gt; for the same 78 hand-picked domains used by our other surveys — news, commerce, SaaS, developer tooling, social, streaming, reference, SEO, education, finance, cloud, local and government — then read each domain's &lt;code&gt;robots.txt&lt;/code&gt; and followed any &lt;code&gt;Sitemap:&lt;/code&gt; line it declared. Every request was attempted twice, once directly and once through a local proxy, because a number of these domains are unreachable from our network without one.&lt;/p&gt;

&lt;p&gt;A file counted as a sitemap only if it returned 200 and the body was XML rather than HTML or empty. Five of them arrive as gzip files (&lt;code&gt;.xml.gz&lt;/code&gt;), which &lt;code&gt;curl --compressed&lt;/code&gt; does not decompress — that flag handles transport encoding, not a file that is itself a &lt;code&gt;.gz&lt;/code&gt;. Those five had to be gunzipped before they could be read at all, and our first pass stored them as binary garbage.&lt;/p&gt;

&lt;p&gt;Then the part that is not usually measured. From each of the 55 sitemaps we extracted five URLs at even intervals and requested them: 251 requests in total. Every &lt;code&gt;3xx&lt;/code&gt; was re-requested with a real GET before being counted, because the first pass used HEAD to save bandwidth and HEAD does not always answer the same way GET does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: 55 of 78 serve one, and 25 of those are not where you would look
&lt;/h2&gt;

&lt;p&gt;55 of 78 domains (70.5%) serve a readable sitemap. But only &lt;strong&gt;30 of those 55&lt;/strong&gt; answer at &lt;code&gt;/sitemap.xml&lt;/code&gt;. The other 25 are reachable only by following the &lt;code&gt;Sitemap:&lt;/code&gt; line in &lt;code&gt;robots.txt&lt;/code&gt;. If you audit sitemap coverage by requesting &lt;code&gt;/sitemap.xml&lt;/code&gt;, you undercount by 45%.&lt;/p&gt;

&lt;p&gt;The declared locations are not variations on a theme. &lt;code&gt;trello.com&lt;/code&gt; points at &lt;code&gt;https://a594014.sitemaphosting7.com/4704043/sitemap_4704043.xml&lt;/code&gt; — a third-party sitemap hosting domain, not a Trello URL at all. &lt;code&gt;netflix.com&lt;/code&gt; uses &lt;code&gt;/sitemap/index&lt;/code&gt;, with no file extension. &lt;code&gt;wise.com&lt;/code&gt; uses &lt;code&gt;/sitemap&lt;/code&gt;. &lt;code&gt;wikipedia.org&lt;/code&gt; points at a MediaWiki REST endpoint, &lt;code&gt;/w/rest.php/site/v1/sitemap/0&lt;/code&gt;, which answered 403 to us. &lt;code&gt;bbc.com&lt;/code&gt; declares &lt;code&gt;https://www.bbc.com/afrique/sitemap.xml&lt;/code&gt;, its French edition. &lt;code&gt;ebay.com&lt;/code&gt; declares &lt;code&gt;/lst/AUCTION-0-index.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The 23 that did not produce a file break down as follows. &lt;strong&gt;12 return a clean 404&lt;/strong&gt; : &lt;code&gt;etsy.com&lt;/code&gt;, &lt;code&gt;linkedin.com&lt;/code&gt;, &lt;code&gt;stackoverflow.com&lt;/code&gt;, &lt;code&gt;python.org&lt;/code&gt;, &lt;code&gt;rust-lang.org&lt;/code&gt;, &lt;code&gt;go.dev&lt;/code&gt;, &lt;code&gt;mozilla.org&lt;/code&gt;, &lt;code&gt;w3.org&lt;/code&gt;, &lt;code&gt;wikimedia.org&lt;/code&gt;, &lt;code&gt;mit.edu&lt;/code&gt;, &lt;code&gt;yelp.com&lt;/code&gt; and &lt;code&gt;wikipedia.org&lt;/code&gt;. &lt;strong&gt;4 return 200 with an HTML page&lt;/strong&gt; : &lt;code&gt;reddit.com&lt;/code&gt; (an 8 KB app shell), &lt;code&gt;pinterest.com&lt;/code&gt; (1.36 MB of it), &lt;code&gt;khanacademy.org&lt;/code&gt; (a 3 KB bot challenge) and &lt;code&gt;cdc.gov&lt;/code&gt;, which redirects &lt;code&gt;/sitemap.xml&lt;/code&gt; to &lt;code&gt;/index.html&lt;/code&gt; and serves the homepage with a 200. &lt;strong&gt;1 returns 200 with an empty body&lt;/strong&gt; : &lt;code&gt;x.com&lt;/code&gt;. &lt;strong&gt;5 answer with an error&lt;/strong&gt; : &lt;code&gt;amazon.com&lt;/code&gt; 500, &lt;code&gt;github.com&lt;/code&gt; 406, &lt;code&gt;quora.com&lt;/code&gt; 403, &lt;code&gt;npmjs.com&lt;/code&gt; 403, &lt;code&gt;searchengineland.com&lt;/code&gt; 403. One, &lt;code&gt;washingtonpost.com&lt;/code&gt;, was unreachable on both paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: what robots.txt promises is not always what is there
&lt;/h2&gt;

&lt;p&gt;74 of 78 domains serve a readable &lt;code&gt;robots.txt&lt;/code&gt;; 55 of them declare a sitemap; 50 of those declarations resolve. Five do not:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;washingtonpost.com&lt;/code&gt; declares a &lt;code&gt;.xml.gz&lt;/code&gt; we could not reach. &lt;code&gt;x.com&lt;/code&gt; declares &lt;code&gt;/sitemap.xml&lt;/code&gt;, which is the empty 200 above. &lt;code&gt;pinterest.com&lt;/code&gt; declares a seasonal-events file — &lt;code&gt;seasonal_events_sitemap_20261129_first_sunday_of_advent_www.pinterest.com.xml&lt;/code&gt; — that returns 404, a generated reference left behind after the file it pointed at was retired. &lt;code&gt;wikipedia.org&lt;/code&gt; declares the REST endpoint, 403 to us. &lt;code&gt;searchengineland.com&lt;/code&gt; declares &lt;code&gt;/sitemap_index.xml&lt;/code&gt;, also 403.&lt;/p&gt;

&lt;p&gt;The failure runs the other way too. &lt;strong&gt;5 sites serve a working sitemap and never declare it&lt;/strong&gt; : &lt;code&gt;shopify.com&lt;/code&gt;, &lt;code&gt;gitlab.com&lt;/code&gt;, &lt;code&gt;ahrefs.com&lt;/code&gt;, &lt;code&gt;screamingfrog.co.uk&lt;/code&gt; and &lt;code&gt;nih.gov&lt;/code&gt;. A crawler that only reads &lt;code&gt;robots.txt&lt;/code&gt; misses those entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: most large sites ship an index, and 5 of them ship it gzipped
&lt;/h2&gt;

&lt;p&gt;38 of the 55 are &lt;code&gt;sitemapindex&lt;/code&gt; files pointing at children; 17 are flat &lt;code&gt;urlset&lt;/code&gt;. The median index references &lt;strong&gt;23 child files&lt;/strong&gt; and is 2,584 bytes. The median flat &lt;code&gt;urlset&lt;/code&gt; carries &lt;strong&gt;617 URLs&lt;/strong&gt; in 449 KB. The largest single file is &lt;code&gt;gitlab.com&lt;/code&gt; at &lt;strong&gt;19,009 URLs in 2.95 MB&lt;/strong&gt; — inside the 50,000 URL / 50 MB limit, but one more product section away from needing to split.&lt;/p&gt;

&lt;p&gt;Five serve their sitemap as a gzip file: &lt;code&gt;nytimes.com&lt;/code&gt;, &lt;code&gt;target.com&lt;/code&gt;, &lt;code&gt;twitch.tv&lt;/code&gt;, &lt;code&gt;vimeo.com&lt;/code&gt; and &lt;code&gt;airbnb.com&lt;/code&gt;. Two omit the &lt;code&gt;&amp;lt;?xml&lt;/code&gt; declaration entirely: &lt;code&gt;theguardian.com&lt;/code&gt; and &lt;code&gt;wise.com&lt;/code&gt;. Both still parse; neither needed to take the risk.&lt;/p&gt;

&lt;p&gt;Four point at hosts other than their own. &lt;code&gt;paypal.com&lt;/code&gt; declares 1,214 child sitemaps, all on &lt;code&gt;www.paypalobjects.com&lt;/code&gt;, its CDN. &lt;code&gt;notion.so&lt;/code&gt; lists 169 children on &lt;code&gt;www.notion.com&lt;/code&gt;. &lt;code&gt;nodejs.org&lt;/code&gt; includes three URLs on &lt;code&gt;openjsf.org&lt;/code&gt; domains. &lt;code&gt;youtube.com&lt;/code&gt; includes three on &lt;code&gt;about.youtube&lt;/code&gt; and sibling hosts. &lt;code&gt;who.int&lt;/code&gt; is the outlier: it declares its index over &lt;code&gt;http://&lt;/code&gt; and references children like &lt;code&gt;http://www.who.int/SiteMaps/sitemap_static.xml.gz&lt;/code&gt;, while the site itself serves HTTPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 4: lastmod is the field sites get wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;18 of 55 sitemaps (33%) carry no &lt;code&gt;lastmod&lt;/code&gt; at all.&lt;/strong&gt; Three carry it inconsistently: &lt;code&gt;asana.com&lt;/code&gt; on 611 of 3,487 entries, &lt;code&gt;squareup.com&lt;/code&gt; on 5 of 16, and &lt;code&gt;nih.gov&lt;/code&gt; on 2,688 of 2,689 — one entry short of complete.&lt;/p&gt;

&lt;p&gt;Where the field exists, it is mostly date-only: &lt;strong&gt;34,952 date-only values against 18,593 with a time&lt;/strong&gt;. Date-only is valid and is what most of this sample chose; the ones carrying microseconds (&lt;code&gt;cnn.com&lt;/code&gt; at &lt;code&gt;2026-09-28T23:24:32.775000+00:00&lt;/code&gt;) are the exception.&lt;/p&gt;

&lt;p&gt;One file carries a value that is not a date. &lt;code&gt;searchenginejournal.com&lt;/code&gt; lists &lt;code&gt;-0001-11-30T00:00:00+00:00&lt;/code&gt; alongside real entries like &lt;code&gt;2026-09-28T20:48:12+00:00&lt;/code&gt;. That is a zero-value timestamp leaking out of a templating layer — we cannot say which stack produced it, but a year of &lt;code&gt;-0001&lt;/code&gt; is not a date anyone set. It has the right shape, so a length check passes it, and it tells a crawler the page was last modified two thousand years ago.&lt;/p&gt;

&lt;p&gt;Meanwhile &lt;strong&gt;8 of 55 still emit &lt;code&gt;changefreq&lt;/code&gt; and 6 still emit &lt;code&gt;priority&lt;/code&gt;&lt;/strong&gt; — fields Google has said publicly it ignores. The volumes are not small: &lt;code&gt;nodejs.org&lt;/code&gt; writes 1,651 &lt;code&gt;changefreq&lt;/code&gt; values, &lt;code&gt;nih.gov&lt;/code&gt; writes 2,689 of them and 1,894 &lt;code&gt;priority&lt;/code&gt; values, &lt;code&gt;cloudflare.com&lt;/code&gt; writes 910 of each, &lt;code&gt;shopify.com&lt;/code&gt; 557, &lt;code&gt;theguardian.com&lt;/code&gt; 423, &lt;code&gt;trello.com&lt;/code&gt; 112, &lt;code&gt;stanford.edu&lt;/code&gt; 27, &lt;code&gt;bbc.com&lt;/code&gt; 5.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 5: one in eight sampled URLs does not answer 200
&lt;/h2&gt;

&lt;p&gt;This is the number that matters, and it is the one nobody publishes. Of &lt;strong&gt;251 URLs sampled from 55 sitemaps&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;178 (70.9%) returned 200.&lt;/strong&gt; &lt;strong&gt;27 (10.8%) returned a redirect&lt;/strong&gt;, confirmed by re-requesting with GET. &lt;strong&gt;3 (1.2%) returned 404.&lt;/strong&gt; &lt;strong&gt;43 (17.1%) returned 401, 403 or 406&lt;/strong&gt; — and those are not sitemap faults. Six domains blocked every URL we tried: &lt;code&gt;nytimes.com&lt;/code&gt;, &lt;code&gt;reuters.com&lt;/code&gt;, &lt;code&gt;bloomberg.com&lt;/code&gt;, &lt;code&gt;ebay.com&lt;/code&gt;, &lt;code&gt;coinbase.com&lt;/code&gt; and &lt;code&gt;uber.com&lt;/code&gt;. Paywalls and bot walls, excluded rather than counted against them.&lt;/p&gt;

&lt;p&gt;Excluding those six leaves &lt;strong&gt;221 URLs: 178 (80.5%) returned 200, 27 (12.2%) redirected and 3 (1.4%) were dead&lt;/strong&gt;. Put another way: &lt;strong&gt;35 of the 55 sitemaps returned 200 for all five samples, and 12 of the 55 had at least one URL that redirects or 404s.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What the redirects are is more useful than how many there are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;walmart.com&lt;/code&gt;: 5 of 5.&lt;/strong&gt; Every sampled &lt;code&gt;/cp/&lt;/code&gt; URL 301s to a &lt;code&gt;/browse/&lt;/code&gt; URL — &lt;code&gt;/cp/blue-party-supplies/4514453&lt;/code&gt; → &lt;code&gt;/browse/party-occasions/blue-party-occasions/2637_1042319_1895000_4514453&lt;/code&gt;. That is a category URL migration the sitemap never absorbed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;stripe.com&lt;/code&gt;: 5 of 5.&lt;/strong&gt; &lt;code&gt;/legal&lt;/code&gt; → &lt;code&gt;https://stripe.com/cn/legal&lt;/code&gt;. A geo redirect: we fetched from a Chinese IP address. The URL in the sitemap is not the URL a crawler in another country receives, and not the one that gets indexed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;zoom.us&lt;/code&gt;: 4 of 5.&lt;/strong&gt; &lt;code&gt;explore.zoom.us/es/customer_stories/eunis2020/&lt;/code&gt; → &lt;code&gt;www.zoom.com/es/customer-stories/all/&lt;/code&gt;. A domain migration, and one path changed shape on the way (&lt;code&gt;/products/zoom-phone/&lt;/code&gt; → &lt;code&gt;/products/voip-phone/&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;semrush.com&lt;/code&gt;: 4 of 5.&lt;/strong&gt; &lt;code&gt;/api-analytics/&lt;/code&gt; → &lt;code&gt;developer.semrush.com/api/v3/analytics/basic-docs/&lt;/code&gt; — off the main domain entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;paypal.com&lt;/code&gt;: 2 of 5.&lt;/strong&gt; &lt;code&gt;/ae/webapps/mpp/mobile-apps?locale.x=ar_AE&lt;/code&gt; → &lt;code&gt;/ae/digital-wallet?locale.x=ar_AE&lt;/code&gt;. The path was replaced; the query string survived.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;cloudflare.com&lt;/code&gt; and &lt;code&gt;stanford.edu&lt;/code&gt;: trailing slashes, in opposite directions.&lt;/strong&gt; &lt;code&gt;/case-studies/fossil&lt;/code&gt; 301s to &lt;code&gt;/case-studies/fossil/&lt;/code&gt;; &lt;code&gt;/health-medicine/&lt;/code&gt; 308s to &lt;code&gt;/health-medicine&lt;/code&gt;. Neither site agrees with the other about which form is canonical, and the sitemap disagrees with both.&lt;/p&gt;

&lt;p&gt;The three genuine 404s: &lt;code&gt;gitlab.com/gitlab-org/security-products/dependencies/golang/toml/-/issues&lt;/code&gt;, and two legacy release posts at &lt;code&gt;nodejs.org/en/blog/release/v08.20&lt;/code&gt; and &lt;code&gt;/v2013.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redirects outnumber dead URLs nine to one.&lt;/strong&gt; That ratio matters because the two failures need different fixes. A 404 is a deletion — remove the entry. A redirect is a stale reference you have to trace to its target and then decide whether the sitemap or the URL is the thing that should change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Audit the URLs, not the file.&lt;/strong&gt; A sitemap that parses cleanly tells you nothing about whether its contents resolve. Sample ten URLs from yours and request them. Twelve of the 55 sitemaps here would have failed that test, and every one of them is a sitemap that "works".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not assume &lt;code&gt;/sitemap.xml&lt;/code&gt;.&lt;/strong&gt; Read the &lt;code&gt;Sitemap:&lt;/code&gt; line in your &lt;code&gt;robots.txt&lt;/code&gt; and request that URL. Twenty-five of the 55 sitemaps in this sample live somewhere else, and five of them are on paths with no &lt;code&gt;.xml&lt;/code&gt; extension. Our &lt;a href="https://www.serpprism.com/tools/robots-txt-tester" rel="noopener noreferrer"&gt;robots.txt tester&lt;/a&gt; reads the file and shows you which directives a crawler would apply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regenerate after every URL migration.&lt;/strong&gt; Walmart's &lt;code&gt;/cp/&lt;/code&gt; entries are what an un-regenerated sitemap looks like at scale: every sampled URL pointing at a structure that no longer exists. The redirect saves the user; it does not save the crawl budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you geo-redirect, test from more than one country.&lt;/strong&gt; Stripe's &lt;code&gt;/cn/legal&lt;/code&gt; is a correct redirect doing its job, and it means the URL in the sitemap is not the URL that gets indexed from most of the world.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick a trailing-slash convention and make the sitemap match it.&lt;/strong&gt; Cloudflare and Stanford chose opposite conventions, and both sitemaps disagree with their own site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Either put &lt;code&gt;lastmod&lt;/code&gt; on every entry or on none.&lt;/strong&gt; A third of this sample ships none and is not visibly worse off. Three ship partial coverage, which is the version with no upside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drop &lt;code&gt;changefreq&lt;/code&gt; and &lt;code&gt;priority&lt;/code&gt;.&lt;/strong&gt; Eight sites here write thousands of values Google ignores. If you serve &lt;code&gt;.xml.gz&lt;/code&gt;, remember that the file is gzip, not just the transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;78 hand-picked well-known domains, not a random sample of the web. Categories are our own manual grouping, several contain five domains, and one site moves a rate by 20 points.&lt;/p&gt;

&lt;p&gt;All requests came from one IP address in China on one day, and that is directly visible in the data: 43 of 251 sampled URLs answered 401, 403 or 406, six domains blocked everything we tried, and Stripe redirected us to its &lt;code&gt;/cn/&lt;/code&gt; edition. Those are excluded rather than counted as defects. A different vantage point would find more 200s and fewer redirects.&lt;/p&gt;

&lt;p&gt;Five URLs per sitemap, evenly spaced. A sitemap with one bad URL among 19,009 — GitLab's case — has roughly a 0.03% chance of showing it in our sample. These numbers describe the sample, not the full files, and the true per-sitemap defect rate is certainly higher than 12 of 55.&lt;/p&gt;

&lt;p&gt;17 of the 38 child sitemaps we fetched were truncated at 400 KB before parsing, so their entry counts are lower bounds.&lt;/p&gt;

&lt;p&gt;The 3xx count was confirmed with GET, but the initial pass used HEAD wherever it worked. Six URLs that looked like redirects turned out to be 403s on GET, and none turned out to be 200s — so the confirmed figure is lower than the raw one. If another host answers HEAD differently, this method over-counts redirects.&lt;/p&gt;

&lt;p&gt;We did not follow redirect chains and did not check whether the redirect target is itself canonical, so "redirects" here includes both harmless normalisation and real migrations.&lt;/p&gt;

&lt;p&gt;We read files discoverable from &lt;code&gt;robots.txt&lt;/code&gt; or &lt;code&gt;/sitemap.xml&lt;/code&gt; only. A sitemap submitted through Search Console and declared nowhere is invisible to us — and the five sites serving a sitemap with no declaration are proof that this configuration exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The script and the shared domain list ship with this site. &lt;code&gt;node scripts/survey-sitemap.mjs&lt;/code&gt; fetches and stores every response; &lt;code&gt;--children&lt;/code&gt; pulls the first child of each index; &lt;code&gt;--probe&lt;/code&gt; samples five URLs per sitemap; &lt;code&gt;--recheck&lt;/code&gt; re-requests every &lt;code&gt;3xx&lt;/code&gt; with GET; &lt;code&gt;--report&lt;/code&gt; re-derives every number from stored data without touching the network; &lt;code&gt;--evidence&lt;/code&gt; prints the raw XML behind each verdict. Our &lt;a href="https://www.serpprism.com/guides/robots-txt-in-the-wild" rel="noopener noreferrer"&gt;robots.txt&lt;/a&gt;, &lt;a href="https://www.serpprism.com/guides/canonical-in-the-wild" rel="noopener noreferrer"&gt;canonical&lt;/a&gt;, &lt;a href="https://www.serpprism.com/guides/hreflang-in-the-wild" rel="noopener noreferrer"&gt;hreflang&lt;/a&gt;, &lt;a href="https://www.serpprism.com/guides/ai-crawlers-in-the-wild" rel="noopener noreferrer"&gt;AI crawler&lt;/a&gt; and &lt;a href="https://www.serpprism.com/guides/llms-txt-in-the-wild" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; surveys use the same domain list from different angles.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>technicalseo</category>
      <category>crawling</category>
    </item>
  </channel>
</rss>
