# CrawlConsole — Lovable (TanStack Start + Cloudflare Workers) Install Guide

This guide installs server-side crawler tracking into a Lovable project. Lovable
apps run on **Cloudflare Workers** via **TanStack Start**, so the integration
lives in the Worker `fetch` handler (`src/server.ts`) — not in client code, and
not in an edge function.

---

## 1. Add the two secrets in Lovable Cloud

Open **Cloud → Secrets** and add:

| Name                          | Value                         |
| ----------------------------- | ----------------------------- |
| `CRAWLCONSOLE_PROJECT_KEY`    | your CrawlConsole project key |
| `CRAWLCONSOLE_TRACKER_KEY`    | your CrawlConsole tracker key |

Rules:
- **Do NOT prefix with `VITE_`**. These must be runtime secrets readable from
  the Worker, not build-time values bundled into the client.
- Names must match exactly — the tracker reads `env.CRAWLCONSOLE_PROJECT_KEY`
  and `env.CRAWLCONSOLE_TRACKER_KEY`.

---

## 2. Create `src/lib/crawlconsole-tracking.ts`

```ts
// src/lib/crawlconsole-tracking.ts
export type CrawlConsoleEnv = {
  CRAWLCONSOLE_PROJECT_KEY?: string;
  CRAWLCONSOLE_TRACKER_KEY?: string;
};

const CRAWLCONSOLE_ENDPOINT = "https://analytics.crawlconsole.com/v1/track";

function config(env: unknown) {
  const e = (env ?? {}) as CrawlConsoleEnv;
  const proc = (typeof process !== "undefined" ? process.env : {}) as CrawlConsoleEnv;
  return {
    projectKey: e.CRAWLCONSOLE_PROJECT_KEY || proc.CRAWLCONSOLE_PROJECT_KEY || "",
    trackerKey: e.CRAWLCONSOLE_TRACKER_KEY || proc.CRAWLCONSOLE_TRACKER_KEY || "",
  };
}

function clientIp(request: Request): string {
  return (
    request.headers.get("cf-connecting-ip") ||
    request.headers.get("x-real-ip") ||
    (request.headers.get("x-forwarded-for") || "").split(",")[0].trim() ||
    ""
  );
}

function skip(url: URL): boolean {
  const p = url.pathname;
  return (
    p.startsWith("/assets/") ||
    p.startsWith("/_build/") ||
    p === "/favicon.ico" ||
    p === "/robots.txt" ||
    p.endsWith(".css") ||
    p.endsWith(".js") ||
    p.endsWith(".map") ||
    p.endsWith(".png") ||
    p.endsWith(".jpg") ||
    p.endsWith(".jpeg") ||
    p.endsWith(".svg") ||
    p.endsWith(".webp") ||
    p.endsWith(".ico")
  );
}

export function trackCrawlerRequest(
  request: Request,
  response: Response,
  env: unknown,
): Promise<void> {
  const cfg = config(env);
  if (!cfg.projectKey || !cfg.trackerKey) {
    console.warn(
      "CrawlConsole server tracking is missing CRAWLCONSOLE_PROJECT_KEY or CRAWLCONSOLE_TRACKER_KEY.",
    );
    return Promise.resolve();
  }

  let url: URL;
  try {
    url = new URL(request.url);
  } catch {
    return Promise.resolve();
  }
  if (skip(url)) return Promise.resolve();

  const path = `${url.pathname}${url.search}`;

  return fetch(CRAWLCONSOLE_ENDPOINT, {
    method: "POST",
    headers: {
      authorization: `Bearer ${cfg.trackerKey}`,
      "content-type": "application/json",
    },
    body: JSON.stringify({
      project_key: cfg.projectKey,
      user_agent: request.headers.get("user-agent") || "",
      path,
      full_path: `${url.origin}${path}`,
      status_code: response.status,
      client_ip: clientIp(request),
      client_ip_raw: request.headers.get("x-forwarded-for") || "",
    }),
  })
    .then(() => undefined)
    .catch(() => undefined);
}
```
