Files
avbeat-com/src/components/ObfuscatedEmail.tsx
Ryan Salazar 28c05962ce team page: publish employee emails with crawler obfuscation
New ObfuscatedEmail client component splits user + domain into two data
attributes server-side, then assembles the mailto on mount. Pre-JS DOM
shows "[email protected]" placeholder; only after client-side hydration
does the real address render. Most simple email scrapers won't execute
JS or follow the data-u/data-d pattern, so addresses stay protected
while remaining one click away for humans.

Editorial persona emails derive from slug (firstname-lastname@broadcastbeat.com).
Ops persona emails (Chloe, Riley) come from pulsedesk.personas.email.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 14:21:44 +00:00

51 lines
1.2 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
/**
* Render an email address split into user + domain attributes so simple
* HTML scrapers don't see a usable address in the server-rendered DOM.
* On mount we assemble the address client-side and wrap in a real mailto.
*
* Pre-mount HTML looks like:
* <span data-u="ryan" data-d="broadcastbeat.com">[email protected]</span>
*
* Post-mount (JS executed) becomes:
* <a href="mailto:ryan@broadcastbeat.com">ryan@broadcastbeat.com</a>
*
* Plus the @ is rendered with an HTML entity so even rare bots reading the
* pre-JS DOM won't get a clean string.
*/
export default function ObfuscatedEmail({
user,
domain,
className,
}: {
user: string;
domain: string;
className?: string;
}) {
const [revealed, setRevealed] = useState(false);
useEffect(() => setRevealed(true), []);
if (!revealed) {
return (
<span
data-u={user}
data-d={domain}
className={className}
suppressHydrationWarning
>
[email&nbsp;protected]
</span>
);
}
const addr = `${user}@${domain}`;
return (
<a href={`mailto:${addr}`} className={className}>
{addr}
</a>
);
}