diff --git a/aleksis/core/dashboard/views/tools.py b/aleksis/core/dashboard/views/tools.py
index 0bcdee0d52aafc23e1e99b82d42687a9f55ee708..5c088dcf6744118713f15924f9d56069dee7d01a 100644
--- a/aleksis/core/dashboard/views/tools.py
+++ b/aleksis/core/dashboard/views/tools.py
@@ -3,9 +3,5 @@ from django.shortcuts import render
 from meta import OPEN_SOURCE_COMPONENTS
 
 
-def offline(request):
-    return render(request, 'common/offline.html')
-
-
 def about(request):
     return render(request, "common/about.html", context={"components": OPEN_SOURCE_COMPONENTS})
diff --git a/aleksis/core/schoolapps/settings.py b/aleksis/core/schoolapps/settings.py
index 36fd54aa760b3c7a73a72754b972a28b659b2241..5d59cc743dcd60426eee3f471799a3bbbe5370e7 100644
--- a/aleksis/core/schoolapps/settings.py
+++ b/aleksis/core/schoolapps/settings.py
@@ -192,36 +192,6 @@ else:
         }
     }
 
-# PWA
-PWA_APP_NAME = 'SchoolApps'
-PWA_APP_DESCRIPTION = "Eine Sammlung an nützlichen Apps für den Schulalltag am Katharineum zu Lübeck"
-PWA_APP_THEME_COLOR = '#da1f3d'
-PWA_APP_BACKGROUND_COLOR = '#ffffff'
-PWA_APP_DISPLAY = 'standalone'
-PWA_APP_SCOPE = '/'
-PWA_APP_ORIENTATION = 'any'
-PWA_APP_START_URL = '/'
-PWA_APP_ICONS = [
-    {
-        "src": "/static/icons/android_192.png",
-        "sizes": "192x192",
-        "type": "image/png"
-    },
-    {
-        "src": "/static/icons/android_512.png",
-        "sizes": "512x512",
-        "type": "image/png"
-    }
-]
-PWA_APP_SPLASH_SCREEN = [
-    {
-        'src': '/static/icons/android_512.png',
-        'media': '(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)'
-    }
-]
-PWA_APP_DIR = 'ltr'
-PWA_SERVICE_WORKER_PATH = os.path.join(BASE_DIR, 'static/common', 'serviceworker.js')
-PWA_APP_LANG = 'de-DE'
 
 LOGGING = {
     'version': 1,
diff --git a/aleksis/core/schoolapps/urls.py b/aleksis/core/schoolapps/urls.py
index ba42b84d22a00a0aefe57eb87e08ac9daf6fba37..b96d8de1368cb7f3af1cf79b6f79012367a60a59 100644
--- a/aleksis/core/schoolapps/urls.py
+++ b/aleksis/core/schoolapps/urls.py
@@ -77,7 +77,6 @@ urlpatterns = [
     #######
     path('faq/', include('faq.urls')),
 
-    path('', include('pwa.urls')),
 
     path('martor/', include('martor.urls')),
 
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index a8ef779f011f048223807a0c8b2ef8f43fbacffa..5ee6c8e493b0c819a1769b92cb4fb6250d96447c 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -73,7 +73,8 @@ INSTALLED_APPS = [
     "aleksis.core",
     "impersonate",
     "two_factor",
-    "material"
+    "material",
+    "pwa",
 ]
 
 INSTALLED_APPS += get_app_packages()
@@ -85,7 +86,6 @@ STATICFILES_FINDERS = [
     "sass_processor.finders.CssFinder",
 ]
 
-
 MIDDLEWARE = [
     #    'django.middleware.cache.UpdateCacheMiddleware',
     "django.middleware.security.SecurityMiddleware",
@@ -120,7 +120,7 @@ TEMPLATES = [
                 "django.contrib.messages.context_processors.messages",
                 "maintenance_mode.context_processors.maintenance_mode",
                 "settings_context_processor.context_processors.settings",
-                "constance.context_processors.config"
+                "constance.context_processors.config",
             ],
         },
     },
@@ -135,7 +135,6 @@ IMAGE_CROPPING_JQUERY_URL = None
 
 WSGI_APPLICATION = "aleksis.core.wsgi.application"
 
-
 # Database
 # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
 
@@ -232,7 +231,13 @@ STATIC_ROOT = _settings.get("static.root", os.path.join(BASE_DIR, "static"))
 MEDIA_ROOT = _settings.get("media.root", os.path.join(BASE_DIR, "media"))
 NODE_MODULES_ROOT = _settings.get("node_modules.root", os.path.join(BASE_DIR, "node_modules"))
 
-YARN_INSTALLED_APPS = ["datatables", "jquery", "materialize-css", "material-design-icons-iconfont", "select2"]
+YARN_INSTALLED_APPS = [
+    "datatables",
+    "jquery",
+    "materialize-css",
+    "material-design-icons-iconfont",
+    "select2",
+]
 
 JS_URL = _settings.get("js_assets.url", STATIC_URL)
 JS_ROOT = _settings.get("js_assets.root", NODE_MODULES_ROOT + "/node_modules")
@@ -245,7 +250,9 @@ ANY_JS = {
     "DataTables": {"js_url": JS_URL + "/datatables/media/js/jquery.dataTables.min.js"},
     "materialize": {"js_url": JS_URL + "/materialize-css/dist/js/materialize.min.js"},
     "jQuery": {"js_url": JS_URL + "/jquery/dist/jquery.min.js"},
-    "material-design-icons": {"css_url": JS_URL + "/material-design-icons-iconfont/dist/material-design-icons.css"},
+    "material-design-icons": {
+        "css_url": JS_URL + "/material-design-icons-iconfont/dist/material-design-icons.css"
+    },
 }
 
 SASS_PROCESSOR_AUTO_INCLUDE = False
@@ -253,7 +260,10 @@ SASS_PROCESSOR_CUSTOM_FUNCTIONS = {
     "get-colour": "aleksis.core.util.sass_helpers.get_colour",
     "get-config": "aleksis.core.util.sass_helpers.get_config",
 }
-SASS_PROCESSOR_INCLUDE_DIRS = [_settings.get("materialize.sass_path", JS_ROOT + "/materialize-css/sass/"), STATIC_ROOT]
+SASS_PROCESSOR_INCLUDE_DIRS = [
+    _settings.get("materialize.sass_path", JS_ROOT + "/materialize-css/sass/"),
+    STATIC_ROOT,
+]
 
 ADMINS = _settings.get("contact.admins", [])
 SERVER_EMAIL = _settings.get("contact.from", "root@localhost")
@@ -272,7 +282,7 @@ if _settings.get("mail.server.host", None):
 
 TEMPLATE_VISIBLE_SETTINGS = ["ADMINS", "DEBUG"]
 
-CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
+CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend"
 CONSTANCE_ADDITIONAL_FIELDS = {
     "image_field": ["django.forms.ImageField", {}],
     "email_field": ["django.forms.EmailField", {}],
@@ -326,3 +336,31 @@ if _settings.get("2fa.twilio.sid", None):
     TWILIO_CALLER_ID = _settings.get("2fa.twilio.callerid")
 
 _settings.populate_obj(sys.modules[__name__])
+
+PWA_APP_NAME = "AlekSIS"  # dbsettings
+PWA_APP_DESCRIPTION = "AlekSIS – The free school information system"  # dbsettings
+PWA_APP_THEME_COLOR = _settings.get("pwa.color", "#da1f3d")  # dbsettings
+PWA_APP_BACKGROUND_COLOR = "#ffffff"
+PWA_APP_DISPLAY = "standalone"
+PWA_APP_SCOPE = "/"
+PWA_APP_ORIENTATION = "any"
+PWA_APP_START_URL = "/"
+PWA_APP_ICONS = [  # three icons to upload dbsettings
+    {"src": STATIC_URL + "/icons/android_192.png", "sizes": "192x192"},
+    {"src": STATIC_URL + "/icons/android_512.png", "sizes": "512x512"},
+]
+PWA_APP_ICONS_APPLE = [
+    {"src": STATIC_URL + "/icons/apple_76.png", "sizes": "76x76"},
+    {"src": STATIC_URL + "/icons/apple_114.png", "sizes": "114x114"},
+    {"src": STATIC_URL + "/icons/apple_152.png", "sizes": "152x152"},
+    {"src": STATIC_URL + "/icons/apple_180.png", "sizes": "180x180"},
+]
+PWA_APP_SPLASH_SCREEN = [
+    {
+        "src": STATIC_URL + "/icons/android_512.png",
+        "media": "(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)",
+    }
+]
+PWA_APP_DIR = "ltr"
+PWA_SERVICE_WORKER_PATH = os.path.join(STATIC_ROOT, "js", "serviceworker.js")
+# PWA_APP_LANG = 'de-DE'
diff --git a/aleksis/core/static/common/favicon.ico b/aleksis/core/static/common/favicon.ico
deleted file mode 100644
index 1405a12165083ca70bcdfffeacc527f5986a3785..0000000000000000000000000000000000000000
Binary files a/aleksis/core/static/common/favicon.ico and /dev/null differ
diff --git a/aleksis/core/static/common/logo.png b/aleksis/core/static/common/logo.png
deleted file mode 100644
index 005e0b6c64a3475a0ccaa875caaf7673c14b8fd2..0000000000000000000000000000000000000000
Binary files a/aleksis/core/static/common/logo.png and /dev/null differ
diff --git a/aleksis/core/static/common/serviceworker.js b/aleksis/core/static/common/serviceworker.js
deleted file mode 100644
index 76a52613b4a8a08e45d06b1fdcc54a74e47f5913..0000000000000000000000000000000000000000
--- a/aleksis/core/static/common/serviceworker.js
+++ /dev/null
@@ -1,115 +0,0 @@
-//This is the SchoolApps service worker
-
-const CACHE = "schoolapps-cache";
-
-const precacheFiles = [
-    '',
-    '/faq/',
-];
-
-const offlineFallbackPage = '/offline';
-
-const avoidCachingPaths = [
-    '/admin',
-    '/settings',
-    '/support',
-    '/tools',
-    '/faq/ask',
-    '/aub/apply_for',
-    '/aub/check1',
-    '/aub/check2',
-    '/aktuell.pdf',
-    '/accounts/login',
-    '/timetable/aktuell.pdf',
-    '/api',
-];
-
-function pathComparer(requestUrl, pathRegEx) {
-  return requestUrl.match(new RegExp(pathRegEx));
-}
-
-function comparePaths(requestUrl, pathsArray) {
-  if (requestUrl) {
-    for (let index = 0; index < pathsArray.length; index++) {
-      const pathRegEx = pathsArray[index];
-      if (pathComparer(requestUrl, pathRegEx)) {
-        return true;
-      }
-    }
-  }
-
-  return false;
-}
-
-self.addEventListener("install", function (event) {
-  console.log("[SchoolApps PWA] Install Event processing.");
-
-  console.log("[SchoolApps PWA] Skipping waiting on install.");
-  self.skipWaiting();
-
-  event.waitUntil(
-    caches.open(CACHE).then(function (cache) {
-      console.log("[SchoolApps PWA] Caching pages during install.");
-
-      return cache.addAll(precacheFiles).then(function () {
-        return cache.add(offlineFallbackPage);
-      });
-    })
-  );
-});
-
-// Allow sw to control of current page
-self.addEventListener("activate", function (event) {
-  console.log("[SchoolApps PWA] Claiming clients for current page.");
-  event.waitUntil(self.clients.claim());
-});
-
-// If any fetch fails, it will look for the request in the cache and serve it from there first
-self.addEventListener("fetch", function (event) {
-  if (event.request.method !== "GET") return;
-  networkFirstFetch(event);
-});
-
-function networkFirstFetch(event) {
-  event.respondWith(
-    fetch(event.request)
-      .then(function (response) {
-        // If request was successful, add or update it in the cache
-        console.log("[SchoolApps PWA] Network request successful.");
-        event.waitUntil(updateCache(event.request, response.clone()));
-        return response;
-      })
-      .catch(function (error) {
-        console.log("[SchoolApps PWA] Network request failed. Serving content from cache: " + error);
-        return fromCache(event);
-      })
-  );
-}
-
-function fromCache(event) {
-  // Check to see if you have it in the cache
-  // Return response
-  // If not in the cache, then return offline fallback page
-  return caches.open(CACHE).then(function (cache) {
-    return cache.match(event.request)
-    .then(function (matching) {
-      if (!matching || matching.status === 404) {
-        console.log("[SchoolApps PWA] Cache request failed. Serving offline fallback page.");
-        // Use the precached offline page as fallback
-        return caches.match(offlineFallbackPage)
-      }
-
-      return matching;
-    });
-  });
-}
-
-function updateCache(request, response) {
-  if (!comparePaths(request.url, avoidCachingPaths)) {
-    return caches.open(CACHE).then(function (cache) {
-      return cache.put(request, response);
-    });
-  }
-
-  return Promise.resolve();
-}
diff --git a/aleksis/core/static/js/serviceworker.js b/aleksis/core/static/js/serviceworker.js
new file mode 100644
index 0000000000000000000000000000000000000000..93d9797c78db9756cba6f1f6caa46fc8c14ae57b
--- /dev/null
+++ b/aleksis/core/static/js/serviceworker.js
@@ -0,0 +1,105 @@
+// This is the AlekSIS service worker
+
+const CACHE = "aleksis-cache";
+
+const precacheFiles = [
+    '',
+];
+
+const offlineFallbackPage = '/offline';
+
+const avoidCachingPaths = [
+    '/admin',
+    '/settings',
+    '/accounts/login'
+]; // TODO: More paths are needed
+
+function pathComparer(requestUrl, pathRegEx) {
+    return requestUrl.match(new RegExp(pathRegEx));
+}
+
+function comparePaths(requestUrl, pathsArray) {
+    if (requestUrl) {
+        for (let index = 0; index < pathsArray.length; index++) {
+            const pathRegEx = pathsArray[index];
+            if (pathComparer(requestUrl, pathRegEx)) {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+self.addEventListener("install", function (event) {
+    console.log("[AlekSIS PWA] Install Event processing.");
+
+    console.log("[AlekSIS PWA] Skipping waiting on install.");
+    self.skipWaiting();
+
+    event.waitUntil(
+        caches.open(CACHE).then(function (cache) {
+            console.log("[AlekSIS PWA] Caching pages during install.");
+
+            return cache.addAll(precacheFiles).then(function () {
+                return cache.add(offlineFallbackPage);
+            });
+        })
+    );
+});
+
+// Allow sw to control of current page
+self.addEventListener("activate", function (event) {
+    console.log("[AlekSIS PWA] Claiming clients for current page.");
+    event.waitUntil(self.clients.claim());
+});
+
+// If any fetch fails, it will look for the request in the cache and serve it from there first
+self.addEventListener("fetch", function (event) {
+    if (event.request.method !== "GET") return;
+    networkFirstFetch(event);
+});
+
+function networkFirstFetch(event) {
+    event.respondWith(
+        fetch(event.request)
+            .then(function (response) {
+                // If request was successful, add or update it in the cache
+                console.log("[AlekSIS PWA] Network request successful.");
+                event.waitUntil(updateCache(event.request, response.clone()));
+                return response;
+            })
+            .catch(function (error) {
+                console.log("[AlekSIS PWA] Network request failed. Serving content from cache: " + error);
+                return fromCache(event);
+            })
+    );
+}
+
+function fromCache(event) {
+    // Check to see if you have it in the cache
+    // Return response
+    // If not in the cache, then return offline fallback page
+    return caches.open(CACHE).then(function (cache) {
+        return cache.match(event.request)
+            .then(function (matching) {
+                if (!matching || matching.status === 404) {
+                    console.log("[AlekSIS PWA] Cache request failed. Serving offline fallback page.");
+                    // Use the precached offline page as fallback
+                    return caches.match(offlineFallbackPage)
+                }
+
+                return matching;
+            });
+    });
+}
+
+function updateCache(request, response) {
+    if (!comparePaths(request.url, avoidCachingPaths)) {
+        return caches.open(CACHE).then(function (cache) {
+            return cache.put(request, response);
+        });
+    }
+
+    return Promise.resolve();
+}
diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html
index a612ec66af3e782faa6ec441b0303425995fc474..e98f71587ed93f987ed710c0dd7721f86f21a407 100644
--- a/aleksis/core/templates/core/base.html
+++ b/aleksis/core/templates/core/base.html
@@ -1,7 +1,7 @@
 {# -*- engine:django -*- #}
 
 
-{% load i18n menu_generator static sass_tags any_js %}
+{% load i18n menu_generator static sass_tags any_js pwa %}
 
 
 <!DOCTYPE html>
@@ -14,7 +14,13 @@
 
   <title>School Information System</title>
 
-  {% include 'core/icons.html' %}
+  {# Favicons #}
+  <link href="{% static "icons/favicon_16.png" %}" rel="icon" type="image/png" sizes="16x16">
+  <link href="{% static "icons/favicon_32.png" %}" rel="icon" type="image/png" sizes="32x32">
+  <link href="{% static "icons/favicon_48.png" %}" rel="icon" type="image/png" sizes="48x48">
+
+  <!-- PWA -->
+  {% progressive_web_app_meta %}
 
   {# CSS #}
   {% include_css "material-design-icons" %}
diff --git a/aleksis/core/templates/core/icons.html b/aleksis/core/templates/core/icons.html
deleted file mode 100644
index 53e98aadd7f53a25431dfc1fa6d72aa9b53617a1..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/core/icons.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% load static %}
-
-<link rel="shortcut icon" type="image/png" href="{% static 'img/aleksis-logo.png' %}" />
-<link rel="shortcut icon" sizes="196x196" href="{% static 'img/aleksis-logo.png' %}" />
-<link rel="apple-touch-icon" href="{% static 'img/aleksis-logo.png' %}" />
diff --git a/aleksis/core/templates/partials/header.html b/aleksis/core/templates/partials/header.html
index ceb4065c957413766d0716a3bffe7c4a4953d5d9..c3edc1caa05eb2e6863ffef1ea3e7672059b05bb 100644
--- a/aleksis/core/templates/partials/header.html
+++ b/aleksis/core/templates/partials/header.html
@@ -68,10 +68,8 @@
     <!-- Favicon -->
     <link rel="shortcut icon" type="image/x-icon" href="{% static 'common/favicon.ico' %}">
 
-    <!-- PWA -->
-    {% progressive_web_app_meta %}
 
-    <!--------->
+  <!--------->
     <!-- CSS -->
     <!--------->
     <link href="{% static 'css/materialdesignicons-webfont/material-icons.css' %}" rel="stylesheet">
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index 17e71cc0c4d928e52bd0ecdd3f24c801b1574c3d..0ac57aabe965aa6017d455a3468eddf43f0abe80 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -11,6 +11,8 @@ from two_factor.urls import urlpatterns as tf_urls
 from . import views
 
 urlpatterns = [
+    path("", include("pwa.urls"), name="pwa"),
+    path("offline/", views.offline, name="offline"),
     path("admin/", admin.site.urls),
     path("data_management/", views.data_management, name="data_management"),
     path("status/", views.system_status, name="system_status"),
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index c435da07c021dc8b8c557a70b3e4be46eb26f66d..709ebe26d917c4d5fad9ffe2afc0ccdbab4bb2db 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -25,6 +25,10 @@ def index(request: HttpRequest) -> HttpResponse:
     return render(request, "core/index.html", context)
 
 
+def offline(request):
+    return render(request, "common/offline.html")
+
+
 @login_required
 def persons(request: HttpRequest) -> HttpResponse:
     context = {}
diff --git a/pyproject.toml b/pyproject.toml
index 72552092a7ad82aca51d4cc3e0fbb07cde049595..15f4716bb42aaaf793586ec572d5998c50867217 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -49,6 +49,7 @@ requests = "^2.22"
 django-two-factor-auth = { version = "^1.10.0", extras = [ "YubiKey", "phonenumbers", "Call", "SMS" ] }
 django-yarnpkg = "^6.0"
 django-material = "^1.6.0"
+django-pwa = "^1.0.6"
 django-constance = {git = "https://github.com/jazzband/django-constance", rev = "590fa02eb30e377da0eda5cc3a84254b839176a7", extras = ["database"]}
 
 [tool.poetry.extras]
diff --git a/requirements.txt b/requirements.txt
index 8b767d1c91300b74fb4bce041688565787ae8949..08d6e1aa88a09416668bf8e0238fe0ef44cff4bf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,6 +4,5 @@ django-filter
 django_react_templatetags
 PyPDF2
 martor
-django-pwa
 django_widget_tweaks
 ics