diff --git a/apps/nextjs/src/app/[locale]/(home)/(board)/layout.tsx b/apps/nextjs/src/app/[locale]/(home)/(board)/layout.tsx
new file mode 100644
index 000000000..3467dc941
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/(home)/(board)/layout.tsx
@@ -0,0 +1,5 @@
+import definition from "../../boards/(content)/(home)/_definition";
+
+const { layout } = definition;
+
+export default layout;
diff --git a/apps/nextjs/src/app/[locale]/(home-board)/page.tsx b/apps/nextjs/src/app/[locale]/(home)/(board)/page.tsx
similarity index 64%
rename from apps/nextjs/src/app/[locale]/(home-board)/page.tsx
rename to apps/nextjs/src/app/[locale]/(home)/(board)/page.tsx
index 7554444e5..79d1685df 100644
--- a/apps/nextjs/src/app/[locale]/(home-board)/page.tsx
+++ b/apps/nextjs/src/app/[locale]/(home)/(board)/page.tsx
@@ -1,4 +1,4 @@
-import definition from "../boards/(content)/(home)/_definition";
+import definition from "../../boards/(content)/(home)/_definition";
const { generateMetadataAsync: generateMetadata, page } = definition;
diff --git a/apps/nextjs/src/app/[locale]/(home)/not-found.tsx b/apps/nextjs/src/app/[locale]/(home)/not-found.tsx
new file mode 100644
index 000000000..b88f6c393
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/(home)/not-found.tsx
@@ -0,0 +1,3 @@
+import HomeBoardNotFoundPage from "../boards/(content)/not-found";
+
+export default HomeBoardNotFoundPage;
diff --git a/apps/nextjs/src/app/[locale]/(home-board)/layout.tsx b/apps/nextjs/src/app/[locale]/(home-board)/layout.tsx
deleted file mode 100644
index 9b69474ba..000000000
--- a/apps/nextjs/src/app/[locale]/(home-board)/layout.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import definition from "../boards/(content)/(home)/_definition";
-
-const { layout } = definition;
-
-export default layout;
diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/_definition.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/_definition.tsx
similarity index 79%
rename from apps/nextjs/src/app/[locale]/boards/(content)/[name]/_definition.tsx
rename to apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/_definition.tsx
index 82e53110d..d3b65a5e9 100644
--- a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/_definition.tsx
+++ b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/_definition.tsx
@@ -1,6 +1,6 @@
import { api } from "@homarr/api/server";
-import { createBoardContentPage } from "../_creator";
+import { createBoardContentPage } from "../../_creator";
export default createBoardContentPage<{ locale: string; name: string }>({
async getInitialBoardAsync({ name }) {
diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/layout.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/layout.tsx
similarity index 100%
rename from apps/nextjs/src/app/[locale]/boards/(content)/[name]/layout.tsx
rename to apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/layout.tsx
diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/page.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/page.tsx
similarity index 100%
rename from apps/nextjs/src/app/[locale]/boards/(content)/[name]/page.tsx
rename to apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/page.tsx
diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/not-found.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/not-found.tsx
new file mode 100644
index 000000000..9c398669b
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/not-found.tsx
@@ -0,0 +1,18 @@
+import { IconLayoutOff } from "@tabler/icons-react";
+
+import { getScopedI18n } from "@homarr/translation/server";
+
+import { BoardNotFound } from "~/components/board/not-found";
+
+export default async function BoardNotFoundPage() {
+ const tNotFound = await getScopedI18n("board.error.notFound");
+ return (
+
+ );
+}
diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/not-found.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/not-found.tsx
new file mode 100644
index 000000000..ba84e0593
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/boards/(content)/not-found.tsx
@@ -0,0 +1,47 @@
+import { IconHomeOff } from "@tabler/icons-react";
+
+import { auth } from "@homarr/auth/next";
+import { db } from "@homarr/db";
+import { boards } from "@homarr/db/schema/sqlite";
+import { getI18n } from "@homarr/translation/server";
+
+import type { BoardNotFoundProps } from "~/components/board/not-found";
+import { BoardNotFound } from "~/components/board/not-found";
+
+export default async function NotFoundBoardHomePage() {
+ const boardNotFoundProps = await getPropsAsync();
+
+ return ;
+}
+
+const getPropsAsync = async (): Promise => {
+ const boardCount = await db.$count(boards);
+ const t = await getI18n();
+
+ if (boardCount === 0) {
+ return {
+ icon: { src: "/favicon.ico", alt: "Homarr logo" },
+ title: t("board.error.noBoard.title"),
+ description: t("board.error.noBoard.description"),
+ link: { label: t("board.error.noBoard.link"), href: "/manage/boards" },
+ notice: t("board.error.noBoard.notice"),
+ };
+ }
+
+ const session = await auth();
+ const isAdmin = session?.user.permissions.includes("admin");
+ const type = isAdmin ? "admin" : session !== null ? "user" : "anonymous";
+ const href = {
+ admin: "/manage/settings",
+ user: `/manage/users/${session?.user.id}/general`,
+ anonymous: "/manage/boards",
+ }[type];
+
+ return {
+ icon: IconHomeOff,
+ title: t(`board.error.homeBoard.title`),
+ description: t(`board.error.homeBoard.${type}.description`),
+ link: { label: t(`board.error.homeBoard.${type}.link`), href },
+ notice: t(`board.error.homeBoard.${type}.notice`),
+ };
+};
diff --git a/apps/nextjs/src/components/board/not-found.tsx b/apps/nextjs/src/components/board/not-found.tsx
new file mode 100644
index 000000000..d597597de
--- /dev/null
+++ b/apps/nextjs/src/components/board/not-found.tsx
@@ -0,0 +1,41 @@
+import { Anchor, AppShellMain, Center, Flex, Group, Image, Text, Title } from "@mantine/core";
+
+import type { TablerIcon } from "@homarr/ui";
+
+import { fullHeightWithoutHeaderAndFooter } from "~/constants";
+import { MainHeader } from "../layout/header";
+import { HomarrLogoWithTitle } from "../layout/logo/homarr-logo";
+import { ClientShell } from "../layout/shell";
+
+export interface BoardNotFoundProps {
+ icon: TablerIcon | { src: string; alt: string };
+ title: string;
+ description: string;
+ link: {
+ label: string;
+ href: string;
+ };
+ notice: string;
+}
+
+export const BoardNotFound = ({ icon: Icon, title, description, link, notice }: BoardNotFoundProps) => {
+ return (
+
+ } hasNavigation={false} />
+
+
+
+
+ {"src" in Icon ? : }
+
+ {title}
+
+ {description}
+ {link.label}
+ {notice}
+
+
+
+
+ );
+};
diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json
index 8997a4deb..516d0d5a5 100644
--- a/packages/translation/src/lang/en.json
+++ b/packages/translation/src/lang/en.json
@@ -1978,6 +1978,38 @@
}
}
}
+ },
+ "error": {
+ "noBoard": {
+ "title": "Welcome to Homarr",
+ "description": "A sleek, modern dashboard that puts all of your apps and services at your fingertips.",
+ "link": "Create your first board",
+ "notice": "To make this page disappear, create a board and set it as the home board"
+ },
+ "notFound": {
+ "title": "Board not found",
+ "description": "The board specified was either not found or you don't have access to it.",
+ "link": "View all boards",
+ "notice": "Check the link or contact an administrator if you think it should be accessible"
+ },
+ "homeBoard": {
+ "title": "No home board",
+ "admin": {
+ "description": "You haven't set a home board for the server yet.",
+ "link": "Configure server-wide home board",
+ "notice": "To make this page disappear for all users, set a home board for the server"
+ },
+ "user": {
+ "description": "You haven't set a home board yet.",
+ "link": "Configure your home board",
+ "notice": "To make this page disappear, specify the home board in your preferences"
+ },
+ "anonymous": {
+ "description": "The server administrator hasn't set a home board yet.",
+ "link": "View public boards",
+ "notice": "To make this page disappear, ask the server administrator to set a home board for the server"
+ }
+ }
}
},
"management": {