diff --git a/ui/app/manager/src/index.ts b/ui/app/manager/src/index.ts
index 0d610418ab..d4f22fe2b5 100644
--- a/ui/app/manager/src/index.ts
+++ b/ui/app/manager/src/index.ts
@@ -171,6 +171,10 @@ fetch(configURL)
return { ...appConfig };
})
+ .catch((e) => {
+ console.error("Failed to load the manager app config, using the defaults", e);
+ return {} as ManagerAppConfig;
+ })
.then((appConfig: ManagerAppConfig) => {
// Set locales and load path
if (!appConfig.manager) {
diff --git a/ui/app/manager/test/startup.test.ts b/ui/app/manager/test/startup.test.ts
new file mode 100644
index 0000000000..6f17b65244
--- /dev/null
+++ b/ui/app/manager/test/startup.test.ts
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2026, OpenRemote Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+import { expect } from "@openremote/test";
+import { test } from "./fixtures/manager.js";
+
+// What a reverse proxy answers while the manager restarts
+const serviceUnavailable = {
+ status: 503,
+ contentType: "text/html",
+ body: "
503 Service Unavailable
",
+};
+
+/**
+ * @given The manager app config can't be fetched
+ * @when The manager is opened
+ * @then The login page is shown
+ */
+test("should show the login page when the app config can't be fetched", async ({ page, manager }) => {
+ await page.route("**/api/master/configuration/manager", (route) => route.abort("connectionreset"));
+ await manager.goToRealmStartPage("master");
+ // The app was never added to the page, which stayed blank
+ await expect(page.getByRole("textbox", { name: "Username or email" })).toBeVisible();
+});
+
+/**
+ * @given The manager info endpoint answers with an HTML error page
+ * @when The manager is opened
+ * @then The login page is shown
+ */
+test("should show the login page when the manager info is not JSON", async ({ page, manager }) => {
+ await page.route("**/api/master/info", (route) => route.fulfill(serviceUnavailable));
+ await manager.goToRealmStartPage("master");
+ // Initialization waited forever for the info, which left the page blank
+ await expect(page.getByRole("textbox", { name: "Username or email" })).toBeVisible();
+});
diff --git a/ui/component/core/src/index.ts b/ui/component/core/src/index.ts
index 435dd09eb6..f48e7d730f 100644
--- a/ui/component/core/src/index.ts
+++ b/ui/component/core/src/index.ts
@@ -457,7 +457,11 @@ export class Manager implements EventProviderFactory {
const json = await new Promise((resolve, reject) => {
const oReq = new XMLHttpRequest();
oReq.addEventListener("load", () => {
- resolve(JSON.parse(oReq.responseText));
+ try {
+ resolve(JSON.parse(oReq.responseText));
+ } catch (e) {
+ reject(e);
+ }
});
oReq.addEventListener("error", () => {
reject(new Error("Failed to contact the manager"));