diff --git a/aleksis/core/assets/components/about/About.vue b/aleksis/core/assets/components/about/About.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4a1a30fdf621998358774ce8cafe8fcb25069352
--- /dev/null
+++ b/aleksis/core/assets/components/about/About.vue
@@ -0,0 +1,62 @@
+<template>
+  <div class="mt-4 mb-4">
+    <v-row align="stretch">
+      <v-col class="d-flex align-stretch">
+        <v-card class="d-flex flex-column">
+          <v-card-title>About AlekSIS</v-card-title>
+          <v-card-text>
+            <p>
+              This platform is powered by AlekSIS®, a web-based school information system (SIS) which can be used
+              to manage and/or publish organisational artifacts of educational institutions. AlekSIS is free software
+              and can be used by anyone.
+            </p>
+            <p>
+              AlekSIS® is a registered trademark of the AlekSIS open source project, represented by Teckids e.V.
+            </p>
+          </v-card-text>
+          <v-spacer></v-spacer>
+          <v-card-actions>
+            <v-btn text color="primary" href="https://aleksis.org/">Website of AlekSIS</v-btn>
+            <v-btn text color="primary" href="https://edugit.org/AlekSIS/">Source code</v-btn>
+          </v-card-actions>
+
+        </v-card>
+      </v-col>
+      <v-col class="d-flex align-stretch">
+        <v-card class="d-flex flex-column">
+          <v-card-title>Licence information</v-card-title>
+          <v-card-text>
+            <p>
+              The core and the official apps of AlekSIS are licenced under the EUPL, version 1.2 or later. For licence
+              information from third-party apps, if installed, refer to the respective components below. The
+              licences are marked like this:
+            </p>
+            <p>
+              <v-chip color="green" text-color="white">Free/Open Source Licence</v-chip>
+              <v-chip color="green" text-color="white">Other Licence</v-chip>
+            </p>
+          </v-card-text>
+          <v-spacer></v-spacer>
+          <v-card-actions>
+            <v-btn text color="primary" href="https://eupl.eu">Full licence text</v-btn>
+            <v-btn text color="primary"
+                   href="https://joinup.ec.europa.eu/collection/eupl/guidelines-users-and-developers">
+              More information about the EUPL
+            </v-btn>
+          </v-card-actions>
+        </v-card>
+      </v-col>
+    </v-row>
+    <installed-apps-list/>
+  </div>
+</template>
+
+<script>
+import InstalledAppsList from "./InstalledAppsList.vue";
+
+export default {
+  name: "About",
+  components: {VCheckbox, InstalledAppsList}
+}
+</script>
+
diff --git a/aleksis/core/assets/components/about/InstalledAppCard.vue b/aleksis/core/assets/components/about/InstalledAppCard.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ac97ac293df28b22ed6fa358777030dbd36ce76b
--- /dev/null
+++ b/aleksis/core/assets/components/about/InstalledAppCard.vue
@@ -0,0 +1,63 @@
+<template>
+  <v-col cols="6" class="d-flex align-stretch">
+    <v-card :id="app.name" class="d-flex flex-column flex-grow-1">
+      <v-card-title>
+        {{ app.verboseName }}
+        <v-chip v-if="app.licence.flags.isFsfLibre" color="green" text-color="white" class="ml-4">Free Software</v-chip>
+        <v-chip v-else-if="app.licence.flags.isOsiApproved" color="green" text-color="white" class="ml-4">Open Source
+        </v-chip>
+      </v-card-title>
+
+      <v-card-subtitle>
+        {{ app.version }}
+      </v-card-subtitle>
+
+      <v-card-text>
+        <p v-if="app.copyrights.length !== 0">
+          <span v-for="(copyright, index) in app.copyrights" :key="index">
+            Copyright © {{ copyright.years }}
+            <a :href="'mailto:' + copyright.email">{{ copyright.name }}</a>
+            <br/>
+          </span>
+        </p>
+
+        <p v-if="app.licence">
+          This app is licenced under {{ app.licence.verboseName }}.
+        </p>
+        <div v-for="licence in app.licence.licences" class="mb-2" :key="licence.name">
+          <v-chip v-if="licence.isOsiApproved || licence.isFsfLibre" color="green" text-color="green" outlined
+                  :href="licence.url">
+            {{ licence.name }}
+          </v-chip>
+          <v-chip v-else color="orange" text-color="orange" outlined :href="licence.url">
+            {{ licence.name }}
+          </v-chip>
+        </div>
+      </v-card-text>
+
+      <v-spacer></v-spacer>
+
+      <v-card-actions v-if="app.urls.length !== 0">
+        <v-btn
+            v-for="url in app.urls"
+            color="primary"
+            text
+            :href="url.url"
+        >
+          {{ url.name }}
+        </v-btn>
+      </v-card-actions>
+    </v-card>
+  </v-col>
+</template>
+<script>
+export default {
+  name: "InstalledAppCard",
+  props: {
+    app: {
+      type: Object,
+      required: true,
+    },
+  },
+}
+</script>
diff --git a/aleksis/core/assets/components/about/InstalledAppsList.vue b/aleksis/core/assets/components/about/InstalledAppsList.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b49f2b3198ea2b40183528df94dac6c47bcf8f85
--- /dev/null
+++ b/aleksis/core/assets/components/about/InstalledAppsList.vue
@@ -0,0 +1,25 @@
+<template>
+  <ApolloQuery
+      :query="require('./installedApps.graphql')"
+  >
+    <template v-slot="{ result: { error, data }, isLoading }">
+      <v-row>
+        <InstalledAppCard
+            v-for="app in data.installedApps"
+            :key="app.name"
+            :app="app"
+        />
+      </v-row>
+    </template>
+  </ApolloQuery>
+</template>
+
+<script>
+import InstalledAppCard from "./InstalledAppCard.vue";
+
+export default {
+  name: "InstalledAppsList",
+  components: {InstalledAppCard}
+}
+</script>
+
diff --git a/aleksis/core/assets/components/about/installedApps.graphql b/aleksis/core/assets/components/about/installedApps.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..01ceaf99eb8d79d44ee7014d9f582d7dfeb54ace
--- /dev/null
+++ b/aleksis/core/assets/components/about/installedApps.graphql
@@ -0,0 +1,29 @@
+{
+  installedApps {
+    name
+    verboseName
+    version
+    copyrights {
+      years
+      name
+      email
+    }
+    licence {
+      verboseName
+      flags {
+        isFsfLibre
+        isOsiApproved
+      }
+      licences {
+        isFsfLibre
+        isOsiApproved
+        name
+        url
+      }
+    }
+    urls {
+      name
+      url
+    }
+  }
+}
diff --git a/aleksis/core/assets/index.js b/aleksis/core/assets/index.js
index 94f4131baf7181ea2a299c518f5fe942b850926d..03c33d7313dc3a51f237ac0cea45d0e220fae1d6 100644
--- a/aleksis/core/assets/index.js
+++ b/aleksis/core/assets/index.js
@@ -2,3 +2,7 @@ import '@mdi/font/css/materialdesignicons.css'
 
 import "./util"
 import "./app"
