While working on Gorby, I’ve been loading the Umami analytics script in index.html, resulting in number of visits being higher than it actually is. Although it’s not a major issue, it’s somewhat annoying. Here’s how I solved it by creating a small Vite plugin.
TL;DR - Full Code
Here’s the complete vite.config.js file with the plugin that includes the analytics script at build time:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
  plugins: [
    react(),
    {
      name: "add-script-to-html-head",
      apply: "build",
      transformIndexHtml() {
        return {
          tags: [
            {
              tag: "script",
              attrs: {
                async: true,
                src: "https://analytics.example.com/script.js",
                "data-website-id": "ABC-123-XYZ-456-...",
              },
              injectTo: "head",
            },
          ],
        };
      },
    },
  ],
});
Transforming HTML
Vite comes with a handy transformIndexHtml hook that makes it relatively easy to add and replace things in HTML entry point files. There’s also vite-plugin-html that makes this easier, but adding it felt like an overkill for loading just a single script. Anyhow, since transformIndexHtml can return either an HTML string or an array of tags, this can be solved in two ways:
- By returning an array containing a single 
scripttag with my script attributes: 
  tags: [
    {
      tag: "script",
      attrs: {
        async: true,
        src: "https://analytics.example.com/script.js",
        "data-website-id": "ABC-123-XYZ-456-...",
      },
      injectTo: "head",
    },
  ]
- By simply replacing 
</head>with our script tag: 
  transformIndexHtml(html) {
    const scriptTag = `<script async src="https://analytics.example.com/script.js" data-website-id="ABC-123-XYZ-456-..."></script>`;
    return html.replace('</head>', `${scriptTag}</head>`);
  }
In both cases, this plugin can be configured to run only at build time using conditional application: apply: "build"
Alternative approaches
Some other ideas I haven’t tried but think could work include using HTML Env Replacement to define the entire script tag as an .env variable and include it in the head. Additionally, since Gorby is a React app, resource preloading could be an option once React 19 is released.