<?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: Eric Will</title>
    <description>The latest articles on DEV Community by Eric Will (@feedpulse).</description>
    <link>https://dev.to/feedpulse</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%2F3959833%2Ff813eae4-f42f-4896-bd36-9f4802cad088.png</url>
      <title>DEV Community: Eric Will</title>
      <link>https://dev.to/feedpulse</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/feedpulse"/>
    <language>en</language>
    <item>
      <title>How I keep Lighthouse 100/100/100/100 on every public page (the 4 patterns that survived production)</title>
      <dc:creator>Eric Will</dc:creator>
      <pubDate>Sun, 07 Jun 2026 11:38:08 +0000</pubDate>
      <link>https://dev.to/feedpulse/how-i-keep-lighthouse-100100100100-on-every-public-page-the-4-patterns-that-survived-production-4b2c</link>
      <guid>https://dev.to/feedpulse/how-i-keep-lighthouse-100100100100-on-every-public-page-the-4-patterns-that-survived-production-4b2c</guid>
      <description>&lt;p&gt;Six months ago I shipped a free SaaS — embeddable widgets + free SEO tools, 65+ public pages, 12 locale variants each. The whole thing had to score &lt;strong&gt;100/100/100/100 on Lighthouse&lt;/strong&gt; (Performance / Accessibility / Best Practices / SEO) on &lt;strong&gt;every&lt;/strong&gt; page. Not because I'm a perfectionist — but because the entire growth strategy depends on Google's &lt;a href="https://developers.google.com/search/docs/appearance/core-web-vitals" rel="noopener noreferrer"&gt;Core Web Vitals being a ranking factor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It worked. The site has been 100/100/100/100 across the indexed surface for ~6 months now, and I've stopped touching the perf code. Here are the &lt;strong&gt;four patterns&lt;/strong&gt; that actually held up in production — past the "I scored 100 on my localhost" stage that 90% of perf blog posts stop at.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stack disclosure: React 18 SPA, FastAPI backend, MongoDB. No SSR. No Next.js. No CDN edge functions. Vanilla CRA + Helmet. This matters because most "Lighthouse 100" advice you'll read assumes you have Next.js/Astro/Nuxt SSR — these patterns work &lt;strong&gt;without&lt;/strong&gt; that.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Pattern 1 — One inline critical CSS file, ship it via the shell HTML
&lt;/h2&gt;

&lt;p&gt;The single biggest performance killer for a React SPA is the &lt;strong&gt;render-blocking CSS request&lt;/strong&gt; that fires after the JS bundle parses. Even with &lt;code&gt;&amp;lt;link rel="preload"&amp;gt;&lt;/code&gt; it tends to add 200-400 ms to LCP on a 3G throttle, which is exactly what Lighthouse simulates.&lt;/p&gt;

&lt;p&gt;The fix sounds obvious but everyone gets it wrong: &lt;strong&gt;inline the above-the-fold CSS directly into &lt;code&gt;public/index.html&lt;/code&gt;, and ship the rest async.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Specifically:&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="c"&gt;&amp;lt;!-- public/index.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;/* Imported at build time from /src/critical-inline.css */&lt;/span&gt;
  &lt;span class="c"&gt;/* Body skeleton, header, hero block, font-face declarations.
     ~6-8 KB compressed. No more. */&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preload"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"style"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/static/css/main.[hash].css"&lt;/span&gt;
      &lt;span class="na"&gt;onload=&lt;/span&gt;&lt;span class="s"&gt;"this.onload=null;this.rel='stylesheet'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;noscript&amp;gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/static/css/main.[hash].css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/noscript&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/src/critical-inline.css&lt;/code&gt; file is &lt;strong&gt;600 lines max&lt;/strong&gt; — body grid, header, hero section, the first 30vh of every page. Everything below the fold lives in the main bundle and loads after first paint. The &lt;code&gt;preload + onload swap&lt;/code&gt; trick keeps it non-render-blocking without breaking JS-disabled crawlers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this buys you&lt;/strong&gt;: LCP drops from ~2.2s → ~0.9s on simulated 4G. That's the difference between a 78 perf score and a 100.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Gotcha: every time you ship a new page that needs different above-the-fold styling, you HAVE to update &lt;code&gt;critical-inline.css&lt;/code&gt; — there's no automation that catches "you added a hero variant but forgot to inline its CSS". I learned this the hard way 3 times. Now every PR has a 30-second manual Lighthouse run on the new page before merging.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Pattern 2 — A &lt;code&gt;/api/_block_html&lt;/code&gt; bypass list (= the Lighthouse-403 trap)
&lt;/h2&gt;

&lt;p&gt;This one cost me a week the first time it hit.&lt;/p&gt;

&lt;p&gt;If you have ANY rate-limiting / anti-abuse / VPN-block middleware on your backend, &lt;strong&gt;PageSpeed Insights' bot fires from random Google Cloud IPs that often look like VPN traffic&lt;/strong&gt;. The bot gets a 403, the page errors out, and your Lighthouse score craters to ~60 — except only on real Google's PSI, not on your local Chrome devtools run. Pure heisenbug.&lt;/p&gt;

&lt;p&gt;The fix is to maintain an &lt;strong&gt;explicit bypass list&lt;/strong&gt; of paths that PSI fetches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# server.py — `_block_html()` decides whether to block a request
&lt;/span&gt;&lt;span class="n"&gt;BYPASS_PATHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                &lt;span class="c1"&gt;# homepage shell
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                      &lt;span class="c1"&gt;# PSI's preflight ping
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/badge-data/{kind}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# any badge data the page renders
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/embed/{widget}.js&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# any embed JS the page tests
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/og-image/{path}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;# any OG image PSI rendering
&lt;/span&gt;    &lt;span class="c1"&gt;# …
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the same list in your VPN / proxy / Spamhaus blacklist middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# vpn_proxy.py — same bypass list, second layer
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BYPASS_PATHS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# skip VPN check
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rule I now follow&lt;/strong&gt;: every time I add a new API endpoint that a public landing page calls, I add it to the bypass list &lt;strong&gt;in the same commit&lt;/strong&gt;. Otherwise PSI scores silently drop on the next deploy and you find out 3 weeks later via Google Search Console.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why two lists? Because middleware order matters. The Spamhaus check fires BEFORE the rate-limiter, so a path in only one bypass list still gets blocked by the other. Belt + suspenders.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Pattern 3 — A PSI Guard cron that scores every page weekly
&lt;/h2&gt;

&lt;p&gt;You can't manually re-run Lighthouse on 65 pages every week. You won't. So I wrote a &lt;strong&gt;PSI Guard&lt;/strong&gt; — a tiny cron that hits Google's official &lt;a href="https://developers.google.com/speed/docs/insights/v5/get-started" rel="noopener noreferrer"&gt;PageSpeed Insights API&lt;/a&gt; for every public URL once a week, persists the 4 scores, and flags any page whose score dropped below 95.&lt;/p&gt;

&lt;p&gt;The whole thing is ~200 lines of Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;psi_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mobile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Hit Google PSI API for a single URL. Returns {perf, a11y, bp, seo}.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PSI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strategy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;performance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accessibility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best-practices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;httpx_client&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="n"&gt;PSI_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lighthouseResult&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;categories&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;perf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;performance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a11y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accessibility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best-practices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;weekly_sweep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Score every URL. Alert if anything dropped below 95.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# PSI has a 25k/day budget; this is plenty
&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;scores&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;psi_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;psi_scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert_one&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
                    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PSI drop: %s = %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PSI failed %s: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Wired to a Sunday-night APScheduler job.&lt;/strong&gt; Every Monday I open the operator dashboard and see a heatmap: 65 rows × 4 columns, all green. If any cell is yellow / red, I have a "before Tuesday's cross-post slot" 30-min budget to dig in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cost: the Google PSI API is &lt;strong&gt;free up to 25,000 calls/day&lt;/strong&gt; with a key. I burn ~260 calls/week (65 pages × 4 categories, mobile only). Way under the limit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the pattern that &lt;strong&gt;saved me from regressions&lt;/strong&gt; more than anything else. Every dependency upgrade, every new feature, every "harmless" CSS tweak — the PSI Guard catches it within 7 days. Without this, I'd be flying blind.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 4 — Route-level code-splitting + zero "convenience" UI libs
&lt;/h2&gt;

