diff --git a/web/rollup.config.js b/web/rollup.config.js
index 388b54a6a..3124e5099 100644
--- a/web/rollup.config.js
+++ b/web/rollup.config.js
@@ -70,24 +70,32 @@ export function manualChunks(id) {
     }
 }
 
-export const PLUGINS = [
-    cssimport(),
-    markdown(),
-    nodeResolve({ extensions, browser: true }),
-    commonjs(),
-    babel({
-        extensions,
-        babelHelpers: "runtime",
-        include: ["src/**/*"],
-    }),
-    replace({
-        "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
-        "process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
-        "preventAssignment": true,
-    }),
-    sourcemaps(),
-    isProdBuild && terser(),
-].filter((p) => p);
+export const defaultOptions = {
+    plugins: [
+        cssimport(),
+        markdown(),
+        nodeResolve({ extensions, browser: true }),
+        commonjs(),
+        babel({
+            extensions,
+            babelHelpers: "runtime",
+            include: ["src/**/*"],
+        }),
+        replace({
+            "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
+            "process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
+            "preventAssignment": true,
+        }),
+        sourcemaps(),
+        isProdBuild && terser(),
+    ].filter((p) => p),
+    watch: {
+        clearScreen: false,
+    },
+    preserveEntrySignatures: false,
+    cache: true,
+    context: "window",
+};
 
 // Polyfills (imported first)
 export const POLY = {
@@ -110,9 +118,6 @@ export const POLY = {
             copyOnce: false,
         }),
     ].filter((p) => p),
