3 min read
Building a Vite plugin for modifying HTML
How to create a Vite plugin to add a tracking script to index.html at build time

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 script tag 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.