Version 2 is coming up

Netlify (Free Plan)

Netlify Access Log Forwarding with edge functions

Send access logs from any Netlify site to an HTTP endpoint using Edge Functions, Netlify Blobs, and a scheduled function. Logs are batched and sent every minute.

Works on all Netlify plans including the free tier.

Outbound requests to your log endpoint are authenticated via an x-api-key header. The key is stored as a Netlify secret env var (LOG_API_KEY) and never shipped to the browser — client logs go through a same-origin proxy function that injects the key server-side.

Architecture

Request → Edge Function → Netlify Blobs (buffer)

         Scheduled Function (every 1 min) → batch POST (x-api-key) → your endpoint

         Cleanup: deletes sent entries from Blobs

Client SPA navigation → useAccessLog hook → /api/access-log proxy fn (adds x-api-key) → your endpoint

Setup

1. Create the Edge Function

Create netlify/edge-functions/access-log.ts:

import { getStore } from "@netlify/blobs";
import type { Config, Context } from "@netlify/edge-functions";

export default async (request: Request, context: Context) => {
  const startTime = Date.now();
  const response = await context.next();
  const duration = Date.now() - startTime;
  const url = new URL(request.url);

  const logEntry = {
    timestamp: new Date().toISOString(),
    method: request.method,
    url: request.url,
    path: url.pathname,
    query: url.search,
    status_code: response.status,
    duration_ms: duration,
    client_ip: context.ip,
    user_agent: request.headers.get("user-agent"),
    referer: request.headers.get("referer"),
    content_type: response.headers.get("content-type"),
    country: context.geo.country?.code,
    city: context.geo.city,
    region: context.server.region,
    request_id: context.requestId,
    site: context.site.name,
    deploy_id: context.deploy.id,
  };

  const key = `${Date.now()}-${crypto.randomUUID()}`;

  context.waitUntil(
    getStore("access-logs")
      .setJSON(key, logEntry)
      .catch((err) => console.error("Failed to buffer access log:", err))
  );

  return response;
};

export const config: Config = {
  path: "/*",
};

To exclude static assets, add excludedPath to the config:

2. Create the Scheduled Flush Function

Create netlify/functions/flush-access-logs.mts:

3. Create the Client Log Proxy Function

The browser cannot ship the API key (it would leak in the bundle) and navigator.sendBeacon cannot set custom headers. Solution: a same-origin proxy function that adds x-api-key server-side.

Create netlify/functions/log-proxy.mts:

4. Update netlify.toml

Add the edge function declaration to your netlify.toml:

5. Install Dependencies

6. Configure the API Key Secret

Set LOG_API_KEY as a Netlify secret env var. Get your API Key from your Limy dashboard

CLI:

7. Deploy

Push to your connected git repo. Netlify deploys automatically. Make sure LOG_API_KEY is set in the target deploy context (production / deploy previews / branch).

Configuration

Setting
Where
Default

Log endpoint URL

flush-access-logs.mts + log-proxy.mts

— (required)

API key

Netlify env var LOG_API_KEY (mark as secret)

— (required)

Flush interval (server)

flush-access-logs.mtsconfig.schedule

Every minute (* * * * *)

Flush interval (client)

useAccessLog.tsFLUSH_INTERVAL_MS

60,000 ms

Client batch size

useAccessLog.tsBATCH_SIZE

100 events

Excluded paths

access-log.tsconfig.excludedPath

None (all paths logged)

Custom User-Agent

flush-access-logs.mts headers

LimyAnalyticsNetlifyAccessLogs

Payload Format

Your endpoint receives a JSON array of log entries:

Client-side navigation entries include "type": "client_navigation" and do not contain server-side fields like client_ip, country, or region.

Free Tier Limits

Resource
Limit

Edge Function invocations

3M / month

Edge Function CPU time

50 ms / request

Scheduled Function execution

30 seconds

Netlify Blobs storage

1 GB

Last updated