<?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>Building a 6-book series: the Tier Zero roadmap from Systemfall to Tier IX</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Fri, 04 Sep 2026 01:47:19 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/building-a-6-book-series-the-tier-zero-roadmap-from-systemfall-to-tier-ix-33ld</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/building-a-6-book-series-the-tier-zero-roadmap-from-systemfall-to-tier-ix-33ld</guid>
      <description>&lt;p&gt;Tier Zero is 6 books. One falling system. One protagonist who refuses to stay assigned.&lt;/p&gt;

&lt;p&gt;Start at Systemfall (Book 1) to Tier IX (Book 6).&lt;/p&gt;

&lt;p&gt;If you like Cradle, Nightfall, or anything with a hard magic system and a rebel streak — this is your next binge.&lt;/p&gt;

&lt;p&gt;The premise: in a world where human potential is quantified into Tiers by a controlling system, one person drops to Tier Zero and becomes untrackable. What follows is a progression-fantasy climb through a cyberpunk dystopia where the rules of power are enforced by something that can no longer see him.&lt;/p&gt;

&lt;p&gt;Book 1 Systemfall is on Kindle now. Happy to answer worldbuilding questions in the comments.&lt;/p&gt;

&lt;h1&gt;
  
  
  ProgressionFantasy #BookSeries #IndieAuthor #SciFi
&lt;/h1&gt;

</description>
    </item>
    <item>
      <title>I wrote a 6-book cyberpunk progression fantasy. Here's the premise.</title>
      <dc:creator>任宏涛</dc:creator>
      <pubDate>Thu, 03 Sep 2026 07:11:50 +0000</pubDate>
      <link>https://dev.to/_6614b04c8831704fcf9b2/i-wrote-a-6-book-cyberpunk-progression-fantasy-heres-the-premise-4cnb</link>
      <guid>https://dev.to/_6614b04c8831704fcf9b2/i-wrote-a-6-book-cyberpunk-progression-fantasy-heres-the-premise-4cnb</guid>
      <description>&lt;p&gt;Tier Zero follows a world where human potential is quantified into Tiers by a controlling system — until one person drops to Tier Zero and becomes untrackable.&lt;/p&gt;

&lt;p&gt;The series spans six books (Systemfall → Tier IX), blending progression-fantasy power curves with cyberpunk dystopia and a hard, consistent magic system.&lt;/p&gt;

&lt;p&gt;If that sounds like your kind of read, Book 1 "Systemfall" is on Kindle now. Happy to answer worldbuilding questions in the comments.&lt;/p&gt;

&lt;h1&gt;
  
  
  SelfPublishing #SciFi #Cyberpunk #ProgressionFantasy
&lt;/h1&gt;

</description>
    </item>
  </channel>
</rss>
