<?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: Ravi Kumar</title>
    <description>The latest articles on DEV Community by Ravi Kumar (@ravi_kumar_524ae0f5457b12).</description>
    <link>https://dev.to/ravi_kumar_524ae0f5457b12</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2863661%2F6f73a8d8-a1d6-470e-b4da-36bebd7e927b.jpg</url>
      <title>DEV Community: Ravi Kumar</title>
      <link>https://dev.to/ravi_kumar_524ae0f5457b12</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ravi_kumar_524ae0f5457b12"/>
    <language>en</language>
    <item>
      <title>I Built a Social Card Validator in a Weekend (and Here's What I Learned About Open Graph Tags)</title>
      <dc:creator>Ravi Kumar</dc:creator>
      <pubDate>Mon, 09 Feb 2026 21:06:09 +0000</pubDate>
      <link>https://dev.to/ravi_kumar_524ae0f5457b12/i-built-a-social-card-validator-in-a-weekend-and-heres-what-i-learned-about-open-graph-tags-5d5l</link>
      <guid>https://dev.to/ravi_kumar_524ae0f5457b12/i-built-a-social-card-validator-in-a-weekend-and-heres-what-i-learned-about-open-graph-tags-5d5l</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I shipped &lt;a href="https://linkpreview.io" rel="noopener noreferrer"&gt;LinkPreview.io&lt;/a&gt; - a tool that shows how your URL looks when shared on 6 social platforms. No signup, no BS, just paste a URL and see all your preview cards + get a scored list of fixes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Ugh, Not Again" Moment
&lt;/h2&gt;

&lt;p&gt;You know that feeling when you share your blog post on Twitter, and instead of your beautiful featured image, it shows... nothing? Or worse, a random screenshot from halfway down the article?&lt;/p&gt;

&lt;p&gt;I had this happen with my podcast episode pages. Everything looked fine when I tested locally. The &lt;code&gt;og:image&lt;/code&gt; tag was there in the HTML. But Twitter refused to show the image. I spent 30 minutes jumping between:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Twitter Card Validator (which required me to log in)&lt;/li&gt;
&lt;li&gt;Facebook Sharing Debugger (different login)&lt;/li&gt;
&lt;li&gt;LinkedIn Post Inspector (another login)&lt;/li&gt;
&lt;li&gt;Manually sharing to Slack to see what happened&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each tool showed me slightly different data. Each one required authentication. And none of them showed me what my link would &lt;em&gt;actually&lt;/em&gt; look like in the feed.&lt;/p&gt;

&lt;p&gt;I thought: "There has to be a better way."&lt;/p&gt;

&lt;p&gt;Narrator: &lt;em&gt;There wasn't.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I built one.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;LinkPreview.io&lt;/strong&gt; - Paste any URL, see instant previews for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Twitter/X (both large image + compact cards)&lt;/li&gt;
&lt;li&gt;Facebook (both large + link preview cards)&lt;/li&gt;
&lt;li&gt;LinkedIn&lt;/li&gt;
&lt;li&gt;Slack&lt;/li&gt;
&lt;li&gt;Discord&lt;/li&gt;
&lt;li&gt;Google Search (standard + rich result)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus, it scores your meta tags 0-100 and gives you actionable fixes with copy-paste code snippets.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://linkpreview.io" rel="noopener noreferrer"&gt;linkpreview.io&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tech Stack (and Why)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SvelteKit&lt;/strong&gt; - I chose Svelte 5 (with runes) because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The preview cards need instant reactivity when meta tags change&lt;/li&gt;
&lt;li&gt;I wanted a tiny bundle size (&amp;lt;30KB gzipped)&lt;/li&gt;
&lt;li&gt;Svelte's fine-grained reactivity made state management trivial&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare Pages&lt;/strong&gt; - Edge deployment, zero config, serverless API routes. I can deploy with &lt;code&gt;git push&lt;/code&gt;. It's beautiful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cheerio&lt;/strong&gt; - Server-side HTML parsing. Lighter than Puppeteer, perfect for extracting &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tailwind v4&lt;/strong&gt; - Dark mode first, utility-first, fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hard Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. SSRF Protection (Or: How I Almost Built an IP Scanner)
&lt;/h3&gt;

&lt;p&gt;The core feature is: user pastes a URL, my server fetches it, extracts meta tags, returns JSON.&lt;/p&gt;

&lt;p&gt;Simple, right?&lt;/p&gt;

&lt;p&gt;Wrong.&lt;/p&gt;

&lt;p&gt;If I just do &lt;code&gt;fetch(userUrl)&lt;/code&gt;, an attacker can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Point me at internal IPs: &lt;code&gt;http://192.168.1.1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Exfiltrate cloud metadata: &lt;code&gt;http://169.254.169.254/latest/meta-data/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use my server to port-scan: &lt;code&gt;http://internal-admin-panel:8080&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My solution:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isPrivateIp&lt;/span&gt; &lt;span class="p"&gt;}&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;./ssrf-protection.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;safeFetch&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="c1"&gt;// 1. Parse and validate URL&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&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;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Pre-redirect IP check&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&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="nf"&gt;isPrivateIp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Private IP addresses are not allowed&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="c1"&gt;// 3. Fetch with redirect: 'manual' so we can check each hop&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&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;fetch&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;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manual&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Follow redirects manually, checking each destination IP&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&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;300&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&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;400&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="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;location&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;redirectIp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&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;hostname&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="nf"&gt;isPrivateIp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirectIp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&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 to private IP blocked&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;response&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;fetch&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="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manual&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&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;The trick: even if the &lt;em&gt;initial&lt;/em&gt; URL is public, a redirect can send you to a private IP. You have to check &lt;em&gt;every hop&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I also added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Response size cap (5MB max)&lt;/li&gt;
&lt;li&gt;Timeout (10 seconds)&lt;/li&gt;
&lt;li&gt;Rate limiting (10 req/min per IP)&lt;/li&gt;
&lt;li&gt;Cloud metadata endpoint blocking (&lt;code&gt;169.254.169.254&lt;/code&gt;, &lt;code&gt;metadata.google.internal&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Character Truncation is a Nightmare
&lt;/h3&gt;

&lt;p&gt;Every platform truncates titles and descriptions at different lengths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Title Limit&lt;/th&gt;
&lt;th&gt;Description Limit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Twitter&lt;/td&gt;
&lt;td&gt;70 chars&lt;/td&gt;
&lt;td&gt;200 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Facebook&lt;/td&gt;
&lt;td&gt;~100 chars&lt;/td&gt;
&lt;td&gt;~300 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;60 chars&lt;/td&gt;
&lt;td&gt;160 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinkedIn&lt;/td&gt;
&lt;td&gt;120 chars&lt;/td&gt;
&lt;td&gt;~150 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack&lt;/td&gt;
&lt;td&gt;No limit&lt;/td&gt;
&lt;td&gt;~150 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discord&lt;/td&gt;
&lt;td&gt;No limit&lt;/td&gt;
&lt;td&gt;~200 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are all &lt;em&gt;undocumented&lt;/em&gt; and vary by context (e.g., Twitter timeline vs. DMs).&lt;/p&gt;

&lt;p&gt;I built visual truncation bars:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;titleLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$derived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metaTags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&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;twitterLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;70&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;truncationPercent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$derived&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;titleLength&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;twitterLimit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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;isOverLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$derived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;titleLength&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;twitterLimit&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;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"truncation-bar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fill"&lt;/span&gt;
    &lt;span class="na"&gt;class:red=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isOverLimit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width: {Math.min(truncationPercent, 100)}%"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;titleLength&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;twitterLimit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bar turns red when you're over the limit. Simple, but effective.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Recommendation Scoring is Subjective
&lt;/h3&gt;

&lt;p&gt;How do you score meta tags? I built a weighted system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Critical issues (20 points each):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing &lt;code&gt;og:title&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing &lt;code&gt;og:image&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Image too small (&amp;lt;1200x627)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Major issues (10 points each):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing &lt;code&gt;og:description&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Title too long (&amp;gt;60 chars)&lt;/li&gt;
&lt;li&gt;Image aspect ratio off&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Minor issues (5 points each):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing &lt;code&gt;twitter:card&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Title has site name at the end ("My Post | MySite.com")&lt;/li&gt;
&lt;li&gt;Description too short (&amp;lt;50 chars)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start at 100, subtract points for each issue. Simple, transparent, and users immediately understand "52/100" means "you need to fix stuff."&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Why Svelte 5 Runes Are Amazing
&lt;/h3&gt;

&lt;p&gt;Old Svelte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doubled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&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;p&gt;New Svelte (runes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;doubled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$derived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&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;p&gt;The &lt;code&gt;$derived&lt;/code&gt; rune is &lt;em&gt;so much clearer&lt;/em&gt; than &lt;code&gt;$:&lt;/code&gt;. It reads like: "this value is derived from that state." No magic, no confusion.&lt;/p&gt;

&lt;p&gt;For this project, every preview card is a derived value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;let twitterCard = $derived(&lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;metaTags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter:title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;metaTags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;og:title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;metaTags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;metaTags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter:description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;metaTags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;og:description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;metaTags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter:image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;metaTags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;og:image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;metaTags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter:card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;summary_large_image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reactivity just works. Change one meta tag, all 6 platform cards update instantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned About Open Graph
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Most CMSs get it wrong by default&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WordPress, Ghost, Wix - they all generate &lt;code&gt;og:image&lt;/code&gt; thumbnails that are too small. The default is usually 800x600, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Twitter recommends 1200x675 (2:1 ratio)&lt;/li&gt;
&lt;li&gt;Facebook recommends 1200x630 (1.91:1 ratio)&lt;/li&gt;
&lt;li&gt;LinkedIn &lt;em&gt;requires&lt;/em&gt; 1200x627 minimum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your image is too small, platforms either don't show it or stretch it to look terrible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Twitter cards fallback to Open Graph, but inconsistently&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Twitter &lt;em&gt;usually&lt;/em&gt; falls back to &lt;code&gt;og:title&lt;/code&gt; if &lt;code&gt;twitter:title&lt;/code&gt; is missing. But not always. Sometimes it uses the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag. Sometimes it just shows the URL.&lt;/p&gt;

&lt;p&gt;The safe bet: always set both &lt;code&gt;og:*&lt;/code&gt; and &lt;code&gt;twitter:*&lt;/code&gt; tags explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Google doesn't care about Open Graph&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google Search ignores &lt;code&gt;og:title&lt;/code&gt; and &lt;code&gt;og:description&lt;/code&gt; completely. It uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag (truncates at 60 chars)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;meta name="description"&amp;gt;&lt;/code&gt; (truncates at 160 chars)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But if you have JSON-LD structured data, Google prefers that over meta tags. So now you need &lt;em&gt;three&lt;/em&gt; sets of metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Facebook's debugger lies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Facebook Sharing Debugger shows you a cached version of your page. Even if you update your meta tags, the debugger still shows the old version until you click "Scrape Again."&lt;/p&gt;

&lt;p&gt;I've seen developers waste hours debugging a non-existent problem because they didn't know about the cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistakes I Made
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. I almost over-engineered the database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My original plan: store every URL validation in Cloudflare D1, let users create accounts, save history, etc.&lt;/p&gt;

&lt;p&gt;Then I thought: "Why? What value does that add in Phase 1?"&lt;/p&gt;

&lt;p&gt;Answer: None.&lt;/p&gt;

&lt;p&gt;So I made it stateless. The server extracts tags, returns JSON, forgets everything. No database, no user accounts, no complexity. You can still share results via URL params (&lt;code&gt;linkpreview.io?url=example.com&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Ship the simplest version that solves the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. I spent too long on visual polish&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wasted 2 hours tweaking the box shadow on the preview cards. Does it matter? No. Does it &lt;em&gt;feel&lt;/em&gt; better? Yes.&lt;/p&gt;

&lt;p&gt;But here's the thing: users don't care about your box shadow. They care about whether your tool solves their problem. Polish matters, but not as much as functionality.&lt;/p&gt;

&lt;p&gt;Ship fast, polish later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. I didn't validate my assumptions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I assumed people wanted side-by-side comparison (test staging vs. production URLs). So I built a whole UI for it.&lt;/p&gt;

&lt;p&gt;Then I realized: nobody asked for this. It's a Phase 2 feature at best.&lt;/p&gt;

&lt;p&gt;I cut it. Saved myself 4 hours.&lt;/p&gt;

&lt;p&gt;Validate demand before building features.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;This is Phase 1. I kept it intentionally minimal to ship fast. Phase 2 ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bulk validation&lt;/strong&gt; - Upload a CSV, validate 100 URLs at once&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled monitoring&lt;/strong&gt; - Alert me if my preview changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API access&lt;/strong&gt; - Integrate into CI/CD pipelines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chrome extension&lt;/strong&gt; - Right-click any page → validate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pricing model: freemium. Free for single URLs, paid for bulk/monitoring/API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://linkpreview.io" rel="noopener noreferrer"&gt;linkpreview.io&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Source code (coming soon): &lt;a href="https://github.com/ravichosun/LinkPreview" rel="noopener noreferrer"&gt;https://github.com/ravichosun/LinkPreview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SvelteKit (Svelte 5)&lt;/li&gt;
&lt;li&gt;Tailwind CSS v4&lt;/li&gt;
&lt;li&gt;Cloudflare Pages&lt;/li&gt;
&lt;li&gt;Cheerio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total build time: ~1 weekend + polish&lt;br&gt;&lt;br&gt;
Total lines of code: ~8,000&lt;br&gt;&lt;br&gt;
Client bundle size: &amp;lt;30KB gzipped&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ship fast, validate demand&lt;/strong&gt; - Don't build Phase 2 features until Phase 1 proves valuable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSRF protection is non-negotiable&lt;/strong&gt; - If you're fetching user URLs, validate every redirect hop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Svelte 5 runes are incredible&lt;/strong&gt; - Derived state makes reactive UIs trivial&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Graph is a mess&lt;/strong&gt; - Every platform interprets tags differently; test everything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateless &amp;gt; stateful&lt;/strong&gt; - No database = faster, simpler, cheaper&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;If you've ever shared a link on social media and been frustrated by broken previews, give &lt;a href="https://linkpreview.io" rel="noopener noreferrer"&gt;LinkPreview.io&lt;/a&gt; a try. And if you find bugs (you will), let me know in the comments!&lt;/p&gt;

&lt;p&gt;Happy shipping 🚀&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Built a Timestamp Converter That Doesn't Suck (And It's Free)</title>
      <dc:creator>Ravi Kumar</dc:creator>
      <pubDate>Sat, 31 Jan 2026 19:58:31 +0000</pubDate>
      <link>https://dev.to/ravi_kumar_524ae0f5457b12/i-built-a-timestamp-converter-that-doesnt-suck-and-its-free-318l</link>
      <guid>https://dev.to/ravi_kumar_524ae0f5457b12/i-built-a-timestamp-converter-that-doesnt-suck-and-its-free-318l</guid>
      <description>&lt;p&gt;How many times have you been debugging API logs, staring at &lt;code&gt;1706745600&lt;/code&gt;, and thought "What date is this even?"&lt;/p&gt;

&lt;p&gt;You Google "unix timestamp converter," click the first result, and get hit with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three banner ads&lt;/li&gt;
&lt;li&gt;A dropdown to select "Unix timestamp (seconds)" &lt;/li&gt;
&lt;li&gt;Another dropdown to select output format&lt;/li&gt;
&lt;li&gt;More ads&lt;/li&gt;
&lt;li&gt;Oh, and it wants you to sign up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I got tired of this. So over a weekend, I built &lt;strong&gt;&lt;a href="https://timestampconverter.net" rel="noopener noreferrer"&gt;TimeStampConverter.net&lt;/a&gt;&lt;/strong&gt; - a timestamp converter that just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes It Different?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Auto-Detection
&lt;/h3&gt;

&lt;p&gt;Paste &lt;strong&gt;anything&lt;/strong&gt;. The tool figures out if it's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unix timestamp (seconds): &lt;code&gt;1706745600&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Unix timestamp (milliseconds): &lt;code&gt;1706745600000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ISO 8601: &lt;code&gt;2024-01-31T12:00:00Z&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;RFC 2822: &lt;code&gt;Thu, 31 Jan 2024 12:00:00 GMT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Human readable: &lt;code&gt;January 31, 2024&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No dropdowns. No clicking. It just knows.&lt;/p&gt;

&lt;h3&gt;
  
  
  One Paste, Seven Formats
&lt;/h3&gt;

&lt;p&gt;One paste shows all these formats simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unix (seconds):     1706745600
Unix (ms):          1706745600000
ISO 8601 (UTC):     2024-01-31T17:00:00.000Z
ISO 8601 (Local):   2024-01-31T12:00:00.000-05:00
RFC 2822:           Wed, 31 Jan 2024 12:00:00 -0500
Human:              January 31, 2024 12:00:00 PM
Relative:           2 hours ago
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each has a copy button. Click once, paste into your code.&lt;/p&gt;

&lt;h3&gt;
  
  
  400+ Timezones
&lt;/h3&gt;

&lt;p&gt;Searchable timezone dropdown. Type "Tokyo" and it jumps there. Convert to any timezone instantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Date Math Built-In
&lt;/h3&gt;

&lt;p&gt;Need to know what timestamp is "30 days from now"? There's a calculator for that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add/subtract: years, months, days, hours, minutes, seconds&lt;/li&gt;
&lt;li&gt;Duration between two timestamps&lt;/li&gt;
&lt;li&gt;Handles DST and leap years correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bulk Conversion
&lt;/h3&gt;

&lt;p&gt;Paste multiple timestamps (one per line), convert them all at once. Perfect for log analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL Sharing
&lt;/h3&gt;

&lt;p&gt;Share specific timestamps: &lt;code&gt;https://timestampconverter.net?ts=1706745600&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dark Mode by Default
&lt;/h3&gt;

&lt;p&gt;Because you're probably using this at 2 AM debugging prod issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero Tracking
&lt;/h3&gt;

&lt;p&gt;No analytics. No ads. No signup. No cookies. Everything runs client-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;I kept it &lt;strong&gt;brutally simple&lt;/strong&gt;:&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;// That's it. That's the whole dependency list.&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dependencies&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dayjs&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;^1.11.10&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vanilla JavaScript&lt;/strong&gt; - No React, no Vue, no build step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;day.js&lt;/strong&gt; - Lightweight date library (~2KB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Pages&lt;/strong&gt; - Free hosting with global CDN&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total bundle size&lt;/strong&gt;: ~50KB&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Vanilla JS?
&lt;/h3&gt;

&lt;p&gt;I know what you're thinking: "Nobody uses vanilla JS anymore!"&lt;/p&gt;

&lt;p&gt;But here's the thing - this app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Doesn't need state management&lt;/li&gt;
&lt;li&gt;Doesn't need routing&lt;/li&gt;
&lt;li&gt;Doesn't need a virtual DOM&lt;/li&gt;
&lt;li&gt;Needs to load &lt;strong&gt;fast&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A React app with similar functionality would be 200KB+. This loads in under 100ms anywhere in the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Auto-Detection Works
&lt;/h2&gt;

&lt;p&gt;The secret sauce is a simple regex-based detection:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectTimestampFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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="nx"&gt;trimmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Unix timestamp (10 digits = seconds)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{10}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unix-seconds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmed&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="c1"&gt;// Unix timestamp (13 digits = milliseconds)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{13}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unix-ms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmed&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="c1"&gt;// ISO 8601 (starts with YYYY-MM-DDT)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{4}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;T/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iso8601&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// RFC 2822 (starts with day name)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Za-z&lt;/span&gt;&lt;span class="se"&gt;]{3}&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\s\d{2}\s[&lt;/span&gt;&lt;span class="sr"&gt;A-Za-z&lt;/span&gt;&lt;span class="se"&gt;]{3}\s\d{4}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rfc2822&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Try generic date parsing&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dayjs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmed&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;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;generic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once detected, converting is trivial with day.js:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;convertTimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;detected&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;date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unix-seconds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dayjs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unix-ms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dayjs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dayjs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;unixSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unix&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;unixMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;iso8601UTC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;iso8601Local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;rfc2822&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ddd, DD MMM YYYY HH:mm:ss ZZ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;humanReadable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MMMM D, YYYY h:mm:ss A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromNow&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment on Cloudflare Pages
&lt;/h2&gt;

&lt;p&gt;This was the easiest part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Build (or just use raw files)&lt;/span&gt;
&lt;span class="c"&gt;# (nothing to build for vanilla JS!)&lt;/span&gt;

&lt;span class="c"&gt;# 2. Deploy&lt;/span&gt;
wrangler pages deploy &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--project-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;timestamp

&lt;span class="c"&gt;# Done. Live globally in ~30 seconds.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloudflare gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unlimited bandwidth (actually unlimited on free tier)&lt;/li&gt;
&lt;li&gt;300+ edge locations&lt;/li&gt;
&lt;li&gt;Automatic HTTPS&lt;/li&gt;
&lt;li&gt;Preview deployments for every git push&lt;/li&gt;
&lt;li&gt;Built-in analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All for &lt;strong&gt;$0/month&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Sometimes Vanilla JS is the Right Choice
&lt;/h3&gt;

&lt;p&gt;Not everything needs a framework. For small utilities, vanilla JS + a tiny library can beat any framework on performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Auto-Detection is Magical UX
&lt;/h3&gt;

&lt;p&gt;Users love tools that "just work." No configuration, no dropdowns, just paste and go.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Dark Mode Should Be Default
&lt;/h3&gt;

&lt;p&gt;Especially for dev tools. We're often using these at night or in dark IDEs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Cloudflare Pages is Underrated
&lt;/h3&gt;

&lt;p&gt;Vercel and Netlify get all the love, but Cloudflare's free tier is genuinely unlimited and faster for global users.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Solve Your Own Problem
&lt;/h3&gt;

&lt;p&gt;I built this because I was frustrated. Now I use it 20+ times a day. That's validation enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Some features I'm considering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser extension (right-click timestamp → convert)&lt;/li&gt;
&lt;li&gt;API endpoint for programmatic access&lt;/li&gt;
&lt;li&gt;Cron expression validator&lt;/li&gt;
&lt;li&gt;ISO week number calculator&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But honestly? The tool does what I need. I might just leave it as-is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://timestampconverter.net" rel="noopener noreferrer"&gt;TimeStampConverter.net&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No signup, no tracking, no BS. Just timestamps.&lt;/p&gt;

&lt;p&gt;If you find it useful, bookmark it. That's all I ask.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Is this open source?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: Not yet, but I'm considering it. If there's interest, I'll put it on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How do you make money from this?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: I don't. It costs $0/month to run. Maybe I'll add a "buy me a coffee" button someday.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can I use this for my app/API?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: Everything runs client-side, so yeah, go nuts. If you need an API endpoint for server-side conversions, let me know - I might build one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What about privacy?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: All conversions happen in your browser. I literally cannot see what you're converting. Cloudflare logs basic CDN metrics (requests per region, bandwidth) but no user data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can you add features?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: Maybe! Drop a comment below or email me through the site.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>JSON Diff Tool</title>
      <dc:creator>Ravi Kumar</dc:creator>
      <pubDate>Fri, 30 Jan 2026 17:26:36 +0000</pubDate>
      <link>https://dev.to/ravi_kumar_524ae0f5457b12/json-diff-tool-3i44</link>
      <guid>https://dev.to/ravi_kumar_524ae0f5457b12/json-diff-tool-3i44</guid>
      <description>&lt;p&gt;Quick tool announcement: I built a browser-based JSON diff tool because I didn't like how the current options were cluttered with ads and didn't have the features I wanted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes it different?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Client-side only&lt;/strong&gt; - Your JSON never touches a server. Everything runs in your browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shareable links&lt;/strong&gt; - Generate URLs with the diff encoded directly in them. No database, no expiration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dead simple&lt;/strong&gt; - Paste two JSON docs, see the differences. That's it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actually free&lt;/strong&gt; - No freemium model, no "upgrade to compare larger files", just free.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I use it:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Comparing API responses while debugging&lt;/li&gt;
&lt;li&gt;Checking what changed in config files&lt;/li&gt;
&lt;li&gt;Sharing diffs with teammates (just send the URL!)&lt;/li&gt;
&lt;li&gt;Verifying test data structures&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Link
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jsondifference.com/" rel="noopener noreferrer"&gt;https://jsondifference.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try it out and let me know what you think! Open to feature suggestions.&lt;/p&gt;

&lt;h1&gt;
  
  
  DevTools #WebDev
&lt;/h1&gt;

</description>
      <category>javascript</category>
      <category>productivity</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
