diff --git a/aleksis/core/assets/app.js b/aleksis/core/assets/app.js
index 1ca58bdc9757233e354e643c117b2d315d3e4384..c90386ef2c2206323ee976f6d2e921ac92524a7f 100644
--- a/aleksis/core/assets/app.js
+++ b/aleksis/core/assets/app.js
@@ -13,6 +13,7 @@ import VueI18n from "vue-i18n";
 
 import messages from "./messages.json";
 import dateTimeFormats from "./dateTimeFormats.js";
+import routes from "./routes.js";
 
 Vue.use(Vuetify);
 
@@ -75,6 +76,7 @@ const apolloProvider = new VueApollo({
 
 const router = new VueRouter({
   mode: "history",
+  routes,
 });
 
 if (document.getElementById('sentry_settings') !== null) {
diff --git a/aleksis/core/assets/index.js b/aleksis/core/assets/index.js
index 911bcfcdd3dc7df8d291e5f372babe01ac9694a9..b99642563a5279ebf524304ac5cd422b1f142846 100644
--- a/aleksis/core/assets/index.js
+++ b/aleksis/core/assets/index.js
@@ -1,783 +1,2 @@
 import "./util";
 import "./app";
-
-import LegacyBaseTemplate from "./components/LegacyBaseTemplate.vue";
-import Parent from "./components/Parent.vue";
-
-// This imports all known AlekSIS app entrypoints
-// The list is generated by util/frontent_helpers.py and passed to Vite,
-//  which aliases the app package names into virtual JavaScript modules
-//  and generates importing code at bundle time.
-import apps from "aleksisAppImporter";
-
-import CeleryProgress from "./components/celery_progress/CeleryProgress.vue";
-import About from "./components/about/About.vue";
-import Error404 from "./components/Error404.vue";
-
-for (const { appName, appRoutes } of Object.entries(apps)) {
-  window.router.addRoute({
-    path: `/app/${appName}`,
-    name: `${appName}`,
-    children: appRoutes,
-  });
-}
-
-window.router.addRoute({
-  path: "/account/login/",
-  name: "core.account.login",
-  component: LegacyBaseTemplate,
-});
-window.router.addRoute({
-  path: "/accounts/login/",
-  name: "core.accounts.login",
-  component: LegacyBaseTemplate,
-  meta: {
-    icon: "mdi-login-variant",
-    titleKey: "accounts.login.menu_title",
-  },
-});
-window.router.addRoute({
-  path: "/accounts/signup/",
-  name: "core.accounts.signup",
-  component: LegacyBaseTemplate,
-  meta: {
-    icon: "mdi-account-plus-outline",
-    titleKey: "accounts.signup.menu_title",
-  },
-});
-window.router.addRoute({
-  path: "/invitations/code/enter/",
-  name: "core.invitations.enterCode",
-  component: LegacyBaseTemplate,
-  meta: {
-    icon: "mdi-key-outline",
-    titleKey: "accounts.invitation.accept_invitation.menu_title",
-    permission: "core.invite_enabled", // FIXME
-  },
-});
-window.router.addRoute({
-  path: "",
-  name: "dashboard",
-  component: LegacyBaseTemplate,
-  meta: {
-    inMenu: true,
-    icon: "mdi-home-outline",
-    titleKey: "dashboard.menu_title",
-    permission: "core.view_dashboard_rule",
-  },
-});
-window.router.addRoute({
-  path: "/people",
-  name: "core.people",
-  component: Parent,
-  meta: {
-    inMenu: true,
-    titleKey: "people",
-    icon: "mdi-account-group-outline",
-    permission: "core.view_people_menu_rule",
-  },
-  children: [
-    {
-      path: "/persons",
-      component: LegacyBaseTemplate,
-      name: "core.persons",
-      meta: {
-        inMenu: true,
-        titleKey: "person.menu_title",
-        icon: "mdi-account-outline",
-        permission: "core.view_persons_rule",
-      },
-    },
-    {
-      path: "/persons/create/",
-      component: LegacyBaseTemplate,
-      name: "core.createPerson",
-    },
-    {
-      path: "/persons/:id(\\d+)/",
-      component: LegacyBaseTemplate,
-      name: "core.personById",
-    },
-    {
-      path: "/persons/:id(\\d+)/edit/",
-      component: LegacyBaseTemplate,
-      name: "core.editPerson",
-    },
-    {
-      path: "/persons/:id(\\d+)/delete/",
-      component: LegacyBaseTemplate,
-      name: "core.deletePerson",
-    },
-    {
-      path: "/persons/:id(\\d+)/invite/",
-      component: LegacyBaseTemplate,
-      name: "core.invitePerson",
-    },
-    {
-      path: "/groups",
-      component: LegacyBaseTemplate,
-      name: "core.groups",
-      meta: {
-        inMenu: true,
-        titleKey: "group.menu_title",
-        icon: "mdi-account-multiple-outline",
-        permission: "core.view_groups_rule",
-      },
-    },
-    {
-      path: "/groups/create",
-      component: LegacyBaseTemplate,
-      name: "core.createGroup",
-    },
-    {
-      path: "/groups/:id(\\d+)",
-      component: LegacyBaseTemplate,
-      name: "core.group",
-    },
-    {
-      path: "/groups/:id(\\d+)/edit",
-      component: LegacyBaseTemplate,
-      name: "core.editGroup",
-    },
-    {
-      path: "/groups/:id(\\d+)/delete",
-      component: LegacyBaseTemplate,
-      name: "core.deleteGroup",
-    },
-    {
-      path: "/groups/group_types",
-      component: LegacyBaseTemplate,
-      name: "core.groupTypes",
-      meta: {
-        inMenu: true,
-        titleKey: "group.group_type.menu_title",
-        icon: "mdi-shape-outline",
-        permission: "core.view_grouptypes_rule",
-      },
-    },
-    {
-      path: "/groups/group_types/create",
-      component: LegacyBaseTemplate,
-      name: "core.createGroupType",
-    },
-    {
-      path: "/groups/group_types/:id(\\d+)/delete",
-      component: LegacyBaseTemplate,
-      name: "core.deleteGroupType,",
-    },
-    {
-      path: "/groups/group_types/:id(\\d+)/edit",
-      component: LegacyBaseTemplate,
-      name: "core.editGroupType",
-    },
-
-    {
-      path: "/groups/child_groups/",
-      component: LegacyBaseTemplate,
-      name: "core.groupsChildGroups",
-      meta: {
-        inMenu: true,
-        titleKey: "group.groups_and_child_groups",
-        icon: "mdi-account-multiple-plus-outline",
-        permission: "core.assign_child_groups_to_groups_rule",
-      },
-    },
-    {
-      path: "/groups/additional_fields",
-      component: LegacyBaseTemplate,
-      name: "core.additionalFields",
-      meta: {
-        inMenu: true,
-        titleKey: "group.additional_field.menu_title",
-        icon: "mdi-palette-swatch-outline",
-        permission: "core.view_additionalfields_rule",
-      },
-    },
-    {
-      path: "/groups/additional_fields/:id(\\d+)/edit",
-      component: LegacyBaseTemplate,
-      name: "core.editAdditionalField,",
-    },
-    {
-      path: "/groups/additional_fields/create",
-      component: LegacyBaseTemplate,
-      name: "core.createAdditionalField",
-    },
-    {
-      path: "/groups/additional_fields/:id(\\d+)/delete",
-      component: LegacyBaseTemplate,
-      name: "core.deleteAdditionalField",
-    },
-    {
-      path: "/invitations/send-invite",
-      component: LegacyBaseTemplate,
-      name: "core.invite_person",
-      meta: {
-        inMenu: true,
-        titleKey: "accounts.invitation.invite_person.menu_title",
-        icon: "mdi-account-plus-outline",
-        permission: "core.invite_rule",
-      },
-    },
-  ],
-});
-
-window.router.addRoute({
-  path: "#",
-  component: LegacyBaseTemplate,
-  name: "core.administration",
-  meta: {
-    inMenu: true,
-    titleKey: "administration.menu_title",
-    icon: "mdi-security",
-    permission: "core.view_admin_menu_rule",
-  },
-  children: [
-    {
-      path: "/announcements/",
-      component: LegacyBaseTemplate,
-      name: "core.announcements",
-      meta: {
-        inMenu: true,
-        titleKey: "announcement.menu_title",
-        icon: "mdi-message-alert-outline",
-        permission: "core.view_announcements_rule",
-      },
-    },
-    {
-      path: "/announcements/create/",
-      component: LegacyBaseTemplate,
-      name: "core.addAnnouncement",
-    },
-    {
-      path: "/announcements/edit/:id(\\d+)/",
-      component: LegacyBaseTemplate,
-      name: "core.editAnnouncement",
-    },
-    {
-      path: "/announcements/delete/:id(\\d+)/",
-      component: LegacyBaseTemplate,
-      name: "core.deleteAnnouncement",
-    },
-    {
-      path: "/school_terms/",
-      component: LegacyBaseTemplate,
-      name: "core.school_terms",
-      meta: {
-        inMenu: true,
-        titleKey: "school_term.menu_title",
-        icon: "mdi-calendar-range-outline",
-        permission: "core.view_schoolterm_rule",
-      },
-    },
-    {
-      path: "/school_terms/create/",
-      component: LegacyBaseTemplate,
-      name: "core.create_school_term",
-    },
-    {
-      path: "/school_terms/:pk(\\d+)/",
-      component: LegacyBaseTemplate,
-      name: "core.editSchoolTerm",
-    },
-    {
-      path: "/dashboard_widgets/",
-      component: LegacyBaseTemplate,
-      name: "core.dashboardWidgets",
-      meta: {
-        inMenu: true,
-        titleKey: "dashboard.dashboard_widget.menu_title",
-        icon: "mdi-view-dashboard-outline",
-        permission: "core.view_dashboardwidget_rule",
-      },
-    },
-    {
-      path: "/dashboard_widgets/:pk(\\d+)/edit/",
-      component: LegacyBaseTemplate,
-      name: "core.editDashboardWidget",
-    },
-    {
-      path: "/dashboard_widgets/:pk(\\d+)/delete/",
-      component: LegacyBaseTemplate,
-      name: "core.deleteDashboardWidget",
-    },
-    {
-      path: "/dashboard_widgets/:app/:model/new/",
-      component: LegacyBaseTemplate,
-      name: "core.createDashboardWidget",
-    },
-    {
-      path: "/dashboard_widgets/default/",
-      component: LegacyBaseTemplate,
-      name: "core.editDefaultDashboard",
-    },
-    {
-      path: "/status/",
-      component: LegacyBaseTemplate,
-      name: "core.system_status",
-      meta: {
-        inMenu: true,
-        titleKey: "administration.system_status.menu_title",
-        icon: "mdi-power-settings",
-        permission: "core.view_system_status_rule",
-      },
-    },
-    {
-      path: "/preferences/site/",
-      component: LegacyBaseTemplate,
-      name: "core.preferencesSite",
-      meta: {
-        inMenu: true,
-        titleKey: "preferences.site.menu_title",
-        icon: "mdi-tune",
-        permission: "core.change_site_preferences_rule",
-      },
-    },
-    {
-      path: "/preferences/site/:pk(\\d+)/",
-      component: LegacyBaseTemplate,
-      name: "core.preferencesSiteByPk",
-    },
-    {
-      path: "/preferences/site/:pk(\\d+)/:section/",
-      component: LegacyBaseTemplate,
-      name: "core.preferencesSiteByPkSection",
-    },
-    {
-      path: "/preferences/site/:section/",
-      component: LegacyBaseTemplate,
-      name: "core.preferencesSiteSection",
-    },
-    {
-      path: "/data_checks/",
-      component: LegacyBaseTemplate,
-      name: "core.checkData",
-      meta: {
-        inMenu: true,
-        titleKey: "data_check.menu_title",
-        icon: "mdi-list-status",
-        permission: "core.view_datacheck_rule", // FIXME PERMISSION
-      },
-    },
-    {
-      path: "/data_checks/run/",
-      component: LegacyBaseTemplate,
-      name: "core.runDataChecks",
-    },
-    {
-      path: "/data_checks/:pk(\\d+)/:solve_option/",
-      component: LegacyBaseTemplate,
-      name: "core.solveDataCheck",
-    },
-
-    {
-      path: "/permissions/global/user/",
-      component: LegacyBaseTemplate,
-      name: "core.managerUserGlobalPermissions",
-      meta: {
-        inMenu: true,
-        titleKey: "permissions.manage.menu_title",
-        icon: "mdi-shield-outline",
-        permission: "core.manage_permissions_rule",
-      },
-    },
-    {
-      path: "/permissions/global/group/",
-      component: LegacyBaseTemplate,
-      name: "core.manageGroupGlobalPermissions",
-    },
-    {
-      path: "/permissions/object/user/",
-      component: LegacyBaseTemplate,
-      name: "core.manageUserObjectPermissions",
-    },
-    {
-      path: "/permissions/object/group/",
-      component: LegacyBaseTemplate,
-      name: "core.manageGroupObjectPermissions",
-    },
-    {
-      path: "/permissions/global/user/:pk(\\d+)/delete/",
-      component: LegacyBaseTemplate,
-      name: "core.deleteUserGlobalPermission,",
-    },
-    {
-      path: "/permissions/global/group/:pk(\\d+)/delete/",
-      component: LegacyBaseTemplate,
-      name: "core.deleteGroupGlobalPermission",
-    },
-    {
-      path: "/permissions/object/user/:pk(\\d+)/delete/",
-      component: LegacyBaseTemplate,
-      name: "core.deleteUserObjectPermission",
-    },
-    {
-      path: "/permissions/object/group/:pk(\\d+)/delete/",
-      component: LegacyBaseTemplate,
-      name: "core.deleteGroupObjectPermission",
-    },
-    {
-      path: "/permissions/assign/",
-      component: LegacyBaseTemplate,
-      name: "core.selectPermissionforAssign",
-    },
-    {
-      path: "/permissions/:pk(\\d+)/assign/",
-      component: LegacyBaseTemplate,
-      name: "core.assignPermission",
-    },
-
-    {
-      path: "/oauth/applications/",
-      component: LegacyBaseTemplate,
-      name: "core.oauthApplications",
-      meta: {
-        inMenu: true,
-        titleKey: "oauth.application.menu_title",
-        icon: "mdi-gesture-tap-hold",
-        permission: "core.view_django_admin_rule", //FIXME: add permission
-      },
-    },
-    {
-      path: "/oauth/applications/register/",
-      component: LegacyBaseTemplate,
-      name: "core.registerOauthApplication,",
-    },
-    {
-      path: "/oauth/applications/:pk(\\d+)/",
-      component: LegacyBaseTemplate,
-      name: "core.oauthApplication",
-    },
-    {
-      path: "/oauth/applications/:pk(\\d+)/delete/",
-      component: LegacyBaseTemplate,
-      name: "core.delete_oauth2_application,",
-    },
-    {
-      path: "/oauth/applications/:pk(\\d+)/edit/",
-      component: LegacyBaseTemplate,
-      name: "core.editOauthApplication",
-    },
-    {
-      path: "/admin/",
-      component: LegacyBaseTemplate,
-      name: "core.admin",
-      meta: {
-        inMenu: true,
-        titleKey: "administration.backend_admin.menu_title",
-        icon: "mdi-database-cog-outline",
-        newTab: true,
-      },
-    },
-  ],
-});
-
-// ACCOUNT MENU
-
-// STOP IMPERSONATION FIXME
-window.router.addRoute({
-  path: "/person/",
-  component: LegacyBaseTemplate,
-  name: "core.person",
-  meta: {
-    inAccountMenu: true,
-    titleKey: "person.account_menu_title",
-    icon: "mdi-account-outline",
-    permission: "core.view_person_rule", // FIXME PERMISSION
-  },
-});
-
-window.router.addRoute({
-  path: "/preferences/person/",
-  component: LegacyBaseTemplate,
-  name: "core.preferencesPerson",
-  meta: {
-    inAccountMenu: true,
-    titleKey: "preferences.person.menu_title",
-    icon: "mdi-cog-outline",
-    permission: "core.change_person_preferences_rule", // FIXME PERMISSION
-  },
-});
-
-window.router.addRoute({
-  path: "/preferences/person/:pk(\\d+)/",
-  component: LegacyBaseTemplate,
-  name: "core.preferencesPersonByPk",
-});
-window.router.addRoute({
-  path: "/preferences/person/:pk(\\d+)/:section/",
-  component: LegacyBaseTemplate,
-  name: "core.preferencesPersonByPkSection",
-});
-window.router.addRoute({
-  path: "/preferences/person/:section/",
-  component: LegacyBaseTemplate,
-  name: "core.preferencesPersonSection",
-});
-
-window.router.addRoute({
-  path: "/account/two_factor/",
-  component: LegacyBaseTemplate,
-  name: "core.twoFactor",
-  meta: {
-    inAccountMenu: true,
-    titleKey: "accounts.two_factor.menu_title",
-    icon: "mdi-two-factor-authentication",
-  },
-});
-window.router.addRoute({
-  path: "/account/two_factor/setup/",
-  component: LegacyBaseTemplate,
-  name: "core.twoFactor.setup",
-});
-window.router.addRoute({
-  path: "/account/two_factor/qrcode/",
-  component: LegacyBaseTemplate,
-  name: "core.twoFactor.qrcode",
-});
-window.router.addRoute({
-  path: "/account/two_factor/setup/complete/",
-  component: LegacyBaseTemplate,
-  name: "core.twoFactor.setupComplete",
-});
-window.router.addRoute({
-  path: "/account/two_factor/backup/tokens/",
-  component: LegacyBaseTemplate,
-  name: "core.twoFactor.backupTokens",
-});
-window.router.addRoute({
-  path: "/account/two_factor/backup/phone/register",
-  component: LegacyBaseTemplate,
-  name: "core.twoFactor.registerBackupPhone",
-});
-window.router.addRoute({
-  path: "/account/two_factor/disable/",
-  component: LegacyBaseTemplate,
-  name: "core.twoFactor.disable",
-});
-
-window.router.addRoute({
-  path: "/accounts/password/change/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.changePassword",
-  meta: {
-    inAccountMenu: true,
-    titleKey: "accounts.change_password.menu_title",
-    icon: "mdi-form-textbox-password",
-    permission: "core.can_change_password", // FIXME PERMISSION
-  },
-});
-window.router.addRoute({
-  path: "/accounts/password/set/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.setPassword",
-});
-window.router.addRoute({
-  path: "/accounts/password/reset/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.resetPassword",
-});
-window.router.addRoute({
-  path: "/accounts/password/reset/done/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.resetPasswordDone",
-});
-window.router.addRoute({
-  path: "/accounts/password/reset/key/:key/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.resetPasswordConfirm",
-});
-window.router.addRoute({
-  path: "/accounts/password/reset/key/done/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.resetPasswordConfirmDone",
-});
-window.router.addRoute({
-  path: "/accounts/inactive/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.inactive",
-});
-window.router.addRoute({
-  path: "/accounts/email/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.email",
-});
-window.router.addRoute({
-  path: "/accounts/confirm-email/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.confirmEmail",
-});
-window.router.addRoute({
-  path: "/accounts/confirm-email/:key/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.confirmEmailKey",
-});
-
-window.router.addRoute({
-  path: "/accounts/social/login/cancelled/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.socialLoginCancelled",
-});
-window.router.addRoute({
-  path: "/accounts/social/login/error/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.socialLoginError",
-});
-window.router.addRoute({
-  path: "/accounts/social/signup/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.socialSignup",
-});
-window.router.addRoute({
-  path: "/accounts/social/connections/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.socialConnections",
-  meta: {
-    inAccountMenu: true,
-    titleKey: "accounts.social_connections.menu_title",
-    icon: "mdi-earth",
-    permission: "core.can_change_password", // FIXME PERMISSION
-  },
-});
-window.router.addRoute({
-  path: "/oauth/authorized_tokens/",
-  component: LegacyBaseTemplate,
-  name: "core.oauth.authorizedTokens",
-  meta: {
-    inAccountMenu: true,
-    titleKey: "oauth.authorized_token.menu_title",
-    icon: "mdi-gesture-tap-hold",
-    permission: "core.can_change_password", // FIXME PERMISSION
-  },
-});
-window.router.addRoute({
-  path: "/oauth/authorized_tokens/:pk(\\d+)/delete/",
-  component: LegacyBaseTemplate,
-  name: "core.oauth.deleteAuthorizedToken",
-});
-
-window.router.addRoute({
-  path: "/ical/",
-  component: LegacyBaseTemplate,
-  name: "core.icalFeeds",
-  meta: {
-    inAccountMenu: true,
-    titleKey: "ical_feed.menu_title",
-    icon: "mdi-calendar-multiple",
-    permission: "core.view_ical_rule",
-  },
-});
-window.router.addRoute({
-  path: "/ical/create/",
-  component: LegacyBaseTemplate,
-  name: "core.createIcalFeed",
-});
-window.router.addRoute({
-  path: "/ical/:pk(\\d+)/edit/",
-  component: LegacyBaseTemplate,
-  name: "core.editIcalFeed",
-});
-window.router.addRoute({
-  path: "/ical/:pk(\\d+)/delete/",
-  component: LegacyBaseTemplate,
-  name: "core.deleteIcalFeed",
-});
-window.router.addRoute({
-  path: "/ical/:slug('.ics'$)",
-  component: LegacyBaseTemplate,
-  name: "core.icalFeed",
-});
-
-window.router.addRoute({
-  path: "/accounts/logout/",
-  component: LegacyBaseTemplate,
-  name: "core.accounts.logout",
-  meta: {
-    inAccountMenu: true,
-    titleKey: "accounts.logout.menu_title",
-    icon: "mdi-logout-variant",
-    permission: "core.can_change_password", // FIXME PERMISSION
-    divider: true,
-  },
-});
-
-window.router.addRoute({
-  path: "/invitations/code/enter",
-  component: LegacyBaseTemplate,
-  name: "core.enter_invitation_code",
-});
-window.router.addRoute({
-  path: "/invitations/code/generate",
-  component: LegacyBaseTemplate,
-  name: "core.generate_invitation_code",
-});
-window.router.addRoute({
-  path: "/invitations/disabled",
-  component: LegacyBaseTemplate,
-  name: "core.invite_disabled",
-});
-
-window.router.addRoute({
-  path: "/dashboard/edit/",
-  component: LegacyBaseTemplate,
-  name: "core.editDashboard",
-});
-
-window.router.addRoute({
-  path: "/preferences/group/",
-  component: LegacyBaseTemplate,
-  name: "core.preferencesGroup",
-});
-
-window.router.addRoute({
-  path: "/preferences/group/:pk(\\d+)/",
-  component: LegacyBaseTemplate,
-  name: "core.preferencesGroupByPk",
-});
-
-window.router.addRoute({
-  path: "/preferences/group/:pk(\\d+)/:section/",
-  component: LegacyBaseTemplate,
-  name: "core.preferencesGroupByPkSection",
-});
-
-window.router.addRoute({
-  path: "/preferences/group/:section/",
-  component: LegacyBaseTemplate,
-  name: "core.preferencesGroupSection",
-});
-window.router.addRoute({
-  path: "/health/pdf/",
-  component: LegacyBaseTemplate,
-  name: "core.testPdf",
-});
-
-window.router.addRoute({
-  path: "/pdfs/:pk(\\d+)/",
-  component: LegacyBaseTemplate,
-  name: "core.redirectToPdfUrl",
-});
-
-window.router.addRoute({
-  path: "/search/",
-  component: LegacyBaseTemplate,
-  name: "core.haystack_search",
-});
-
-window.router.addRoute({
-  path: "/celery_progress/:taskId",
-  component: CeleryProgress,
-  props: true,
-  name: "core.celery_progress",
-});
-window.router.addRoute({
-  path: "/about",
-  component: About,
-  name: "core.about",
-});
-
-window.router.addRoute({
-  path: "/*",
-  component: Error404,
-  name: "core.error404",
-});
diff --git a/aleksis/core/assets/routes.js b/aleksis/core/assets/routes.js
new file mode 100644
index 0000000000000000000000000000000000000000..2512412f4c0f0556c9c9ae954fd2d89349c5942e
--- /dev/null
+++ b/aleksis/core/assets/routes.js
@@ -0,0 +1,763 @@
+// This imports all known AlekSIS app entrypoints
+// The list is generated by util/frontent_helpers.py and passed to Vite,
+//  which aliases the app package names into virtual JavaScript modules
+//  and generates importing code at bundle time.
+import apps from "aleksisAppImporter";
+
+const routes = [
+  {
+    path: "/account/login/",
+    name: "core.account.login",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+  },
+  {
+    path: "/accounts/login/",
+    name: "core.accounts.login",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    meta: {
+      icon: "mdi-login-variant",
+      titleKey: "accounts.login.menu_title",
+    },
+  },
+  {
+    path: "/accounts/signup/",
+    name: "core.accounts.signup",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    meta: {
+      icon: "mdi-account-plus-outline",
+      titleKey: "accounts.signup.menu_title",
+    },
+  },
+  {
+    path: "/invitations/code/enter/",
+    name: "core.invitations.enterCode",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    meta: {
+      icon: "mdi-key-outline",
+      titleKey: "accounts.invitation.accept_invitation.menu_title",
+      permission: "core.invite_enabled", // FIXME
+    },
+  },
+  {
+    path: "",
+    name: "dashboard",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    meta: {
+      inMenu: true,
+      icon: "mdi-home-outline",
+      titleKey: "dashboard.menu_title",
+      permission: "core.view_dashboard_rule",
+    },
+  },
+  {
+    path: "/people",
+    name: "core.people",
+    component: () => import("./components/Parent.vue"),
+    meta: {
+      inMenu: true,
+      titleKey: "people",
+      icon: "mdi-account-group-outline",
+      permission: "core.view_people_menu_rule",
+    },
+    children: [
+      {
+        path: "/persons",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.persons",
+        meta: {
+          inMenu: true,
+          titleKey: "person.menu_title",
+          icon: "mdi-account-outline",
+          permission: "core.view_persons_rule",
+        },
+      },
+      {
+        path: "/persons/create/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.createPerson",
+      },
+      {
+        path: "/persons/:id(\\d+)/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.personById",
+      },
+      {
+        path: "/persons/:id(\\d+)/edit/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.editPerson",
+      },
+      {
+        path: "/persons/:id(\\d+)/delete/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.deletePerson",
+      },
+      {
+        path: "/persons/:id(\\d+)/invite/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.invitePerson",
+      },
+      {
+        path: "/groups",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.groups",
+        meta: {
+          inMenu: true,
+          titleKey: "group.menu_title",
+          icon: "mdi-account-multiple-outline",
+          permission: "core.view_groups_rule",
+        },
+      },
+      {
+        path: "/groups/create",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.createGroup",
+      },
+      {
+        path: "/groups/:id(\\d+)",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.group",
+      },
+      {
+        path: "/groups/:id(\\d+)/edit",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.editGroup",
+      },
+      {
+        path: "/groups/:id(\\d+)/delete",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.deleteGroup",
+      },
+      {
+        path: "/groups/group_types",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.groupTypes",
+        meta: {
+          inMenu: true,
+          titleKey: "group.group_type.menu_title",
+          icon: "mdi-shape-outline",
+          permission: "core.view_grouptypes_rule",
+        },
+      },
+      {
+        path: "/groups/group_types/create",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.createGroupType",
+      },
+      {
+        path: "/groups/group_types/:id(\\d+)/delete",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.deleteGroupType,",
+      },
+      {
+        path: "/groups/group_types/:id(\\d+)/edit",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.editGroupType",
+      },
+
+      {
+        path: "/groups/child_groups/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.groupsChildGroups",
+        meta: {
+          inMenu: true,
+          titleKey: "group.groups_and_child_groups",
+          icon: "mdi-account-multiple-plus-outline",
+          permission: "core.assign_child_groups_to_groups_rule",
+        },
+      },
+      {
+        path: "/groups/additional_fields",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.additionalFields",
+        meta: {
+          inMenu: true,
+          titleKey: "group.additional_field.menu_title",
+          icon: "mdi-palette-swatch-outline",
+          permission: "core.view_additionalfields_rule",
+        },
+      },
+      {
+        path: "/groups/additional_fields/:id(\\d+)/edit",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.editAdditionalField,",
+      },
+      {
+        path: "/groups/additional_fields/create",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.createAdditionalField",
+      },
+      {
+        path: "/groups/additional_fields/:id(\\d+)/delete",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.deleteAdditionalField",
+      },
+      {
+        path: "/invitations/send-invite",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.invite_person",
+        meta: {
+          inMenu: true,
+          titleKey: "accounts.invitation.invite_person.menu_title",
+          icon: "mdi-account-plus-outline",
+          permission: "core.invite_rule",
+        },
+      },
+    ],
+  },
+  {
+    path: "#",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.administration",
+    meta: {
+      inMenu: true,
+      titleKey: "administration.menu_title",
+      icon: "mdi-security",
+      permission: "core.view_admin_menu_rule",
+    },
+    children: [
+      {
+        path: "/announcements/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.announcements",
+        meta: {
+          inMenu: true,
+          titleKey: "announcement.menu_title",
+          icon: "mdi-message-alert-outline",
+          permission: "core.view_announcements_rule",
+        },
+      },
+      {
+        path: "/announcements/create/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.addAnnouncement",
+      },
+      {
+        path: "/announcements/edit/:id(\\d+)/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.editAnnouncement",
+      },
+      {
+        path: "/announcements/delete/:id(\\d+)/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.deleteAnnouncement",
+      },
+      {
+        path: "/school_terms/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.school_terms",
+        meta: {
+          inMenu: true,
+          titleKey: "school_term.menu_title",
+          icon: "mdi-calendar-range-outline",
+          permission: "core.view_schoolterm_rule",
+        },
+      },
+      {
+        path: "/school_terms/create/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.create_school_term",
+      },
+      {
+        path: "/school_terms/:pk(\\d+)/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.editSchoolTerm",
+      },
+      {
+        path: "/dashboard_widgets/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.dashboardWidgets",
+        meta: {
+          inMenu: true,
+          titleKey: "dashboard.dashboard_widget.menu_title",
+          icon: "mdi-view-dashboard-outline",
+          permission: "core.view_dashboardwidget_rule",
+        },
+      },
+      {
+        path: "/dashboard_widgets/:pk(\\d+)/edit/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.editDashboardWidget",
+      },
+      {
+        path: "/dashboard_widgets/:pk(\\d+)/delete/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.deleteDashboardWidget",
+      },
+      {
+        path: "/dashboard_widgets/:app/:model/new/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.createDashboardWidget",
+      },
+      {
+        path: "/dashboard_widgets/default/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.editDefaultDashboard",
+      },
+      {
+        path: "/status/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.system_status",
+        meta: {
+          inMenu: true,
+          titleKey: "administration.system_status.menu_title",
+          icon: "mdi-power-settings",
+          permission: "core.view_system_status_rule",
+        },
+      },
+      {
+        path: "/preferences/site/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.preferencesSite",
+        meta: {
+          inMenu: true,
+          titleKey: "preferences.site.menu_title",
+          icon: "mdi-tune",
+          permission: "core.change_site_preferences_rule",
+        },
+      },
+      {
+        path: "/preferences/site/:pk(\\d+)/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.preferencesSiteByPk",
+      },
+      {
+        path: "/preferences/site/:pk(\\d+)/:section/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.preferencesSiteByPkSection",
+      },
+      {
+        path: "/preferences/site/:section/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.preferencesSiteSection",
+      },
+      {
+        path: "/data_checks/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.checkData",
+        meta: {
+          inMenu: true,
+          titleKey: "data_check.menu_title",
+          icon: "mdi-list-status",
+          permission: "core.view_datacheck_rule", // FIXME PERMISSION
+        },
+      },
+      {
+        path: "/data_checks/run/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.runDataChecks",
+      },
+      {
+        path: "/data_checks/:pk(\\d+)/:solve_option/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.solveDataCheck",
+      },
+
+      {
+        path: "/permissions/global/user/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.managerUserGlobalPermissions",
+        meta: {
+          inMenu: true,
+          titleKey: "permissions.manage.menu_title",
+          icon: "mdi-shield-outline",
+          permission: "core.manage_permissions_rule",
+        },
+      },
+      {
+        path: "/permissions/global/group/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.manageGroupGlobalPermissions",
+      },
+      {
+        path: "/permissions/object/user/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.manageUserObjectPermissions",
+      },
+      {
+        path: "/permissions/object/group/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.manageGroupObjectPermissions",
+      },
+      {
+        path: "/permissions/global/user/:pk(\\d+)/delete/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.deleteUserGlobalPermission,",
+      },
+      {
+        path: "/permissions/global/group/:pk(\\d+)/delete/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.deleteGroupGlobalPermission",
+      },
+      {
+        path: "/permissions/object/user/:pk(\\d+)/delete/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.deleteUserObjectPermission",
+      },
+      {
+        path: "/permissions/object/group/:pk(\\d+)/delete/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.deleteGroupObjectPermission",
+      },
+      {
+        path: "/permissions/assign/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.selectPermissionforAssign",
+      },
+      {
+        path: "/permissions/:pk(\\d+)/assign/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.assignPermission",
+      },
+
+      {
+        path: "/oauth/applications/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.oauthApplications",
+        meta: {
+          inMenu: true,
+          titleKey: "oauth.application.menu_title",
+          icon: "mdi-gesture-tap-hold",
+          permission: "core.view_django_admin_rule", //FIXME: add permission
+        },
+      },
+      {
+        path: "/oauth/applications/register/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.registerOauthApplication,",
+      },
+      {
+        path: "/oauth/applications/:pk(\\d+)/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.oauthApplication",
+      },
+      {
+        path: "/oauth/applications/:pk(\\d+)/delete/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.delete_oauth2_application,",
+      },
+      {
+        path: "/oauth/applications/:pk(\\d+)/edit/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.editOauthApplication",
+      },
+      {
+        path: "/admin/",
+        component: () => import("./components/LegacyBaseTemplate.vue"),
+        name: "core.admin",
+        meta: {
+          inMenu: true,
+          titleKey: "administration.backend_admin.menu_title",
+          icon: "mdi-database-cog-outline",
+          newTab: true,
+        },
+      },
+    ],
+  },
+
+  // ACCOUNT MENU
+
+  // STOP IMPERSONATION FIXME
+  {
+    path: "/person/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.person",
+    meta: {
+      inAccountMenu: true,
+      titleKey: "person.account_menu_title",
+      icon: "mdi-account-outline",
+      permission: "core.view_person_rule", // FIXME PERMISSION
+    },
+  },
+  {
+    path: "/preferences/person/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.preferencesPerson",
+    meta: {
+      inAccountMenu: true,
+      titleKey: "preferences.person.menu_title",
+      icon: "mdi-cog-outline",
+      permission: "core.change_person_preferences_rule", // FIXME PERMISSION
+    },
+  },
+  {
+    path: "/preferences/person/:pk(\\d+)/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.preferencesPersonByPk",
+  },
+  {
+    path: "/preferences/person/:pk(\\d+)/:section/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.preferencesPersonByPkSection",
+  },
+  {
+    path: "/preferences/person/:section/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.preferencesPersonSection",
+  },
+  {
+    path: "/account/two_factor/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.twoFactor",
+    meta: {
+      inAccountMenu: true,
+      titleKey: "accounts.two_factor.menu_title",
+      icon: "mdi-two-factor-authentication",
+    },
+  },
+  {
+    path: "/account/two_factor/setup/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.twoFactor.setup",
+  },
+  {
+    path: "/account/two_factor/qrcode/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.twoFactor.qrcode",
+  },
+  {
+    path: "/account/two_factor/setup/complete/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.twoFactor.setupComplete",
+  },
+  {
+    path: "/account/two_factor/backup/tokens/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.twoFactor.backupTokens",
+  },
+  {
+    path: "/account/two_factor/backup/phone/register",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.twoFactor.registerBackupPhone",
+  },
+  {
+    path: "/account/two_factor/disable/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.twoFactor.disable",
+  },
+  {
+    path: "/accounts/password/change/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.changePassword",
+    meta: {
+      inAccountMenu: true,
+      titleKey: "accounts.change_password.menu_title",
+      icon: "mdi-form-textbox-password",
+      permission: "core.can_change_password", // FIXME PERMISSION
+    },
+  },
+  {
+    path: "/accounts/password/set/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.setPassword",
+  },
+  {
+    path: "/accounts/password/reset/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.resetPassword",
+  },
+  {
+    path: "/accounts/password/reset/done/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.resetPasswordDone",
+  },
+  {
+    path: "/accounts/password/reset/key/:key/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.resetPasswordConfirm",
+  },
+  {
+    path: "/accounts/password/reset/key/done/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.resetPasswordConfirmDone",
+  },
+  {
+    path: "/accounts/inactive/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.inactive",
+  },
+  {
+    path: "/accounts/email/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.email",
+  },
+  {
+    path: "/accounts/confirm-email/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.confirmEmail",
+  },
+  {
+    path: "/accounts/confirm-email/:key/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.confirmEmailKey",
+  },
+  {
+    path: "/accounts/social/login/cancelled/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.socialLoginCancelled",
+  },
+  {
+    path: "/accounts/social/login/error/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.socialLoginError",
+  },
+  {
+    path: "/accounts/social/signup/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.socialSignup",
+  },
+  {
+    path: "/accounts/social/connections/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.socialConnections",
+    meta: {
+      inAccountMenu: true,
+      titleKey: "accounts.social_connections.menu_title",
+      icon: "mdi-earth",
+      permission: "core.can_change_password", // FIXME PERMISSION
+    },
+  },
+  {
+    path: "/oauth/authorized_tokens/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.oauth.authorizedTokens",
+    meta: {
+      inAccountMenu: true,
+      titleKey: "oauth.authorized_token.menu_title",
+      icon: "mdi-gesture-tap-hold",
+      permission: "core.can_change_password", // FIXME PERMISSION
+    },
+  },
+  {
+    path: "/oauth/authorized_tokens/:pk(\\d+)/delete/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.oauth.deleteAuthorizedToken",
+  },
+  {
+    path: "/ical/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.icalFeeds",
+    meta: {
+      inAccountMenu: true,
+      titleKey: "ical_feed.menu_title",
+      icon: "mdi-calendar-multiple",
+      permission: "core.view_ical_rule",
+    },
+  },
+  {
+    path: "/ical/create/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.createIcalFeed",
+  },
+  {
+    path: "/ical/:pk(\\d+)/edit/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.editIcalFeed",
+  },
+  {
+    path: "/ical/:pk(\\d+)/delete/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.deleteIcalFeed",
+  },
+  {
+    path: "/ical/:slug('.ics'$)",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.icalFeed",
+  },
+  {
+    path: "/accounts/logout/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.accounts.logout",
+    meta: {
+      inAccountMenu: true,
+      titleKey: "accounts.logout.menu_title",
+      icon: "mdi-logout-variant",
+      permission: "core.can_change_password", // FIXME PERMISSION
+      divider: true,
+    },
+  },
+  {
+    path: "/invitations/code/enter",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.enter_invitation_code",
+  },
+  {
+    path: "/invitations/code/generate",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.generate_invitation_code",
+  },
+  {
+    path: "/invitations/disabled",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.invite_disabled",
+  },
+  {
+    path: "/dashboard/edit/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.editDashboard",
+  },
+  {
+    path: "/preferences/group/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.preferencesGroup",
+  },
+  {
+    path: "/preferences/group/:pk(\\d+)/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.preferencesGroupByPk",
+  },
+  {
+    path: "/preferences/group/:pk(\\d+)/:section/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.preferencesGroupByPkSection",
+  },
+  {
+    path: "/preferences/group/:section/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.preferencesGroupSection",
+  },
+  {
+    path: "/health/pdf/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.testPdf",
+  },
+  {
+    path: "/pdfs/:pk(\\d+)/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.redirectToPdfUrl",
+  },
+  {
+    path: "/search/",
+    component: () => import("./components/LegacyBaseTemplate.vue"),
+    name: "core.haystack_search",
+  },
+  {
+    path: "/celery_progress/:taskId",
+    component: () => import("./components/celery_progress/CeleryProgress.vue"),
+    props: true,
+    name: "core.celery_progress",
+  },
+  {
+    path: "/about",
+    component: () => import("./components/about/About.vue"),
+    name: "core.about",
+  }
+];
+
+for (const [ appName, appRoutes ] of Object.entries(apps)) {
+  routes.push({
+    path: `/app/${appName}`,
+    component:  () => import("./components/Parent.vue"),
+    name: `${appName}`,
+    children: appRoutes,
+  });
+}
+
+routes.push(
+  {
+    path: "/*",
+    component: () => import("./components/Error404.vue"),
+    name: "core.error404",
+  }
+)
+
+export default routes;
diff --git a/aleksis/core/vite.config.js b/aleksis/core/vite.config.js
index d10251f436ee84291941f7bf2f71f781dff1768e..eca21638d6b94939a15d2e0b897f6a6e37d03df3 100644
--- a/aleksis/core/vite.config.js
+++ b/aleksis/core/vite.config.js
@@ -14,11 +14,11 @@ function generateAppImporter(entrypoints) {
   let code = "let appObjects = {};";
   for (const appPackage of Object.keys(entrypoints)) {
     let appName = appPackage.split(".").slice(-1)[0];
-    appName = appName.charAt(0).toUpperCase() + appName.substring(1);
+    let importAppName = appName.charAt(0).toUpperCase() + appName.substring(1);
 
     code += `console.debug("Importing AlekSIS app entrypoint for ${appPackage}");\n`;
-    code += `import ${appName} from '${appPackage}';\n`;
-    code += `appObjects.push(${appName});\n`;
+    code += `import ${importAppName} from '${appPackage}';\n`;
+    code += `appObjects["${appName}"] = ${importAppName};\n`;
   }
   code += "export default appObjects;\n";
   return code;