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.