> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lumenstat.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Framework recipes

> The pinned tracker tag for plain HTML, Next.js, React with Vite, Astro, SvelteKit and Nuxt, with preview deploys kept out of your numbers.

Every recipe below renders the same tag: the release URL, its integrity hash, `crossorigin="anonymous"` and your website id. Put it in the one place your framework renders on every page, and render it once. Each recipe also sets `data-domains`, so preview builds and local development send nothing.

Replace `YOUR-WEBSITE-ID` with the id from your project's **Setup** page, and `example.org,www.example.org` with the exact hostnames you serve in production.

## Keep previews and local development out

The tracker reads `data-domains` as a comma-separated list of hostnames. When it is set, the tracker sends nothing unless `location.hostname` is exactly one of them. Without it, two things go wrong:

* **Previews on a subdomain are counted.** The collect endpoint accepts events from your website's domain and every subdomain of it, so a preview at `preview.example.org` passes the origin check and adds to your production numbers.
* **Previews on another domain and `localhost` are flagged.** They fail the origin check as `origin_mismatch`. They are never counted, but they fill your **Flagged, not counted** card with your own traffic.

The match is exact, not by suffix. List `www.example.org` and `example.org` separately if both serve pages.

> [tab] HTML
>
> Paste the tag into the `<head>` of every page, or of the shared template your pages are built from.
>
> ```html index.html theme={null}
> <!doctype html>
> <html lang="en">
>   <head>
>     <meta charset="utf-8" />
>     <title>Example</title>
>     <script
>       defer
>       src="https://t.lumenstat.com/v/1.0.0/l.js"
>       integrity="sha384-cBzCCJcHCWSGbuy/f+zavGm/HjUWv77eni4DyLfq+FfKigpin1mul1ekLXILze/3"
>       crossorigin="anonymous"
>       data-website-id="YOUR-WEBSITE-ID"
>       data-domains="example.org,www.example.org"
>     ></script>
>   </head>
>   <body>
>     ...
>   </body>
> </html>
> ```

> [tab] Next.js
>
> For the App Router, render `next/script` from the root layout. `strategy="afterInteractive"` loads the tracker after hydration. `next/script` passes `integrity`, `crossOrigin` and the `data-*` props through to the `<script>` element.
>
> ```tsx components/lumenstat-script.tsx theme={null}
> import Script from 'next/script';
>
> // Release 1.0.0 never changes; the integrity hash makes browsers refuse any other bytes.
> const TRACKER_SRC = 'https://t.lumenstat.com/v/1.0.0/l.js';
> const TRACKER_INTEGRITY =
>   'sha384-cBzCCJcHCWSGbuy/f+zavGm/HjUWv77eni4DyLfq+FfKigpin1mul1ekLXILze/3';
> const WEBSITE_ID = 'YOUR-WEBSITE-ID';
> // Previews and other hosts load the script but send nothing.
> const TRACKED_HOSTS = 'example.org,www.example.org';
>
> export function LumenstatScript() {
>   return (
>     <Script
>       src={TRACKER_SRC}
>       integrity={TRACKER_INTEGRITY}
>       crossOrigin="anonymous"
>       data-website-id={WEBSITE_ID}
>       data-domains={TRACKED_HOSTS}
>       strategy="afterInteractive"
>     />
>   );
> }
> ```
>
> ```tsx app/layout.tsx theme={null}
> import type { ReactNode } from 'react';
> import { LumenstatScript } from '@/components/lumenstat-script';
>
> export default function RootLayout({ children }: { children: ReactNode }) {
>   return (
>     <html lang="en">
>       <body>
>         {children}
>         <LumenstatScript />
>       </body>
>     </html>
>   );
> }
> ```
>
> Write `crossOrigin` in camel case: it is the React prop that renders `crossorigin`. To leave the script out of non-production builds entirely, return `null` from `LumenstatScript` based on an environment variable your production deploys set. Keep `data-domains` as well: it also covers a production build that someone runs on another host.

> [tab] React (Vite)
>
> Vite serves `index.html` from the project root as the entry page. Add the tag to its `<head>`. Vite leaves a classic script with an absolute URL as written.
>
> ```html index.html theme={null}
> <!doctype html>
> <html lang="en">
>   <head>
>     <meta charset="UTF-8" />
>     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
>     <title>Example</title>
>     <script
>       defer
>       src="https://t.lumenstat.com/v/1.0.0/l.js"
>       integrity="sha384-cBzCCJcHCWSGbuy/f+zavGm/HjUWv77eni4DyLfq+FfKigpin1mul1ekLXILze/3"
>       crossorigin="anonymous"
>       data-website-id="YOUR-WEBSITE-ID"
>       data-domains="example.org,www.example.org"
>     ></script>
>   </head>
>   <body>
>     <div id="root"></div>
>     <script type="module" src="/src/main.tsx"></script>
>   </body>
> </html>
> ```
>
> Route changes made with React Router, or any router that calls `history.pushState`, are counted as page views. Do not also render the tag from a React component.

