Patrick Altman
Keep Your Vue Apps Fresh v2

Keep Your Vue Apps Fresh v2

Introduction

A few years ago, I posted about how to keep Vue apps up to date. Conceptually, this solution is still valid, however, a lot has changed with how I create web applications over this time. Not least of which is a move to GraphQL instead of lots of different REST endpoints.

The Problem

Just to recap that previous article and the problem we are solving, in a SPA (single page app), your users have the app open in their browser for longer period of time than when every page is served new from the backend. In SPA as your user is navigating around the naviation happens client side and the your front end is merely fetching data needed for each component or view.

With this occurring, it is quite easy for you to deploy updates to the server with no way of telling your users to refresh and get the updates in a new bundle.

What we want to have happen is anytime a deployment occurs that the user is notified that a new version is available and they can refresh to get the latest version.

The Solution

Backend

First we need to define a type, hook the type up to our GraphQL schema, and implement the resolver:

@strawberry.type
class Version:
    version: str


def get_version(info: "StrawberryDjangoContext") -> Version:
    return Version(version=settings.RELEASE_VERSION)  # This setting pulls the version from the environment


@strawberry.type
class Query:
    version: Version = strawberry.field(resolver=get_version)

That's it for the backend. This enables us to query for the latest version that has been deployed as defined by the RELEASE_VERSION environment variable. I set this through my CI process running on GitHub during deployment.

Frontend

Now we need the frontend to not only query this version and check against it's own version, but to poll it periodically and when there is a mismatch, to display a notice instructing the user to refresh.

import { computed } from 'vue';
import { useQuery, provideApolloClient } from '@vue/apollo-composable';

import config from '@/config';
import { graphql } from '@/gql';

const QUERY = graphql(/* GraphQL */ `
  query Version {
    version {
      version
    }
  }
`);

export default () => {
  const { result } = useQuery(QUERY, undefined, {
    fetchPolicy: 'no-cache',
    pollInterval: 15000, // poll every 15 seconds
  });
  const version = computed(() => result.value?.version.version);
  const versionMismatch = computed(() =>
    version.value === undefined
    ? false
    : version.value !== config.RELEASE_VERSION
  );
  return {
    version,
    versionMismatch,
  };
};

We use that composable to know when to display the notice banner in our main layout component that the entire app is wrapped in:

<script setup lang="ts">
  import useVersion from '@/compositions/useVersion';

  const { version, versionMismatch } = useVersion();

  const reload = () => {
    window.location.reload();
  };
</script>

<template>
  <div>
    <div v-if="versionMistach" class="mb-8 mx-auto max-w-7xl py-8 px-4 sm:px-6 lg:px-8 bg-teal-100 border-b border-teal-300 text-teal-600">
      <div class="text-xl">Version {{ version }} has been released.</div>
      <div class="mt-2"><a href="" @click.prevent="reload" class="cursor-pointer underline text-teal-900">Click here to refresh</a> and get the latest version.</div>
    </div>
    <router-view />
  </div>
</template>

You might have noticed a config.RELEASE_VERSION in the composable. This is a global config variable that is built in a Django template <head /> section. This is how we get the version from the backend into the frontend:

<html>
  <head>
    ...
    <script>
      window.MyAppConfig = {
        ...
        RELEASE_VERSION: '{{ RELEASE_VERSION }}',
      };
    </script>
  </head>
  ...
</html>

Then the config:

interface MyAppConfig {
  RELEASE_VERSION: string;
}

const config: MyAppConfig = (window as any).MyAppConfig || {};

export default config;

That's It!

The Apollo client takes care of the polling so it feels "pushed" to the client. Soon after a deploy is completed, a message is shown a user without them having to do anything, telling them about the newer version and a link that they can easily click to refresh and pull the new bundle.

Would love to hear from you. You can find me on Twitter @paltman