logo
image
Jun 10, 2026 - 07:54 PM

React and Next.js WhatsApp Widget: Component Code and Install Instructions

React WhatsApp button and a Next.js compatible component that works in both the App Router and the Pages Router. The component is fully typed, supports a dynamic phone number from props, and includes a small server-action-ready example for Next.js 14+. Target link: wa.me/966500000000.

1. The React component (TSX)

Create components/WuttWhatsApp.tsx:

import { CSSProperties, MouseEvent } from "react";

export type WuttWhatsAppProps = {
    phone: string;
    message?: string;
    label?: string;
    position?: "bottom-right" | "bottom-left";
    onClick?: (e: MouseEvent<HTMLAnchorElement>) => void;
};

export default function WuttWhatsApp({
    phone,
    message = "Hi, I'd like to know more about Wutt.",
    label = "Chat with us",
    position = "bottom-right",
    onClick,
}: WuttWhatsAppProps) {
    const digits = phone.replace(/\D/g, "");
    const href   = `https://wa.me/${digits}?text=${encodeURIComponent(message)}`;

    const style: CSSProperties = {
        position: "fixed",
        [position === "bottom-left" ? "left" : "right"]: 20,
        bottom: 20,
        zIndex: 9999,
        display: "inline-flex",
        alignItems: "center",
        gap: 10,
        background: "#25D366",
        color: "#fff",
        padding: "12px 18px",
        borderRadius: 999,
        textDecoration: "none",
        fontWeight: 700,
        boxShadow: "0 10px 30px rgba(0,0,0,.25)",
    };

    return (
        <a href={href} target="_blank" rel="noopener" style={style} onClick={onClick} aria-label="Chat on WhatsApp">
            <svg viewBox="0 0 32 32" width="22" height="22" aria-hidden="true">
                <path fill="#fff" d="M16 3C9 3 3.5 8.5 3.5 15.5c0 2.4.7 4.7 1.9 6.7L3 29l7-1.8c1.9 1 4 1.5 6 1.5 7 0 12.5-5.5 12.5-12.5S23 3 16 3z" />
            </svg>
            <span>{label}</span>
        </a>
    );
}

2. Next.js App Router usage

App Router lives in app/. To load the widget on every page, create app/components/WuttClient.tsx that imports the component with "use client":

"use client";
import dynamic from "next/dynamic";

const WuttWhatsApp = dynamic(() => import("./WuttWhatsApp"), { ssr: false });

export default function WuttClient() {
    return (
        <WuttWhatsApp
            phone="966500000000"
            message="Hi, I'd like to know more about Wutt."
            label="Chat"
        />
    );
}

Then drop it into app/layout.tsx:

import WuttClient from "./components/WuttClient";

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="en">
            <body>
                {children}
                <WuttClient />
            </body>
        </html>
    );
}

The dynamic(..., { ssr: false }) import prevents the widget from being rendered on the server — useful because the link is client-side-only and avoids hydration warnings.

3. Pages Router usage

If you're on the Pages Router, drop the import into pages/_app.tsx:

import type { AppProps } from "next/app";
import WuttWhatsApp from "../components/WuttWhatsApp";

export default function MyApp({ Component, pageProps }: AppProps) {
    return (
        <>
            <Component {...pageProps} />
            <WuttWhatsApp
                phone="966500000000"
                message="Hi, I'd like to know more about Wutt."
            />
        </>
    );
}

4. Dynamic phone number from props

For a multi-tenant store or a multi-branch site, pass the phone down through props, a context, or a server-fetched value. Here's a CMS-driven example using a server component that passes the phone to a client component:

// app/[branch]/page.tsx  (Server Component)
import { notFound } from "next/navigation";
import WuttClient from "../components/WuttClient";

async function getBranch(slug: string) {
    const res = await fetch(`https://cms.example.com/branches/${slug}`, { cache: "no-store" });
    if (!res.ok) return null;
    return res.json();
}

export default async function BranchPage({ params }: { params: { branch: string } }) {
    const branch = await getBranch(params.branch);
    if (!branch) notFound();

    return (
        <main>
            <h1>{branch.name}</h1>
            <p>{branch.address}</p>
            <WuttClient phone={branch.whatsapp} message={`Hi, I'm at the ${branch.name} branch.`} />
        </main>
    );
}

If you don't have a CMS yet, use a small environment lookup:

// app/components/WuttClient.tsx
"use client";
import WuttWhatsApp from "./WuttWhatsApp";
export default function WuttClient() {
    return <WuttWhatsApp phone={process.env.NEXT_PUBLIC_WUTT_PHONE || "966500000000"} />;
}

Server actions (Next.js 14+)

To log a click into your own analytics before opening WhatsApp, hook into a server action:

// app/actions.ts
"use server";
export async function logWuttClick(phone: string) {
    // e.g. await fetch("https://api.wutt.net/v1/clicks", { method: "POST", body: JSON.stringify({ phone }) });
    return { ok: true };
}
// components/WuttWhatsApp.tsx (excerpt)
import { logWuttClick } from "@/app/actions";
const onClick = async () => { await logWuttClick(digits); };
// pass to <a onClick={onClick}>

Add Wutt to your site — start $1 trial

Drop the widget in, then keep every reply, click, lead, and order inside Wutt Inbox, Wutt Book, Wutt Reviews, Wutt Loyalty, and the CRM follow-up workspace.

Start $1 trial