+
+import About from "./components/about/About.vue";
+
+window.router.addRoute({ path: "/about", component: About });
diff --git a/aleksis/core/schema.py b/aleksis/core/schema.py
index 7e637aa20612a5d486732d85201c05febcfeed3b..b7efdb3e5115262c986e97e42046d5a187293b63 100644
--- a/aleksis/core/schema.py
+++ b/aleksis/core/schema.py
@@ -1,9 +1,12 @@
 import graphene
+from django.apps import apps
+from graphene import ObjectType
 from graphene_django import DjangoObjectType
 from graphene_django.forms.mutation import DjangoModelFormMutation
 
 from .forms import PersonForm
 from .models import Group, Notification, Person
+from .util.apps import AppConfig
 from .util.core_helpers import get_app_module, get_app_packages, has_person
 
 
@@ -27,6 +30,41 @@ class GroupType(DjangoObjectType):
         model = Group
 
 
+class AppURLType(ObjectType):
+    name = graphene.String(required=True)
+    url = graphene.String(required=True)
+
+class CopyrightType(ObjectType):
+    years = graphene.String(required=True)
+    name = graphene.String(required=True)
+    email = graphene.String(required=True)
+
+class LicenceFlagsType(ObjectType):
+    isFsfLibre = graphene.Boolean(required=True)
+    isOsiApproved = graphene.Boolean(required=True)
+
+class SubLicenceType(ObjectType):
+    isDeprecatedLicenseId = graphene.Boolean(default_value=False)
+    isFsfLibre = graphene.Boolean(default_value=False)
+    isOsiApproved = graphene.Boolean(default_value=False)
+    licenseId = graphene.String(required=True)
+    name = graphene.String(required=True)
+    referenceNumber = graphene.Int(default_value=-1)
+    url = graphene.String()
+
+class LicenceType(ObjectType):
+    verbose_name = graphene.String(required=True)
+    flags = graphene.Field(LicenceFlagsType, required=True)
+    licences = graphene.List(SubLicenceType)
+
+class AppType(ObjectType):
+    copyrights = graphene.List(CopyrightType)
+    licence = graphene.Field(LicenceType)
+    name = graphene.String(required=True)
+    verbose_name = graphene.String(required=True)
+    version = graphene.String()
+    urls = graphene.List(AppURLType)
+
 class PersonMutation(DjangoModelFormMutation):
     person = graphene.Field(PersonType)
 
