@m13v/seo-components 0.34.0 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -16,6 +16,20 @@ export interface DmShortLinkRedirectConfig {
|
|
|
16
16
|
posthogHostEnv?: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Bot User-Agent regex. Bots (Twitter card prefetch, LinkedIn unfurl,
|
|
21
|
+
* Slack/Discord/Telegram/WhatsApp link-preview crawlers, generic Google/Bing
|
|
22
|
+
* bots) hammer /r/<code> within seconds of mint to fetch link previews. They
|
|
23
|
+
* inflated the legacy `clicks` counter by ~20x. When this regex matches, the
|
|
24
|
+
* resolver:
|
|
25
|
+
* 1. Skips the counter increment (passes `?bot=1` so the server side
|
|
26
|
+
* records the hit in post_link_clicks / dm_link_clicks with is_bot=true
|
|
27
|
+
* but does NOT bump post_links.clicks / dm_links.clicks).
|
|
28
|
+
* 2. Skips the PostHog `*_short_link_clicked` event (no fake conversions).
|
|
29
|
+
* 3. Still 302s to target_url so previews render.
|
|
30
|
+
*/
|
|
31
|
+
const BOT_UA_RE = /bot|crawler|spider|Twitterbot|LinkedInBot|Slackbot|facebookexternalhit|Discordbot|TelegramBot|WhatsApp|Applebot|Googlebot|Bingbot|YandexBot|DuckDuckBot|redditbot|Pinterest|Embedly|Snapchat/i;
|
|
32
|
+
|
|
19
33
|
/**
|
|
20
34
|
* Factory for `GET /r/[code]`.
|
|
21
35
|
*
|
|
@@ -34,12 +48,17 @@ export interface DmShortLinkRedirectConfig {
|
|
|
34
48
|
*
|
|
35
49
|
* Behavior:
|
|
36
50
|
* 1. Read `code` from the route param. Reject non-alphanumeric / wrong-length.
|
|
37
|
-
* 2.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
51
|
+
* 2. Read User-Agent. If it matches BOT_UA_RE, pass `?bot=1` to the resolver
|
|
52
|
+
* so it logs the hit but does NOT bump the human-facing counter, and
|
|
53
|
+
* skip the PostHog event.
|
|
54
|
+
* 3. Hit `<resolverBase>/api/short-links/<code>?bot=<0|1>`. The resolver
|
|
55
|
+
* always appends a row to post_link_clicks / dm_link_clicks (with
|
|
56
|
+
* is_bot stamped accordingly). It only increments the legacy clicks
|
|
57
|
+
* counter and stamps first/last click timestamps when is_bot=false.
|
|
58
|
+
* 4. For post rail links: inject UTM params into the target URL.
|
|
59
|
+
* 5. Fire the appropriate PostHog event (dm_short_link_clicked or
|
|
60
|
+
* post_short_link_clicked) fire-and-forget, non-blocking; skipped for bots.
|
|
61
|
+
* 6. 302 to the resolved target_url. On miss/error, 302 to "/".
|
|
43
62
|
*/
|
|
44
63
|
export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConfig) {
|
|
45
64
|
const {
|
|
@@ -62,6 +81,14 @@ export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConf
|
|
|
62
81
|
return Response.redirect(homeUrl, 302);
|
|
63
82
|
}
|
|
64
83
|
|
|
84
|
+
// Bot detection lives at the edge so the server side can split humans vs
|
|
85
|
+
// bots in post_link_clicks / dm_link_clicks. Every UTF-8 string the user
|
|
86
|
+
// agent presents passes through this; matched UAs do NOT count as a real
|
|
87
|
+
// click and do NOT fire PostHog events.
|
|
88
|
+
const ua = req.headers.get("user-agent") || "";
|
|
89
|
+
const isBot = BOT_UA_RE.test(ua);
|
|
90
|
+
const referrer = req.headers.get("referer") || "";
|
|
91
|
+
|
|
65
92
|
let target: string | null = null;
|
|
66
93
|
let dmId: number | null = null;
|
|
67
94
|
let postId: number | null = null;
|
|
@@ -70,7 +97,19 @@ export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConf
|
|
|
70
97
|
let platform: string | null = null;
|
|
71
98
|
|
|
72
99
|
try {
|
|
73
|
-
const
|
|
100
|
+
const params = new URLSearchParams();
|
|
101
|
+
if (isBot) params.set("bot", "1");
|
|
102
|
+
// Forward UA + referrer so the server can persist them in
|
|
103
|
+
// *_link_clicks. We pass them through query params (not headers)
|
|
104
|
+
// because Next.js fetch normalizes some headers and the resolver
|
|
105
|
+
// is a separate origin. Truncate to keep the URL manageable.
|
|
106
|
+
if (ua) params.set("ua", ua.slice(0, 500));
|
|
107
|
+
if (referrer) params.set("ref", referrer.slice(0, 500));
|
|
108
|
+
const qs = params.toString();
|
|
109
|
+
const resolverUrl =
|
|
110
|
+
`${RESOLVER}/api/short-links/${encodeURIComponent(code)}` +
|
|
111
|
+
(qs ? `?${qs}` : "");
|
|
112
|
+
const resp = await fetch(resolverUrl, {
|
|
74
113
|
cache: "no-store",
|
|
75
114
|
signal: AbortSignal.timeout(4000),
|
|
76
115
|
});
|
|
@@ -118,8 +157,10 @@ export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConf
|
|
|
118
157
|
const posthogKey = process.env[posthogKeyEnv];
|
|
119
158
|
const posthogHost = (process.env[posthogHostEnv] || "https://us.i.posthog.com").replace(/\/+$/, "");
|
|
120
159
|
|
|
160
|
+
// Skip both PostHog events for bots: they do not represent intent, and
|
|
161
|
+
// counting them as `*_short_link_clicked` would taint funnel stats.
|
|
121
162
|
// DM rail event
|
|
122
|
-
if (target && posthogKey && dmId != null) {
|
|
163
|
+
if (!isBot && target && posthogKey && dmId != null) {
|
|
123
164
|
fetch(`${posthogHost}/i/v0/e/`, {
|
|
124
165
|
method: "POST",
|
|
125
166
|
headers: { "Content-Type": "application/json" },
|
|
@@ -136,7 +177,7 @@ export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConf
|
|
|
136
177
|
}
|
|
137
178
|
|
|
138
179
|
// Post rail event
|
|
139
|
-
if (target && posthogKey && (postId != null || replyId != null)) {
|
|
180
|
+
if (!isBot && target && posthogKey && (postId != null || replyId != null)) {
|
|
140
181
|
fetch(`${posthogHost}/i/v0/e/`, {
|
|
141
182
|
method: "POST",
|
|
142
183
|
headers: { "Content-Type": "application/json" },
|