@tdhftw/emdash-plugin-umami 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3 (2026-10-28)
4
+
5
+ - New tracker setting for sites that use Astro's `<ClientRouter />`: every page change, including
6
+ back and forward, is counted once. Without it, Umami misses back and forward navigation, and a
7
+ tag with `data-astro-rerun` counts each link click twice. Needs Umami 3.2 or later.
8
+ - README: the setting above, and how to switch over from a tracker tag in the theme.
9
+
3
10
  ## 0.1.2 (2026-10-28)
4
11
 
5
12
  First release on npm. No code changes since 0.1.1.
package/README.md CHANGED
@@ -74,6 +74,14 @@ every public page:
74
74
  templates do.
75
75
  - **Avoid double counting.** Remove any Umami script the theme already includes, or every visit
76
76
  is counted twice.
77
+ - **Astro's `<ClientRouter />`.** If your layout uses it, also turn on **The site uses Astro's
78
+ `<ClientRouter />`**. Pages then change without a full load: Umami on its own misses back and
79
+ forward, and a tracker tag with `data-astro-rerun` counts every link click twice. With the
80
+ setting on, the plugin turns off Umami's automatic pageviews (`data-auto-pageview="false"`, so
81
+ Umami 3.2 or later) and sends one pageview per page change from a small inline script. Click
82
+ events (`data-umami-event`) keep working.
83
+ - **Switching over from a theme tag.** Remove the theme's tag and turn the plugin's tracker on
84
+ as close together as you can: a gap loses visits, an overlap counts them twice.
77
85
  - **Custom script URL.** Set one if you serve a renamed tracker or proxy it.
78
86
  - **Cached pages.** If your site caches pages, cached copies change over only when their cache
79
87
  expires.
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { z } from "astro/zod";
4
4
  /** Plugin id. One URL segment: routes mount at /_emdash/api/plugins/umami/<route>. */
5
5
  export declare const PLUGIN_ID = "umami";
6
6
  /** Keep in sync with package.json; test/plugin.test.ts enforces it. */
7
- export declare const PLUGIN_VERSION = "0.1.2";
7
+ export declare const PLUGIN_VERSION = "0.1.3";
8
8
  type Hosting = "self-hosted" | "cloud";
9
9
  type AuthMode = "apiKey" | "password";
10
10
  type CloudRegion = "default" | "us" | "eu";
@@ -79,6 +79,7 @@ interface UmamiSettingsValues {
79
79
  username: string | null;
80
80
  websiteId: string | null;
81
81
  injectScript: boolean;
82
+ clientRouter: boolean;
82
83
  scriptUrl: string | null;
83
84
  }
84
85
  //#endregion
@@ -125,6 +126,7 @@ interface SaveSettingsInput {
125
126
  password?: string | null;
126
127
  websiteId?: string | null;
127
128
  injectScript?: boolean;
129
+ clientRouter?: boolean;
128
130
  scriptUrl?: string | null;
129
131
  }
