Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ coverage/

# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.*
!.env.example

# Python
__pycache__/
Expand Down
10 changes: 10 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Umami website ID, from the tracking code panel in the Umami dashboard.
#
# Copy this file to .env.production.local and paste the ID in. That file is
# gitignored, and `vite build` reads it while `vite dev` does not — so the ID
# never enters the repository, and running the dev server does not put localhost
# traffic in the stats.
#
# Leave it empty, or skip the file entirely, and analytics are a no-op: no script
# tag, no requests. That is what a fork gets.
VITE_UMAMI_WEBSITE_ID=
5 changes: 5 additions & 0 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import App from './App.tsx';
import { Provider } from './context/provider.tsx';
import './index.css';
import './styles/scrollbars.css';
import { initAnalytics } from './utils/analytics.ts';

// Straight to the document rather than through a component, since it has nothing
// to say about the tree.
initAnalytics();

createRoot(document.getElementById('root')!).render(
<StrictMode>
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/utils/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Umami, injected at runtime and only when a website ID was built in.
*
* The ID comes from `.env.production.local`, which is gitignored, so a clone or
* a fork gets no script tag, no request and no ID — analytics are simply absent
* rather than broken or pointed at someone else's account. See `.env.example`.
*
* `.env.production.local` is read by `vite build` and never by `vite dev`, so
* working locally cannot put localhost traffic in the stats. The `PROD` guard
* says the same thing twice, in case the value is ever put in a file the dev
* server does read.
*
* Umami is cookieless, which is why there is no consent banner in the app.
* Anything added here that sets a cookie changes that.
*/
const websiteId = import.meta.env.VITE_UMAMI_WEBSITE_ID;

export function initAnalytics() {
if (!websiteId || !import.meta.env.PROD) return;

const script = document.createElement('script');
script.src = 'https://cloud.umami.is/script.js';
// The tracker reads its configuration off `document.currentScript`, so the
// attributes have to be set before it is appended, and it has to stay a
// classic script — as a module `currentScript` is null and it gives up
// without a word. For the same reason there is no `data-host-url`: absent,
// it defaults to Umami Cloud's own endpoint.
script.setAttribute('data-website-id', websiteId);
document.head.appendChild(script);
}
6 changes: 6 additions & 0 deletions frontend/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="vite/client" />

interface ImportMetaEnv {
/** Umami website ID. Optional: absent from any build but the live one. */
readonly VITE_UMAMI_WEBSITE_ID?: string;
}
Loading