Web Analytics Without Third-Party Scripts (2026)

David Viejo
David Viejo · Updated · Published

To add web analytics without third-party scripts, serve the tracking code from your own domain and send events to your own server. Build it in about 100 lines, self-host Umami or Plausible Community Edition, or use Temps, whose React SDK posts to /api/_temps on your own site.

Can I Add Web Analytics Without a Third-Party Script?

Yes. You can add web analytics to any website without loading a script from an external domain or sending data to one. The approach is called first-party analytics: the tracking code is served from your own domain (or bundled into your app), events are sent to your own server, and nothing touches google-analytics.com, plausible.io or any other external host.

This matters for three reasons. Many ad blockers and privacy browsers block requests to known analytics hosts, so third-party analytics undercount, especially on technical audiences; serving from your own domain avoids most of those blocks. Third-party analytics send your visitors' data to another company, which raises international-transfer questions in the EU. And third-party trackers can follow visitors across sites, which first-party analytics on your own domain do not.

This guide covers how first-party analytics work under the hood, how to build a minimal system yourself, and how Temps includes analytics as a built-in platform feature, with no separate service or subscription.

TL;DR: Third-party analytics scripts are often blocked, add JavaScript you don't control, and send visitor data to an external company. First-party analytics, served from your own domain and stored on your own server, address all three. You can build it yourself in about 100 lines, self-host a standalone tool such as Umami or Plausible Community Edition, or use Temps, which builds first-party analytics into the platform that deploys your app: the React SDK sends tracking to your own domain via basePath="/api/_temps", so there is no separate analytics service to run.


Why Are Third-Party Analytics Scripts a Problem?

Third-party analytics break in four distinct ways: performance, data control, accuracy and legal exposure.

Performance Costs You Don't See

A third-party analytics tag is JavaScript you don't control, loaded from another origin. The download is only part of the cost: the script has to be parsed and executed, sets cookies and fires its own network requests. On slower mobile CPUs that work competes with your own code for the main thread, and Core Web Vitals measure the result.

Your Users' Data Leaves Your Control

When a visitor lands on your site, GA4 sends their IP address, browser details, screen size, referrer and page URL to Google's servers. Even with Google's IP handling, the request itself still reaches Google before anything is truncated. Data protection authorities in Austria, France and Italy ruled against particular Google Analytics deployments in 2022 over exactly these transfers to the US.

Ad Blockers Hide Part of Your Audience

Common filter lists block Google Analytics and other well-known analytics hosts by name. Every visitor whose blocker drops the request is invisible to your dashboard, and on developer-focused sites that can be a large share of traffic. You end up making decisions on data with a systematic gap.

GDPR Turned Analytics into a Compliance Task

In the EU, setting non-essential cookies, including analytics cookies, generally requires prior consent under the ePrivacy rules, and sending data to a third-party processor adds GDPR obligations on top. That means a consent banner, and visitors who decline drop out of your data, making already-incomplete numbers less complete.


Temps vs. Plausible vs. Umami: First-Party Analytics Compared

FeatureTempsPlausibleUmami
Tracking on your own domainYes. The React SDK posts to /api/_temps on your site, which the Temps proxy handlesCloud needs a proxy setup; self-hosted runs on a domain you chooseSelf-hosted runs on a domain you choose; same-origin needs a proxy
Third-party cookiesNone. Two first-party cookies set by your own domainNone (cookie-free)None (cookie-free)
Consent in the EUGenerally needed for its two first-party cookiesCommonly run without a bannerCommonly run without a banner
Self-hostableYes. Free, MIT / Apache 2.0Yes (Community Edition; ClickHouse + PostgreSQL)Yes (Node.js + PostgreSQL)
Session replay built-inYes (opt-in, enableSessionRecording={true})NoYes (v3.1+)
Error tracking built-inYesNoNo
Uptime monitoring built-inYesNoNo
Included in a deployment platformYes: analytics, hosting, databases and email in one installNo, standalone serviceNo, standalone service
PricingFree to self-host; Temps Cloud coming soon from $29/moCloud from $9/mo (10k pageviews); Community Edition freeFree to self-host; Umami Cloud paid

Prices verified on on each vendor's pricing page.

The key structural difference: Plausible and Umami are standalone analytics tools you deploy alongside your app. Temps is a deployment platform that includes analytics natively, so analytics already run on the same server as your application. For a head-to-head, see Temps vs Plausible.


What Temps Analytics Does Differently