@@ -59,6 +97,8 @@ class Query(graphene.ObjectType):
     person_by_id = graphene.Field(PersonType, id=graphene.ID())
     who_am_i = graphene.Field(PersonType)
 
+    installed_apps = graphene.List(AppType)
+
     def resolve_notifications(root, info, **kwargs):
         # FIXME do permission stuff
         return Notification.objects.all()
@@ -76,6 +116,8 @@ class Query(graphene.ObjectType):
         else:
             return None
 
+    def resolve_installed_apps(root, info, **kwargs):
+        return [app.get_dict() for app in apps.get_app_configs() if isinstance(a, AppConfig)]
 
 class Mutation(graphene.ObjectType):
     update_person = PersonMutation.Field()
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 3313076e8d2649bbbc0fa885e59ea9c5d84dcf62..bd65cc85d48fc653a315326cf6f097e3cb4c4dab 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -178,7 +178,7 @@ MIDDLEWARE = [
     "django.middleware.security.SecurityMiddleware",
     "django.contrib.sessions.middleware.SessionMiddleware",
     "django.contrib.auth.middleware.AuthenticationMiddleware",
-    "debug_toolbar.middleware.DebugToolbarMiddleware",
+    # "debug_toolbar.middleware.DebugToolbarMiddleware",
     "django.middleware.locale.LocaleMiddleware",
     "django.middleware.http.ConditionalGetMiddleware",
     "django.contrib.sites.middleware.CurrentSiteMiddleware",
diff --git a/aleksis/core/templates/core/pages/about.html b/aleksis/core/templates/core/pages/about.html
index 6c44d90cbfe51df457f2bd09f600dfb676b7b97d..3489a50e2395309b078a0c1b2fa053b6a728fac6 100644
--- a/aleksis/core/templates/core/pages/about.html
+++ b/aleksis/core/templates/core/pages/about.html
@@ -1,5 +1,5 @@
 {# -*- engine:django -*- #}
-{% extends "core/base.html" %}
+{% extends "core/vue_base.html" %}
 {% load i18n %}
 
 
@@ -7,121 +7,5 @@
 {% block page_title %}{% blocktrans %}AlekSIS® – The Free School Information System{% endblocktrans %}{% endblock %}
 
 {% block content %}
-
-  <div class="row">
-    <div class="col s12">
-      <div class="card">
-        <div class="card-content">
-          <span class="card-title">{% blocktrans %}About AlekSIS{% endblocktrans %}</span>
-          <p>
-            {% blocktrans %}
-              This platform is powered by AlekSIS®, a web-based school information system (SIS) which can be used
-              to manage and/or publish organisational artifacts of educational institutions. AlekSIS is free software and
-              can be used by anyone.
-            {% endblocktrans %}
-          </p>
-          <p>
-            {% blocktrans %}
-              AlekSIS® is a registered trademark of the AlekSIS open source project, represented by Teckids e.V.
-            {% endblocktrans %}
-          </p>
-        </div>
-        <div class="card-action">
-          <a class="" href="https://aleksis.org/">{% trans "Website of AlekSIS" %}</a>
-          <a class="" href="https://edugit.org/AlekSIS/">{% trans "Source code" %}</a>
-        </div>
-      </div>
-    </div>
-  </div>
-  <div class="row">
-    <div class="col s12">
-      <div class="card">
-        <div class="card-content">
-          <span class="card-title">{% trans "Licence information" %}</span>
-          <p>
-            {% blocktrans %}
-              The core and the official apps of AlekSIS are licenced under the EUPL, version 1.2 or later. For licence
-              information from third-party apps, if installed, refer to the respective components below. The
-              licences are marked like this:
-            {% endblocktrans %}
-          </p>
-          <br/>
-          <p>
-            <span class="chip green white-text">{% trans "Free/Open Source Licence" %}</span>
-            <span class="chip orange white-text">{% trans "Other Licence" %}</span>
-          </p>
-        </div>
-        <div class="card-action">
-          <a href="https://eupl.eu">{% trans "Full licence text" %}</a>
-          <a href="https://joinup.ec.europa.eu/collection/eupl/guidelines-users-and-developers">{% trans "More information about the EUPL" %}</a>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <div class="row">
-    {% for app_config in app_configs %}
-      <div class="col s12 m12 l6">
-        <div class="card " id="{{ app_config.name }}">
-          <div class="card-content">
-            {% if app_config.get_licence.1.isFsfLibre %}
-              <span class="chip green white-text right">Free Software</span>
-            {% elif app_config.get_licence.1.isOsiApproved %}
-              <span class="chip green white-text right">Open Source</span>
-            {% endif %}
-
-            <span class="card-title">{{ app_config.get_name }} <small>{{ app_config.get_version }}</small></span>
-
-            {% if app_config.get_copyright %}
-              <p>
-                {% for holder in app_config.get_copyright %}
-                  Copyright © {{ holder.0 }}
-
-                  {% if holder.2 %}
-                    <a href="mailto:{{ holder.2 }}">{{ holder.1 }}</a>
-                  {% else %}
-                    {{ holder.1 }}
-                  {% endif %}
-
-                  <br/>
-                {% endfor %}
-              </p>
-              <br/>
-            {% endif %}
-
-            {% if app_config.get_licence %}
-              {% with licence=app_config.get_licence %}
-                <p>
-                  {% blocktrans with licence=licence.0 %}
-                    This app is licenced under {{ licence }}.
-                  {% endblocktrans %}
-                </p>
-                <br/>
-                <p>
-                  {% for l in licence.2 %}
-                    <a class="chip white-text {% if l.isOsiApproved or l.isFsfLibre %}green{% else %}orange{% endif %}"
-                       href="{{ l.url }}">
-                      {{ l.name }}
-                    </a>
-                  {% endfor %}
-                </p>
-              {% endwith %}
-            {% endif %}
-          </div>
-          {% if app_config.get_urls %}
-            <div class="card-action">
-              {% for url_name, url in app_config.get_urls.items %}
-                <a href="{{ url }}">{{ url_name }}</a>
-              {% endfor %}
-            </div>
-          {% endif %}
-        </div>
-      </div>
-      {% if forloop.counter|divisibleby:2 %}
-        </div>
-        <div class="row">
-      {% endif %}
-    {% endfor %}
-    </div>
-
+  <router-view></router-view>
 {% endblock %}
diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py
index 5b3898dd835506ade932ebccf30e4cf0de15486e..4fdd4740015a22df3fdce4bbe6b3a835d2b5a2e0 100644
--- a/aleksis/core/util/apps.py
+++ b/aleksis/core/util/apps.py
@@ -35,6 +35,10 @@ class AppConfig(django.apps.AppConfig):
         # Getting an app ready means it should look at its config once
         self.preference_updated(self)
 
+    def get_dict(self):
+        return {"name": self.name, "verbose_name": self.get_name(), "version": self.get_version(), "copyrights": self.get_copyright_dicts(), "licence": self.get_licence_dict(), "urls": self.get_urls_dict()}
+
+
     def get_distribution_name(self):
         """Get distribution name of application package."""
         if hasattr(self, "dist_name"):
@@ -129,11 +133,26 @@ class AppConfig(django.apps.AppConfig):
             return ("Unknown", [default_dict])
 
     @classmethod
+    def get_licence_dict(cls):
+        """Get licence information of application package."""
+        licence = cls.get_licence()
+        return {
+            "verbose_name": licence[0],
+            "flags": licence[1],
+            "licences": licence[2],
+        }
+    @classmethod
     def get_urls(cls):
         """Get list of URLs for this application package."""
         return getattr(cls, "urls", {})
         # TODO Try getting from distribution if not set
 
+    @classmethod
+    def get_urls_dict(cls):
+        """Get list of URLs for this application package."""
+        urls = cls.get_urls()
+        return [{"name": key, "url": value} for key, value in urls.items()]
+
     @classmethod
     def get_copyright(cls) -> Sequence[tuple[str, str, str]]:
         """Get copyright information tuples for application package."""
@@ -155,6 +174,12 @@ class AppConfig(django.apps.AppConfig):
         return copyrights_processed
         # TODO Try getting from distribution if not set
 
+    @classmethod
+    def get_copyright_dicts(cls):
+        """Get copyright information dictionaries for application package."""
+        infos = cls.get_copyright()
+        return [{"years": info[0], "name": info[1], "email": info[2] } for info in infos]
+
     def preference_updated(
         self,
         sender: Any,