lytics

Guide

Add analytics to an Astro website

Put the lytics snippet in a shared Astro layout and every page reports its pageviews, including navigations made by Astro's <ClientRouter />. This guide goes from an empty account to the first pageview on your dashboard.

Checked against Astro 7.3 and the current lytics tracker: the script tags as Astro renders them, and the events a real browser sends on ordinary links and with <ClientRouter />. Other versions and other routers have not been checked.

What lytics measures on an Astro site

With only the snippet installed:

Signed-in users, logins and your own events appear once your pages make the calls in step 5. Clicks, form submits and visits to a path can also become named events through tags, without changing the site.

What it stores, and what it does not

The tracking script sets no cookies and writes nothing to localStorage, sessionStorageor any other storage on the visitor's device. The id you pass to identify is held in the open page's memory and is gone after a full reload. The collector does not read cookies. Like any script, the browser caches the tracking script itself, for up to five minutes.

To tell visitors apart without storing anything on their device, lytics hashes the day, the app, the IP address and the browser's user agent. The salt for each day is deleted after the next, so a day's hashes cannot be traced back once it is over. That has consequences you should know before you read the numbers:

The page's address is sent with each event and only its path is kept. Raw events are kept for 400 days. Whether your site needs a consent notice depends on where you operate and on everything else the site does; this guide does not decide that for you. How lytics counts visitors without cookiesshows the hash step by step.

1. Create an account and add your site

  1. Create an account (or sign in) and go to Your apps.
  2. Under Add an app, give it a name and list the production domains the site is served from, separated by commas — for example example.com, www.example.com. *.example.com allows every subdomain and example.com itself.
  3. Open the new app. Its Install section shows the snippet with your site key in it.

Events from a page on a host that is not on the list are dropped, so another site that copies your snippet does not add to your numbers. With the list empty, every host is accepted — fine while trying it out, not for staying that way. To test on astro dev before deploying, add localhost under the app's Settings, and remove it again afterwards.

2. Add the snippet to a shared layout

The Install section gives you two tags. The first queues calls made before the script has loaded; the second loads the script without blocking the page:

<script>window.lytics=window.lytics||function(){(lytics.q=lytics.q||[]).push(arguments)};</script>
<script defer src="https://lytics.lp-data.cloud/api/t/lyt_REPLACE_WITH_YOUR_SITE_KEY.js"></script>

In Astro, add is:inline to both tags and nothing else — step 3 explains why. A small component keeps them in one place:

src/components/Analytics.astro
---
// src/components/Analytics.astro
// The lytics snippet, unchanged except for is:inline on both tags.
---
<script is:inline>window.lytics=window.lytics||function(){(lytics.q=lytics.q||[]).push(arguments)};</script>
<script is:inline defer src="https://lytics.lp-data.cloud/api/t/lyt_REPLACE_WITH_YOUR_SITE_KEY.js"></script>
Replace lyt_REPLACE_WITH_YOUR_SITE_KEY with your app's site key, or paste the src from its Install section. The placeholder is not a key: until you replace it the script answers 404 and nothing is counted.

Then render it in the <head> of the layout every page uses. If the site has several layouts, add it to each one, so that no page is left out and none loads it twice.

src/layouts/BaseLayout.astro
---
// src/layouts/BaseLayout.astro — or whichever layout every page uses
import Analytics from '../components/Analytics.astro'
const { title } = Astro.props
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>{title}</title>
    <Analytics />
  </head>
  <body>
    <slot />
  </body>
</html>

To count visits rather than every pageview, add data-track="visits" to the second tag, as described in the docs.

3. Why is:inline

Astro processes a plain <script> tag: it bundles it and turns it into a module. In Astro7.3, the snippet pasted as it is comes out like this:

<script type="module">window.lytics=window.lytics||function(){(lytics.q=lytics.q||[]).push(arguments)};</script>
<script defer src="https://lytics.lp-data.cloud/api/t/lyt_REPLACE_WITH_YOUR_SITE_KEY.js"></script>

A module runs only after the whole page has been parsed. So does the deferred script, so pageviews still arrive — but the queue does not exist while the page is loading, and any lytics(…) call made then, such as an identify in the page body, fails with lytics is not defined and is lost. astro check also reports the bare lytics name as an error in processed scripts.