Tracking goes to your own domain. The React SDK's basePath="/api/_temps" prop sends every tracking request to your own site, where the Temps proxy handles it, and the SDK is bundled into your app rather than loaded from an analytics host. Blockers that match known analytics hostnames see only requests to your own origin, although a rule that targets the /api/_temps path could still block them.

First-party cookies, data on your own server. Temps identifies visitors with two first-party, HttpOnly cookies set by the Temps proxy on your domain: _temps_visitor_id (365 days) and _temps_sid (30 minutes). Visitor data stays in your own database and is not sent to a third-party analytics company, so there is no international transfer to an analytics vendor. The cookies are not cookie-free analytics, though: the data ownership and privacy docs note that they generally need consent in the EU.

Included in self-hosted Temps at no extra cost. Temps is free to self-host under MIT / Apache 2.0, and that covers the deployment platform, web analytics, session replay, error tracking, uptime monitoring, managed databases and transactional email in one install. There is no separate analytics tier, no per-seat fee and no bandwidth bill. Temps Cloud, a hosted control plane for your own servers, is coming soon from $29/month.


How Does First-Party Analytics Work?

First-party analytics flip the third-party model. Instead of loading a script from google-analytics.com, you serve it from yoursite.com/t.js or bundle it into your app. Instead of beaconing data to an external server, requests go to yoursite.com/api/collect.

Because everything stays on the same origin, there are no cross-origin requests and no third-party cookies, and blockers that work by hostname have nothing to match. Filter lists that target common script names or paths can still catch some setups, so a neutral path helps.

Do You Still Need Cookies?

It depends on the implementation. Some analytics tools (Plausible, Umami) use no cookies at all. They group page views into sessions with server-side heuristics instead, such as a hash of the visitor's IP address and User-Agent with a salt that rotates daily.

Temps uses two first-party cookies (_temps_visitor_id, _temps_sid) set by your own domain to recognise returning visitors and stitch sessions. They are first-party cookies, not cross-site tracking cookies, and the data never leaves your server, but in the EU they generally still need consent. If you need a fully cookie-free setup, the DIY approach below, Plausible or Umami do that.


How to Build a Minimal First-Party Analytics System

You can build a working first-party analytics system in under 100 lines of code. The client-side snippet is about 25 lines of JavaScript. The server endpoint depends on your stack, but the logic is the same in any language.

The JavaScript Snippet

// analytics.js — serve this from YOUR domain, not an external CDN
(function() {
  const endpoint = '/api/collect';

  function track(event, data) {
    const payload = JSON.stringify({
      event: event,
      url: window.location.pathname,
      referrer: document.referrer || null,
      screen: window.innerWidth + 'x' + window.innerHeight,
      ...data
    });
    if (navigator.sendBeacon) {
      navigator.sendBeacon(endpoint, payload);
    } else {
      fetch(endpoint, {
        method: 'POST',
        body: payload,
        keepalive: true
      });
    }
  }

  track('pageview', {
    utm_source: new URLSearchParams(location.search).get('utm_source'),
    utm_medium: new URLSearchParams(location.search).get('utm_medium'),
    utm_campaign: new URLSearchParams(location.search).get('utm_campaign')
  });
})();

That's 25 lines, well under a kilobyte minified, and it doesn't block rendering because sendBeacon runs in the background.

The Server Endpoint

// collect.js — your server-side endpoint
const crypto = require('crypto');
const { pool } = require('./db'); // your PostgreSQL pool

function getDailySalt() {
  return new Date().toISOString().split('T')[0];
}

function hashSession(ip, userAgent) {
  return crypto
    .createHash('sha256')
    .update(ip + userAgent + getDailySalt())
    .digest('hex')
    .substring(0, 16);
}

app.post('/api/collect', async (req, res) => {
  const { event, url, referrer, screen, utm_source,
          utm_medium, utm_campaign } = req.body;
  const ip = req.headers['x-forwarded-for'] || req.ip;
  const userAgent = req.headers['user-agent'];
  const sessionHash = hashSession(ip, userAgent);

  await pool.query(`
    INSERT INTO page_views
      (timestamp, event, url_path, referrer, country,
       device_type, browser, session_hash,
       utm_source, utm_medium, utm_campaign)
    VALUES
      (NOW(), $1, $2, $3, geoip_country($4),
       parse_device($5), parse_browser($5), $6,
       $7, $8, $9)
  `, [event, url, referrer, ip, userAgent,
      sessionHash, utm_source, utm_medium, utm_campaign]);

  res.status(202).end();
});