> [tab] Astro
>
> Add the tag to the layout every page uses. `is:inline` tells Astro to render the tag exactly as written instead of processing and bundling it.
>
> ```astro src/layouts/Layout.astro theme={null}
> ---
> const { title } = Astro.props;
> ---
>
> <html lang="en">
>   <head>
>     <meta charset="utf-8" />
>     <title>{title}</title>
>     <script
>       is:inline
>       defer
>       src="https://t.lumenstat.com/v/1.0.0/l.js"
>       integrity="sha384-cBzCCJcHCWSGbuy/f+zavGm/HjUWv77eni4DyLfq+FfKigpin1mul1ekLXILze/3"
>       crossorigin="anonymous"
>       data-website-id="YOUR-WEBSITE-ID"
>       data-domains="example.org,www.example.org"
>     ></script>
>   </head>
>   <body>
>     <slot />
>   </body>
> </html>
> ```
>
> If some pages use a different layout, add the tag there too, or move it into a shared head component. One copy per page.

> [tab] SvelteKit
>
> `src/app.html` is the page template for every route. Put the tag in its `<head>`, before `%sveltekit.head%`.
>
> ```html src/app.html theme={null}
> <!doctype html>
> <html lang="en">
>   <head>
>     <meta charset="utf-8" />
>     <meta name="viewport" content="width=device-width, initial-scale=1" />
>     <script
>       defer
>       src="https://t.lumenstat.com/v/1.0.0/l.js"
>       integrity="sha384-cBzCCJcHCWSGbuy/f+zavGm/HjUWv77eni4DyLfq+FfKigpin1mul1ekLXILze/3"
>       crossorigin="anonymous"
>       data-website-id="YOUR-WEBSITE-ID"
>       data-domains="example.org,www.example.org"
>     ></script>
>     %sveltekit.head%
>   </head>
>   <body data-sveltekit-preload-data="hover">
>     <div style="display: contents">%sveltekit.body%</div>
>   </body>
> </html>
> ```
>
> If you set a Content Security Policy in `svelte.config.js` under `kit.csp`, add `https://t.lumenstat.com` to `script-src` and `connect-src`.

> [tab] Nuxt
>
> Use one of the two options, not both. `app.head` in `nuxt.config.ts` adds the tag to every page. `useHead` in `app.vue` does the same from the root component.
>
> ```ts nuxt.config.ts theme={null}
> export default defineNuxtConfig({
>   app: {
>     head: {
>       script: [
>         {
>           src: 'https://t.lumenstat.com/v/1.0.0/l.js',
>           defer: true,
>           integrity: 'sha384-cBzCCJcHCWSGbuy/f+zavGm/HjUWv77eni4DyLfq+FfKigpin1mul1ekLXILze/3',
>           crossorigin: 'anonymous',
>           'data-website-id': 'YOUR-WEBSITE-ID',
>           'data-domains': 'example.org,www.example.org',
>         },
>       ],
>     },
>   },
> });
> ```
>
> ```vue app.vue theme={null}
> <script setup lang="ts">
> useHead({
>   script: [
>     {
>       src: 'https://t.lumenstat.com/v/1.0.0/l.js',
>       defer: true,
>       integrity: 'sha384-cBzCCJcHCWSGbuy/f+zavGm/HjUWv77eni4DyLfq+FfKigpin1mul1ekLXILze/3',
>       crossorigin: 'anonymous',
>       'data-website-id': 'YOUR-WEBSITE-ID',
>       'data-domains': 'example.org,www.example.org',
>     },
>   ],
> });
> </script>
>
> <template>
>   <NuxtPage />
> </template>
> ```

## Check the result

Load a production page, open the developer tools and confirm two things: the rendered `<script>` element carries `integrity` and `crossorigin`, and the Network tab shows a `POST` to `https://t.lumenstat.com/e` with a response that contains a `cache` token. Then reload **Setup** and look at **Is data arriving?**. If something is off, the troubleshooting list in [Add the tracker](https://docs.lumenstat.com/web/add-the-tracker.md#troubleshooting) covers each failure.

On a preview or on `localhost`, the Network tab should show no request to `/e` at all. That is `data-domains` working.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.