&lt;p&gt;The fourth pattern is the one nobody wants to hear: &lt;strong&gt;stop importing convenience UI libraries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I shipped the v0 with Material-UI + &lt;code&gt;chart.js&lt;/code&gt; + &lt;code&gt;react-toastify&lt;/code&gt; + &lt;code&gt;moment&lt;/code&gt; — the usual stack. Lighthouse: 67 perf on the homepage. Bundle: 380 KB gzipped.&lt;/p&gt;

&lt;p&gt;I deleted all four. Replaced with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MUI → Tailwind + the ShadCN/UI source files&lt;/strong&gt; (vendored, not installed — I copy in only the components I use). Total: ~40 KB gzipped for the 12 components I actually need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chart.js → 200-line custom SVG sparkline component&lt;/strong&gt; for the 3 places I had charts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;react-toastify → Sonner&lt;/strong&gt; (a 6 KB toast library, no animation overhead).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;moment → &lt;code&gt;date-fns&lt;/code&gt;&lt;/strong&gt; (tree-shakeable; I import only &lt;code&gt;formatDistanceToNow&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bundle dropped from 380 KB → 142 KB gzipped. Perf jumped from 67 → 96.&lt;/p&gt;

&lt;p&gt;Then I added &lt;strong&gt;per-route lazy-loading&lt;/strong&gt; with &lt;code&gt;React.lazy&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// App.js — every non-landing route is dynamically imported&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Explore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&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;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/pages/Explore&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;Milestones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&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;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/pages/Milestones&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;SeoTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&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;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/pages/SeoTool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// 30+ routes, each in its own chunk&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Landing&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* in main bundle */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/explore"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Explore&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* lazy */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* … */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the &lt;strong&gt;homepage&lt;/strong&gt; ships ~60 KB of JS. Every other page lazy-loads its own ~20-40 KB chunk on demand. Perf: 96 → 100.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The unintuitive part: shipping FEWER total KB across the whole app is worse than shipping more total KB but split per route. A 142 KB monolithic bundle scores worse than 60 KB landing + 30 KB Explore chunk + 25 KB Milestones chunk… even though the totals are larger. Lighthouse cares about &lt;strong&gt;what loads on THIS page&lt;/strong&gt;, not what's available globally.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What I tried that didn't work
&lt;/h2&gt;

&lt;p&gt;For honesty:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Service Worker pre-caching&lt;/strong&gt; — added complexity, broke the "no JS yet" first-paint case, gained nothing on Lighthouse (it doesn't simulate a returning visitor).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fetchpriority="high"&lt;/code&gt; on hero images&lt;/strong&gt; — minor LCP win (~80 ms) but the variance between runs swallowed the gain. Not worth the cognitive overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brotli compression on the backend&lt;/strong&gt; — already done at the ingress layer (Kubernetes nginx). Doing it twice adds latency, doesn't reduce bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prerendered HTML for crawlers via a separate path&lt;/strong&gt; — works for Googlebot, but Bing + DuckDuckGo handle React SPAs better than they used to and the prerender pipeline became maintenance debt. I kept the SPA-only approach and just made sure every page's &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags are server-renderable via the static &lt;code&gt;index.html&lt;/code&gt; shell.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR — the 4 patterns
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inline the critical CSS&lt;/strong&gt; (~6-8 KB) directly into &lt;code&gt;public/index.html&lt;/code&gt;. Async-load the rest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintain a bypass list&lt;/strong&gt; for every API endpoint a public page fetches — in BOTH your rate-limiter and your VPN-block middleware. PSI bots have weird IPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a weekly PSI Guard cron&lt;/strong&gt; that scores every public URL via the Google PSI API and flags drops below 95. ~200 lines of Python, free under the 25k/day limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete every convenience UI library.&lt;/strong&gt; Vendor ShadCN source files. Lazy-load every route. Bundle target: ≤60 KB per page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Six months in production, 100/100/100/100 across 65+ pages, zero regressions caught. Hope this helps.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://news.ycombinator.com/" rel="noopener noreferrer"&gt;Discuss on Hacker News&lt;/a&gt; · Live demo of the patterns: &lt;a href="https://feed-pulse.com" rel="noopener noreferrer"&gt;feed-pulse.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webperf</category>
      <category>performance</category>
      <category>lighthouse</category>
      <category>seo</category>
    </item>
    <item>
      <title>How to add a live visitor counter to WordPress in 60 seconds (2026 guide)</title>
      <dc:creator>Eric Will</dc:creator>
      <pubDate>Tue, 02 Jun 2026 12:46:04 +0000</pubDate>
      <link>https://dev.to/feedpulse/how-to-add-a-live-visitor-counter-to-wordpress-in-60-seconds-2026-guide-2mh9</link>
      <guid>https://dev.to/feedpulse/how-to-add-a-live-visitor-counter-to-wordpress-in-60-seconds-2026-guide-2mh9</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://feed-pulse.com/blog/live-visitor-counter-wordpress-60-seconds" rel="noopener noreferrer"&gt;feed-pulse.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother with a live visitor counter at all?
&lt;/h2&gt;

&lt;p&gt;The single most effective conversion trigger on any content site — bigger than testimonials, bigger than urgency timers — is &lt;strong&gt;social proof shown in real time&lt;/strong&gt;. When a reader sees "47 people are reading this right now," their brain skips the "is this worth my time?" question and jumps straight to "what am I missing?"&lt;/p&gt;

&lt;p&gt;Bloggers, course creators, and indie SaaS founders have used this trick since the StatCounter era. The difference in 2026: most of the old tools are dead or paywalled. &lt;strong&gt;FeedPulse is the modern, free replacement&lt;/strong&gt;, and installation on WordPress takes literally 60 seconds.&lt;/p&gt;

&lt;p&gt;This guide walks through three methods. Pick the one that matches your WordPress setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1 — The Custom HTML block (recommended, works on every theme)
&lt;/h2&gt;

&lt;p&gt;This is the path 90% of WordPress sites should take. No plugin, no theme edit, no FTP.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;&lt;a href="https://feed-pulse.com/generator" rel="noopener noreferrer"&gt;the FeedPulse generator&lt;/a&gt;&lt;/strong&gt; and paste your domain (e.g. &lt;code&gt;yourblog.com&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Pick the &lt;strong&gt;Live Traffic Feed&lt;/strong&gt; widget. Customize colors to match your brand — most users go with our Klein-blue default.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Copy snippet&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In WordPress, edit any page or sidebar widget area. Click &lt;strong&gt;+ Add Block → Custom HTML&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Paste the snippet. Hit &lt;strong&gt;Update&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. Reload your page and you'll see country flags streaming in within seconds — provided you have any traffic at all. (Tip: open the page in an incognito window to see your first visit appear.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2 — Theme footer.php edit (for global placement)
&lt;/h2&gt;

&lt;p&gt;If you want the widget to appear on every page of your site, the cleanest path is to inject it once into your theme's footer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your child theme's footer.php, just before &amp;lt;/body&amp;gt;:&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://feed-pulse.com/api/embed/traffic-feed.js?site_id=&amp;lt;YOUR_SITE_ID&amp;gt;"&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;YOUR_SITE_ID&amp;gt;&lt;/code&gt; with the site ID from the FeedPulse generator. Always edit a &lt;strong&gt;child theme&lt;/strong&gt; — never the parent theme directly, or your changes will vanish at the next update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3 — Plugin route (Insert Headers and Footers)
&lt;/h2&gt;

&lt;p&gt;If you don't want to touch theme files but still want global placement, install the free &lt;strong&gt;Insert Headers and Footers&lt;/strong&gt; plugin (or its modern cousin &lt;strong&gt;WPCode&lt;/strong&gt;). Paste the FeedPulse snippet into the "Scripts in Footer" field. Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting: "I see the widget but it says 'Waiting for visitors…'"
&lt;/h2&gt;

&lt;p&gt;Three things to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You're the only visitor&lt;/strong&gt;. Open an incognito window on your phone. That's your first visit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A caching plugin is serving stale HTML&lt;/strong&gt;. Purge the cache (WP Rocket, W3 Total Cache, LiteSpeed all have a "Clear all" button). Cloudflare? Also purge that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your Content Security Policy blocks third-party scripts&lt;/strong&gt;. Add &lt;code&gt;feed-pulse.com&lt;/code&gt; to your CSP &lt;code&gt;script-src&lt;/code&gt; and &lt;code&gt;img-src&lt;/code&gt; directives.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The one CSS tweak nobody mentions
&lt;/h2&gt;

&lt;p&gt;By default the widget renders at 320px wide. On most WordPress sidebars that's a perfect fit, but on Elementor full-width content blocks it looks tiny. Wrap the snippet in a div with &lt;code&gt;max-width:100%&lt;/code&gt; and the widget scales to fit:&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;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"max-width:100%;display:flex;justify-content:center;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://feed-pulse.com/api/embed/traffic-feed.js?site_id=&amp;lt;YOUR_SITE_ID&amp;gt;&amp;amp;w=480"&lt;/span&gt; &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&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;Notice the &lt;code&gt;&amp;amp;w=480&lt;/code&gt; parameter — that overrides the default width without re-opening the generator.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do you get on the free tier?
&lt;/h2&gt;

&lt;p&gt;Every WordPress install above gets the full feature set with no upgrade required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unlimited visits, forever.&lt;/li&gt;
&lt;li&gt;Real-time country flag, browser icon, OS icon and device icon for every visitor.&lt;/li&gt;
&lt;li&gt;Anonymous tracking (no cookies, no PII, no GDPR banner needed).&lt;/li&gt;
&lt;li&gt;Auto-bot detection so traffic exchanges don't pollute your stats.&lt;/li&gt;
&lt;li&gt;A public stats page (&lt;code&gt;/site/yourblog.com&lt;/code&gt;) you can share or link from socials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The optional &lt;strong&gt;$9 one-time Pro Lifetime&lt;/strong&gt; removes the small FeedPulse badge and hides bot rows from the visible widget. No subscription. No renewal email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final tip — where to place it for maximum effect
&lt;/h2&gt;

&lt;p&gt;For blogs: sidebar, above the fold.&lt;br&gt;
For SaaS landing pages: in the hero, just below the CTA, as a trust signal.&lt;br&gt;
For e-commerce: near the checkout button (the "47 people viewed this in the last hour" psychology).&lt;/p&gt;

&lt;p&gt;Try it. Watch your bounce rate drop within the first week. &lt;strong&gt;&lt;a href="https://feed-pulse.com/generator" rel="noopener noreferrer"&gt;Generate your free widget →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>wordpresslivevisitorcounter</category>
      <category>realtimevisitorwordpress</category>
      <category>wordpresstrafficwidget</category>
      <category>feedjitwordpressalternative</category>
    </item>
    <item>
      <title>How to embed an npm Downloads badge on your website (2026 guide, live weekly count, no API key)</title>
      <dc:creator>Eric Will</dc:creator>
      <pubDate>Tue, 02 Jun 2026 12:41:21 +0000</pubDate>
      <link>https://dev.to/feedpulse/how-to-embed-an-npm-downloads-badge-on-your-website-2026-guide-live-weekly-count-no-api-key-48ih</link>
      <guid>https://dev.to/feedpulse/how-to-embed-an-npm-downloads-badge-on-your-website-2026-guide-live-weekly-count-no-api-key-48ih</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://feed-pulse.com/blog/how-to-embed-npm-downloads-badge-on-website" rel="noopener noreferrer"&gt;feed-pulse.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you maintain an npm package — a CLI tool, a React component library, a build plugin, a utility — your weekly download count is one of the cleanest single signals visitors read as "this is widely used, I can trust it." Putting that number on your &lt;strong&gt;website&lt;/strong&gt; (not just your npm page) is one of the highest-ROI 60-second additions you can make to a package landing, a docs site, a SaaS marketing page, or a portfolio.&lt;/p&gt;

&lt;p&gt;This guide walks every option — Shields.io's badge, npm-stat.com's chart, and a one-line drop-in pill — and shows which to pick. If you're skimming: &lt;a href="https://feed-pulse.com/free-npm-downloads-badge" rel="noopener noreferrer"&gt;FeedPulse's free npm Downloads badge&lt;/a&gt; is the lightest path. No API key, no signup, supports scoped packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an npm Downloads badge?
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;npm Downloads badge&lt;/strong&gt; is a small visual element you embed on a third-party website that shows your package's download count over a fixed period (typically last day, last week, or last month), pulled live from npm's official download counter API.&lt;/p&gt;

&lt;p&gt;npm exposes the download count through its public unmetered endpoint at &lt;code&gt;https://api.npmjs.org/downloads/point/last-week/your-package&lt;/code&gt;. The endpoint is open, unauthenticated, and lets you query any of three windows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;last-day&lt;/code&gt; — yesterday's downloads (npm reports one day behind; today's number is always pulled tomorrow)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;last-week&lt;/code&gt; — the past 7 days&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;last-month&lt;/code&gt; — the past 30 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "last week" total is the canonical brag number — long enough to smooth daily noise, short enough to feel fresh. The other two are for specific contexts: &lt;code&gt;last-day&lt;/code&gt; for spikes after a launch, &lt;code&gt;last-month&lt;/code&gt; for stability narratives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your website needs one
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The credibility delta is huge
&lt;/h3&gt;

&lt;p&gt;"3.2M downloads / week" beats "popular package" by an order of magnitude in conversion testing. A live download count is the single most-recognized "this is real" signal in the npm ecosystem; even seasoned developers will skim a package page in 2-3 seconds, see the number, and decide.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Social proof that updates itself
&lt;/h3&gt;

&lt;p&gt;A static "downloaded by thousands" line on your landing page is fine, but every visitor wonders if it's current. A live badge that says "npm 3.2M / wk" collapses the doubt.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Native fit with other developer signals
&lt;/h3&gt;

&lt;p&gt;A download count alone says "people install this." Combined with a &lt;a href="https://feed-pulse.com/free-github-stars-badge" rel="noopener noreferrer"&gt;GitHub Stars badge&lt;/a&gt;, it also says "people like this." Side by side, the two signals together convert significantly better than either alone. Full play in &lt;a href="https://feed-pulse.com/blog/how-to-embed-github-stars-badge-on-website" rel="noopener noreferrer"&gt;the GitHub Stars how-to&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. SEO body content
&lt;/h3&gt;

&lt;p&gt;For a thin page (a single-package marketing landing, a side-project README mirror), the badge adds visible, unique on-page content. Combined with the badge's own &lt;a href="https://feed-pulse.com/free-npm-downloads-badge" rel="noopener noreferrer"&gt;keyword-rich landing&lt;/a&gt;, the page becomes an indexable result for "npm downloads badge" queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Shields.io SVG badge (the developer default)
&lt;/h2&gt;

&lt;p&gt;The most-used solution. Shields.io renders an SVG badge live:&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;alt=&lt;/span&gt;&lt;span class="s"&gt;"npm downloads"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://img.shields.io/npm/dw/your-package"&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;&lt;strong&gt;Pros:&lt;/strong&gt; Universal, looks at home in a README, no JS.&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Re-fetched on &lt;strong&gt;every page load&lt;/strong&gt; — no client cache.&lt;/li&gt;
&lt;li&gt;Soft rate limit on the free tier; high-traffic pages occasionally 503.&lt;/li&gt;
&lt;li&gt;Locked to Shields' canonical pill style.&lt;/li&gt;
&lt;li&gt;No click-through behavior — you'd wrap in &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best fit: READMEs, technical docs that look like docs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 2: npm-stat.com chart embed
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://npm-stat.com" rel="noopener noreferrer"&gt;npm-stat.com&lt;/a&gt; renders a richer downloads chart you can iframe:&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;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://npm-stat.com/charts.html?package=your-package&amp;amp;from=2025-06-01&amp;amp;to=2026-05-30"&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;"400"&lt;/span&gt; &lt;span class="na"&gt;frameborder=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Time-series chart instead of a single number; useful for "growth story" pages.&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Big iframe (~150-300 KB).&lt;/li&gt;
&lt;li&gt;charts.html itself is heavy; performance impact is noticeable.&lt;/li&gt;
&lt;li&gt;Locked styling — no brand fit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best fit: an "About this package" page where you genuinely want to show the growth curve. Bad fit for a hero badge.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 3: Custom-built (the hard way)
&lt;/h2&gt;

&lt;p&gt;npm's downloads endpoint is open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.npmjs.org/downloads/point/last-week/your-package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;"downloads"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3245678&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&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-05-23"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"end"&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-05-29"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-package"&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;You can call this from your backend, cache it, render however. The catch: you build and maintain the visual, handle scoped packages (&lt;code&gt;@scope/pkg&lt;/code&gt; → the slash needs URL-encoding), handle the "package doesn't exist" 404 case, handle accessibility, theming, locales.&lt;/p&gt;

&lt;p&gt;For one website, rarely worth it. For many, it might be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 4: The one-line drop-in badge (recommended)
&lt;/h2&gt;

&lt;p&gt;This is what we built &lt;a href="https://feed-pulse.com/free-npm-downloads-badge" rel="noopener noreferrer"&gt;FeedPulse's free npm Downloads badge&lt;/a&gt; to be. One line of HTML, ~4 KB gzipped, picks up your package's weekly downloads live, renders a small embeddable pill with seven theme options.&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;async&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://feed-pulse.com/api/embed/npm-downloads.js?package=your-package&amp;amp;period=last-week&amp;amp;theme=npm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;What you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm 3.2M / wk&lt;/strong&gt; pill in npm's signature red (or 6 alternative themes — obsidian, ivory, indigo, emerald, slate, transparent).&lt;/li&gt;
&lt;li&gt;Live count cached server-side for 6 hours so your site never hammers npm's API.&lt;/li&gt;
&lt;li&gt;Click-through opens the package page on npm.&lt;/li&gt;
&lt;li&gt;Auto-formats large counts (1.2k → 3.2M → 412B if you ever make it that far).&lt;/li&gt;
&lt;li&gt;Lighthouse 100/100/100/100 verified on the badge's &lt;a href="https://feed-pulse.com/free-npm-downloads-badge" rel="noopener noreferrer"&gt;own landing page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Three period options: &lt;code&gt;last-day&lt;/code&gt;, &lt;code&gt;last-week&lt;/code&gt; (default), &lt;code&gt;last-month&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Scoped packages supported transparently — paste &lt;code&gt;@scope/your-package&lt;/code&gt; and the slash is handled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://feed-pulse.com/free-npm-downloads-badge" rel="noopener noreferrer"&gt;Open the customizer&lt;/a&gt;, paste your package name, pick a period + theme, copy the snippet. No account, no email.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scoped packages
&lt;/h3&gt;

&lt;p&gt;If your package is &lt;code&gt;@types/node&lt;/code&gt; or &lt;code&gt;@vercel/og&lt;/code&gt; (anything starting with &lt;code&gt;@&lt;/code&gt;), paste the full slug &lt;strong&gt;including the @&lt;/strong&gt; into the customizer. The snippet handles the URL-encoding automatically:&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;async&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://feed-pulse.com/api/embed/npm-downloads.js?package=%40types%2Fnode&amp;amp;period=last-week&amp;amp;theme=npm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;%40&lt;/code&gt; and &lt;code&gt;%2F&lt;/code&gt; are URL-encoded &lt;code&gt;@&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt; respectively — paste &lt;code&gt;@types/node&lt;/code&gt; directly in the customizer and the encoding happens for you.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Embedding on specific platforms
&lt;/h2&gt;

&lt;p&gt;The snippet is plain HTML and works wherever you can paste a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  WordPress
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gutenberg:&lt;/strong&gt; &lt;strong&gt;Custom HTML&lt;/strong&gt; block. Paste. Done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classic editor:&lt;/strong&gt; switch to &lt;strong&gt;Text&lt;/strong&gt; tab. Paste.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sidebar / footer:&lt;/strong&gt; Appearance → Widgets → Custom HTML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict installs:&lt;/strong&gt; use &lt;a href="https://wordpress.org/plugins/code-snippets/" rel="noopener noreferrer"&gt;Code Snippets&lt;/a&gt; plugin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Static-site generators (Hugo, Jekyll, Astro, Next.js, Eleventy)
&lt;/h3&gt;

&lt;p&gt;Drop into your layout template before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docusaurus / VuePress / MkDocs
&lt;/h3&gt;

&lt;p&gt;Paste the snippet directly in any &lt;code&gt;.md&lt;/code&gt; / &lt;code&gt;.mdx&lt;/code&gt; page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shopify, Webflow, Ghost, Framer, Carrd
&lt;/h3&gt;

&lt;p&gt;Same pattern as for the &lt;a href="https://feed-pulse.com/blog/how-to-embed-github-stars-badge-on-website" rel="noopener noreferrer"&gt;GitHub Stars badge&lt;/a&gt; — Custom Code block, paste, publish.&lt;/p&gt;

&lt;h3&gt;
  
  
  README.md
&lt;/h3&gt;

&lt;p&gt;GitHub READMEs strip &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags, so use Shields.io's SVG badge there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;npm downloads&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://img.shields.io/npm/dw/your-package&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use FeedPulse on your website; use Shields.io on your README.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which period should you display?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;last-week&lt;/code&gt;&lt;/strong&gt; — default. Best signal-to-noise. Use this on every marketing surface unless you have a specific reason not to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;last-day&lt;/code&gt;&lt;/strong&gt; — only useful right after a launch when daily numbers are climbing fast. Avoid otherwise — daily counts fluctuate weekly (weekends are quieter for most dev tools) and a low number on a slow day looks bad.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;last-month&lt;/code&gt;&lt;/strong&gt; — for stability narratives. "Steady 12M / month" reads stronger than a wobbly week-by-week story for established packages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can swap the period later just by changing the &lt;code&gt;period=&lt;/code&gt; query parameter; you don't need to re-customize.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance comparison
&lt;/h2&gt;

&lt;p&gt;Lighthouse mobile, fast 3G, Moto G4 emulation, against a clean 100/100/100/100 landing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Initial bytes&lt;/th&gt;
&lt;th&gt;TBT impact&lt;/th&gt;
&lt;th&gt;Perf score Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shields.io SVG&lt;/td&gt;
&lt;td&gt;~2 KB&lt;/td&gt;
&lt;td&gt;+20 ms&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;npm-stat iframe&lt;/td&gt;
&lt;td&gt;~210 KB&lt;/td&gt;
&lt;td&gt;+480 ms&lt;/td&gt;
&lt;td&gt;-12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom backend + render&lt;/td&gt;
&lt;td&gt;~10 KB&lt;/td&gt;
&lt;td&gt;+90 ms&lt;/td&gt;
&lt;td&gt;-2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FeedPulse drop-in&lt;/td&gt;
&lt;td&gt;~4 KB&lt;/td&gt;
&lt;td&gt;+50 ms&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Privacy + GDPR
&lt;/h2&gt;

&lt;p&gt;One server-side proxied request per (period, package) per 6-hour cache window. No cookies, no fingerprinting, no visitor IP leaks to npm. GDPR-safe by default. Full data flow in the &lt;a href="https://feed-pulse.com/free-npm-downloads-badge" rel="noopener noreferrer"&gt;npm Downloads FAQ&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"The badge shows '—' instead of a number."&lt;/strong&gt;&lt;br&gt;
Either the package doesn't exist, the spelling is off, or it's a scoped package whose &lt;code&gt;@&lt;/code&gt; got dropped. Double-check the slug matches &lt;code&gt;https://www.npmjs.com/package/...&lt;/code&gt; exactly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Numbers look truncated as 3.2M."&lt;/strong&gt;&lt;br&gt;
Intentional. Predictable pill width across layouts. If you absolutely need long-form numbers, file a &lt;a href="https://feed-pulse.com/widgets" rel="noopener noreferrer"&gt;widget idea&lt;/a&gt; for a &lt;code&gt;format=full&lt;/code&gt; toggle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"My package just went viral but the badge shows yesterday's number."&lt;/strong&gt;&lt;br&gt;
npm's downloads endpoint itself is always one day behind (it's how npm aggregates). FeedPulse adds a 6-hour cache on top of that, so the badge can be up to ~30 hours behind npm's latest published figure. For 99% of "look at our weekly downloads" use cases, ~30 hours is plenty fresh.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Scoped package isn't working."&lt;/strong&gt;&lt;br&gt;
Make sure you typed the full slug with the leading &lt;code&gt;@&lt;/code&gt;. &lt;code&gt;types/node&lt;/code&gt; won't work; &lt;code&gt;@types/node&lt;/code&gt; will.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to pair with the npm Downloads badge
&lt;/h2&gt;

&lt;p&gt;Downloads solo is fine, but the developer audience reads multiple signals at once. Stack with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://feed-pulse.com/free-github-stars-badge" rel="noopener noreferrer"&gt;Free GitHub Stars badge&lt;/a&gt; — the one-two combo. Conversion lifts 10-20%. &lt;a href="https://feed-pulse.com/blog/how-to-embed-github-stars-badge-on-website" rel="noopener noreferrer"&gt;Embedding guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://feed-pulse.com/free-discord-members-widget" rel="noopener noreferrer"&gt;Free Discord server widget&lt;/a&gt; — if your package has a community / support channel. &lt;a href="https://feed-pulse.com/blog/how-to-embed-discord-server-widget-on-website" rel="noopener noreferrer"&gt;Embedding guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://feed-pulse.com/free-youtube-live-widget" rel="noopener noreferrer"&gt;Free YouTube live widget&lt;/a&gt; — if you stream development sessions or office hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together: "people use it" (downloads), "people like it" (stars), "people talk about it" (Discord), "people watch it being built" (YouTube live).&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;An npm Downloads badge is the cleanest single trust signal you can put on a developer-audience landing. Shields.io is canonical for READMEs. The FeedPulse drop-in is the fastest path to a styled, themable, performant badge on your &lt;strong&gt;website&lt;/strong&gt;. Both are free; pick by surface.&lt;/p&gt;

&lt;p&gt;If you want a live badge on your site in the next 60 seconds, &lt;a href="https://feed-pulse.com/free-npm-downloads-badge" rel="noopener noreferrer"&gt;open the customizer&lt;/a&gt;, paste your package name, copy the snippet. Done.&lt;/p&gt;

</description>
      <category>npmdownloadsbadge</category>
      <category>npmdownloadswidget</category>
      <category>embednpmdownloads</category>
      <category>npmweeklydownloadsbadge</category>
    </item>
    <item>
      <title>How to embed a GitHub Stars badge on your website (2026 guide, live count, no API key)</title>
      <dc:creator>Eric Will</dc:creator>
      <pubDate>Tue, 02 Jun 2026 12:08:10 +0000</pubDate>
      <link>https://dev.to/feedpulse/how-to-embed-a-github-stars-badge-on-your-website-2026-guide-live-count-no-api-key-dg8</link>
      <guid>https://dev.to/feedpulse/how-to-embed-a-github-stars-badge-on-your-website-2026-guide-live-count-no-api-key-dg8</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://feed-pulse.com/blog/how-to-embed-github-stars-badge-on-website" rel="noopener noreferrer"&gt;feed-pulse.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every project page on the internet wants to brag about its GitHub star count — and rightly so. A star count is the cleanest single signal a developer-audience visitor reads as "this is worth my time." Putting that count on your &lt;strong&gt;website&lt;/strong&gt; (not just your README) is one of the highest-ROI 60-second additions you can make to an OSS landing, a docs site, a personal portfolio, or a SaaS marketing page.&lt;/p&gt;

&lt;p&gt;This guide walks every option — the Shields.io badge most developers know, the GitHub native counter, and a one-line drop-in pill — and shows which one to pick for which use case. If you're skimming: &lt;a href="https://feed-pulse.com/free-github-stars-badge" rel="noopener noreferrer"&gt;FeedPulse's free GitHub Stars badge&lt;/a&gt; is the lightest, fastest path. No API key, no signup, no re-fetch tax on every page load.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a GitHub Stars badge?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;GitHub Stars badge&lt;/strong&gt; is a small visual element you embed on a third-party website that shows your repo's current star count, pulled live from GitHub's API. The number updates as your repo gains or loses stars.&lt;/p&gt;

&lt;p&gt;GitHub exposes the star count through its &lt;a href="https://docs.github.com/en/rest/repos/repos#get-a-repository" rel="noopener noreferrer"&gt;public REST API&lt;/a&gt; at &lt;code&gt;https://api.github.com/repos/{owner}/{repo}&lt;/code&gt;. The &lt;code&gt;stargazers_count&lt;/code&gt; field on the response is the number you display. The endpoint is open and doesn't require authentication for public repos.&lt;/p&gt;

&lt;p&gt;There are two ways to render this number: as an SVG image (the Shields.io approach) or as live text inside a small HTML pill. Both work; their performance and customization characteristics differ.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your website needs one
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Instant credibility for developer audiences
&lt;/h3&gt;

&lt;p&gt;A star count is the single most-recognized OSS trust signal among developers. Search-engine clicks land on your site for 2-5 seconds before the visitor decides whether you're worth their time; a visible star count in that window converts skim-readers to readers far better than a paragraph of prose.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Social proof that updates itself
&lt;/h3&gt;

&lt;p&gt;A static "starred by 4,200 developers" sentence is fine, but every visitor wonders if that number is current. A live badge collapses the doubt instantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. SEO body content
&lt;/h3&gt;

&lt;p&gt;For a thin page (a project's marketing landing, a side-project README mirror), a star-count widget adds visible, unique on-page content that helps search rankings — particularly for "alternatives to X" pages where the SERP rewards comparable freshness signals across pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Natural pairing with other dev-audience widgets
&lt;/h3&gt;

&lt;p&gt;If you ship an npm package, your visitor is looking for &lt;strong&gt;both&lt;/strong&gt; the star count and the &lt;a href="https://feed-pulse.com/free-npm-downloads-badge" rel="noopener noreferrer"&gt;weekly download count&lt;/a&gt; before they choose to install. Pair the two badges side by side; conversion typically lifts 10-20% versus showing only one. Full strategy in our &lt;a href="https://feed-pulse.com/blog/how-to-embed-npm-downloads-badge-on-website" rel="noopener noreferrer"&gt;npm Downloads how-to&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Shields.io SVG badge (the developer default)
&lt;/h2&gt;

&lt;p&gt;The most-used solution. Shields.io renders an SVG badge live on every page load:&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;alt=&lt;/span&gt;&lt;span class="s"&gt;"GitHub stars"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://img.shields.io/github/stars/owner/repo?style=flat"&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;&lt;strong&gt;Pros:&lt;/strong&gt; Universally familiar, looks at home in a README or docs page, no JS to run.&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The SVG is re-fetched on &lt;strong&gt;every page load&lt;/strong&gt; — there's no client-side cache, so a popular page hammers Shields.io's CDN.&lt;/li&gt;
&lt;li&gt;Shields.io's free tier has a soft rate limit; pages with multiple Shields badges intermittently 503.&lt;/li&gt;
&lt;li&gt;The badge is styled to match Shields' canonical pill — not your site palette.&lt;/li&gt;
&lt;li&gt;No click-through behavior; you'd need to wrap it in an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best fit: READMEs, technical docs that look like docs, anywhere the Shields style is "the style".&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 2: GitHub's "buttons" script (the official approach)
&lt;/h2&gt;

&lt;p&gt;GitHub publishes &lt;a href="https://buttons.github.io/" rel="noopener noreferrer"&gt;a tiny JS library&lt;/a&gt; for embedding follow / star / fork buttons:&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;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"github-button"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://github.com/owner/repo"&lt;/span&gt; &lt;span class="na"&gt;data-show-count=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Star&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://buttons.github.io/buttons.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; First-party, includes an inline "Star" button that opens the GitHub starring flow.&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The buttons.js library is ~16 KB gzipped and adds ~150-200 ms of script evaluation.&lt;/li&gt;
&lt;li&gt;The visual is locked to GitHub's house style (the gray "Star | 4.2k" pill); no theming.&lt;/li&gt;
&lt;li&gt;The button is rendered inside an iframe sandbox per origin, which adds CORS round-trips and another 100-200 ms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best fit: pages where you want the inline "Star" CTA itself (clicking it opens the star flow), not just the count.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 3: Custom-built (the hard way)
&lt;/h2&gt;

&lt;p&gt;GitHub's API endpoint is public and unauthenticated for public repos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.github.com/repos/microsoft/vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can call this from your own backend, cache the response for 5-30 minutes, and render the result however you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The catch:&lt;/strong&gt; the unauthenticated API rate limit is 60 requests/hour per IP. If your widget hits the API directly from the browser, every page-load-from-a-different-visitor consumes one of those 60. You'll get rate-limited within minutes on any moderately-trafficked page. You'd need a server-side proxy, retry-with-backoff, theming you maintain yourself.&lt;/p&gt;

&lt;p&gt;For a single site, rarely worth it. For a SaaS or a directory, it might be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 4: The one-line drop-in badge (recommended)
&lt;/h2&gt;

&lt;p&gt;This is what we built &lt;a href="https://feed-pulse.com/free-github-stars-badge" rel="noopener noreferrer"&gt;FeedPulse's free GitHub Stars badge&lt;/a&gt; to be. One line of HTML, ~4 KB gzipped, picks up your repo's star count live, renders a small embeddable pill with seven theme options.&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;async&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://feed-pulse.com/api/embed/github-stars.js?repo=owner/repo&amp;amp;theme=github"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;What you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;★ 4,234&lt;/strong&gt; pill in GitHub's own dark-blue (or 6 alternative themes — obsidian, ivory, indigo, emerald, slate, transparent).&lt;/li&gt;
&lt;li&gt;Live count cached server-side for 6 hours so your visitors never trigger a GitHub API call directly. (Shared cache across all FeedPulse embedders means one upstream call serves thousands of pages.)&lt;/li&gt;
&lt;li&gt;Click-through opens the repo on GitHub.&lt;/li&gt;
&lt;li&gt;Auto-truncates large counts (★ 4.2k, ★ 12.4M).&lt;/li&gt;
&lt;li&gt;Lighthouse 100/100/100/100 verified on the badge's &lt;a href="https://feed-pulse.com/free-github-stars-badge" rel="noopener noreferrer"&gt;own landing page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;12 locales for the "stars" suffix (estrellas / étoiles / звёзд / 星 / نجمة / etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://feed-pulse.com/free-github-stars-badge" rel="noopener noreferrer"&gt;Open the customizer&lt;/a&gt;, enter your &lt;code&gt;owner/repo&lt;/code&gt; slug, pick a theme, copy the snippet. No account, no email, no upgrade screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embedding on specific platforms
&lt;/h2&gt;

&lt;p&gt;The snippet is plain HTML and works on any platform that lets you paste a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  WordPress
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gutenberg:&lt;/strong&gt; &lt;strong&gt;Custom HTML&lt;/strong&gt; block. Paste. Done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classic editor:&lt;/strong&gt; switch to &lt;strong&gt;Text&lt;/strong&gt; tab, paste, save.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sidebar / footer:&lt;/strong&gt; Appearance → Widgets → Custom HTML widget. Paste.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict installs that strip &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;:&lt;/strong&gt; &lt;a href="https://wordpress.org/plugins/code-snippets/" rel="noopener noreferrer"&gt;Code Snippets&lt;/a&gt; or &lt;a href="https://wordpress.org/plugins/insert-headers-and-footers/" rel="noopener noreferrer"&gt;Insert Headers and Footers&lt;/a&gt; plugin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Static-site generators (Hugo, Jekyll, Astro, Next.js, Eleventy)
&lt;/h3&gt;

&lt;p&gt;Drop the snippet into your layout template (right before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; so it doesn't block first paint).&lt;/p&gt;

&lt;h3&gt;
  
  
  Docusaurus / VuePress / MkDocs
&lt;/h3&gt;

&lt;p&gt;Inside your homepage &lt;code&gt;.md&lt;/code&gt; / &lt;code&gt;.mdx&lt;/code&gt; file: most MDX-aware generators let you paste raw HTML inline. The snippet works without changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shopify
&lt;/h3&gt;

&lt;p&gt;Settings → Theme code → &lt;code&gt;theme.liquid&lt;/code&gt;. Paste before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webflow
&lt;/h3&gt;

&lt;p&gt;Page settings → Custom Code → Before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; tag. Paste. Publish.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ghost / Carrd / Framer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ghost:&lt;/strong&gt; Code Injection → Site Footer. Paste.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Carrd:&lt;/strong&gt; add an &lt;strong&gt;Embed&lt;/strong&gt; element, paste.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framer:&lt;/strong&gt; add a Custom Code block, paste, position.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  README.md
&lt;/h3&gt;

&lt;p&gt;GitHub READMEs strip &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags, so the JS pill doesn't work there directly — but &lt;a href="https://shields.io/badges/git-hub-repo-stars" rel="noopener noreferrer"&gt;Shields.io's badge&lt;/a&gt; does. &lt;strong&gt;Use FeedPulse's pill on your website; use Shields.io's SVG on your README.&lt;/strong&gt; Best tool for each surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance comparison
&lt;/h2&gt;

&lt;p&gt;Lighthouse mobile, fast 3G, Moto G4 emulation, against a clean 100/100/100/100 landing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Initial bytes&lt;/th&gt;
&lt;th&gt;TBT impact&lt;/th&gt;
&lt;th&gt;Perf score Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shields.io SVG&lt;/td&gt;
&lt;td&gt;~2 KB&lt;/td&gt;
&lt;td&gt;+20 ms&lt;/td&gt;
&lt;td&gt;0 (but adds a third-party origin)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub buttons.js&lt;/td&gt;
&lt;td&gt;~16 KB&lt;/td&gt;
&lt;td&gt;+180 ms&lt;/td&gt;
&lt;td&gt;-4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom backend + render&lt;/td&gt;
&lt;td&gt;~10 KB&lt;/td&gt;
&lt;td&gt;+90 ms&lt;/td&gt;
&lt;td&gt;-2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FeedPulse drop-in&lt;/td&gt;
&lt;td&gt;~4 KB&lt;/td&gt;
&lt;td&gt;+50 ms&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Shields.io approach is fastest on first byte but introduces a separate origin that fails periodically. The FeedPulse drop-in is functionally tied at parity but with predictable uptime (the cache is shared across all embedders, so the upstream call rate is constant regardless of total embed count).&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching and freshness
&lt;/h2&gt;

&lt;p&gt;For a non-trending repo, fresh-within-6-hours is plenty. FeedPulse caches the count server-side for 6 hours per repo. The cache key is the &lt;code&gt;owner/repo&lt;/code&gt; slug, so all embedders of the same repo share one upstream call.&lt;/p&gt;

&lt;p&gt;When the cache misses, the widget hits GitHub's public API with an unauthenticated request — which has a 60/hour limit per IP. Because the request comes from FeedPulse's IP (not your visitor's), and the per-repo cache lasts 6 hours, the practical headroom is effectively unlimited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy + GDPR
&lt;/h2&gt;

&lt;p&gt;One server-side proxied request per 6-hour window. No cookies, no fingerprinting, no visitor IP leaks to GitHub. GDPR-safe by default. Full data flow in the &lt;a href="https://feed-pulse.com/free-github-stars-badge" rel="noopener noreferrer"&gt;GitHub Stars FAQ&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"The badge shows ★ — instead of a number."&lt;/strong&gt;&lt;br&gt;
Either the repo doesn't exist, is private, or the slug has a typo. Double-check &lt;code&gt;owner/repo&lt;/code&gt; matches &lt;code&gt;https://github.com/owner/repo&lt;/code&gt; exactly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Numbers look truncated."&lt;/strong&gt;&lt;br&gt;
Counts ≥1,000 render as &lt;code&gt;4.2k&lt;/code&gt;; counts ≥1,000,000 render as &lt;code&gt;4.2M&lt;/code&gt;. Intentional — keeps the pill width predictable inside narrow layouts. If you need long-form, file a &lt;a href="https://feed-pulse.com/widgets" rel="noopener noreferrer"&gt;widget idea request&lt;/a&gt; for a &lt;code&gt;format=full&lt;/code&gt; toggle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"My count jumped 50 stars but the badge still shows the old count."&lt;/strong&gt;&lt;br&gt;
6-hour cache window. The new count will appear at the next refresh. Append &lt;code&gt;&amp;amp;v=2&lt;/code&gt; to the snippet's &lt;code&gt;src&lt;/code&gt; to bust the per-embedder cache lookup if needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to pair with the GitHub Stars badge
&lt;/h2&gt;

&lt;p&gt;GitHub Stars solo is fine, but the developer audience reads multiple signals at once. Stack with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://feed-pulse.com/free-npm-downloads-badge" rel="noopener noreferrer"&gt;Free npm Downloads badge&lt;/a&gt; — if you ship a package, side-by-side stars + weekly downloads tells the full story. Full &lt;a href="https://feed-pulse.com/blog/how-to-embed-npm-downloads-badge-on-website" rel="noopener noreferrer"&gt;how-to&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://feed-pulse.com/free-discord-members-widget" rel="noopener noreferrer"&gt;Free Discord server widget&lt;/a&gt; — proves you have a community, not just code. &lt;a href="https://feed-pulse.com/blog/how-to-embed-discord-server-widget-on-website" rel="noopener noreferrer"&gt;Guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://feed-pulse.com/free-youtube-live-widget" rel="noopener noreferrer"&gt;Free YouTube live widget&lt;/a&gt; — if you stream development sessions, the LIVE pill appears auto when you start.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together: "people like it" (stars) + "people use it" (downloads) + "people talk about it" (Discord) + "people watch it being built" (YouTube live). That's a complete momentum signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;A GitHub Stars badge is the developer-audience equivalent of social proof. The Shields.io SVG is the canonical README pick. The FeedPulse drop-in is the fastest path to a styled, performant, themable badge on your &lt;strong&gt;website&lt;/strong&gt;. Both are free; pick by where the badge lives.&lt;/p&gt;

&lt;p&gt;If you want a live badge on your site in the next 60 seconds, &lt;a href="https://feed-pulse.com/free-github-stars-badge" rel="noopener noreferrer"&gt;open the customizer&lt;/a&gt;, paste &lt;code&gt;owner/repo&lt;/code&gt;, copy the snippet. Done.&lt;/p&gt;

</description>
      <category>githubstarsbadge</category>
      <category>githubstarswidget</category>
      <category>embedgithubstars</category>
      <category>githubstarcountbadge</category>
    </item>
    <item>
      <title>How to embed a Discord server widget on your website (2026 guide, no bot, no API key)</title>
      <dc:creator>Eric Will</dc:creator>
      <pubDate>Tue, 02 Jun 2026 11:43:46 +0000</pubDate>
      <link>https://dev.to/feedpulse/how-to-embed-a-discord-server-widget-on-your-website-2026-guide-no-bot-no-api-key-35a9</link>
      <guid>https://dev.to/feedpulse/how-to-embed-a-discord-server-widget-on-your-website-2026-guide-no-bot-no-api-key-35a9</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://feed-pulse.com/blog/how-to-embed-discord-server-widget-on-website" rel="noopener noreferrer"&gt;feed-pulse.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you run a Discord community — for an open-source project, a SaaS, a creator brand, or a niche hobby — you probably want your website to surface the same energy your server does. The single most effective way to do that: a small &lt;strong&gt;embeddable Discord widget&lt;/strong&gt; that shows your live online-member count, server name, and a click-through invite right on your landing page, OSS docs, or blog.&lt;/p&gt;

&lt;p&gt;This guide walks every option — Discord's own iframe, building it yourself, and a one-line drop-in widget — so you can pick the one that fits your site. Spoiler: the lightest path is the &lt;a href="https://feed-pulse.com/free-discord-members-widget" rel="noopener noreferrer"&gt;FeedPulse free Discord server widget&lt;/a&gt;, which loads in under 50 ms, has no signup, and doesn't make you invite a bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Discord server widget?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Discord server widget&lt;/strong&gt; is a small visual element you embed on a third-party website that shows live data about your Discord server — most commonly the number of members currently online, the server's name, and an optional one-click invite link. The data is pulled in real time from Discord's own public API, so you never have to manually update it when your community grows.&lt;/p&gt;

&lt;p&gt;Discord &lt;a href="https://discord.com/developers/docs/resources/guild#get-guild-widget" rel="noopener noreferrer"&gt;exposes a public, unauthenticated widget endpoint&lt;/a&gt; at &lt;code&gt;https://discord.com/api/guilds/{server-id}/widget.json&lt;/code&gt; specifically for this purpose. As long as the server owner has the "Enable Server Widget" toggle on (Server Settings → Widget), anyone can read live counts from that endpoint.&lt;/p&gt;

&lt;p&gt;The number it surfaces is the same one Discord shows inside the app: members currently in any non-offline presence state (online, idle, do-not-disturb). It updates within seconds of someone joining or leaving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your website needs one
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Trust signal that updates itself
&lt;/h3&gt;

&lt;p&gt;A static "Join our 3,000-member Discord" line on your landing page is fine — but every visitor wonders if that 3,000 is still accurate or was hand-typed in 2023. A live widget showing "● 3,421 online" right now collapses that doubt instantly. Social-proof research from 2024 onward consistently shows live counters convert 8-14% better than static numbers on community-CTA sections.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Conversion to invite-click is dramatically higher
&lt;/h3&gt;

&lt;p&gt;The same studies show that a "Join the Discord →" button next to a live online count gets 2-3× the click-through rate of an isolated "Join Discord" button. The visible activity creates urgency: there are people in there &lt;em&gt;right now&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. SEO body content
&lt;/h3&gt;

&lt;p&gt;Search engines that crawl your page see the widget as additional unique content — particularly valuable for thin pages (a sparse OSS landing, a side-project README) where every bit of original-looking on-page content helps ranking. Combined with our &lt;a href="https://feed-pulse.com/free-discord-members-widget" rel="noopener noreferrer"&gt;Discord widget landing's keyword-rich H1 + FAQ&lt;/a&gt;, the page becomes a real result for "discord widget for website" queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Native fit with creator / OSS / SaaS audiences
&lt;/h3&gt;

&lt;p&gt;GitHub Stars badges, npm download counts, and Discord widgets all live in the same visual family — small live pills that prove your project is alive. Pairing them creates a "this thing has momentum" message no static line ever achieves. We've documented &lt;a href="https://feed-pulse.com/blog/how-to-embed-github-stars-badge-on-website" rel="noopener noreferrer"&gt;how to embed a GitHub Stars badge&lt;/a&gt; and &lt;a href="https://feed-pulse.com/blog/how-to-embed-npm-downloads-badge-on-website" rel="noopener noreferrer"&gt;an npm downloads badge&lt;/a&gt; for the same reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Discord's official iframe (the heavy way)
&lt;/h2&gt;

&lt;p&gt;Discord ships its own embeddable widget. To use it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your server's &lt;strong&gt;Server Settings → Widget&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Toggle &lt;strong&gt;Enable Server Widget&lt;/strong&gt; on.&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;Premade Widget&lt;/strong&gt; &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; snippet.&lt;/li&gt;
&lt;li&gt;Paste it anywhere on your site.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The snippet looks like:&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;iframe&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://discord.com/widget?id=YOUR_SERVER_ID&amp;amp;theme=dark"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"350"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"500"&lt;/span&gt;
  &lt;span class="na"&gt;allowtransparency=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;frameborder=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
  &lt;span class="na"&gt;sandbox=&lt;/span&gt;&lt;span class="s"&gt;"allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; First-party, "official" badge.&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's a &lt;strong&gt;full iframe&lt;/strong&gt; — ~150-300 KB of HTML/CSS/JS loaded into your page, which hurts Lighthouse Performance scores noticeably (we've measured -8 to -14 points on otherwise-100 landing pages).&lt;/li&gt;
&lt;li&gt;The styling is fixed (light/dark theme only; no brand-palette fit).&lt;/li&gt;
&lt;li&gt;It takes up a 350×500 box — huge for a sidebar or footer.&lt;/li&gt;
&lt;li&gt;It runs a separate sandbox, adding 200-400 ms of script evaluation time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your site is performance-tolerant, this is fine. If you care about PSI scores, see Method 3.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 2: Custom-built (the hard way)
&lt;/h2&gt;

&lt;p&gt;Discord's widget endpoint is public and unauthenticated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://discord.com/api/guilds/YOUR_SERVER_ID/widget.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;"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;"YOUR_SERVER_ID"&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;"Your Server Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instant_invite"&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://discord.gg/..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"presence_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3421&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"members"&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="err"&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;You can call this from your own backend, cache it for 5-15 minutes, and render however you want. If you have a backend and a few hours, this is the right thing to do. The catch: you have to design + maintain the visual yourself, handle the "widget is disabled" case (HTTP 403) and the "server deleted" case (HTTP 404) so the badge doesn't break, and handle locale, light/dark mode, and accessibility (color contrast must pass WCAG AA on whatever palette you pick).&lt;/p&gt;

&lt;p&gt;If you're building this for one site, the time cost almost always exceeds the value. For a SaaS or a directory: maybe worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3: The one-line drop-in widget (recommended)
&lt;/h2&gt;

&lt;p&gt;This is what we built &lt;a href="https://feed-pulse.com/free-discord-members-widget" rel="noopener noreferrer"&gt;FeedPulse's Discord server widget&lt;/a&gt; to be. One line of HTML, 4 KB gzipped, picks up your server's live online count and renders a small embeddable pill with seven theme options. No bot to invite, no OAuth flow, no API key — paste and go.&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;async&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://feed-pulse.com/api/embed/discord-members.js?guild=YOUR_SERVER_ID&amp;amp;theme=discord"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;What it gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;● 3,421 online&lt;/strong&gt; pill in Discord blurple (or 6 alternative themes — obsidian, ivory, indigo, emerald, slate, transparent).&lt;/li&gt;
&lt;li&gt;Live count, refreshed hourly server-side (so your site never hammers Discord's API).&lt;/li&gt;
&lt;li&gt;Click-through opens the server's instant-invite URL automatically (if set in your Widget settings).&lt;/li&gt;
&lt;li&gt;Auto-hides gracefully (renders "—") if you ever disable Widget — no broken layout.&lt;/li&gt;
&lt;li&gt;Lighthouse 100/100/100/100 verified on the &lt;a href="https://feed-pulse.com/free-discord-members-widget" rel="noopener noreferrer"&gt;widget's own landing page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Optional &lt;code&gt;show_name=1&lt;/code&gt; toggle to append your server name to the pill.&lt;/li&gt;
&lt;li&gt;12 locales (online / en línea / en ligne / オンライン / в сети / etc.) auto-detected from page &lt;code&gt;lang&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://feed-pulse.com/free-discord-members-widget" rel="noopener noreferrer"&gt;Open the customizer&lt;/a&gt;, pick a theme, paste your server ID, copy the snippet. No account, no email.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding your Discord server ID
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Server Settings → Widget&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Toggle &lt;strong&gt;Enable Server Widget&lt;/strong&gt; on (if it isn't already).&lt;/li&gt;
&lt;li&gt;The numeric &lt;strong&gt;Server ID&lt;/strong&gt; is shown at the top of that page — a 17-20 digit string.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Paste that string into the customizer's "Discord server ID" field. Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embedding it on specific platforms
&lt;/h2&gt;

&lt;p&gt;The snippet is plain HTML and works wherever you can paste a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  WordPress
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gutenberg:&lt;/strong&gt; &lt;strong&gt;Custom HTML&lt;/strong&gt; block. Paste. Done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classic editor:&lt;/strong&gt; switch to &lt;strong&gt;Text&lt;/strong&gt; tab (not Visual), paste, save.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sidebar/footer:&lt;/strong&gt; Appearance → Widgets → Custom HTML widget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict installs that strip &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;:&lt;/strong&gt; install &lt;a href="https://wordpress.org/plugins/code-snippets/" rel="noopener noreferrer"&gt;Code Snippets&lt;/a&gt; or &lt;a href="https://wordpress.org/plugins/insert-headers-and-footers/" rel="noopener noreferrer"&gt;Insert Headers and Footers&lt;/a&gt; and drop it in the footer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Shopify
&lt;/h3&gt;

&lt;p&gt;Settings → Theme code → &lt;code&gt;theme.liquid&lt;/code&gt;. Paste before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt;. For page-specific embeds wrap it in a Shopify section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webflow
&lt;/h3&gt;

&lt;p&gt;Page → Settings → Custom Code → "Before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; tag". Paste. Publish.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ghost / Substack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ghost:&lt;/strong&gt; Code Injection → Site Footer. Paste.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Substack:&lt;/strong&gt; restricts script tags inside posts but allows iframe embeds — use Method 1 there, or link to your widget landing page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Squarespace
&lt;/h3&gt;

&lt;p&gt;Code Block (inside any section). Paste. Save. (Code Block requires a Business plan or above on Squarespace.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Wix
&lt;/h3&gt;

&lt;p&gt;Add → Embed → Embed a Widget → HTML iframe. Paste.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static sites (Hugo, Jekyll, Next.js, Astro)
&lt;/h3&gt;

&lt;p&gt;Drop the snippet into your layout template (&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; or before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance comparison
&lt;/h2&gt;

&lt;p&gt;Lighthouse mobile, fast 3G, Moto G4 emulation, against a clean 100/100/100/100 landing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Initial bytes&lt;/th&gt;
&lt;th&gt;TBT impact&lt;/th&gt;
&lt;th&gt;Perf score Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Discord native iframe&lt;/td&gt;
&lt;td&gt;~210 KB&lt;/td&gt;
&lt;td&gt;+480 ms&lt;/td&gt;
&lt;td&gt;-12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom backend + manual render&lt;/td&gt;
&lt;td&gt;~12 KB&lt;/td&gt;
&lt;td&gt;+90 ms&lt;/td&gt;
&lt;td&gt;-2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FeedPulse drop-in&lt;/td&gt;
&lt;td&gt;~4 KB&lt;/td&gt;
&lt;td&gt;+50 ms&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The iframe is 210 KB because it ships its own React-based mini-app for the member list. The FeedPulse widget renders a single pill with vanilla DOM, no framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy + GDPR
&lt;/h2&gt;

&lt;p&gt;The widget makes &lt;strong&gt;one server-side proxied request to Discord's public widget endpoint per 6-hour cache window&lt;/strong&gt;, regardless of how many of your visitors load the page. No cookies are set, no fingerprinting runs, and no visitor IP touches Discord's servers (the proxy abstracts it). GDPR-safe by default — no cookie banner needed.&lt;/p&gt;

&lt;p&gt;Full data flow in the &lt;a href="https://feed-pulse.com/free-discord-members-widget" rel="noopener noreferrer"&gt;Discord widget's FAQ section&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"The widget shows '—' instead of a count."&lt;/strong&gt;&lt;br&gt;
Your server doesn't have &lt;strong&gt;Enable Server Widget&lt;/strong&gt; toggled on. Open Server Settings → Widget → toggle it on. Within 1-6 hours the cache refreshes and the widget picks up the live count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Click-through doesn't open my invite."&lt;/strong&gt;&lt;br&gt;
Set the &lt;strong&gt;Invite Channel&lt;/strong&gt; in Server Settings → Widget. Discord generates a permanent invite link tied to that channel and the widget will use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"The pill looks wrong on my dark theme site."&lt;/strong&gt;&lt;br&gt;
Switch the theme parameter — &lt;code&gt;?theme=obsidian&lt;/code&gt; is built for dark backgrounds, &lt;code&gt;?theme=transparent&lt;/code&gt; inherits whatever &lt;code&gt;color&lt;/code&gt; your CSS sets on the parent. The &lt;a href="https://feed-pulse.com/free-discord-members-widget" rel="noopener noreferrer"&gt;customizer page&lt;/a&gt; has a live preview for all 7 themes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"My server has 12,000 members but the count shows 800."&lt;/strong&gt;&lt;br&gt;
That's correct. The widget shows &lt;strong&gt;currently online&lt;/strong&gt; members, not total members. Discord's API only exposes the online-presence count to the public widget endpoint — total membership requires a bot, which the widget intentionally avoids.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to pair with the Discord widget
&lt;/h2&gt;

&lt;p&gt;If you're already running a community-driven site, pair the Discord widget with these for compound social proof:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://feed-pulse.com/free-github-stars-badge" rel="noopener noreferrer"&gt;Free GitHub Stars badge&lt;/a&gt; — if your project is on GitHub, show the live star count next to the Discord widget. Full &lt;a href="https://feed-pulse.com/blog/how-to-embed-github-stars-badge-on-website" rel="noopener noreferrer"&gt;how-to guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://feed-pulse.com/free-npm-downloads-badge" rel="noopener noreferrer"&gt;Free npm Downloads badge&lt;/a&gt; — if you ship an npm package, show weekly downloads. &lt;a href="https://feed-pulse.com/blog/how-to-embed-npm-downloads-badge-on-website" rel="noopener noreferrer"&gt;How-to guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://feed-pulse.com/free-youtube-live-widget" rel="noopener noreferrer"&gt;Free YouTube live widget&lt;/a&gt; — if you stream events on YouTube, an auto-detecting "LIVE NOW" pill appears the moment you go live.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://feed-pulse.com/free-twitch-live-widget" rel="noopener noreferrer"&gt;Free Twitch live widget&lt;/a&gt; — same auto-detection for Twitch streamers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stacking these four creates a "this project has momentum" signal across every channel a visitor might check. We've written more about the strategy in &lt;a href="https://feed-pulse.com/blog/why-traffic-exchanges-hurt-seo" rel="noopener noreferrer"&gt;why traffic exchanges hurt SEO&lt;/a&gt; and the &lt;a href="https://feed-pulse.com/blog/free-feedjit-alternative-comparison-2026" rel="noopener noreferrer"&gt;free Feedjit alternative&lt;/a&gt; breakdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The Discord server widget is one of the highest-ROI single additions you can make to a community-driven landing page. The official iframe is heavy but works everywhere. A custom build is cleanest if you have engineering hours to spend. The FeedPulse drop-in is the fastest path to a live widget that scores 100 on Lighthouse and looks like it belongs on your site, not bolted on.&lt;/p&gt;

&lt;p&gt;If you want a working widget on your site in the next 60 seconds, &lt;a href="https://feed-pulse.com/free-discord-members-widget" rel="noopener noreferrer"&gt;open the customizer&lt;/a&gt;, paste your server ID, copy the snippet, paste it on your site. Done. No account, no email, no upgrade prompt.&lt;/p&gt;

</description>
      <category>discordwidgetforwebsite</category>
      <category>discordserverwidget</category>
      <category>embeddiscordwidget</category>
      <category>howtoembeddiscordwidget</category>
    </item>
  </channel>
</rss>
