I build websites for luxury service brands. Yacht charters in Ibiza and Malta, private clinics in Dubai, perfume houses in Monaco. Different clients, same conversion pattern:
Nobody fills out the contact form. They message on WhatsApp.
After the tenth time I copy-pasted the same three files between projects, I extracted them into an npm package. This week I published it.
npm install nextjs-whatsapp-cta
- npm: nextjs-whatsapp-cta
- GitHub: nazirabas/nextjs-whatsapp-cta
Below, the design decisions I made and why.
The problem with existing WhatsApp libs
I checked npm before writing my own. The top results fell into three buckets:
| Category | Example issue |
|---|---|
| Massive UI kits | 200 KB import to render one green pill |
| Component libraries | Baked-in styling that fights your Tailwind setup |
| "Simple" libs | Pulls in moment, axios, or lodash as transitive deps |
None handled the two things I actually needed:
- UTM tracking that survives the WhatsApp jump. Attribution must land in the message body, because query params are stripped on some WhatsApp clients.
- GA4 event firing without a provider wrapper. If window.gtag exists, fire the event. If not, no-op. No wrapping component, no SDK import.
So I wrote three files.
What is in the package
src/
├── lib.ts // buildWhatsAppUrl, trackWhatsAppClick, normalizePhone
├── WhatsAppButton.tsx // inline pill CTA (Tailwind class-based)
└── WhatsAppFloating.tsx // site-wide floating widget (inline styles)
200 lines total. Zero runtime deps. Peers: react and react-dom (>= 18).
Tarball is 5.7 KB. Unpacked 23.6 KB.
Usage
import { WhatsAppButton, WhatsAppFloating } from "nextjs-whatsapp-cta";
// Inline CTA on a hero section
<WhatsAppButton
phone="+971501824077"
message="Hi, I'd like to book the Sunseeker 62 for Sunday."
utm={{ source: "website", medium: "hero", campaign: "summer-2026" }}
trackingLabel="hero-cta"
>
Book on WhatsApp
</WhatsAppButton>
// Site-wide floating widget in root layout
<WhatsAppFloating
phone="+971501824077"
message="Hi, coming from your website."
position="bottom-right"
/>
Click either one, and if window.gtag exists this fires automatically:
gtag("event", "whatsapp_click", { event_label: "hero-cta" });
No provider wrap. No useEffect. No GA4 SDK.
Three design choices worth explaining
1. UTMs go in the message body, not the URL
WhatsApp strips query params on some clients. If your CRM relies on knowing which ad triggered a chat, the only reliable place is inside the pre-filled message. It looks a little ugly ((utm_source=website&utm_medium=hero) at the bottom), but it survives forwarding, screenshots, and cross-device sync.
// From src/lib.ts
if (utm) {
const utmString = Object.entries(utm)
.filter(([, v]) => Boolean(v))
.map(([k, v]) => `utm_${k}=${v}`)
.join("&");
if (utmString) body = `${message}\n\n(${utmString})`;
}
2. Phone normalization is aggressive
Every production site I have inherited has the same number formatted three different ways across three CTAs. My URL builder strips everything except digits and always produces the same canonical wa.me link.
export function normalizePhone(phone: string): string {
return phone.replace(/[^\d]/g, "");
}
Accepts +971 50 182 4077, 971501824077, +971-50-182-4077. Outputs the same thing.
3. The floating widget is not lazy-loaded
For every other component I would say "of course lazy-load it." Not this one. The floating WhatsApp button is often the primary CTA on service-brand sites. It should render in first paint, not after your analytics bundle finishes loading.
It also uses inline styles (not Tailwind classes) so it renders correctly even on a marketing page that has not hydrated your design system yet.
What I intentionally left out
- No auto-open on scroll. Dark pattern.
- No sound effects. Please.
- No pre-configured "hello" popup. The user came to your site, they know why they are here.
- No custom modal wrapping WhatsApp. WhatsApp handles the chat surface, do not reinvent it.
If you want any of those, fork it. It is MIT.
Why open source it
Because 90% of my client work lives in private repos. Nothing to show on GitHub. This is my way of saying "here is how I actually think about conversion patterns" without leaking client work.
If you are a Next.js dev building for a service brand in a WhatsApp-first region (Middle East, LATAM, South Asia, Southern Europe), this saves you a day of scaffolding.
Install and try it
npm install nextjs-whatsapp-cta
- npm: https://www.npmjs.com/package/nextjs-whatsapp-cta
- GitHub: https://github.com/nazirabas/nextjs-whatsapp-cta
- License: MIT
Issues and PRs welcome. If you use it in production, drop a comment. I would love to see how it looks in your project.
About me: I am Nazir Abbas, a web developer and SEO specialist based in Dubai. Founder of Digi-Soft-Rank, building premium websites for luxury brands across Dubai, Monaco, Ibiza, and Malta. Say hi on LinkedIn or book a call.
Top comments (0)