The Database Schema

CREATE TABLE page_views (
  id          BIGSERIAL PRIMARY KEY,
  timestamp   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  event       VARCHAR(50) NOT NULL DEFAULT 'pageview',
  url_path    TEXT NOT NULL,
  referrer    TEXT,
  country     VARCHAR(2),
  device_type VARCHAR(20),
  browser     VARCHAR(50),
  os          VARCHAR(50),
  screen      VARCHAR(20),
  session_hash VARCHAR(16),
  utm_source  VARCHAR(100),
  utm_medium  VARCHAR(100),
  utm_campaign VARCHAR(100)
);

CREATE INDEX idx_pv_timestamp ON page_views (timestamp DESC);
CREATE INDEX idx_pv_url ON page_views (url_path, timestamp DESC);
CREATE INDEX idx_pv_session ON page_views (session_hash, timestamp);

If you're using TimescaleDB, convert this to a hypertable for automatic time-based partitioning:

SELECT create_hypertable('page_views', 'timestamp');

Session Heuristics Without Cookies

The hashSession function above combines IP + User-Agent + a daily salt into a hash. Two requests from the same browser on the same day produce the same hash, and that is your session identifier.

Users behind the same NAT with the same browser will be merged into one session. For aggregate analytics, that is accurate enough. You trade individual-level precision for no cookies, which in many EU countries means you can skip the consent banner for this measurement. You are still processing IP addresses, so GDPR's other rules (a lawful basis, retention limits, transparency) still apply.


Which Self-Hosted Tools Serve Analytics First-Party?

Any analytics tool you host on your own domain is first-party. The common choices are:

  • Plausible Community Edition: the privacy-first standard, cookie-free, self-hosted with Docker Compose (ClickHouse and PostgreSQL).
  • Umami: free and MIT-licensed, cookie-free, runs on Node.js with PostgreSQL.
  • PostHog: product analytics with replay and feature flags; self-hosting is a Docker Compose "hobby" install that PostHog recommends only for low volumes (4 vCPU / 16 GB RAM recommended, per its self-host docs, October 2026). See Temps vs PostHog.
  • Temps: analytics built into the platform that deploys your app, alongside session replay, error tracking and uptime monitoring.

Each of the first three is a separate service you run, patch and back up next to your app; Temps is the one where analytics come with the hosting. For the full comparison, including GoatCounter, Ackee, Rybbit and Matomo, see our list of Plausible alternatives.


How Temps Handles Analytics Without Third-Party Scripts

Temps includes first-party web analytics as a built-in platform feature: no separate service, no extra database and no additional deployment. Every app you deploy through Temps can send analytics to the same install, stored in the same TimescaleDB database that powers the rest of the platform. The Temps analytics page gives the overview, and the analytics docs cover setup.

Analytics and hosting need the same things: a database, a server and an HTTP endpoint. Combining them removes a whole category of operational overhead.

Setup for React / Next.js

// app/layout.tsx
import { TempsAnalyticsProvider } from '@temps-sdk/react-analytics';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <TempsAnalyticsProvider
          basePath="/api/_temps"
          enableSessionRecording={true}
        >
          {children}
        </TempsAnalyticsProvider>
      </body>
    </html>
  );
}

basePath="/api/_temps" sends all tracking requests to your own site, and the SDK is bundled with your app, so the browser never contacts an external analytics domain. enableSessionRecording={true} turns on session replay (it is off by default), which records the DOM and user input, with inputs masked, and stores recordings on your own infrastructure. Leave it out if you only want analytics.

Tracking Custom Events

Custom events are what funnels are built from; building conversion funnels without Google Analytics shows the event schema and step-conversion queries.

import { useTrackEvent } from '@temps-sdk/react-analytics';

export function SignupButton() {
  const trackEvent = useTrackEvent();

  return (
    <button
      onClick={async () => {
        await trackEvent('signup_clicked', { plan: 'free' });
      }}
    >
      Get started
    </button>
  );
}

Accessing Analytics in Components

import { useEffect } from 'react';
import { useTempsAnalytics } from '@temps-sdk/react-analytics';

export function TrackOnMount({ pageName }: { pageName: string }) {
  const { trackEvent } = useTempsAnalytics();

  useEffect(() => {
    trackEvent('page_viewed', { name: pageName });
  }, [pageName]);

  return null;
}

For Static / HTML Sites

The analytics docs show the browser script loaded from unpkg:

