Skip to content

Analytics

Analytics is one config block in src/config/site.ts. Set the provider, paste your ID, save. The theme injects the right snippet into <head> at build time — nothing to install, nothing to import.

src/config/site.ts
analytics: {
provider: null as "google" | "plausible" | "umami" | null,
// googleId: "G-XXXXXXXXXX",
// plausibleDomain: "example.com",
// umamiWebsiteId: "your-id",
// umamiScriptUrl: "https://analytics.example.com/script.js",
}

Set provider to one of the four values, then fill in the matching ID below.

  1. Create a GA4 property at analytics.google.com.
  2. Copy the measurement ID — it starts with G-.
  3. Set:
analytics: {
provider: "google",
googleId: "G-ABC123DEF4",
}

What gets injected:

<script async src="https://www.googletagmanager.com/gtag/js?id=G-ABC123DEF4"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){ dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-ABC123DEF4');
</script>

Privacy-friendly, cookie-free, lightweight (~1 KB).

  1. Add your site at plausible.io (or your self-hosted instance).
  2. Set:
analytics: {
provider: "plausible",
plausibleDomain: "yourdomain.com",
}

What gets injected:

<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script>

Open-source, privacy-friendly, self-hostable.

  1. Get your website ID and script URL from your Umami dashboard.
  2. Set:
analytics: {
provider: "umami",
umamiWebsiteId: "abc-123-def-456",
umamiScriptUrl: "https://cloud.umami.is/script.js",
// or your self-hosted: "https://analytics.yourdomain.com/script.js"
}

umamiScriptUrl is optional — if you skip it, it defaults to https://cloud.umami.is/script.js.

What gets injected:

<script defer src="https://cloud.umami.is/script.js" data-website-id="abc-123-def-456"></script>
analytics: { provider: null }

Nothing gets injected. Useful during development.

Open src/layouts/Layout.astro and search for getAnalyticsScript. It’s a function that reads siteConfig.analytics, returns the right HTML string, and the layout injects it inside <head> via <Fragment set:html={...}>. No client-side branching, no waterfall — pure SSG.

src/layouts/Layout.astro (excerpt)
function getAnalyticsScript() {
const { provider } = siteConfig.analytics;
if (!provider) return "";
const a = siteConfig.analytics;
if (provider === "google" && a.googleId) { /* … */ }
if (provider === "plausible" && a.plausibleDomain) { /* … */ }
if (provider === "umami" && a.umamiWebsiteId) { /* … */ }
return "";
}

If you want Fathom, Mixpanel, PostHog, or something else, edit the same function in Layout.astro:

if (provider === "fathom" && a.fathomSiteId) {
return `<script src="https://cdn.usefathom.com/script.js" data-site="${a.fathomSiteId}" defer></script>`;
}

…and add "fathom" to the provider union type:

analytics: {
provider: "fathom" as "google" | "plausible" | "umami" | "fathom" | null,
fathomSiteId: "ABCDEFGH",
}

For one-off pixels, verification tags, or anything else that goes in <head>, use siteConfig.headScripts — a raw HTML string injected after analytics:

headScripts: `
<script defer src="https://customer.io/pixel.js" data-site="123"></script>
<meta name="google-site-verification" content="abc123" />
`,
  • Contact form — wire your contact form to Formspree, FormSubmit, or Netlify.
  • Newsletter — wire the newsletter form to Mailchimp, ConvertKit, or Buttondown.
  • Deploy → Custom domain — point a real domain at the build so analytics tracks the right host.