diff --git a/site/src/components/tools/ShareBar.tsx b/site/src/components/tools/ShareBar.tsx index 1a15132..837e186 100644 --- a/site/src/components/tools/ShareBar.tsx +++ b/site/src/components/tools/ShareBar.tsx @@ -85,7 +85,7 @@ const btn = export function ShareBar({ url, embedUrl, title, slug }: Props) { const t = useTranslations("tool"); - const { copyResult } = useToolEvents(slug); + const { copyResult, share } = useToolEvents(slug); const [linkCopied, setLinkCopied] = useState(false); const [resultCopied, setResultCopied] = useState(false); const [hasResultLink, setHasResultLink] = useState(false); @@ -140,11 +140,15 @@ export function ShareBar({ url, embedUrl, title, slug }: Props) { if (typeof navigator !== "undefined" && navigator.share) { try { await navigator.share({ title, url }); + // 成功時だけ送る。キャンセルは reject されるのでここには来ない。 + share("native"); return; } catch { /* user cancelled — fall through */ } } + // フォールバックは copy() 側が copy_result("link") を送るため share は送らない + // (同一操作を2イベントで数えると共有率が二重計上になる)。 copy(url, setLinkCopied, "link"); } @@ -179,6 +183,8 @@ export function ShareBar({ url, embedUrl, title, slug }: Props) { className={btn} aria-label={`${t("share")}: ${name}`} title={name} + // target="_blank" なので現ページは破棄されず、遷移前の同期送出で取りこぼさない。 + onClick={() => share(name)} > diff --git a/site/src/lib/analytics/events.ts b/site/src/lib/analytics/events.ts index 2e10e06..afee2cc 100644 --- a/site/src/lib/analytics/events.ts +++ b/site/src/lib/analytics/events.ts @@ -94,3 +94,15 @@ export function trackCopyResult(p: ToolEventParams): void { export function trackPresetClick(p: ToolEventParams): void { emit("preset_click", toParams(p)); } + +/** + * 共有導線を踏んだとき(ネイティブ共有・SNSリンク)。 + * + * copy_result はクリップボードのみを見ており、ShareBar の主要導線 + * (navigator.share / X / Facebook / LINE / WhatsApp / Reddit)は + * 一切計測されていなかった。label に共有先を入れて内訳を取る。 + * 外部遷移は計測を待たずに起きるので、送出は必ず遷移前に行う。 + */ +export function trackShare(p: ToolEventParams): void { + emit("share", toParams(p)); +} diff --git a/site/src/lib/analytics/useToolEvents.ts b/site/src/lib/analytics/useToolEvents.ts index 27dab39..d851ee0 100644 --- a/site/src/lib/analytics/useToolEvents.ts +++ b/site/src/lib/analytics/useToolEvents.ts @@ -1,7 +1,7 @@ "use client"; import { useCallback, useRef } from "react"; -import { trackCalculate, trackCopyResult, trackPresetClick } from "./events"; +import { trackCalculate, trackCopyResult, trackPresetClick, trackShare } from "./events"; /** * ツールコンポーネントから GA4 イベントを送るためのフック。 @@ -33,5 +33,12 @@ export function useToolEvents(slug: string) { [slug], ); - return { calculate, copyResult, presetClick }; + // share は calculate と違い間引かない。共有先(X / LINE / native ...)ごとの + // 内訳を取るのが目的で、同一着地で複数先に共有するのは正当な行動のため。 + const share = useCallback( + (label?: string) => trackShare({ tool: slug, ...(label ? { label } : {}) }), + [slug], + ); + + return { calculate, copyResult, presetClick, share }; }