DEV Community

Cover image for Engineering a Zero-Dependency, Privacy-First Audio Widget in Under 30KB
nayeem
nayeem

Posted on

Engineering a Zero-Dependency, Privacy-First Audio Widget in Under 30KB

Embeds are one of the easiest ways to make someone else's page slow. A typical "free widget" brings a framework, a font, a tracking script and a cookie banner problem. I wanted to build one that a blogger could drop into a post without thinking about any of that.

The widget plays a 165 Hz pulse for clearing water from phone speakers. The goals were: no cookies, no tracking, no external requests, and under 30 KB.

Generate the sound, don't download it
An MP3 of a 60-second tone is several hundred KB. Generating it with Web Audio costs a few lines:

let ctx, osc, gain;

function start() {
  ctx ??= new AudioContext();
  osc = new OscillatorNode(ctx, { frequency: 165 });
  gain = new GainNode(ctx, { gain: 0 });
  osc.connect(gain).connect(ctx.destination);
  osc.start();
  // pulse: 0.8s on, 0.2s off
  for (let t = ctx.currentTime; t < ctx.currentTime + 60; t += 1) {
    gain.gain.setTargetAtTime(0.8, t, 0.01);
    gain.gain.setTargetAtTime(0, t + 0.8, 0.01);
  }
}

function stop() {
  gain?.gain.setTargetAtTime(0, ctx.currentTime, 0.02);
  osc?.stop(ctx.currentTime + 0.1);
}
Enter fullscreen mode Exit fullscreen mode

No network request, no codec, and the audio is identical on every device.

Rules I followed
Nothing runs until a tap. The AudioContext is created in the click handler. That's also what browsers require for audio anyway.
The animation only runs while sound plays. The waveform's requestAnimationFrame loop stops on Stop, so an idle widget uses no CPU.
System fonts only. A web font alone can be bigger than the whole widget.
No storage, no cookies, no analytics inside the iframe. Nothing to disclose in the host's privacy policy.
Theme via query string. ?theme=light or ?theme=dark. No config script on the host page.

The embed code

<iframe
  src="https://speakercleaningtool.com/embed/water-eject?theme=dark"
  width="360" height="420"
  loading="lazy"
  title="Water Eject speaker cleaner"
  style="border:0;border-radius:12px;max-width:100%">
</iframe>
Enter fullscreen mode Exit fullscreen mode

loading="lazy" means the iframe isn't fetched until the reader scrolls near it, so it doesn't affect the host page's first load. Always set width and height to avoid layout shift.

SEO detail I learned the hard way
My first version let the bare widget URL get indexed, and SEO audits flagged it as a thin page with no heading. The fix was a noindex, follow robots meta plus a canonical pointing at the full tool page. Don't block it in robots.txt, though. If crawlers can't fetch the page, they never see the noindex.

Use it or copy the approach
The widget and setup steps for WordPress, Webflow, Ghost and plain HTML are on the embed page(opens in a new tab). It's free to use. If you're building your own embed, I hope the checklist above saves you a few audit warnings.

— Nayeem, Software Developer

Top comments (0)