@smolcap/ai-tracker-nextjs 1.0.0 → 1.1.0
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/README.md +43 -11
- package/dist/index.d.ts +11 -6
- package/dist/index.js +19 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,39 +1,71 @@
|
|
|
1
1
|
# @smolcap/ai-tracker-nextjs
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Track AI bot visits on your Next.js site with AI Tracker. The tracker runs in a fire-and-forget
|
|
4
|
+
pattern inside Next.js proxy code: requests are not awaited and responses are never touched, so
|
|
5
|
+
there is zero impact on page load times.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
5
8
|
|
|
6
9
|
```sh
|
|
7
10
|
npm install @smolcap/ai-tracker-nextjs
|
|
8
11
|
```
|
|
9
12
|
|
|
10
|
-
`proxy.ts`
|
|
13
|
+
Create or update `proxy.ts` in the root of your project (inside `src/` if you use it;
|
|
14
|
+
`middleware.ts` before Next.js 16):
|
|
11
15
|
|
|
12
16
|
```ts
|
|
13
17
|
import { withAiTracker } from "@smolcap/ai-tracker-nextjs";
|
|
14
18
|
|
|
15
19
|
export default withAiTracker({
|
|
16
|
-
siteKey: process.env.AI_TRACKER_KEY
|
|
17
|
-
endpoint: "https://your-ai-tracker-dashboard.example",
|
|
20
|
+
siteKey: process.env.AI_TRACKER_KEY!,
|
|
18
21
|
});
|
|
19
22
|
|
|
20
23
|
export const config = {
|
|
21
|
-
matcher: [
|
|
24
|
+
matcher: [
|
|
25
|
+
"/robots.txt",
|
|
26
|
+
"/sitemap.xml",
|
|
27
|
+
"/((?!api|_next/static|_next/image|favicon.ico).*)",
|
|
28
|
+
],
|
|
22
29
|
};
|
|
23
30
|
```
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
Set `AI_TRACKER_KEY` to your site key, then deploy as usual.
|
|
33
|
+
|
|
34
|
+
## Custom proxy logic
|
|
35
|
+
|
|
36
|
+
Pass your existing proxy as the second argument. It is called with the same arguments and whatever
|
|
37
|
+
it returns or throws reaches Next.js unchanged.
|
|
27
38
|
|
|
28
39
|
```ts
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
import { type AiTrackerConfig, withAiTracker } from "@smolcap/ai-tracker-nextjs";
|
|
41
|
+
import { NextResponse, type NextRequest } from "next/server";
|
|
42
|
+
|
|
43
|
+
const aiTrackerConfig: AiTrackerConfig = {
|
|
44
|
+
siteKey: process.env.AI_TRACKER_KEY!,
|
|
45
|
+
debug: process.env.NODE_ENV === "development",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default withAiTracker(aiTrackerConfig, (request: NextRequest) => {
|
|
49
|
+
const response = NextResponse.next();
|
|
50
|
+
response.headers.set("x-custom-header", "value");
|
|
51
|
+
return response;
|
|
31
52
|
});
|
|
32
53
|
```
|
|
33
54
|
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
| Option | Type | |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `siteKey` | `string` | Your site key. Defaults to the `AI_TRACKER_KEY` environment variable. While empty, tracking is off. |
|
|
60
|
+
| `endpoint` | `string` | Custom AI Tracker origin. Most sites can omit this. |
|
|
61
|
+
| `debug` | `boolean` | Logs why a visit was not recorded. |
|
|
62
|
+
|
|
63
|
+
## Behaviour
|
|
64
|
+
|
|
34
65
|
- Only `GET`/`HEAD` page requests whose user agent looks like a crawler are reported, in the
|
|
35
66
|
background through `event.waitUntil`, with a 1.5 s timeout. Everything else returns immediately.
|
|
36
|
-
- A missing key or invalid endpoint turns tracking off with one warning;
|
|
67
|
+
- A missing key or invalid endpoint turns tracking off with one warning; nothing ever throws.
|
|
68
|
+
- A site key only accepts visits for its own hostname; previews and other hostnames are rejected.
|
|
37
69
|
- The proxy runs before rendering, so status codes are not recorded. Visitor IPs are read only from
|
|
38
70
|
headers set by Vercel (`x-vercel-forwarded-for`) or Cloudflare (`cf-connecting-ip`).
|
|
39
71
|
- Remove it by deleting the file (or unwrapping your proxy) and redeploying.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
export declare const DEFAULT_ENDPOINT = "https://ai-tracker.smol.capital";
|
|
1
2
|
export type AiTrackerConfig = {
|
|
2
|
-
/** The site key for this exact hostname.
|
|
3
|
-
siteKey
|
|
4
|
-
/**
|
|
5
|
-
endpoint
|
|
3
|
+
/** The site key for this exact hostname. Defaults to the AI_TRACKER_KEY environment variable; tracking is off while it is empty. */
|
|
4
|
+
siteKey?: string;
|
|
5
|
+
/** Custom AI Tracker origin. Most sites can omit this. */
|
|
6
|
+
endpoint?: string;
|
|
7
|
+
/** Logs why a visit was not recorded. Use only while developing or troubleshooting. */
|
|
8
|
+
debug?: boolean;
|
|
6
9
|
};
|
|
7
10
|
type FetchEvent = {
|
|
8
11
|
waitUntil(promise: Promise<unknown>): void;
|
|
9
12
|
};
|
|
10
|
-
|
|
11
|
-
export declare function withAiTracker
|
|
13
|
+
type Middleware<R extends Request, E extends FetchEvent, T> = (request: R, event: E) => T;
|
|
14
|
+
export declare function withAiTracker(config?: AiTrackerConfig): Middleware<Request, FetchEvent, void>;
|
|
15
|
+
export declare function withAiTracker<R extends Request, E extends FetchEvent, T>(middleware: Middleware<R, E, T>): Middleware<R, E, T>;
|
|
16
|
+
export declare function withAiTracker<R extends Request, E extends FetchEvent, T>(config: AiTrackerConfig, middleware: Middleware<R, E, T>): Middleware<R, E, T>;
|
|
12
17
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// Classification and verification live on the tracker; this only forwards candidate visits.
|
|
4
4
|
const BOT_HINTS = /bot|crawler|spider|crawl|gpt|claude|perplexity|bing|applebot|bytespider|ccbot|amazon|amzn|meta-|duckassist|mistral|google|copilot|grok|kimi|qwen|cohere|msnbot/i;
|
|
5
5
|
const ASSET_PATH = /\.(?:js|mjs|cjs|css|map|png|jpe?g|gif|webp|avif|svg|ico|bmp|tiff?|woff2?|ttf|otf|eot|mp[34]|webm|ogg|wav|m4[av]|mov|wasm)$|^\/(?:api|_next|_vercel|cdn-cgi)(?:\/|$)/i;
|
|
6
|
-
|
|
6
|
+
export const DEFAULT_ENDPOINT = "https://ai-tracker.smol.capital";
|
|
7
|
+
function track(request, event, ingest, siteKey, debug) {
|
|
7
8
|
const userAgent = request.headers.get("user-agent") ?? "";
|
|
8
9
|
if ((request.method !== "GET" && request.method !== "HEAD") || !BOT_HINTS.test(userAgent))
|
|
9
10
|
return;
|
|
@@ -20,23 +21,32 @@ function track(request, event, ingest, siteKey) {
|
|
|
20
21
|
// Manual redirects never forward the site key.
|
|
21
22
|
redirect: "manual",
|
|
22
23
|
signal: AbortSignal.timeout(1500),
|
|
23
|
-
}).then((response) =>
|
|
24
|
+
}).then((response) => {
|
|
25
|
+
if (debug && !response.ok)
|
|
26
|
+
console.warn(`[ai-tracker] Visit rejected: HTTP ${response.status}`);
|
|
27
|
+
return response.body?.cancel();
|
|
28
|
+
}).catch((error) => { if (debug)
|
|
29
|
+
console.warn("[ai-tracker] Visit not sent:", error instanceof Error ? error.message : error); }));
|
|
24
30
|
}
|
|
25
|
-
export function withAiTracker(
|
|
31
|
+
export function withAiTracker(first, second) {
|
|
32
|
+
const middleware = typeof first === "function" ? first : second;
|
|
26
33
|
// A throw while this module loads would take the whole site down, so bad configuration only disables tracking.
|
|
27
|
-
let ingest;
|
|
34
|
+
let ingest, siteKey, debug = false;
|
|
28
35
|
try {
|
|
29
|
-
const
|
|
36
|
+
const config = (typeof first === "function" ? undefined : first) ?? {};
|
|
37
|
+
siteKey = config.siteKey || process.env.AI_TRACKER_KEY;
|
|
38
|
+
debug = config.debug === true;
|
|
39
|
+
const url = new URL("/api/ingest", config.endpoint || DEFAULT_ENDPOINT);
|
|
30
40
|
if (url.protocol === "https:" && !url.username && !url.password)
|
|
31
41
|
ingest = url;
|
|
32
42
|
}
|
|
33
43
|
catch { }
|
|
34
|
-
if (!ingest || !
|
|
35
|
-
console.warn("[ai-tracker] Tracking is off: set
|
|
44
|
+
if (!ingest || !siteKey)
|
|
45
|
+
console.warn("[ai-tracker] Tracking is off: set AI_TRACKER_KEY (and use an https endpoint).");
|
|
36
46
|
return (request, event) => {
|
|
37
47
|
try {
|
|
38
|
-
if (ingest &&
|
|
39
|
-
track(request, event, ingest,
|
|
48
|
+
if (ingest && siteKey)
|
|
49
|
+
track(request, event, ingest, siteKey, debug);
|
|
40
50
|
}
|
|
41
51
|
catch {
|
|
42
52
|
// Tracking must never fail a request.
|
package/package.json
CHANGED