Analytics
Analytics measures page views and declared events with the shared Umami instance, on by default, and provides the privacy dialog where visitors turn statistics off.
01Preview
02Usage
Mount one Analytics in the root layout, inside Page so its dialog follows the page language. Footer then shows the privacy control after its notice.
<script>
import { page } from "$app/state";
import { Analytics, Footer, Header, Main, Page, SkipLink } from "@dg/ui";
/** @type {{ children?: import('svelte').Snippet }} */
let { children } = $props();
</script>
<Page lang="en">
<SkipLink />
<Header app="Example" path={page.url.pathname} />
<Main>{@render children?.()}</Main>
<Footer />
<Analytics {page} />
</Page>
To measure actions, declare their names once and describe them in the dialog. track sends a declared name and drops any other.
<script>
import { page } from "$app/state";
import { Analytics, Button, track } from "@dg/ui";
</script>
<Analytics {page} events={["archive_opened"]}>
<p>This site also counts openings of the archive.</p>
</Analytics>
<Button label="Open the archive" onclick={() => track("archive_opened")} />
03Notes
- The host serves
/analytics-config.jsonas{ "endpoint": "https://…/api/send", "website": "…" }. Workspace Caddyfiles serve the distributed·gallery statistics website, shared by every app, by default. Without this file, an HTTPS endpoint without credentials and a website ID, statistics stay off andFootershows no control. Development servers serve no configuration. - Statistics are on by default. The dialog turns them off and on again; the choice is saved in
localStorageunder Umami's ownumami.disabledkey, with no expiry. Do Not Track and Global Privacy Control always turn them off, and the dialog hides its control. Unreadable storage counts as turned off; when a choice cannot be saved, statistics stay off for the visit and the dialog reports it. - Each change of path sends one page view. The path is sent without query or fragment and starts with the domain, such as
/example.com/about, so reports keep the apps of the shared website apart without a filter; pages with status 404 are recorded under/404, such as/example.com/404. The domain is also Umami's distinct ID, which it hashes into the visitor identifier, so no identifier spans two sites. The first measurement carries the origin of an external referrer; no title, screen size or language is sent. Events carry the current path and their name, never custom properties. - Event names are fixed identifiers. Never derive them from content. Measurement failures are silent and never affect the page.
Analyticsrenders only its dialog and forwards no attributes. Mount it once per application.
04Parameters
| Name | Type | Default | Description |
|---|---|---|---|
page | { url: { pathname: string }, status: number } | Required | SvelteKit's page state from $app/state, which supplies the path and status of each view. |
events | string[] | [] | Event names that track may send. |
children | Snippet | undefined | App-specific measurement details, shown in the dialog's formatted text after the general description, usually a paragraph. |
05Accessibility
The privacy control in Footer is a native button named by the dialog title, styled as a link in the notice typography, with the shared control target height. The dialog follows Dialog: it is named by its title, traps focus and returns it to the control on close. Statistics status uses .feedback; a choice that cannot be saved is announced as an alert. The built-in text follows the Page language.