<?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: Rahib Ali</title>
    <description>The latest articles on DEV Community by Rahib Ali (@rahibaly).</description>
    <link>https://dev.to/rahibaly</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%2F4161638%2F48bc8b44-436e-4936-86c5-2a5781509765.png</url>
      <title>DEV Community: Rahib Ali</title>
      <link>https://dev.to/rahibaly</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rahibaly"/>
    <language>en</language>
    <item>
      <title>Redirect chains are silently slowing your site — here's how to find and fix them</title>
      <dc:creator>Rahib Ali</dc:creator>
      <pubDate>Fri, 23 Oct 2026 05:18:00 +0000</pubDate>
      <link>https://dev.to/rahibaly/redirect-chains-are-silently-slowing-your-site-heres-how-to-find-and-fix-them-13je</link>
      <guid>https://dev.to/rahibaly/redirect-chains-are-silently-slowing-your-site-heres-how-to-find-and-fix-them-13je</guid>
      <description>&lt;p&gt;You paste a URL into your browser. The page loads. Everything looks fine — but between your click and that final render, the browser may have silently hopped through two, three, or five redirects: &lt;code&gt;http&lt;/code&gt; to &lt;code&gt;https&lt;/code&gt;, bare domain to &lt;code&gt;www&lt;/code&gt;, old path to new path, a trailing slash added along the way.&lt;/p&gt;

&lt;p&gt;Browsers never show you this. They follow every hop automatically and render the destination. Redirect chains are invisible by design — and that is exactly why they quietly grow into a real problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a chain of redirects hurts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Latency.&lt;/strong&gt; Every 301 or 302 is a full extra HTTP round trip: DNS, TLS handshake, request, response. One hop is noise. Four hops on a mobile connection is a noticeable delay before the first byte of the real page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crawl efficiency.&lt;/strong&gt; Googlebot follows redirect chains, but it gives up after about 10 hops. A page that is only reachable through a long chain gets crawled less often — and &lt;a href="https://developers.google.com/search/docs/crawling-indexing/301-redirects" rel="noopener noreferrer"&gt;Google's own documentation&lt;/a&gt; warns against chaining redirects where you can avoid it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Link equity and canonicalization.&lt;/strong&gt; A permanent move should be one clean 301 straight to the final URL. When old backlinks point at URL A, which 301s to B, which 302s to C, you are leaking both speed and signal at every step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Console noise.&lt;/strong&gt; "Page with redirect" and "Redirect error" reports are usually a chain problem, not a single-redirect problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  See the chain: curl
&lt;/h2&gt;

&lt;p&gt;The fastest honest view of a chain is in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-IL&lt;/span&gt; https://example.com/old-page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-I&lt;/code&gt; fetches headers only, &lt;code&gt;-L&lt;/code&gt; follows redirects. The output shows every hop — status code plus the &lt;code&gt;Location&lt;/code&gt; header it points to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt;
&lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://example.com/new-page&lt;/span&gt;

HTTP/2 302
location: https://example.com/new-page/

HTTP/2 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second hop is a 302 sending a permanent move to a trailing-slash variant — two separate mistakes visible in ten seconds of terminal output.&lt;/p&gt;

&lt;h2&gt;
  
  
  See the chain: DevTools
&lt;/h2&gt;

&lt;p&gt;In Chrome or Firefox DevTools, open the Network tab, tick "Preserve log", and reload. Every request in the chain appears with its status code. This is the best option when you suspect client-side (meta refresh or JavaScript) redirects, which &lt;code&gt;curl&lt;/code&gt; won't reveal — but it's tedious if you need to check more than a couple of URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trace chains in bulk: a tiny Node script
&lt;/h2&gt;

