Alexander Opalic
TIL - Detecting fresh deployments in a Vue 3 SPA with vite + version.json
Learn how to detect fresh deployments in a Vue 3 SPA with vite + version.json
Today I discovered a very simple way for my Vue 3 single-page app to tell users when I’ve released a new update.
The idea
- Build-time: Each time I run
vite build
, a small Vite plugin creates aversion.json
file. It also sets up some information that’s available when the app is built. - Run-time: The app regularly checks the
/version.json
file. - If the
version
ortag
in that file doesn’t match the information the app was built with, we show a message like “New version available – refresh!”
The Vite plugin ( plugins/version-json.ts
)
export default function function versionJson(): Plugin
versionJson(): Plugin {
// …snip…
const const versionDetails: {
name: any;
version: any;
tag: any;
commit: any;
commitTime: any;
created: string;
}
versionDetails = {
name: any
name: pkg.name,
version: any
version: pkg.version, // 2.0.0
tag: any
tag: gitTag(), // v2.0.0-2-gabcdef
commit: any
commit: gitCommitHash(), // abcdef…
commitTime: any
commitTime: gitCommitTimestamp(),
created: string
created: new var Date: DateConstructor
new () => Date (+4 overloads)
Date().Date.toISOString(): string
Returns a date as a string value in ISO format.toISOString(),
}
// 👉 1. Serve /version.json in dev
configureServer(server) {
server.middlewares.use((req: any
req, res: any
res, next: any
next) => {
if (req: any
req.url === '/version.json') {
res: any
res.setHeader('Content-Type', 'application/json')
res: any
res.end(var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.JSON.JSON.stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.stringify(const versionDetails: {
name: any;
version: any;
tag: any;
commit: any;
commitTime: any;
created: string;
}
versionDetails, null, 2))
return
}
next: any
next()
})
}
// 👉 2. Emit dist/version.json in production
closeBundle() {
writeFileSync(resolve(outDir, 'version.json'),
var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.JSON.JSON.stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.stringify({ ...const versionDetails: {
name: any;
version: any;
tag: any;
commit: any;
commitTime: any;
created: string;
}
versionDetails, created: string
created: new var Date: DateConstructor
new () => Date (+4 overloads)
Date().Date.toISOString(): string
Returns a date as a string value in ISO format.toISOString() }, null, 2))
}
}
The plugin also makes import.meta.env.APP_VERSION
and APP_TAG
available, so the app always knows which version it’s running.
The watcher component ( ShowNewVersion.vue
)
<script setup lang="ts">
const const props: DefineProps<LooseRequired<__VLS_Props>, never>
props = const defineProps: <__VLS_Props>() => DefineProps<LooseRequired<__VLS_Props>, never> (+2 overloads)
Vue `<script setup>` compiler macro for declaring component props. The
expected argument is the same as the component `props` option.
Example runtime declaration:
```js
// using Array syntax
const props = defineProps(['foo', 'bar'])
// using Object syntax
const props = defineProps({
foo: String,
bar: {
type: Number,
required: true
}
})
```
Equivalent type-based declaration:
```ts
// will be compiled into equivalent runtime declarations
const props = defineProps<{
foo?: string
bar: number
}>()
```defineProps<{ initialVersion: string | null
initialVersion: string|null; initialTag: string | null
initialTag: string|null }>()
const { const isNewVersion: any
isNewVersion, const details: any
details } = function useCheck(): {
isNewVersion: any;
details: any;
}
useCheck()
function function useCheck(): {
isNewVersion: any;
details: any;
}
useCheck() {
const const isNewVersion: any
isNewVersion = ref(false)
const const details: any
details = ref<string|null>(null)
async function function (local function) poll(): Promise<void>
poll() {
if (!const props: DefineProps<LooseRequired<__VLS_Props>, never>
props.initialVersion: string | null
initialVersion) return
const const res: Response
res = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)fetch('/version.json')
const const v: any
v = await const res: Response
res.Body.json(): Promise<any>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)json()
if (const v: any
v.version !== const props: DefineProps<LooseRequired<__VLS_Props>, never>
props.initialVersion: string
initialVersion || const v: any
v.tag !== const props: DefineProps<LooseRequired<__VLS_Props>, never>
props.initialTag: string | null
initialTag) {
const isNewVersion: any
isNewVersion.value = true
const details: any
details.value = `${const v: any
v.version} (${const v: any
v.tag})`
}
}
onMounted(() => function (local function) poll(): Promise<void>
poll(), function setInterval<[]>(callback: () => void, ms?: number): NodeJS.Timeout (+2 overloads)
Schedules repeated execution of `callback` every `delay` milliseconds.
When `delay` is larger than `2147483647` or less than `1`, the `delay` will be
set to `1`. Non-integer delays are truncated to an integer.
If `callback` is not a function, a `TypeError` will be thrown.
This method has a custom variant for promises that is available using `timersPromises.setInterval()`.setInterval(function (local function) poll(): Promise<void>
poll, 30_000))
return { isNewVersion: any
isNewVersion, details: any
details }
}
</script>
<template>
<div: HTMLAttributes & ReservedProps
div v-if="const isNewVersion: any
isNewVersion" HTMLAttributes.class?: any
class="alert">
A new version is available → refresh! {{ const details: any
details }}
</div: HTMLAttributes & ReservedProps
div>
</template>
How I use it
I run this command:
npm run build # emits dist/version.json
When people use the app, their browser checks the /version.json
file every 30 seconds. As soon as the file’s content changes, the yellow banner appears. They can then do a manual refresh (⌘R
) to load the new version.
Takeaways
- No server changes needed – it’s just one static file.
- It works the same way when I’m developing locally (the file is served on the fly) and in the live app (the file is created once during the build).
- You get information at build-time and a check at run-time, all in one.
- You can find the full code I showed above in this repository → vueNewVersion.
For more details, you can look at the Deep Wiki documentation.
That’s it – one plugin, one tiny component, and an easy way to manage cache-busting!
#vue
#vite