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
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);
}
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>
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
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.