&lt;p&gt;When a migration leaves you with hundreds of legacy URLs, clicking through each one isn't an option. This script follows a chain one hop at a time and prints it — using &lt;code&gt;node:http&lt;/code&gt;/&lt;code&gt;node:https&lt;/code&gt; directly, because &lt;code&gt;fetch&lt;/code&gt; can't do this job: even with &lt;code&gt;redirect: 'manual'&lt;/code&gt; it hands back an opaque redirect response (status &lt;code&gt;0&lt;/code&gt;, headers unreadable). Raw HTTP requests have no such filter:&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;// trace.mjs — usage: node trace.mjs https://example.com/old-page&lt;/span&gt;
&lt;span class="c1"&gt;// Zero dependencies. http.get / https.get never follow redirects,&lt;/span&gt;
&lt;span class="c1"&gt;// so each hop's real status code and Location header stay visible.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:https&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="kd"&gt;get&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;mod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https:&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="nx"&gt;https&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User-Agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redirect-tracer/1.0&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// discard the body, we only need headers&lt;/span&gt;
    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;hop&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;hop&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&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;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`hop &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hop&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;status&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;current&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`\n      → &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;next&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="dl"&gt;''&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;399&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hop&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chain too long — stopping&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it against a list of URLs from your sitemap or backlink export and you'll have a full chain audit in minutes. (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Redirections" rel="noopener noreferrer"&gt;MDN's guide to HTTP redirections&lt;/a&gt; has the full reference on status-code semantics if you want the details.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The chains I keep finding in the wild
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The protocol + host stack.&lt;/strong&gt; &lt;code&gt;http://example.com&lt;/code&gt; → &lt;code&gt;https://example.com&lt;/code&gt; → &lt;code&gt;https://www.example.com&lt;/code&gt;. Two hops before the site even starts loading. Fix: every variant should 301 straight to the canonical URL in a single hop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Trailing-slash ping-pong.&lt;/strong&gt; &lt;code&gt;/page&lt;/code&gt; → &lt;code&gt;/page/&lt;/code&gt; → &lt;code&gt;/page&lt;/code&gt; — usually two CMS rules fighting each other. Pick one canonical form and enforce it once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Migration leftovers.&lt;/strong&gt; &lt;code&gt;old-domain.com/page&lt;/code&gt; → &lt;code&gt;new-domain.com/page&lt;/code&gt; → &lt;code&gt;new-domain.com/renamed-page&lt;/code&gt; → 200. Every legacy backlink pays three hops forever. Point the old URLs directly at their final destinations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The accidental 302.&lt;/strong&gt; A permanent move shipped as a 302 or 307 because it was the framework's default. Search engines keep indexing the old URL and never fully transfer the signal. If the move is permanent, it must be a 301.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Redirect loops.&lt;/strong&gt; A → B → A, and the browser gives up with &lt;code&gt;ERR_TOO_MANY_REDIRECTS&lt;/code&gt;. Almost always two conflicting rules — a www-forcing rule plus a non-www-forcing rule, or an http→https rule fighting an https→http one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Chains ending in errors.&lt;/strong&gt; The chain "works" in the browser but lands on a 404 or 500. Always check what the final hop returns, not just that the chain resolves.&lt;/p&gt;

&lt;h2&gt;
  
  
  A debugging workflow that actually works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trace the full chain&lt;/strong&gt; — &lt;code&gt;curl -IL&lt;/code&gt; for one-offs, the script above for bulk. When I want a quick visual answer without the terminal, I paste the URL into &lt;a href="https://frostrank.com/tools/redirect-chain-checker" rel="noopener noreferrer"&gt;this free redirect chain checker&lt;/a&gt;, which follows every hop server-side and lists each status code and destination, with loop detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count the hops.&lt;/strong&gt; More than two to reach the final page is worth flattening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the status codes.&lt;/strong&gt; Permanent moves must be 301 — a 302/307 in the middle is a bug, not a preference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the destination.&lt;/strong&gt; Confirm the final URL returns 200 and isn't carrying a surprise &lt;code&gt;noindex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix at the source.&lt;/strong&gt; Point the original URL straight at the final destination, and update internal links, canonical tags, and sitemaps to skip the chain entirely. Redirecting to a URL that already redirects is how chains grow — one "quick fix" at a time.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Redirect chains accumulate because nothing complains about them: the browser hides them, users tolerate one extra hop, and each individual redirect looks harmless in isolation. Audit them after every migration, and spot-check your most-linked pages once in a while. The fix is almost always the same — one clean 301, straight to the destination.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>debugging</category>
      <category>http</category>
    </item>
    <item>
      <title>Stop Hand-Writing JSON-LD: Generate Valid Schema Markup in Seconds</title>
      <dc:creator>Rahib Ali</dc:creator>
      <pubDate>Sun, 04 Oct 2026 13:06:31 +0000</pubDate>
      <link>https://dev.to/rahibaly/stop-hand-writing-json-ld-generate-valid-schema-markup-in-seconds-4pe6</link>
      <guid>https://dev.to/rahibaly/stop-hand-writing-json-ld-generate-valid-schema-markup-in-seconds-4pe6</guid>
      <description>&lt;p&gt;If you've ever hand-written JSON-LD structured data, you know the drill: one missing comma, one wrong &lt;code&gt;@type&lt;/code&gt;, and Google silently ignores your rich results. I build SEO tools for a living, and I still don't write schema by hand anymore. Here's why — and the workflow I use instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why hand-written JSON-LD breaks