130
132
  //#endregion
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ async function cached(kv, key, maxAgeMs, load, now = Date.now()) {
34
34
  /** Plugin id. One URL segment: routes mount at /_emdash/api/plugins/umami/<route>. */
35
35
  const PLUGIN_ID = "umami";
36
36
  /** Keep in sync with package.json; test/plugin.test.ts enforces it. */
37
- const PLUGIN_VERSION = "0.1.2";
37
+ const PLUGIN_VERSION = "0.1.3";
38
38
  const PACKAGE_NAME = "@tdhftw/emdash-plugin-umami";
39
39
  //#endregion
40
40
  //#region src/settings.ts
@@ -113,6 +113,12 @@ const settingsSchema = {
113
113
  label: "Add the Umami tracker to public pages",
114
114
  default: false
115
115
  },
116
+ clientRouter: {
117
+ type: "boolean",
118
+ label: "The site uses Astro's <ClientRouter /> (view transitions)",
119
+ description: "Counts every page change, including back and forward, once.",
120
+ default: false
121
+ },
116
122
  scriptUrl: {
117
123
  type: "url",
118
124
  label: "Custom tracker script URL",
@@ -160,6 +166,7 @@ async function readSettings(settings) {
160
166
  password: typeof raw.password === "string" && raw.password.length > 0 ? raw.password : null,
161
167
  websiteId: str(raw.websiteId),
162
168
  injectScript: raw.injectScript === true,
169
+ clientRouter: raw.clientRouter === true,
163
170
  scriptUrl: normalizeScriptUrl(str(raw.scriptUrl))
164
171
  };
165
172
  }
@@ -610,6 +617,7 @@ const saveSettingsInput = z.object({
610
617
  password: secretInput,
611
618
  websiteId: optionalText,
612
619
  injectScript: z.boolean().optional(),
620
+ clientRouter: z.boolean().optional(),
613
621
  scriptUrl: optionalUrl
614
622
  });
615
623
  async function saveSettings(ctx) {
@@ -629,6 +637,7 @@ async function saveSettings(ctx) {
629
637
  put("password", input.password);
630
638
  put("websiteId", input.websiteId);
631
639
  put("injectScript", input.injectScript);
640
+ put("clientRouter", input.clientRouter);
632
641
  put("scriptUrl", input.scriptUrl);
633
642
  await Promise.all(writes);
634
643
  if (input.baseUrl !== void 0 || input.username !== void 0 || input.password !== void 0 || input.hosting !== void 0) await ctx.kv.delete(TOKEN_KEY);
@@ -690,7 +699,16 @@ function defaultScriptUrl(hosting, baseUrl, scriptUrl) {
690
699
  //#endregion
691
700
  //#region src/tracker.ts
692
701
  /**
693
- * The Umami tracker as a page fragment, or null when tracking is off or incomplete.
702
+ * Pageviews for sites using Astro's <ClientRouter />. Umami's own tracking hooks pushState only,
703
+ * so back and forward are missed, and a re-run tracker tag counts forward navigations twice.
704
+ * With `data-auto-pageview="false"` this sends one pageview per `astro:page-load` instead,
705
+ * passing the URL explicitly because Umami's stored URL is stale after a back navigation. On a
706
+ * page without <ClientRouter /> (no `astro-view-transitions-enabled` meta) it tracks once on
707
+ * DOMContentLoaded, so a wrong setting never stops tracking.
708
+ */
709
+ const CLIENT_ROUTER_SCRIPT = `(()=>{let prev;const send=()=>{const u=window.umami;if(!u)return false;const url=location.href;u.track((p)=>({...p,url,title:document.title,...(prev?{referrer:prev}:{})}));prev=location.pathname+location.search;return true};const track=()=>{let n=0;const t=()=>{send()||++n>100||setTimeout(t,50)};t()};document.addEventListener("astro:page-load",track);document.addEventListener("DOMContentLoaded",()=>{document.querySelector('meta[name="astro-view-transitions-enabled"]')||track()})})();`;
710
+ /**
711
+ * The Umami tracker as page fragments, or null when tracking is off or incomplete.
694
712
  * EmDash HTML-escapes attribute values and drops on* attributes when rendering.
695
713
  */
696
714
  async function trackerFragment(event, settings) {
@@ -699,7 +717,7 @@ async function trackerFragment(event, settings) {
699
717
  const s = await readSettings(settings);
700
718
  const src = defaultScriptUrl(s.hosting, s.baseUrl, s.scriptUrl);
701
719
  if (!src || !s.websiteId || !normalizeBaseUrl(src)) return null;
702
- return {
720
+ const tracker = {
703
721
  kind: "external-script",
704
722
  placement: "head",
705
723
  src,
@@ -707,6 +725,19 @@ async function trackerFragment(event, settings) {
707
725
  attributes: { "data-website-id": s.websiteId },
708
726
  key: "umami-tracker"
709
727
  };
728
+ if (!s.clientRouter) return tracker;
729
+ return [{
730
+ ...tracker,
731
+ attributes: {
732
+ ...tracker.attributes,
733
+ "data-auto-pageview": "false"
734
+ }
735
+ }, {
736
+ kind: "inline-script",
737
+ placement: "head",
738
+ code: CLIENT_ROUTER_SCRIPT,
739
+ key: "umami-client-router"
740
+ }];
710
741
  }
711
742
  //#endregion
712
743
  //#region src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdhftw/emdash-plugin-umami",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Umami analytics inside the EmDash CMS admin: an Analytics page, a dashboard widget and optional tracker injection, for self-hosted Umami",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,6 +17,7 @@ interface Draft {
17
17
  password: string;
18
18
  websiteId: string;
19
19
  injectScript: boolean;
20
+ clientRouter: boolean;
20
21
  scriptUrl: string;
21
22
  }
22
23
 
@@ -32,6 +33,7 @@ function draftFrom(s: Settings): Draft {
32
33
  password: "",
33
34
  websiteId: v.websiteId ?? "",
34
35
  injectScript: v.injectScript,
36
+ clientRouter: v.clientRouter,
35
37
  scriptUrl: v.scriptUrl ?? "",
36
38
  };
37
39
  }
@@ -43,6 +45,7 @@ function toInput(d: Draft): SaveSettingsInput {
43
45
  authMode: d.hosting === "cloud" ? "apiKey" : d.authMode,
44
46
  websiteId: d.websiteId || null,
45
47
  injectScript: d.injectScript,
48
+ clientRouter: d.clientRouter,
46
49
  scriptUrl: d.scriptUrl || null,
47
50
  };
48
51
  if (d.hosting === "self-hosted") input.baseUrl = d.baseUrl || null;
@@ -268,11 +271,22 @@ export function SettingsView({ onSaved }: { onSaved?: () => void }) {
268
271
  If your site caches pages, cached copies change over when their cache expires, not the moment you save.
269
272
  </p>
270
273
  {draft.injectScript ? (
271
- <Banner
272
- variant="alert"
273
- title="Remove any tracker already in your theme"
274
- description="If the theme also includes the Umami script, every visit is counted twice. The theme must render EmDashHead."
275
- />
274
+ <>
275
+ <Banner
276
+ variant="alert"
277
+ title="Remove any tracker already in your theme"
278
+ description="If the theme also includes the Umami script, every visit is counted twice. The theme must render EmDashHead."
279
+ />
280
+ <Switch
281
+ label="The site uses Astro's <ClientRouter /> (view transitions)"
282
+ checked={draft.clientRouter}
283
+ onCheckedChange={(checked) => set("clientRouter", checked)}
284
+ />
285
+ <p className="text-sm text-kumo-subtle">
286
+ Turn this on if your layout includes <code>&lt;ClientRouter /&gt;</code>. Pages then change without a full
287
+ load, and this counts each one once, including back and forward. Needs Umami 3.2 or later.
288
+ </p>
289
+ </>
276
290
  ) : null}
277
291
  <p className="text-sm text-kumo-subtle">
278
292
  Tracker script: <code>{tracker ?? "set the Umami URL first"}</code>{" "}
@@ -39,5 +39,6 @@ export interface SaveSettingsInput {
39
39
  password?: string | null;
40
40
  websiteId?: string | null;
41
41
  injectScript?: boolean;
42
+ clientRouter?: boolean;
42
43
  scriptUrl?: string | null;
43
44
  }
@@ -2,7 +2,7 @@
2
2
  export const PLUGIN_ID = "umami";
3
3
 
4
4
  /** Keep in sync with package.json; test/plugin.test.ts enforces it. */
5
- export const PLUGIN_VERSION = "0.1.2";
5
+ export const PLUGIN_VERSION = "0.1.3";
6
6
 
7
7
  export const PACKAGE_NAME = "@tdhftw/emdash-plugin-umami";
8
8
 
@@ -8,6 +8,7 @@ export interface UmamiSettingsValues {
8
8
  username: string | null;
9
9
  websiteId: string | null;
10
10
  injectScript: boolean;
11
+ clientRouter: boolean;
11
12
  scriptUrl: string | null;
12
13
  }
13
14