-    watch: {
-        clearScreen: false,
-    },
 };
 
 export default [
@@ -120,8 +125,6 @@ export default [
     // Flow interface
     {
         input: "./src/interfaces/FlowInterface.ts",
-        context: "window",
-        cache: true,
         output: [
             {
                 format: "es",
@@ -130,16 +133,11 @@ export default [
                 manualChunks: manualChunks,
             },
         ],
-        plugins: PLUGINS,
-        watch: {
-            clearScreen: false,
-        },
+        ...defaultOptions,
     },
     // Admin interface
     {
         input: "./src/interfaces/AdminInterface.ts",
-        context: "window",
-        cache: true,
         output: [
             {
                 format: "es",
@@ -148,16 +146,11 @@ export default [
                 manualChunks: manualChunks,
             },
         ],
-        plugins: PLUGINS,
-        watch: {
-            clearScreen: false,
-        },
+        ...defaultOptions,
     },
     // User interface
     {
         input: "./src/interfaces/UserInterface.ts",
-        context: "window",
-        cache: true,
         output: [
             {
                 format: "es",
@@ -166,9 +159,6 @@ export default [
                 manualChunks: manualChunks,
             },
         ],
-        plugins: PLUGINS,
-        watch: {
-            clearScreen: false,
-        },
+        ...defaultOptions,
     },
 ];
diff --git a/web/src/authentik.css b/web/src/authentik.css
index 2e9d7ffc2..e98426cbb 100644
--- a/web/src/authentik.css
+++ b/web/src/authentik.css
@@ -95,6 +95,11 @@ html > form > input {
     vertical-align: middle;
 }
 
+.pf-c-description-list__description .pf-c-button {
+    margin-right: 6px;
+    margin-bottom: 6px;
+}
+
 @media (prefers-color-scheme: dark) {
     .ak-static-page h1 {
         color: var(--ak-dark-foreground);
diff --git a/web/src/elements/router/Route.ts b/web/src/elements/router/Route.ts
index b6c186de1..7aab1c69f 100644
--- a/web/src/elements/router/Route.ts
+++ b/web/src/elements/router/Route.ts
@@ -1,4 +1,5 @@
 import { TemplateResult, html } from "lit";
+import { until } from "lit/directives/until.js";
 
 export const SLUG_REGEX = "[-a-zA-Z0-9_]+";
 export const ID_REGEX = "\\d+";
@@ -12,39 +13,41 @@ export class Route {
     url: RegExp;
 
     private element?: TemplateResult;
-    private callback?: (args: RouteArgs) => TemplateResult;
+    private callback?: (args: RouteArgs) => Promise<TemplateResult>;
 
-    constructor(url: RegExp, element?: TemplateResult) {
+    constructor(url: RegExp, callback?: (args: RouteArgs) => Promise<TemplateResult>) {
         this.url = url;
-        this.element = element;
+        this.callback = callback;
     }
 
-    redirect(to: string): Route {
-        this.callback = () => {
+    redirect(to: string, raw = false): Route {
+        this.callback = async () => {
             console.debug(`authentik/router: redirecting ${to}`);
-            window.location.hash = `#${to}`;
-            return html``;
-        };
-        return this;
-    }
-
-    redirectRaw(to: string): Route {
-        this.callback = () => {
-            console.debug(`authentik/router: redirecting ${to}`);
-            window.location.hash = `${to}`;
+            if (!raw) {
+                window.location.hash = `#${to}`;
+            } else {
+                window.location.hash = to;
+            }
             return html``;
         };
         return this;
     }
 
     then(render: (args: RouteArgs) => TemplateResult): Route {
+        this.callback = async (args) => {
+            return render(args);
+        };
+        return this;
+    }
+
+    thenAsync(render: (args: RouteArgs) => Promise<TemplateResult>): Route {
         this.callback = render;
         return this;
     }
 
     render(args: RouteArgs): TemplateResult {
         if (this.callback) {
-            return this.callback(args);
+            return html`${until(this.callback(args))}`;
         }
         if (this.element) {
             return this.element;
diff --git a/web/src/flows/FlowExecutor.ts b/web/src/flows/FlowExecutor.ts
index e5739b93b..9bca2303c 100644
--- a/web/src/flows/FlowExecutor.ts
+++ b/web/src/flows/FlowExecutor.ts
@@ -30,26 +30,13 @@ import { WebsocketClient } from "../common/ws";
 import { EVENT_FLOW_ADVANCE, TITLE_DEFAULT } from "../constants";
 import "../elements/LoadingOverlay";
 import { first } from "../utils";
-import "./FlowInspector";
-import "./sources/apple/AppleLoginInit";
-import "./sources/plex/PlexLoginInit";
 import "./stages/RedirectStage";
 import "./stages/access_denied/AccessDeniedStage";
-import "./stages/authenticator_duo/AuthenticatorDuoStage";
-import "./stages/authenticator_sms/AuthenticatorSMSStage";
-import "./stages/authenticator_static/AuthenticatorStaticStage";
-import "./stages/authenticator_totp/AuthenticatorTOTPStage";
-import "./stages/authenticator_validate/AuthenticatorValidateStage";
-import "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
 import "./stages/autosubmit/AutosubmitStage";
 import { StageHost } from "./stages/base";
 import "./stages/captcha/CaptchaStage";
-import "./stages/consent/ConsentStage";
-import "./stages/dummy/DummyStage";
-import "./stages/email/EmailStage";
 import "./stages/identification/IdentificationStage";
 import "./stages/password/PasswordStage";
-import "./stages/prompt/PromptStage";
 
 @customElement("ak-flow-executor")
 export class FlowExecutor extends LitElement implements StageHost {
@@ -229,7 +216,117 @@ export class FlowExecutor extends LitElement implements StageHost {
         } as ChallengeTypes;
     }
 
-    renderChallenge(): TemplateResult {
+    async renderChallengeNativeElement(): Promise<TemplateResult> {
+        switch (this.challenge?.component) {
+            case "ak-stage-access-denied":
+                // Statically imported for performance reasons
+                return html`<ak-stage-access-denied
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-access-denied>`;
+            case "ak-stage-identification":
+                // Statically imported for performance reasons
+                return html`<ak-stage-identification
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-identification>`;
+            case "ak-stage-password":
+                // Statically imported for performance reasons
+                return html`<ak-stage-password
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-password>`;
+            case "ak-stage-captcha":
+                // Statically imported to prevent browsers blocking urls
+                return html`<ak-stage-captcha
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-captcha>`;
+            case "ak-stage-consent":
+                await import("./stages/consent/ConsentStage");
+                return html`<ak-stage-consent
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-consent>`;
+            case "ak-stage-dummy":
+                await import("./stages/dummy/DummyStage");
+                return html`<ak-stage-dummy
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-dummy>`;
+            case "ak-stage-email":
+                await import("./stages/email/EmailStage");
+                return html`<ak-stage-email
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-email>`;
+            case "ak-stage-autosubmit":
+                // Statically imported for performance reasons
+                return html`<ak-stage-autosubmit
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-autosubmit>`;
+            case "ak-stage-prompt":
+                await import("./stages/prompt/PromptStage");
+                return html`<ak-stage-prompt
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-prompt>`;
+            case "ak-stage-authenticator-totp":
+                await import("./stages/authenticator_totp/AuthenticatorTOTPStage");
+                return html`<ak-stage-authenticator-totp
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-authenticator-totp>`;
+            case "ak-stage-authenticator-duo":
+                await import("./stages/authenticator_duo/AuthenticatorDuoStage");
+                return html`<ak-stage-authenticator-duo
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-authenticator-duo>`;
+            case "ak-stage-authenticator-static":
+                await import("./stages/authenticator_static/AuthenticatorStaticStage");
+                return html`<ak-stage-authenticator-static
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-authenticator-static>`;
+            case "ak-stage-authenticator-webauthn":
+                await import("./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage");
+                return html`<ak-stage-authenticator-webauthn
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-authenticator-webauthn>`;
+            case "ak-stage-authenticator-sms":
+                await import("./stages/authenticator_sms/AuthenticatorSMSStage");
+                return html`<ak-stage-authenticator-sms
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-authenticator-sms>`;
+            case "ak-stage-authenticator-validate":
+                await import("./stages/authenticator_validate/AuthenticatorValidateStage");
+                return html`<ak-stage-authenticator-validate
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-stage-authenticator-validate>`;
+            case "ak-flow-sources-plex":
+                await import("./sources/plex/PlexLoginInit");
+                return html`<ak-flow-sources-plex
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-flow-sources-plex>`;
+            case "ak-flow-sources-oauth-apple":
+                await import("./sources/apple/AppleLoginInit");
+                return html`<ak-flow-sources-oauth-apple
+                    .host=${this as StageHost}
+                    .challenge=${this.challenge}
+                ></ak-flow-sources-oauth-apple>`;
+            default:
+                break;
+        }
+        return html`Invalid native challenge element`;
+    }
+
+    async renderChallenge(): Promise<TemplateResult> {
         if (!this.challenge) {
             return html``;
         }
@@ -247,96 +344,7 @@ export class FlowExecutor extends LitElement implements StageHost {
             case ChallengeChoices.Shell:
                 return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
             case ChallengeChoices.Native:
-                switch (this.challenge.component) {
-                    case "ak-stage-access-denied":
-                        return html`<ak-stage-access-denied
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-access-denied>`;
-                    case "ak-stage-identification":
-                        return html`<ak-stage-identification
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-identification>`;
-                    case "ak-stage-password":
-                        return html`<ak-stage-password
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-password>`;
-                    case "ak-stage-captcha":
-                        return html`<ak-stage-captcha
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-captcha>`;
-                    case "ak-stage-consent":
-                        return html`<ak-stage-consent
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-consent>`;
-                    case "ak-stage-dummy":
-                        return html`<ak-stage-dummy
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-dummy>`;
-                    case "ak-stage-email":
-                        return html`<ak-stage-email
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-email>`;
-                    case "ak-stage-autosubmit":
-                        return html`<ak-stage-autosubmit
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-autosubmit>`;
-                    case "ak-stage-prompt":
-                        return html`<ak-stage-prompt
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-prompt>`;
-                    case "ak-stage-authenticator-totp":
-                        return html`<ak-stage-authenticator-totp
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-authenticator-totp>`;
-                    case "ak-stage-authenticator-duo":
-                        return html`<ak-stage-authenticator-duo
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-authenticator-duo>`;
-                    case "ak-stage-authenticator-static":
-                        return html`<ak-stage-authenticator-static
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-authenticator-static>`;
-                    case "ak-stage-authenticator-webauthn":
-                        return html`<ak-stage-authenticator-webauthn
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-authenticator-webauthn>`;
-                    case "ak-stage-authenticator-validate":
-                        return html`<ak-stage-authenticator-validate
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-authenticator-validate>`;
-                    case "ak-stage-authenticator-sms":
-                        return html`<ak-stage-authenticator-sms
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-stage-authenticator-sms>`;
-                    case "ak-flow-sources-plex":
-                        return html`<ak-flow-sources-plex
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-flow-sources-plex>`;
-                    case "ak-flow-sources-oauth-apple":
-                        return html`<ak-flow-sources-oauth-apple
-                            .host=${this as StageHost}
-                            .challenge=${this.challenge}
-                        ></ak-flow-sources-oauth-apple>`;
-                    default:
-                        break;
-                }
-                break;
+                return await this.renderChallengeNativeElement();
             default:
                 console.debug(`authentik/flows: unexpected data type ${this.challenge.type}`);
                 break;
@@ -350,10 +358,20 @@ export class FlowExecutor extends LitElement implements StageHost {
         }
         return html`
             ${this.loading ? html`<ak-loading-overlay></ak-loading-overlay>` : html``}
-            ${this.renderChallenge()}
+            ${until(this.renderChallenge())}
         `;
     }
 
+    async renderInspector(): Promise<TemplateResult> {
+        if (!this.inspectorOpen) {
+            return html``;
+        }
+        await import("./FlowInspector");
+        return html`<ak-flow-inspector
+            class="pf-c-drawer__panel pf-m-width-33"
+        ></ak-flow-inspector>`;
+    }
+
     render(): TemplateResult {
         return html`<div class="pf-c-background-image">
                 <svg
@@ -439,13 +457,7 @@ export class FlowExecutor extends LitElement implements StageHost {
                                 </div>
                             </div>
                         </div>
-
-                        <ak-flow-inspector
-                            class="pf-c-drawer__panel pf-m-width-33 ${this.inspectorOpen
-                                ? ""
-                                : "display-none"}"
-                            ?hidden=${!this.inspectorOpen}
-                        ></ak-flow-inspector>
+                        ${until(this.renderInspector())}
                     </div>
                 </div>
             </div>`;
diff --git a/web/src/interfaces/locale.ts b/web/src/interfaces/locale.ts
index a12527dc3..8a486e02d 100644
--- a/web/src/interfaces/locale.ts
+++ b/web/src/interfaces/locale.ts
@@ -1,96 +1,122 @@
-import { de, en, es, fr, pl, tr, zh } from "make-plural/plurals";
-
 import { Messages, i18n } from "@lingui/core";
 import { detect, fromNavigator, fromUrl } from "@lingui/detect-locale";
 import { t } from "@lingui/macro";
 
 import { LitElement } from "lit";
 
-import { messages as localeDE } from "../locales/de";
-import { messages as localeEN } from "../locales/en";
-import { messages as localeES } from "../locales/es";
-import { messages as localeFR_FR } from "../locales/fr_FR";
-import { messages as localePL } from "../locales/pl";
-import { messages as localeDEBUG } from "../locales/pseudo-LOCALE";
-import { messages as localeTR } from "../locales/tr";
-import { messages as localeZH_Hans } from "../locales/zh-Hans";
-import { messages as localeZH_Hant } from "../locales/zh-Hant";
-import { messages as localeZH_TW } from "../locales/zh_TW";
+interface Locale {
+    locale: Messages;
+    // eslint-disable-next-line @typescript-eslint/ban-types
+    plurals: Function;
+}
 
 export const LOCALES: {
     code: string;
     label: string;
-    // eslint-disable-next-line @typescript-eslint/ban-types
-    plurals: Function;
-    locale: Messages;
+    locale: () => Promise<Locale>;
 }[] = [
     {
         code: "en",
-        plurals: en,
         label: t`English`,
-        locale: localeEN,
+        locale: async () => {
+            return {
+                locale: (await import("../locales/en")).messages,
+                plurals: (await import("make-plural/plurals")).en,
+            };
+        },
     },
     {
         code: "debug",
-        plurals: en,
         label: t`Debug`,
-        locale: localeDEBUG,
+        locale: async () => {
+            return {
+                locale: (await import("../locales/pseudo-LOCALE")).messages,
+                plurals: (await import("make-plural/plurals")).en,
+            };
+        },
     },
     {
         code: "fr",
-        plurals: fr,
         label: t`French`,
-        locale: localeFR_FR,
+        locale: async () => {
+            return {
+                locale: (await import("../locales/fr_FR")).messages,
+                plurals: (await import("make-plural/plurals")).fr,
+            };
+        },
     },
     {
         code: "tr",
-        plurals: tr,
         label: t`Turkish`,
-        locale: localeTR,
+        locale: async () => {
+            return {
+                locale: (await import("../locales/tr")).messages,
+                plurals: (await import("make-plural/plurals")).tr,
+            };
+        },
     },
     {
         code: "es",
-        plurals: es,
         label: t`Spanish`,
-        locale: localeES,
+        locale: async () => {
+            return {
+                locale: (await import("../locales/es")).messages,
+                plurals: (await import("make-plural/plurals")).es,
+            };
+        },
     },
     {
         code: "pl",
-        plurals: pl,
         label: t`Polish`,
-        locale: localePL,
+        locale: async () => {
+            return {
+                locale: (await import("../locales/pl")).messages,
+                plurals: (await import("make-plural/plurals")).pl,
+            };
+        },
     },
     {
         code: "zh_TW",
-        plurals: zh,
         label: t`Taiwanese Mandarin`,
-        locale: localeZH_TW,
+        locale: async () => {
+            return {
+                locale: (await import("../locales/zh_TW")).messages,
+                plurals: (await import("make-plural/plurals")).zh,
+            };
+        },
     },
     {
         code: "zh-CN",
-        plurals: zh,
         label: t`Chinese (simplified)`,
-        locale: localeZH_Hans,
+        locale: async () => {
+            return {
+                locale: (await import("../locales/zh-Hans")).messages,
+                plurals: (await import("make-plural/plurals")).zh,
+            };
+        },
     },
     {
         code: "zh-HK",
-        plurals: zh,
         label: t`Chinese (traditional)`,
-        locale: localeZH_Hant,
+        locale: async () => {
+            return {
+                locale: (await import("../locales/zh-Hant")).messages,
+                plurals: (await import("make-plural/plurals")).zh,
+            };
+        },
     },
     {
         code: "de",
-        plurals: de,
         label: t`German`,
-        locale: localeDE,
+        locale: async () => {
+            return {
+                locale: (await import("../locales/de")).messages,
+                plurals: (await import("make-plural/plurals")).de,
+            };
+        },
     },
 ];
 
-LOCALES.forEach((locale) => {
-    i18n.loadLocaleData(locale.code, { plurals: locale.plurals });
-    i18n.load(locale.code, locale.locale);
-});
-
 const DEFAULT_FALLBACK = () => "en";
 
 export function autoDetectLanguage() {
@@ -102,25 +128,33 @@ export function autoDetectLanguage() {
     }
     if (detected in i18n._messages) {
         console.debug(`authentik/locale: Activating detected locale '${detected}'`);
-        i18n.activate(detected);
+        activateLocale(detected);
     } else {
         console.debug(`authentik/locale: No locale for '${detected}', falling back to en`);
-        i18n.activate(DEFAULT_FALLBACK());
+        activateLocale(DEFAULT_FALLBACK());
     }
 }
 export function activateLocale(code: string) {
     const urlLocale = fromUrl("locale");
     if (urlLocale !== null && urlLocale !== "") {
-        i18n.activate(urlLocale);
-    } else {
-        i18n.activate(code);
+        code = urlLocale;
     }
-    document.querySelectorAll("[data-refresh-on-locale=true]").forEach((el) => {
-        try {
-            (el as LitElement).requestUpdate();
-        } catch {
-            console.debug(`authentik/locale: failed to update element ${el}`);
-        }
+    const locale = LOCALES.find((locale) => locale.code == code);
+    if (!locale) {
+        console.warn(`authentik/locale: failed to find locale for code ${code}`);
+        return;
+    }
+    locale.locale().then((localeData) => {
+        i18n.loadLocaleData(locale.code, { plurals: localeData.plurals });
+        i18n.load(locale.code, localeData.locale);
+        i18n.activate(locale.code);
+        document.querySelectorAll("[data-refresh-on-locale=true]").forEach((el) => {
+            try {
+                (el as LitElement).requestUpdate();
+            } catch {
+                console.debug(`authentik/locale: failed to update element ${el}`);
+            }
+        });
     });
 }
 autoDetectLanguage();
diff --git a/web/src/locales/de.po b/web/src/locales/de.po
index 5ef2eff17..9b596eca3 100644
--- a/web/src/locales/de.po
+++ b/web/src/locales/de.po
@@ -2824,6 +2824,7 @@ msgstr "Server laden"
 #: src/elements/table/Table.ts
 #: src/flows/FlowExecutor.ts
 #: src/flows/FlowExecutor.ts
+#: src/flows/FlowExecutor.ts
 #: src/flows/FlowInspector.ts
 #: src/flows/stages/access_denied/AccessDeniedStage.ts
 #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
diff --git a/web/src/locales/en.po b/web/src/locales/en.po
index 67f93d0e8..9631fcbd5 100644
--- a/web/src/locales/en.po
+++ b/web/src/locales/en.po
@@ -2876,6 +2876,7 @@ msgstr "Load servers"
 #: src/elements/table/Table.ts
 #: src/flows/FlowExecutor.ts
 #: src/flows/FlowExecutor.ts
+#: src/flows/FlowExecutor.ts
 #: src/flows/FlowInspector.ts
 #: src/flows/stages/access_denied/AccessDeniedStage.ts
 #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
diff --git a/web/src/locales/es.po b/web/src/locales/es.po
index bad12d73e..9716f603a 100644
--- a/web/src/locales/es.po
+++ b/web/src/locales/es.po
@@ -2817,6 +2817,7 @@ msgstr "Servidores de carga"
 #: src/elements/table/Table.ts
 #: src/flows/FlowExecutor.ts
 #: src/flows/FlowExecutor.ts
+#: src/flows/FlowExecutor.ts
 #: src/flows/FlowInspector.ts
 #: src/flows/stages/access_denied/AccessDeniedStage.ts
 #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
diff --git a/web/src/locales/fr_FR.po b/web/src/locales/fr_FR.po
index 86a7dc933..10a15dd49 100644
--- a/web/src/locales/fr_FR.po
+++ b/web/src/locales/fr_FR.po
@@ -2848,6 +2848,7 @@ msgstr "Charger les serveurs"
 #: src/elements/table/Table.ts
 #: src/flows/FlowExecutor.ts
 #: src/flows/FlowExecutor.ts
+#: src/flows/FlowExecutor.ts
 #: src/flows/FlowInspector.ts
 #: src/flows/stages/access_denied/AccessDeniedStage.ts
 #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
diff --git a/web/src/locales/pl.po b/web/src/locales/pl.po
index c93d8721a..1cb94ff7a 100644
--- a/web/src/locales/pl.po
+++ b/web/src/locales/pl.po
@@ -2814,6 +2814,7 @@ msgstr "Załaduj serwery"
 #: src/elements/table/Table.ts
 #: src/flows/FlowExecutor.ts
 #: src/flows/FlowExecutor.ts
+#: src/flows/FlowExecutor.ts
 #: src/flows/FlowInspector.ts
 #: src/flows/stages/access_denied/AccessDeniedStage.ts
 #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
diff --git a/web/src/locales/pseudo-LOCALE.po b/web/src/locales/pseudo-LOCALE.po
index 06383bd5f..1747905fd 100644
--- a/web/src/locales/pseudo-LOCALE.po
+++ b/web/src/locales/pseudo-LOCALE.po
@@ -2858,6 +2858,7 @@ msgstr ""
 #: src/elements/table/Table.ts
 #: src/flows/FlowExecutor.ts
 #: src/flows/FlowExecutor.ts
+#: src/flows/FlowExecutor.ts
 #: src/flows/FlowInspector.ts
 #: src/flows/stages/access_denied/AccessDeniedStage.ts
 #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
diff --git a/web/src/locales/tr.po b/web/src/locales/tr.po
index 217c0b9b4..cfd4d3c02 100644
--- a/web/src/locales/tr.po
+++ b/web/src/locales/tr.po
@@ -2818,6 +2818,7 @@ msgstr "Sunucuları yükle"
 #: src/elements/table/Table.ts
 #: src/flows/FlowExecutor.ts
 #: src/flows/FlowExecutor.ts
+#: src/flows/FlowExecutor.ts
 #: src/flows/FlowInspector.ts
 #: src/flows/stages/access_denied/AccessDeniedStage.ts
 #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
diff --git a/web/src/locales/zh-Hans.po b/web/src/locales/zh-Hans.po
index ab677b992..a4d4e4f5d 100644
--- a/web/src/locales/zh-Hans.po
+++ b/web/src/locales/zh-Hans.po
@@ -2802,6 +2802,7 @@ msgstr "加载服务器"
 #: src/elements/table/Table.ts
 #: src/flows/FlowExecutor.ts
 #: src/flows/FlowExecutor.ts
+#: src/flows/FlowExecutor.ts
 #: src/flows/FlowInspector.ts
 #: src/flows/stages/access_denied/AccessDeniedStage.ts
 #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
diff --git a/web/src/locales/zh-Hant.po b/web/src/locales/zh-Hant.po
index 63db418c7..e9b203701 100644
--- a/web/src/locales/zh-Hant.po
+++ b/web/src/locales/zh-Hant.po
@@ -2804,6 +2804,7 @@ msgstr "加载服务器"
 #: src/elements/table/Table.ts
 #: src/flows/FlowExecutor.ts
 #: src/flows/FlowExecutor.ts
+#: src/flows/FlowExecutor.ts
 #: src/flows/FlowInspector.ts
 #: src/flows/stages/access_denied/AccessDeniedStage.ts
 #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
diff --git a/web/src/locales/zh_TW.po b/web/src/locales/zh_TW.po
index 8803f38a6..5d488c1b9 100644
--- a/web/src/locales/zh_TW.po
+++ b/web/src/locales/zh_TW.po
@@ -2804,6 +2804,7 @@ msgstr "加载服务器"
 #: src/elements/table/Table.ts
 #: src/flows/FlowExecutor.ts
 #: src/flows/FlowExecutor.ts
+#: src/flows/FlowExecutor.ts
 #: src/flows/FlowInspector.ts
 #: src/flows/stages/access_denied/AccessDeniedStage.ts
 #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
diff --git a/web/src/pages/flows/FlowViewPage.ts b/web/src/pages/flows/FlowViewPage.ts
index 55af16d40..1ca6d0cdb 100644
--- a/web/src/pages/flows/FlowViewPage.ts
+++ b/web/src/pages/flows/FlowViewPage.ts
@@ -163,6 +163,11 @@ export class FlowViewPage extends LitElement {
                                                                     `inspector&next=/#${window.location.hash}`,
                                                                 )}`;
                                                                 window.open(finalURL, "_blank");
+                                                            })
+                                                            .catch((exc: Response) => {
+                                                                // This request can return a HTTP 400 when a flow
+                                                                // is not applicable.
+                                                                window.open(exc.url, "_blank");
                                                             });
                                                     }}
                                                 >
diff --git a/web/src/routesAdmin.ts b/web/src/routesAdmin.ts
index f9ea6d374..6461a952e 100644
--- a/web/src/routesAdmin.ts
+++ b/web/src/routesAdmin.ts
@@ -2,116 +2,130 @@ import { html } from "lit";
 
 import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "./elements/router/Route";
 import "./pages/admin-overview/AdminOverviewPage";
-import "./pages/admin-overview/DashboardUserPage";
-import "./pages/applications/ApplicationListPage";
-import "./pages/applications/ApplicationViewPage";
-import "./pages/crypto/CertificateKeyPairListPage";
-import "./pages/events/EventInfoPage";
-import "./pages/events/EventListPage";
-import "./pages/events/RuleListPage";
-import "./pages/events/TransportListPage";
-import "./pages/flows/FlowListPage";
-import "./pages/flows/FlowViewPage";
-import "./pages/groups/GroupListPage";
-import "./pages/groups/GroupViewPage";
-import "./pages/outposts/OutpostListPage";
-import "./pages/outposts/ServiceConnectionListPage";
-import "./pages/policies/PolicyListPage";
-import "./pages/policies/reputation/ReputationListPage";
-import "./pages/property-mappings/PropertyMappingListPage";
-import "./pages/providers/ProviderListPage";
-import "./pages/providers/ProviderViewPage";
-import "./pages/sources/SourceListPage";
-import "./pages/sources/SourceViewPage";
-import "./pages/stages/StageListPage";
-import "./pages/stages/invitation/InvitationListPage";
-import "./pages/stages/prompt/PromptListPage";
-import "./pages/system-tasks/SystemTaskListPage";
-import "./pages/tenants/TenantListPage";
-import "./pages/tokens/TokenListPage";
-import "./pages/users/UserListPage";
-import "./pages/users/UserViewPage";
 
 export const ROUTES: Route[] = [
     // Prevent infinite Shell loops
     new Route(new RegExp("^/$")).redirect("/administration/overview"),
     new Route(new RegExp("^#.*")).redirect("/administration/overview"),
-    new Route(new RegExp("^/library$")).redirectRaw("/if/user/"),
-    new Route(
-        new RegExp("^/administration/overview$"),
-        html`<ak-admin-overview></ak-admin-overview>`,
-    ),
-    new Route(
-        new RegExp("^/administration/dashboard/users$"),
-        html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`,
-    ),
-    new Route(
-        new RegExp("^/administration/system-tasks$"),
-        html`<ak-system-task-list></ak-system-task-list>`,
-    ),
-    new Route(new RegExp("^/core/providers$"), html`<ak-provider-list></ak-provider-list>`),
-    new Route(new RegExp(`^/core/providers/(?<id>${ID_REGEX})$`)).then((args) => {
+    new Route(new RegExp("^/library$")).redirect("/if/user/", true),
+    // statically imported since this is the default route
+    new Route(new RegExp("^/administration/overview$"), async () => {
+        return html`<ak-admin-overview></ak-admin-overview>`;
+    }),
+    new Route(new RegExp("^/administration/dashboard/users$"), async () => {
+        await import("./pages/admin-overview/DashboardUserPage");
+        return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`;
+    }),
+    new Route(new RegExp("^/administration/system-tasks$"), async () => {
+        await import("./pages/system-tasks/SystemTaskListPage");
+        return html`<ak-system-task-list></ak-system-task-list>`;
+    }),
+    new Route(new RegExp("^/core/providers$"), async () => {
+        await import("./pages/providers/ProviderListPage");
+        return html`<ak-provider-list></ak-provider-list>`;
+    }),
+    new Route(new RegExp(`^/core/providers/(?<id>${ID_REGEX})$`), async (args) => {
+        await import("./pages/providers/ProviderViewPage");
         return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`;
     }),
-    new Route(
-        new RegExp("^/core/applications$"),
-        html`<ak-application-list></ak-application-list>`,
-    ),
-    new Route(new RegExp(`^/core/applications/(?<slug>${SLUG_REGEX})$`)).then((args) => {
+    new Route(new RegExp("^/core/applications$"), async () => {
+        await import("./pages/applications/ApplicationListPage");
+        return html`<ak-application-list></ak-application-list>`;
+    }),
+    new Route(new RegExp(`^/core/applications/(?<slug>${SLUG_REGEX})$`), async (args) => {
+        await import("./pages/applications/ApplicationViewPage");
         return html`<ak-application-view .applicationSlug=${args.slug}></ak-application-view>`;
     }),
-    new Route(new RegExp("^/core/sources$"), html`<ak-source-list></ak-source-list>`),
-    new Route(new RegExp(`^/core/sources/(?<slug>${SLUG_REGEX})$`)).then((args) => {
+    new Route(new RegExp("^/core/sources$"), async () => {
+        await import("./pages/sources/SourceListPage");
+        return html`<ak-source-list></ak-source-list>`;
+    }),
+    new Route(new RegExp(`^/core/sources/(?<slug>${SLUG_REGEX})$`), async (args) => {
+        await import("./pages/sources/SourceViewPage");
         return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`;
     }),
-    new Route(
-        new RegExp("^/core/property-mappings$"),
-        html`<ak-property-mapping-list></ak-property-mapping-list>`,
-    ),
-    new Route(new RegExp("^/core/tokens$"), html`<ak-token-list></ak-token-list>`),
-    new Route(new RegExp("^/core/tenants$"), html`<ak-tenant-list></ak-tenant-list>`),
-    new Route(new RegExp("^/policy/policies$"), html`<ak-policy-list></ak-policy-list>`),
-    new Route(
-        new RegExp("^/policy/reputation$"),
-        html`<ak-policy-reputation-list></ak-policy-reputation-list>`,
-    ),
-    new Route(new RegExp("^/identity/groups$"), html`<ak-group-list></ak-group-list>`),
-    new Route(new RegExp(`^/identity/groups/(?<uuid>${UUID_REGEX})$`)).then((args) => {
+    new Route(new RegExp("^/core/property-mappings$"), async () => {
+        await import("./pages/property-mappings/PropertyMappingListPage");
+        return html`<ak-property-mapping-list></ak-property-mapping-list>`;
+    }),
+    new Route(new RegExp("^/core/tokens$"), async () => {
+        await import("./pages/tokens/TokenListPage");
+        return html`<ak-token-list></ak-token-list>`;
+    }),
+    new Route(new RegExp("^/core/tenants$"), async () => {
+        await import("./pages/tenants/TenantListPage");
+        return html`<ak-tenant-list></ak-tenant-list>`;
+    }),
+    new Route(new RegExp("^/policy/policies$"), async () => {
+        await import("./pages/policies/PolicyListPage");
+        return html`<ak-policy-list></ak-policy-list>`;
+    }),
+    new Route(new RegExp("^/policy/reputation$"), async () => {
+        await import("./pages/policies/reputation/ReputationListPage");
+        return html`<ak-policy-reputation-list></ak-policy-reputation-list>`;
+    }),
+    new Route(new RegExp("^/identity/groups$"), async () => {
+        await import("./pages/groups/GroupListPage");
+        return html`<ak-group-list></ak-group-list>`;
+    }),
+    new Route(new RegExp(`^/identity/groups/(?<uuid>${UUID_REGEX})$`), async (args) => {
+        await import("./pages/groups/GroupViewPage");
         return html`<ak-group-view .groupId=${args.uuid}></ak-group-view>`;
     }),
-    new Route(new RegExp("^/identity/users$"), html`<ak-user-list></ak-user-list>`),
-    new Route(new RegExp(`^/identity/users/(?<id>${ID_REGEX})$`)).then((args) => {
+    new Route(new RegExp("^/identity/users$"), async () => {
+        await import("./pages/users/UserListPage");
+        return html`<ak-user-list></ak-user-list>`;
+    }),
+    new Route(new RegExp(`^/identity/users/(?<id>${ID_REGEX})$`), async (args) => {
+        await import("./pages/users/UserViewPage");
         return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`;
     }),
-    new Route(
-        new RegExp("^/flow/stages/invitations$"),
-        html`<ak-stage-invitation-list></ak-stage-invitation-list>`,
-    ),
-    new Route(
-        new RegExp("^/flow/stages/prompts$"),
-        html`<ak-stage-prompt-list></ak-stage-prompt-list>`,
-    ),
-    new Route(new RegExp("^/flow/stages$"), html`<ak-stage-list></ak-stage-list>`),
-    new Route(new RegExp("^/flow/flows$"), html`<ak-flow-list></ak-flow-list>`),
-    new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`)).then((args) => {
+    new Route(new RegExp("^/flow/stages/invitations$"), async () => {
+        await import("./pages/stages/invitation/InvitationListPage");
+        return html`<ak-stage-invitation-list></ak-stage-invitation-list>`;
+    }),
+    new Route(new RegExp("^/flow/stages/prompts$"), async () => {
+        await import("./pages/stages/prompt/PromptListPage");
+        return html`<ak-stage-prompt-list></ak-stage-prompt-list>`;
+    }),
+    new Route(new RegExp("^/flow/stages$"), async () => {
+        await import("./pages/stages/StageListPage");
+        return html`<ak-stage-list></ak-stage-list>`;
+    }),
+    new Route(new RegExp("^/flow/flows$"), async () => {
+        await import("./pages/flows/FlowListPage");
+        return html`<ak-flow-list></ak-flow-list>`;
+    }),
+    new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`), async (args) => {
+        await import("./pages/flows/FlowViewPage");
         return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`;
     }),
-    new Route(new RegExp("^/events/log$"), html`<ak-event-list></ak-event-list>`),
-    new Route(new RegExp(`^/events/log/(?<id>${UUID_REGEX})$`)).then((args) => {
+    new Route(new RegExp("^/events/log$"), async () => {
+        await import("./pages/events/EventListPage");
+        return html`<ak-event-list></ak-event-list>`;
+    }),
+    new Route(new RegExp(`^/events/log/(?<id>${UUID_REGEX})$`), async (args) => {
+        await import("./pages/events/EventInfoPage");
         return html`<ak-event-info-page .eventID=${args.id}></ak-event-info-page>`;
     }),
-    new Route(
-        new RegExp("^/events/transports$"),
-        html`<ak-event-transport-list></ak-event-transport-list>`,
-    ),
-    new Route(new RegExp("^/events/rules$"), html`<ak-event-rule-list></ak-event-rule-list>`),
-    new Route(new RegExp("^/outpost/outposts$"), html`<ak-outpost-list></ak-outpost-list>`),
-    new Route(
-        new RegExp("^/outpost/integrations$"),
-        html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`,
-    ),
-    new Route(
-        new RegExp("^/crypto/certificates$"),
-        html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`,
-    ),
+    new Route(new RegExp("^/events/transports$"), async () => {
+        await import("./pages/events/TransportListPage");
+        return html`<ak-event-transport-list></ak-event-transport-list>`;
+    }),
+    new Route(new RegExp("^/events/rules$"), async () => {
+        await import("./pages/events/RuleListPage");
+        return html`<ak-event-rule-list></ak-event-rule-list>`;
+    }),
+    new Route(new RegExp("^/outpost/outposts$"), async () => {
+        await import("./pages/outposts/OutpostListPage");
+        return html`<ak-outpost-list></ak-outpost-list>`;
+    }),
+    new Route(new RegExp("^/outpost/integrations$"), async () => {
+        await import("./pages/outposts/ServiceConnectionListPage");
+        return html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`;
+    }),
+    new Route(new RegExp("^/crypto/certificates$"), async () => {
+        await import("./pages/crypto/CertificateKeyPairListPage");
+        return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`;
+    }),
 ];
diff --git a/web/src/routesUser.ts b/web/src/routesUser.ts
index 273588861..9e92f46dc 100644
--- a/web/src/routesUser.ts
+++ b/web/src/routesUser.ts
@@ -2,12 +2,14 @@ import { html } from "lit";
 
 import { Route } from "./elements/router/Route";
 import "./user/LibraryPage";
-import "./user/user-settings/UserSettingsPage";
 
 export const ROUTES: Route[] = [
     // Prevent infinite Shell loops
     new Route(new RegExp("^/$")).redirect("/library"),
     new Route(new RegExp("^#.*")).redirect("/library"),
-    new Route(new RegExp("^/library$"), html`<ak-library></ak-library>`),
-    new Route(new RegExp("^/settings$"), html`<ak-user-settings></ak-user-settings>`),
+    new Route(new RegExp("^/library$"), async () => html`<ak-library></ak-library>`),
+    new Route(new RegExp("^/settings$"), async () => {
+        await import("./user/user-settings/UserSettingsPage");
+        return html`<ak-user-settings></ak-user-settings>`;
+    }),
 ];