Version 2 is coming up
For the complete documentation index, see llms.txt. This page is also available as Markdown.

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