<script
  defer
  src="https://unpkg.com/@temps-sdk/analytics-browser@0.0.8/dist/temps.min.js"
></script>

Events go to your site's relative /api/_temps endpoints, which the Temps proxy handles for apps it serves. To avoid loading even the script from a third-party CDN, download temps.min.js and serve it from your own domain. Analytics then appear in the same Temps dashboard where you manage deployments.

What You Get Out of the Box

The Temps analytics dashboard includes:

  • Real-time visitors: live visitor count and currently active pages
  • Page views and top pages: time-series charts and a breakdown by path
  • Referrers and campaigns: traffic sources with UTM parameter parsing
  • Device and browser stats: browser, OS and device type
  • Geographic data: country, region and city from MaxMind GeoIP
  • Bot filtering: known crawlers are flagged automatically, and AI crawler traffic gets its own view (see how to track AI crawler traffic)
  • Session replay, error tracking and uptime monitoring in the same dashboard

Why It's Privacy-Friendly

Temps analytics use first-party cookies set by your own domain, not third-party tracking cookies set by an external analytics company. Visitor data, including the IP address the proxy records for geolocation and bot detection, stays on your server and is never sent to a third-party analytics vendor. There is no built-in IP anonymisation setting, so cover IP storage in your privacy notice.

With no transfer to an analytics vendor, the EU-US transfer problem that made Google Analytics contested in Austria, France and Italy doesn't arise. Because you own the infrastructure, you set retention, handle deletion requests and minimise data on your own terms. Consent for the two cookies is still your responsibility; see the data ownership and privacy docs.


Frequently Asked Questions

How do I add web analytics without third-party scripts?

Serve the tracking code from your own domain and send events to your own server. You can write a small snippet and endpoint yourself (about 100 lines, shown above), self-host a tool such as Umami or Plausible Community Edition on your domain, or use Temps, where <TempsAnalyticsProvider> in a React app sends events to /api/_temps on your own site and stores them in your Temps database. For plain HTML sites, serve the Temps browser script from your own domain rather than the CDN.

It depends on whether they store anything on the visitor's device. Cookie-free first-party analytics (the DIY heuristic approach, Plausible, Umami) are commonly run without a consent banner, because the ePrivacy consent rule targets storing or reading information on the device; confirm this for your jurisdiction. Temps sets two first-party cookies (_temps_visitor_id and _temps_sid), and its privacy docs say these generally need consent in the EU, even though the data never leaves your server.

How accurate are first-party analytics compared to Google Analytics?

Usually more complete, because they aren't blocked as often. In Plausible's own comparison on a tech-audience site (first published 2021), Google Analytics missed about 58% of visitors that Plausible counted. Your gap depends on your audience. The trade-off for cookie-free tools is less precise session tracking, since heuristic sessions are not as exact as cookie-based ones.

Can I use first-party analytics with a static site?

Yes. First-party analytics work with any website: static HTML, Next.js, Astro, Hugo or anything else that serves web pages. The tracking snippet is plain JavaScript that sends a beacon to your own endpoint. For static sites deployed on Temps, add the browser script (ideally served from your own domain) and the proxy handles the /api/_temps endpoints.

What's the performance impact of first-party analytics?

Small. A minimal first-party snippet is well under a kilobyte, uses the non-blocking Beacon API and makes no cross-origin requests, so it adds far less work than a full third-party tag manager setup. Measure with your own Core Web Vitals before and after.

How does Temps compare to Plausible for self-hosting?

Both are self-hostable and free. The difference is what you're deploying. Self-hosted Plausible is a separate Docker Compose stack (the Plausible application, ClickHouse and PostgreSQL) to maintain, update and back up. Temps includes analytics in the deployment platform you're already running, along with session replay and error tracking. Plausible is cookie-free; Temps sets two first-party cookies.


Stop Giving Away Your Users' Data

Third-party analytics made sense when there were no alternatives. That's no longer the case. You can track what you need (page views, referrers, campaigns, devices) without sending visitor data to an external company.

Whether you build it yourself with the code above, self-host an open-source tool like Plausible or Umami, or use a platform that includes analytics natively, the goal is the same: stop accepting the performance cost, the missing data and the legal exposure that come with third-party scripts.

If you want analytics built into your deployment platform, Temps handles it out of the box. Deploy your app, add one component, and first-party analytics run on your own server, included free with self-hosted Temps.

curl -fsSL https://temps.sh/deploy.sh | bash
#analytics#privacy#gdpr#first-party#web-analytics#self-hosted#web analytics without third-party scripts