diff --git a/web/src/components/AuthFooter.tsx b/web/src/components/AuthFooter.tsx
index 8431aaa99..f1a115f71 100644
--- a/web/src/components/AuthFooter.tsx
+++ b/web/src/components/AuthFooter.tsx
@@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
-import { getInitialTheme, loadTheme } from "@/utils/theme";
import { loadLocale } from "@/utils/i18n";
+import { getInitialTheme, loadTheme } from "@/utils/theme";
import LocaleSelect from "./LocaleSelect";
import ThemeSelect from "./ThemeSelect";
diff --git a/web/src/components/PagedMemoList/PagedMemoList.tsx b/web/src/components/PagedMemoList/PagedMemoList.tsx
index d0823c678..20b46215e 100644
--- a/web/src/components/PagedMemoList/PagedMemoList.tsx
+++ b/web/src/components/PagedMemoList/PagedMemoList.tsx
@@ -149,7 +149,7 @@ const PagedMemoList = (props: Props) => {
{/* Show skeleton loader during initial load */}
{isLoading ? (
-
+
) : (
<>
{
listMode={layout === "LIST"}
/>
- {/* Loading indicator for pagination - use skeleton for content consistency */}
- {isFetchingNextPage && }
+ {/* Loading indicator for pagination */}
+ {isFetchingNextPage && }
{/* Empty state or back-to-top button */}
{!isFetchingNextPage && (
diff --git a/web/src/components/Skeleton.tsx b/web/src/components/Skeleton.tsx
index 1de83ecf2..945144057 100644
--- a/web/src/components/Skeleton.tsx
+++ b/web/src/components/Skeleton.tsx
@@ -1,13 +1,11 @@
import { cn } from "@/lib/utils";
interface Props {
- type?: "route" | "memo" | "pagination";
showCreator?: boolean;
count?: number;
- showEditor?: boolean;
}
-// Memo card skeleton component
+// Memo card skeleton component for list loading states
const MemoCardSkeleton = ({ showCreator = false, index = 0 }: { showCreator?: boolean; index?: number }) => (
{/* Header section */}
@@ -43,36 +41,17 @@ const MemoCardSkeleton = ({ showCreator = false, index = 0 }: { showCreator?: bo
);
-const Skeleton = ({ type = "route", showCreator = false, count = 4, showEditor = true }: Props) => {
- // Pagination type: simpler, just memos
- if (type === "pagination") {
- return (
-
-
- {Array.from({ length: count }).map((_, index) => (
-
- ))}
-
-
- );
- }
-
- // Route or memo type: with optional wrapper
- return (
-
- {/* Editor skeleton - only for route type */}
- {type === "route" && showEditor && (
-
- )}
-
- {/* Memo skeletons */}
- {Array.from({ length: count }).map((_, index) => (
-
- ))}
-
- );
-};
+/**
+ * Skeleton loading state for memo lists.
+ * Use this for initial memo list loading and pagination.
+ * For generic page/route loading, use Spinner instead.
+ */
+const Skeleton = ({ showCreator = false, count = 4 }: Props) => (
+
+ {Array.from({ length: count }).map((_, index) => (
+
+ ))}
+
+);
export default Skeleton;
diff --git a/web/src/layouts/RootLayout.tsx b/web/src/layouts/RootLayout.tsx
index 69c2a172e..7a296ff37 100644
--- a/web/src/layouts/RootLayout.tsx
+++ b/web/src/layouts/RootLayout.tsx
@@ -2,7 +2,7 @@ import { Suspense, useEffect, useMemo } from "react";
import { Outlet, useLocation, useSearchParams } from "react-router-dom";
import usePrevious from "react-use/lib/usePrevious";
import Navigation from "@/components/Navigation";
-import Skeleton from "@/components/Skeleton";
+import Spinner from "@/components/Spinner";
import { useInstance } from "@/contexts/InstanceContext";
import { useMemoFilterContext } from "@/contexts/MemoFilterContext";
import useCurrentUser from "@/hooks/useCurrentUser";
@@ -47,7 +47,13 @@ const RootLayout = () => {
)}
- }>
+
+
+
+ }
+ >
diff --git a/web/src/main.tsx b/web/src/main.tsx
index 9bc61fc4d..39365f23c 100644
--- a/web/src/main.tsx
+++ b/web/src/main.tsx
@@ -8,6 +8,7 @@ import { RouterProvider } from "react-router-dom";
import "./i18n";
import "./index.css";
import { ErrorBoundary } from "@/components/ErrorBoundary";
+import Spinner from "@/components/Spinner";
import { AuthProvider, useAuth } from "@/contexts/AuthContext";
import { InstanceProvider, useInstance } from "@/contexts/InstanceContext";
import { ViewProvider } from "@/contexts/ViewContext";
@@ -39,7 +40,11 @@ function AppInitializer({ children }: { children: React.ReactNode }) {
}, [initAuth, initInstance]);
if (!authInitialized || !instanceInitialized) {
- return undefined;
+ return (
+
+
+
+ );
}
return <>{children}>;
diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx
index 5bf27269f..fc4fdd26f 100644
--- a/web/src/router/index.tsx
+++ b/web/src/router/index.tsx
@@ -1,7 +1,8 @@
+import type { ComponentType } from "react";
import { lazy, Suspense } from "react";
import { createBrowserRouter } from "react-router-dom";
import App from "@/App";
-import Skeleton from "@/components/Skeleton";
+import Spinner from "@/components/Spinner";
import MainLayout from "@/layouts/MainLayout";
import RootLayout from "@/layouts/RootLayout";
import Home from "@/pages/Home";
@@ -27,6 +28,19 @@ import { ROUTES } from "./routes";
export const Routes = ROUTES;
export { ROUTES };
+// Helper component to reduce Suspense boilerplate for lazy routes
+const LazyRoute = ({ component: Component }: { component: ComponentType }) => (
+
+
+
+ }
+ >
+
+
+);
+
const router = createBrowserRouter([
{
path: "/",
@@ -35,38 +49,10 @@ const router = createBrowserRouter([
{
path: Routes.AUTH,
children: [
- {
- path: "",
- element: (
- }>
-
-
- ),
- },
- {
- path: "admin",
- element: (
- }>
-
-
- ),
- },
- {
- path: "signup",
- element: (
- }>
-
-
- ),
- },
- {
- path: "callback",
- element: (
- }>
-
-
- ),
- },
+ { path: "", element: },
+ { path: "admin", element: },
+ { path: "signup", element: },
+ { path: "callback", element: },
],
},
{
@@ -76,101 +62,21 @@ const router = createBrowserRouter([
{
element: ,
children: [
- {
- path: "",
- element: ,
- },
- {
- path: Routes.EXPLORE,
- element: (
- }>
-
-
- ),
- },
- {
- path: Routes.ARCHIVED,
- element: (
- }>
-
-
- ),
- },
- {
- path: "u/:username",
- element: (
- }>
-
-
- ),
- },
+ { path: "", element: },
+ { path: Routes.EXPLORE, element: },
+ { path: Routes.ARCHIVED, element: },
+ { path: "u/:username", element: },
],
},
- {
- path: Routes.ATTACHMENTS,
- element: (
- }>
-
-
- ),
- },
- {
- path: Routes.INBOX,
- element: (
- }>
-
-
- ),
- },
- {
- path: Routes.SETTING,
- element: (
- }>
-
-
- ),
- },
- {
- path: "memos/:uid",
- element: (
- }>
-
-
- ),
- },
- // Redirect old path to new path.
- {
- path: "m/:uid",
- element: (
- }>
-
-
- ),
- },
- {
- path: "403",
- element: (
- }>
-
-
- ),
- },
- {
- path: "404",
- element: (
- }>
-
-
- ),
- },
- {
- path: "*",
- element: (
- }>
-
-
- ),
- },
+ { path: Routes.ATTACHMENTS, element: },
+ { path: Routes.INBOX, element: },
+ { path: Routes.SETTING, element: },
+ { path: "memos/:uid", element: },
+ // Redirect old path to new path
+ { path: "m/:uid", element: },
+ { path: "403", element: },
+ { path: "404", element: },
+ { path: "*", element: },
],
},
],