diff --git a/aleksis/core/assets/plugins/aleksis.js b/aleksis/core/assets/plugins/aleksis.js
index c12bf8b76a9538d63abc1c1b36abe05eb33d8613..5270397a24435d6b8ad7b9d64fcbe49da406e39a 100644
--- a/aleksis/core/assets/plugins/aleksis.js
+++ b/aleksis/core/assets/plugins/aleksis.js
@@ -11,14 +11,14 @@ const AleksisVue = {};
 AleksisVue.install = function (Vue, options) {
   /**
    * The browser title when the app was loaded.
-   * 
+   *
    * Thus, it is injected from Django in the vue_index template.
    */
   Vue.$pageBaseTitle = document.title;
 
   /**
    * Configure Sentry if desired.
-   * 
+   *
    * It depends on Sentry settings being passed as a DOM object by Django
    * in the vue_index template.
    */
@@ -60,9 +60,9 @@ AleksisVue.install = function (Vue, options) {
 
   /**
    * Set the page title.
-   * 
+   *
    * This will automatically add the base title discovered at app loading time.
-   * 
+   *
    * @param {string} title Specific title to set, or null.
    * @param {Object} route Route to discover title from, or null.
    */
@@ -98,6 +98,27 @@ AleksisVue.install = function (Vue, options) {
     }
   };
 
+  /**
+   * Invalidate state and force reload from server.
+   *
+   * Mostly useful after the user context changes by login/logout/impersonate.
+   */
+  Vue.prototype.$invalidateState = function () {
+    console.info("Invalidating application state");
+
+    this.$apollo
+      .getClient()
+      .resetStore()
+      .then(
+        function () {
+          console.info("GraphQL cache cleared");
+        },
+        function (error) {
+          console.error("Could not clear GraphQL cache:", error);
+        }
+      );
+  };
+
   /**
    * Add navigation guards to account for global loading state and page titles.
    */
@@ -120,6 +141,15 @@ AleksisVue.install = function (Vue, options) {
     this.$router.afterEach((to, from) => {
       vm.contentLoading = false;
     });
+
+    // eslint-disable-next-line no-unused-vars
+    this.$router.beforeEach((to, from, next) => {
+      if (from.meta.invalidate === "leave" || to.meta.invalidate === "enter") {
+        console.debug("Route requests to invalidate state");
+        vm.$invalidateState();
+      }
+      next();
+    });
   };
 };
 
diff --git a/aleksis/core/assets/routes.js b/aleksis/core/assets/routes.js
index 064e913ccb285d505210b96a8b15e432d0a88500..d969797e8e591363c4fcc5d24c0b7e4cd1fc5ab7 100644
--- a/aleksis/core/assets/routes.js
+++ b/aleksis/core/assets/routes.js
@@ -20,6 +20,7 @@ const routes = [
       icon: "mdi-login-variant",
       titleKey: "accounts.login.menu_title",
       validators: [notLoggedInValidator],
+      invalidate: "leave",
     },
   },
   {
@@ -31,6 +32,7 @@ const routes = [
       icon: "mdi-account-plus-outline",
       titleKey: "accounts.signup.menu_title",
       validators: [notLoggedInValidator],
+      invalidate: "leave",
     },
   },
   {
@@ -468,6 +470,9 @@ const routes = [
     path: "/impersonate/:uid(\\d+)/",
     component: () => import("./components/LegacyBaseTemplate.vue"),
     name: "impersonate.impersonateByUserPk",
+    meta: {
+      invalidate: "leave",
+    },
   },
 
   // ACCOUNT MENU
@@ -476,6 +481,9 @@ const routes = [
     path: "/impersonate/stop/",
     component: () => import("./components/LegacyBaseTemplate.vue"),
     name: "impersonate.stop",
+    meta: {
+      invalidate: "leave",
+    },
   },
   {
     path: "/person/",
@@ -694,6 +702,7 @@ const routes = [
       icon: "mdi-logout-variant",
       permission: "core.logout_rule",
       divider: true,
+      invalidate: "leave",
     },
   },
   {