&lt;/h2&gt;

&lt;p&gt;JSON-LD looks simple until it isn't. The classic failure modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trailing commas&lt;/strong&gt; — valid in JS, fatal in JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong nesting&lt;/strong&gt; — &lt;code&gt;geo&lt;/code&gt; inside &lt;code&gt;location&lt;/code&gt; vs. parallel to it for LocalBusiness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing required fields&lt;/strong&gt; — Google needs &lt;code&gt;headline&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt;, &lt;code&gt;datePublished&lt;/code&gt;, and &lt;code&gt;image&lt;/code&gt; for Article. Miss one and you get no rich result, no error message, just silence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unescaped characters&lt;/strong&gt; — quotes inside descriptions breaking the whole block.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The 30-second workflow
&lt;/h2&gt;

&lt;p&gt;For standard schema types, a generator beats hand-writing every time. I built a &lt;a href="https://frostrank.com/tools/json-ld-schema-generator" rel="noopener noreferrer"&gt;free JSON-LD schema generator&lt;/a&gt; at FrostRank — no signup, runs entirely in your browser.&lt;/p&gt;

&lt;p&gt;Pick the type (Article, FAQPage, Product, LocalBusiness, Organization, BreadcrumbList, Person…), fill in the fields, copy the output. It comes wrapped in &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt;, ready to paste into your page.&lt;/p&gt;

&lt;p&gt;Example output for an Organization:&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;"@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;"Example Inc"&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://example.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;"logo"&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://example.com/logo.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sameAs"&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="s2"&gt;"https://twitter.com/example"&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://linkedin.com/company/example"&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;Drop it in your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&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;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Organization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Example Inc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Always validate before you ship
&lt;/h2&gt;

&lt;p&gt;Generated or not, validate everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google's Rich Results Test&lt;/strong&gt; (&lt;a href="https://search.google.com/test/rich-results" rel="noopener noreferrer"&gt;https://search.google.com/test/rich-results&lt;/a&gt;) — paste the URL or code snippet; it flags errors and confirms rich-result eligibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema Markup Validator&lt;/strong&gt; (&lt;a href="https://validator.schema.org/" rel="noopener noreferrer"&gt;https://validator.schema.org/&lt;/a&gt;) — checks your markup against the schema.org spec itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the Rich Results Test shows errors, fix the fields and regenerate — don't patch the JSON by hand and reintroduce the exact mistakes the generator saved you from.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you SHOULD write it by hand
&lt;/h2&gt;

&lt;p&gt;Generators cover the standard types. Reach for hand-written JSON-LD when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're combining multiple entities with &lt;code&gt;@graph&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You need dynamic schema injected at runtime (e.g., a product price pulled from your DB)&lt;/li&gt;
&lt;li&gt;You're doing something exotic, like &lt;code&gt;HowTo&lt;/code&gt; with video clips&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even then, generate the skeleton first and extend it. Starting from valid markup beats starting from a blank file.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Don't hand-write boilerplate schema — generate it.&lt;/li&gt;
&lt;li&gt;Validate with Google's Rich Results Test before deploying.&lt;/li&gt;
&lt;li&gt;Keep a generator bookmarked for the next time someone says "can you add FAQ schema to this page?"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Try the &lt;a href="https://frostrank.com/tools/json-ld-schema-generator" rel="noopener noreferrer"&gt;free JSON-LD schema generator&lt;/a&gt; — it's client-side, so nothing you paste ever leaves your tab. What's your schema workflow — generator, plugin, or hand-rolled?&lt;/p&gt;

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