is:inline tells Astro to leave a tag exactly as written. The first tag then runs straight away and the queue is there before anything can call it. Astro already leaves the second tag alone, because it has an attribute other than src; is:inline just makes that explicit.

Without a client router every link loads a new page, the script runs again, and each load sends one pageview. There is nothing more to set up.

With <ClientRouter />, links swap the page in place instead of loading a new one. The tracker sends a pageview whenever history.pushState or replaceState changes the path, and on back and forward, which is how the router moves between pages. Keep the component in the same <head>:

---
import { ClientRouter } from 'astro:transitions'
import Analytics from '../components/Analytics.astro'
---
<head>
  <Analytics />
  <ClientRouter />
</head>
What the visitor doesWhat lytics records
Opens or reloads a pageOne pageview.
Follows a link the router handlesOne pageview for the new path.
Goes back or forwardOne pageview for the path they land on.
Follows a #fragment link on the same pageNothing: the path did not change.
Changes only the query stringNothing: the query string is never recorded.

The router does not run the snippet again after a navigation, and the script would ignore a second copy if it did. Do not add data-astro-rerun or transition:persist to the tags; neither is needed.

5. Signed-in users and your own events (optional)

If the site has accounts, tell lytics who is signed in. Send your own opaque id, never an email address. With define:vars the value is written into an inline script:

---
// Wherever your layout knows who is signed in.
const user = Astro.locals.user   // however your app stores it
---
{user && <script is:inline define:vars={{ id: user.id }}>lytics('identify', id)</script>}

Under <ClientRouter />, Astro runs an inline script with the same content only once per page load. That is enough here: the tracker remembers the id until the next full load. If signing out does not load a new page, call lytics('logout') when it happens.

From Astro's processed (type-checked) scripts, call window.lytics and declare it once:

// src/lytics.d.ts — lets type-checked scripts call window.lytics
interface Window {
  lytics: (command: string, ...args: unknown[]) => void
}
<button id="export">Export</button>
<script>
  document.getElementById('export')?.addEventListener('click', () => {
    window.lytics('event', 'report_exported', { format: 'csv' })
  })
</script>

login, logout, event names and properties are all in the docs.

6. See your first pageview

  1. Deploy the site to one of the domains you listed (or run astro dev with localhostallowed).
  2. In lytics, open the app's page and keep it open. Until something arrives it says No events yet, and the Live section shows each event as it comes in.
  3. In another tab — an ordinary browser window, not a headless one — open your site. A pageview with the page's path appears in the live feed.
  4. Click a link to another page. One more pageview appears, whether the router handled the link or the browser loaded a new page.
  5. Reload the app's page in lytics: the visit, the visitor and the pageviews are in the numbers for today (UTC).

To look under the hood, open the browser's developer tools on your site. The Network tab shows the script …/api/t/lyt_….js answering 200 and a request to /api/collect for each event.

Troubleshooting

SymptomWhat to check
The script request returns 404 and the body says “unknown site key”.The placeholder lyt_REPLACE_WITH_YOUR_SITE_KEY is still in the page, or the key belongs to an app that was deleted. Copy the snippet again from the app's Install section.
The browser console says lytics is not defined.The first tag is missing is:inline, so Astro made it a module and it runs only after the page has been parsed — later than any lytics(…) call made while the page loads.
Requests to /api/collect answer 204, but nothing reaches the dashboard.The collector answers 204 to everything, kept or not. The usual reason for dropping an event is the hostname: it must match one of the app's domains exactly, or a *. entry. www.example.com and example.com are different hosts, and so are preview deployments and localhost.
Nothing arrives from an automated browser or an uptime check.Events whose user agent looks like a crawler, a headless browser, Lighthouse or a monitoring tool are dropped. Check with an ordinary browser window.
The script or the beacons are blocked before they leave the browser.If your site sends a Content-Security-Policy, it has to allow the script from https://lytics.lp-data.cloud (script-src), requests to it (connect-src) and the inline first tag. Browser extensions that block analytics requests can stop them as well.

Next