diff --git a/.eslintrc.js b/.eslintrc.js
index ff2ba5584efa89dfa6fa80daa55f3f39e9fcdee4..15b0fbe2891c2f685e867206fc6954620237b5d6 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -3,7 +3,7 @@ module.exports = {
     'plugin:vue/strongly-recommended',
   ],
   rules: {
-    // override/add rules settings here, such as:
-    // 'vue/no-unused-vars': 'error'
+    'vue/no-unused-vars': 'off',
+    'vue/multi-word-component-names': 'off'
   }
 }
diff --git a/aleksis/core/templates/core/notifications.html b/aleksis/core/templates/core/notifications.html
index dee85ebb300275851758ce1d3c3bf318fce5671c..d7201867376e8618d08d9511dd94675a1bce1553 100644
--- a/aleksis/core/templates/core/notifications.html
+++ b/aleksis/core/templates/core/notifications.html
@@ -4,42 +4,6 @@
 {% block browser_title %}{% blocktrans %}Notifications{% endblocktrans %}{% endblock %}
 {% block page_title %}{% blocktrans %}Notifications{% endblocktrans %}{% endblock %}
 
-
 {% block content %}
-  {% if object_list %}
-    <v-list two-line>
-      {% for notification in object_list %}
-        <v-list-item>
-          <v-list-item-content>
-            <v-list-item-title>{{ notification.title }}</v-list-item-title>
-
-            <v-list-item-subtitle>
-              <v-icon>mdi-clock-outline</v-icon>
-              {{ notification.created }}
-            </v-list-item-subtitle>
-
-            <v-list-item-subtitle>
-              {{ notification.description|linebreaks }}
-            </v-list-item-subtitle>
-          </v-list-item-content>
-
-          {% if notification.link %}
-            <v-list-item-action>
-              <v-btn text href="{{ notification.link }}">
-                {% blocktrans %}More information →{% endblocktrans %}
-              </v-btn>
-            </v-list-item-action>
-          {% endif %}
-
-          <v-list-item-icon>
-            <v-chip color="primary">{{ notification.sender }}</v-chip>
-          </v-list-item-icon>
-
-        </v-list-item>
-        <v-divider inset></v-divider>
-      {% endfor %}
-    </v-list>
-  {% else %}
-    <p>{% blocktrans %}No notifications available yet.{% endblocktrans %}</p>
-  {% endif %}
+  <notification-list/>
 {% endblock %}
diff --git a/assets/app.js b/assets/app.js
index 1b3595b5cf7ba69cafae78ec7566c57aef975bb8..733a932010af58c27ceae7139de4c5c7fd837137 100644
--- a/assets/app.js
+++ b/assets/app.js
@@ -55,6 +55,7 @@ const apolloClient = new ApolloClient({
 import CacheNotification from "./components/CacheNotification.vue";
 import LanguageForm from "./components/LanguageForm.vue";
 import MessageBox from "./components/MessageBox.vue";
+import NotificationList from "./components/notifications/NotificationList.vue";
 import SidenavSearch from "./components/SidenavSearch.vue";
 
 Vue.component(MessageBox.name, MessageBox); // Load MessageBox globally as other components depend on it
@@ -82,6 +83,7 @@ const app = new Vue({
     components: {
         "cache-notification": CacheNotification,
         "language-form": LanguageForm,
+        "notification-list": NotificationList,
         "sidenav-search": SidenavSearch,
     },
 })
diff --git a/assets/components/notifications/NotificationItem.vue b/assets/components/notifications/NotificationItem.vue
new file mode 100644
index 0000000000000000000000000000000000000000..01e52aa9b4e41194015d242d3b5ea11f0ab60b3f
--- /dev/null
+++ b/assets/components/notifications/NotificationItem.vue
@@ -0,0 +1,34 @@
+<template>
+  <v-list-item>
+    <v-list-item-content>
+      <v-list-item-title>{{ notification.title }}</v-list-item-title>
+
+      <v-list-item-subtitle>
+        <v-icon>mdi-clock-outline</v-icon>
+        {{ notification.created }}
+      </v-list-item-subtitle>
+
+      <v-list-item-subtitle>
+        {{ notification.description }}
+      </v-list-item-subtitle>
+    </v-list-item-content>
+
+    <v-list-item-action v-if="notification.link">
+      <v-btn text :href="notification.link">
+        {{ this.$root.django.gettext('More information →') }}
+      </v-btn>
+    </v-list-item-action>
+
+    <v-list-item-icon>
+      <v-chip color="primary">{{ notification.sender }}</v-chip>
+    </v-list-item-icon>
+  </v-list-item>
+</template>
+
+<script>
+  export default {
+    props: {
+      notification: Object,
+    },
+  }
+</script>
diff --git a/assets/components/notifications/NotificationList.vue b/assets/components/notifications/NotificationList.vue
new file mode 100644
index 0000000000000000000000000000000000000000..815d66aef5d52652bbe332db44b0f17d303621c0
--- /dev/null
+++ b/assets/components/notifications/NotificationList.vue
@@ -0,0 +1,38 @@
+<template>
+  <ApolloQuery
+    :query="gql => gql`{
+      myNotifications: whoAmI {
+        notifications {
+          id
+          title
+          description
+          link
+          created
+          sender
+        }
+      }
+    }`"
+    :pollInterval="1000"
+  >
+    <template v-slot="{ result: { error, data }, isLoading }">
+      <v-list two-line v-if="data && data.myNotifications.notifications">
+        <NotificationItem
+          v-for="notification in data.myNotifications.notifications"
+          :key="notification.id"
+          :notification="notification"
+        />
+      </v-list>
+      <p v-else>{{ this.$root.django.gettext('No notifications available yet.') }}</p>
+    </template>
+  </ApolloQuery>
+</template>
+
+<script>
+  import NotificationItem from "./NotificationItem.vue";
+
+  export default {
+    components: {
+      NotificationItem,
+    },
+  }
+</script>
diff --git a/webpack.config.js b/webpack.config.js
index b15388b8b8c620e20cddfd362c9b274aa8486970..f6c1da7613ecf6ea6ab1fb1eafbb7c30155584dc 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -32,7 +32,16 @@ module.exports = {
     rules: [
       {
         test: /\.vue$/,
-        loader: 'vue-loader'
+        use: {
+          loader: 'vue-loader',
+          options: {
+            transpileOptions: {
+              transforms: {
+                dangerousTaggedTemplateString: true
+              }
+            }
+          }
+        },
       },
       {
         test: /\.(css)$/,