@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 CHANGED
@@ -1,39 +1,71 @@
1
1
  # @smolcap/ai-tracker-nextjs
2
2
 
3
- Reports AI crawler visits to AI Tracker from a Next.js proxy. It never builds, awaits or
4
- changes a response: your pages are served exactly as before, and every tracking error is swallowed.
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` on Next.js 16+, `middleware.ts` on Next.js 13–15, next to `app/` or `pages/`:
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: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
24
+ matcher: [
25
+ "/robots.txt",
26
+ "/sitemap.xml",
27
+ "/((?!api|_next/static|_next/image|favicon.ico).*)",
28
+ ],
22
29
  };
23
30
  ```
24
31
 
25
- Already have a proxy? Pass it as the second argument. It receives the same arguments and its
26
- return value (or thrown error) reaches Next.js untouched:
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
- export default withAiTracker({ siteKey: process.env.AI_TRACKER_KEY, endpoint }, (request) => {
30
- // your existing logic
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; it never throws.
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. Tracking is off while it is empty. */
3
- siteKey: string | undefined;
4
- /** Your AI Tracker dashboard origin, e.g. https://tracker.example */
5
- endpoint: string;
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
- export declare function withAiTracker(config: AiTrackerConfig): (request: Request, event: FetchEvent) => void;
11
- export declare function withAiTracker<R extends Request, E extends FetchEvent, T>(config: AiTrackerConfig, middleware: (request: R, event: E) => T): (request: R, event: E) => T;
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
- function track(request, event, ingest, siteKey) {
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) => response.body?.cancel()).catch(() => { }));
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(config, middleware) {
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 url = new URL("/api/ingest", config.endpoint);
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 || !config.siteKey)
35
- console.warn("[ai-tracker] Tracking is off: set siteKey and an https endpoint.");
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 && config.siteKey)
39
- track(request, event, ingest, config.siteKey);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smolcap/ai-tracker-nextjs",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "AI Tracker for Next.js: reports AI crawler visits from proxy.ts or middleware.ts without touching the response",
5
5
  "license": "MIT",
6
6
  "type": "module",