Relevant source files
The following files were used as context for generating this wiki page:
- server/dashboard/src/app/setup/page.tsx
- server/dashboard/src/app/root/dashboard-client-layout.tsx
- server/dashboard/src/app/root/dashboard/memories/page.tsx
- server/dashboard/src/app/root/dashboard/configuration/page.tsx
- server/dashboard/src/app/root/dashboard/api-keys/page.tsx
- server/dashboard/src/lib/auth.tsx
- server/dashboard/src/app/root/dashboard/requests/page.tsx
- server/dashboard/src/app/setup/layout.tsx
- server/dashboard/src/app/root/dashboard/settings/page.tsx
- server/dashboard/src/app/root/dashboard/entities/page.tsx
- server/dashboard/src/store/store.ts
- server/dashboard/src/app/root/dashboard/components/main-nav.tsx
- server/dashboard/src/app/root/dashboard/components/nav-wrapper.tsx
- server/dashboard/src/middleware.ts
- server/dashboard/src/app/auth/layout.tsx
- server/dashboard/src/app/root/dashboard/layout.tsx
- server/dashboard/src/hooks/use-auth.ts
- server/dashboard/src/app/auth/login/login-form.tsx
- server/dashboard/src/app/root/clientLayout.tsx
- server/dashboard/src/store/rootReducer.ts
- server/dashboard/src/components/ui/form.tsx
- server/dashboard/src/app/root/layout.tsx
- server/dashboard/package.json
- server/dashboard/src/app/root/dashboard/analytics/page.tsx
- server/dashboard/src/utils/api.ts
- server/dashboard/src/hooks/use-api-query.ts
- server/dashboard/src/app/auth/login/page.tsx
- server/dashboard/src/app/api/auth/refresh/route.ts
- server/dashboard/src/components/ui/accordion.tsx
- server/dashboard/src/components/theme-provider.tsx
Overview
The Mem0 self-hosted dashboard is built as a Next.js 15 single-page application that provides a comprehensive web interface for managing memories, system configuration, API keys, and administrative operations. Powered by React 19, Redux Toolkit, and Tailwind CSS, the dashboard architecture implements a modular client layout alongside a secure Next.js middleware routing layer. It orchestrates state management, automated Axios token interception, and first-run initialization flows to seamlessly connect administrators with the underlying FastAPI backend.
Sources: server/dashboard/package.json:1-84, server/dashboard/src/middleware.ts:1-63
Application Layout and Provider Hierarchy
Root Layout Structure and Font Integration
The dashboard architecture organizes layouts across distinct Next.js route groups including root, authentication, and setup. The root client layout defined in DashboardClientLayout injects typography variables and provider trees into the DOM. It loads five font configurations (Inter, InterDisplay, Roboto, Fustat, and DMMono) from local font utilities and applies their CSS variable declarations alongside base font styles directly onto the document body.
export function DashboardClientLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body
className={cn(
Inter.className,
InterDisplay.variable,
Roboto.variable,
Fustat.variable,
DMMono.variable,
)}
suppressHydrationWarning
>Sources: server/dashboard/src/app/root/dashboard-client-layout.tsx:22-38
Similarly, the setup and authentication route layouts establish parallel HTML document roots that load the identical font configuration sets and enforce suppress hydration warnings to prevent server-client attribute mismatches.
return (
<html
lang="en"
className={cn(
Fustat.variable,
InterDisplay.variable,
Inter.variable,
Roboto.variable,
DMMono.variable,
)}
suppressHydrationWarning
>
<body className="font-fustat" suppressHydrationWarning>Sources: server/dashboard/src/app/setup/layout.tsx:17-29, server/dashboard/src/app/auth/layout.tsx:18-30
Theme Injection and Provider Composition
Provider hierarchy wraps application route trees in a specific nested order to ensure state stores, authentication contexts, theme properties, and tooltips are available to all child components. In the root client layout, the Redux store provider sits at the outermost boundary, followed in sequence by the authentication provider, next-themes provider, and the custom client layout.
<Provider store={store}>
<AuthProvider>
<ThemeProvider
attribute="class"
defaultTheme="light"
enableSystem
disableTransitionOnChange
>
<ClientLayout>{children}</ClientLayout>
<Toaster />
</ThemeProvider>
</AuthProvider>
</Provider>Sources: server/dashboard/src/app/root/dashboard-client-layout.tsx:39-51
Theme injection is handled via ThemeProvider, a wrapper around next-themes that accepts attributes, default settings, and transition rules. Across route layouts, theme configuration values differ based on authentication or installation state requirements:
| Route Group / Layout | Default Theme | Enable System | Disable Transition On Change | Attribute |
|---|---|---|---|---|
Root Dashboard (dashboard-client-layout.tsx) | light | true | true | class |
Setup Wizard (setup/layout.tsx) | light | false | true | class |
Authentication (auth/layout.tsx) | system | true | true | class |
Sources: server/dashboard/src/app/root/dashboard-client-layout.tsx:41-46, server/dashboard/src/app/setup/layout.tsx:31-35, server/dashboard/src/app/auth/layout.tsx:32-37
Note
The setup wizard layout intentionally disables system theme detection (enableSystem={false}), forcing a default light theme during initial installation, whereas authentication and dashboard layouts support dynamic system preference matching.
Sources: server/dashboard/src/app/setup/layout.tsx:31-35, server/dashboard/src/app/auth/layout.tsx:32-37
Client Layout and Sidebar Geometry
The inner ClientLayout component enforces session validation logic. It evaluates the current pathname against public prefixes (/login and /setup) and monitors authentication state. If a user is unauthenticated on a protected path, it renders an AuthLoadingState containing a theme-aware logo and linear progress indicator while executing a router replacement to /login.
const isPublicPage =
pathname.startsWith("/login") || pathname.startsWith("/setup");
useEffect(() => {
if (!isPublicPage && !isLoading && !user) {
router.replace("/login");
}
}, [isLoading, isPublicPage, router, user]);
if (isLoading && !isPublicPage) {
return <AuthLoadingState />;
}
if (!isPublicPage && !user) {
return <AuthLoadingState />;
}Sources: server/dashboard/src/app/root/clientLayout.tsx:31-46
Within the dashboard sub-layout, structural dimensions for navigation and content containers are governed by fixed constants exported from the client layout module.
export const SIDEBAR_WIDTH = 180;
export const COLLAPSED_SIDEBAR_WIDTH = 64;
export const COLLAPSED_SIDEBAR_PADDING = 16;
export const COLLAPSED_SIDEBAR_WIDTH_WITHOUT_PADDING =
COLLAPSED_SIDEBAR_WIDTH - COLLAPSED_SIDEBAR_PADDING;Sources: server/dashboard/src/app/root/clientLayout.tsx:10-14
The dashboard view layout reads sidebar collapse state from the Redux store (state.layout.isSidebarCollapsed) to dynamically calculate horizontal offsets and content area width styles for the main scrollable viewport container.
const isSidebarCollapsed = useSelector(
(state: RootState) => state.layout.isSidebarCollapsed,
);
return (
<>
<NavWrapper />
<div
className="mt-[48px] rounded-tl-lg relative h-[calc(100vh-56px)] bg-surface-default-primary border border-memBorder-primary overflow-hidden transition-all duration-300 ease-in-out font-fustat"
style={{
left: `${isSidebarCollapsed ? COLLAPSED_SIDEBAR_WIDTH : SIDEBAR_WIDTH}px`,
width: `calc(100vw - ${isSidebarCollapsed ? COLLAPSED_SIDEBAR_WIDTH + 8 : SIDEBAR_WIDTH + 8}px)`,
}}Sources: server/dashboard/src/app/root/dashboard/layout.tsx:15-27
Client-Side Authentication Session Management
AuthProvider Context and Session Lifecycle
Client-side session handling centers around AuthProvider, a client component ("use client") that manages user profile state, loading indicators, and authentication actions. The AuthContext supplies consumer hooks with user records, role checking via isAdmin, and actions for login, register, logout, and refreshUser.
export interface AuthUser {
id: string;
name: string;
email: string;
role: string;
created_at: string;
}
interface AuthContextValue {
user: AuthUser | null;
isLoading: boolean;
isAdmin: boolean;
login: (email: string, password: string) => Promise<void>;
register: (name: string, email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
refreshUser: () => Promise<void>;
}
export const AuthContext = createContext<AuthContextValue>({
user: null,
isLoading: true,
isAdmin: false,
login: async () => {},
register: async () => {},
logout: async () => {},
refreshUser: async () => {},
});Sources: server/dashboard/src/lib/auth.tsx:13-39
When the component mounts, an effect hook invokes refreshSession() to exchange an HTTP-only refresh cookie for an access token. If successful, it calls loadUser() to fetch user details from AUTH_ENDPOINTS.ME. In case of failure or network rejection, user resets to null, and isLoading sets to false.
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<AuthUser | null>(null);
const [isLoading, setIsLoading] = useState(true);
const loadUser = useCallback(async () => {
const res = await api.get<AuthUser>(AUTH_ENDPOINTS.ME);
setUser(res.data);
}, []);
useEffect(() => {
let active = true;
(async () => {
try {
const ok = await refreshSession();
if (ok && active) await loadUser();
} catch {
if (active) setUser(null);
} finally {
if (active) setIsLoading(false);
}
})();
return () => {
active = false;
};
}, [loadUser]);Sources: server/dashboard/src/lib/auth.tsx:64-88
Warning
Component unmounting sets active = false to prevent asynchronous state updates on unmounted component instances during initial session validation.
Sources: server/dashboard/src/lib/auth.tsx:73-87
API Token Handlers and Cookie Routes
Client authentication helpers communicate with Next.js API route handlers to read, write, and purge session tokens. The backend refresh route manages secure cookies named mem0_refresh_token using parameters configured in COOKIE_OPTIONS.
const COOKIE_NAME = "mem0_refresh_token";
function shouldUseSecureCookie() {
const dashboardUrl = process.env.DASHBOARD_URL;
if (!dashboardUrl) {
return process.env.NODE_ENV === "production";
}
try {
return new URL(dashboardUrl).protocol === "https:";
} catch {
return process.env.NODE_ENV === "production";
}
}
const COOKIE_OPTIONS = {
httpOnly: true,
secure: shouldUseSecureCookie(),
sameSite: "lax" as const,
path: "/",
maxAge: 30 * 24 * 60 * 60, // 30 days
};Sources: server/dashboard/src/app/api/auth/refresh/route.ts:6-27
| Route Method | Action | Request Payload | Response | Error Code |
|---|---|---|---|---|
| POST | Validates existing refresh token and issues new tokens | None (reads cookie) | { access_token } | 401 |
| PUT | Stores refresh token in HTTP-only cookie | { refresh_token } | { ok: true } | 400 |
| DELETE | Clears refresh token cookie | None | { ok: true } | — |
Sources: server/dashboard/src/app/api/auth/refresh/route.ts:29-75
Authentication hooks like useAuth provide clean access to the context throughout component trees by wrapping useContext(AuthContext).
export function useAuth() {
return useContext(AuthContext);
}Sources: server/dashboard/src/hooks/use-auth.ts:6-8
Route Protection and Edge Middleware
Middleware Execution and Path Interception
The dashboard relies on Next.js edge middleware running prior to route resolution to inspect incoming requests and enforce authentication policies. The middleware matches requests against paths excluding static assets and static files as defined by the matcher configuration.
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|fonts|images|icons).*)"],
};Sources: server/dashboard/src/middleware.ts:65-67
Incoming path evaluation begins by checking against a static array of public prefixes. If a request pathname starts with any entry in PUBLIC_PATHS, the middleware bypasses all checks and allows execution to proceed.
const PUBLIC_PATHS = [
"/_next",
"/api/auth",
"/api/health",
"/fonts",
"/favicon",
];
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) {
return NextResponse.next();
}Sources: server/dashboard/src/middleware.ts:5-18
| Public Path Prefix | Purpose |
|---|---|
/_next | Next.js internal static chunks and assets |
/api/auth | Authentication endpoints and token refresh routes |
/api/health | System health check inspection route |
/fonts | Application font files |
/favicon | Browser icon assets |
Sources: server/dashboard/src/middleware.ts:5-11
Setup Status Verification and Routing
When a request targets root paths (/, /login, or /setup), the middleware queries the backend server setup status endpoint to determine whether initial deployment registration is required.
if (pathname === "/" || pathname === "/login" || pathname === "/setup") {
try {
const res = await fetch(
`${getServerApiUrl()}${AUTH_ENDPOINTS.SETUP_STATUS}`,
);Sources: server/dashboard/src/middleware.ts:22-26
Warning
If the backend API is unreachable during setup status checks, the network request throws an error which is caught locally, causing the middleware to gracefully fall through to standard navigation rules without blocking requests.
Sources: server/dashboard/src/middleware.ts:37-39
Depending on the boolean flag needsSetup returned from the API, the middleware enforces routing rules:
if (res.ok) {
const { needsSetup } = await res.json();
if (needsSetup && pathname !== "/setup") {
return NextResponse.redirect(new URL("/setup", request.url));
}
if (!needsSetup && pathname === "/setup") {
return NextResponse.redirect(new URL("/login", request.url));
}
}Sources: server/dashboard/src/middleware.ts:27-36
Token Verification and Protected Navigation
For routes outside the public or setup pathways, the middleware checks for the presence of the HTTP-only refresh token cookie (mem0_refresh_token).
const hasRefreshToken = request.cookies.has("mem0_refresh_token");Sources: server/dashboard/src/middleware.ts:20-20
If a user visits the application root (/), redirection behavior branches based on whether the refresh token cookie exists. If present, the user is forwarded to /dashboard/requests; otherwise, they are routed to /login. Similarly, base dashboard paths (/dashboard or /dashboard/) automatically rewrite to /dashboard/requests.
if (pathname === "/") {
return NextResponse.redirect(
new URL(hasRefreshToken ? "/dashboard/requests" : "/login", request.url),
);
}
if (pathname === "/dashboard" || pathname === "/dashboard/") {
return NextResponse.redirect(new URL("/dashboard/requests", request.url));
}Sources: server/dashboard/src/middleware.ts:46-54
When a protected route is accessed without the mem0_refresh_token cookie, the middleware constructs a redirect response targeting /login and appends the original destination to a next query parameter so the client can resume its path after authentication.
if (!hasRefreshToken) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("next", pathname);
return NextResponse.redirect(loginUrl);
}Sources: server/dashboard/src/middleware.ts:56-60
Axios Client and Token Interception
Centralized API Client Setup and Bearer Token Injection
The dashboard instantiates a centralized Axios instance configured with a base URL sourced from process.env.NEXT_PUBLIC_API_URL. To authenticate outgoing requests, a request interceptor inspects the in-memory token cache and injects a Bearer authorization header if a token exists.
let cachedToken: string | null = null;
const LOGIN_PATH = "/login";
export const setAccessToken = (token: string | null) => {
cachedToken = token;
};
export const getAccessToken = (): string | null => {
return cachedToken;
};
Sources: server/dashboard/src/utils/api.ts:3-13
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
});
api.interceptors.request.use(
async (config) => {
if (cachedToken) {
config.headers = config.headers ?? {};
config.headers.Authorization = `Bearer ${cachedToken}`;
}
return config;
},
(error) => {
return Promise.reject(error);
},
);Sources: server/dashboard/src/utils/api.ts:42-57
Note
Tokens are held exclusively in runtime memory via cachedToken and manipulated using setAccessToken and getAccessToken helpers, preventing persistent storage vulnerabilities on the client side.
Sources: server/dashboard/src/utils/api.ts:3-12
Response Interception and Token Refresh Flow
When an API request encounters a 401 Unauthorized response, the response interceptor triggers an automated token recovery sequence. It first clears the stored token via handleTokenError and issues a POST request to /api/auth/refresh with credentials included.
const handleTokenError = () => {
cachedToken = null;
};Sources: server/dashboard/src/utils/api.ts:14-16
const refreshAccessToken = async () => {
const refreshResponse = await fetch("/api/auth/refresh", {
method: "POST",
credentials: "include",
});
if (!refreshResponse.ok) {
return null;
}
const data = await refreshResponse.json();
setAccessToken(data.access_token);
return data.access_token as string;
};Sources: server/dashboard/src/utils/api.ts:24-37
If the refresh endpoint returns a valid new access token, the client updates its cache, attaches the new Bearer token to the original request configuration, and retries the failed call. If the refresh attempt fails or throws an exception, the client clears the token cache and executes a window redirect to /login.
api.interceptors.response.use(
(response) => response,
async (error: AxiosError<{ error?: string }>) => {
if (error.response?.status === 401) {
handleTokenError();
try {
const nextToken = await refreshAccessToken();
if (nextToken && error.config) {
error.config.headers = error.config.headers ?? {};
error.config.headers.Authorization = `Bearer ${nextToken}`;
return api.request(error.config);
}
} catch {}
handleTokenError();
redirectToLogin();
}Sources: server/dashboard/src/utils/api.ts:59-76
const redirectToLogin = () => {
if (typeof window !== "undefined") {
window.location.href = LOGIN_PATH;
}
};Sources: server/dashboard/src/utils/api.ts:18-22
Streaming Request Handling
Alongside the standard Axios instance, the API module exposes a custom postStream helper designed to handle server-sent event or streaming payload requests via the native fetch API.
const postStream = async (url: string, data: unknown): Promise<Response> => {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${url}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: cachedToken ? `Bearer ${cachedToken}` : "",
},
body: JSON.stringify(data),
});Sources: server/dashboard/src/utils/api.ts:86-94
The postStream function evaluates response statuses explicitly, throwing an unauthorized error and triggering redirectToLogin if a 401 status code occurs, or parsing error payloads when requests fail.
if (response.status === 401) {
handleTokenError();
redirectToLogin();
throw new Error("Unauthorized");
}
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || "Request failed");
}
return response;
};Sources: server/dashboard/src/utils/api.ts:96-108
Data Fetching Hook Lifecycle
The useApiQuery Execution Model
The useApiQuery custom hook manages asynchronous data fetching, loading flags, error strings, and manual refetch capabilities for dashboard components like ApiKeysPage and ConfigurationPage. It accepts a generic fetcher function alongside a UseApiQueryOptions configuration object specifying enabled, errorToast, and initialData.
interface UseApiQueryOptions<T> {
enabled?: boolean;
errorToast?: string;
initialData?: T;
}
interface UseApiQueryResult<T> {
data: T | undefined;
isLoading: boolean;
error: string;
refetch: () => Promise<void>;
}
export function useApiQuery<T>(
fetcher: () => Promise<T>,
options: UseApiQueryOptions<T> = {},
): UseApiQueryResult<T> {Sources: server/dashboard/src/hooks/use-api-query.ts:5-21
State management tracks resolved items via data, active request status via isLoading, and failure messages via error. A React useRef tracks the latest fetcher reference so callbacks execute the most up-to-date query logic without capturing stale closures.
const [data, setData] = useState<T | undefined>(initialData);
const [isLoading, setIsLoading] = useState(enabled);
const [error, setError] = useState("");
const fetcherRef = useRef(fetcher);
fetcherRef.current = fetcher;
Sources: server/dashboard/src/hooks/use-api-query.ts:24-30
Call-Chain Execution Walkthrough
ApiKeysPageorConfigurationPageinvokesuseApiQueryduring component initialization, passing a custom async fetcher and options.useApiQuerydefines theruncallback viauseCallback, which marksisLoadingas true and clears any existingerrorstring.runexecutesfetcherRef.current()to retrieve network data, storing the resolved value indataupon success.- If an exception occurs,
getErrorMessageprocesses the error alongside any providederrorToastlabel, populateserror, and invokestoast()if an error toast option was specified. - The
finallyblock setsisLoadingto false, and auseEffecthook triggersrunautomatically on mount ifenabledis true.
const run = useCallback(async () => {
setIsLoading(true);
setError("");
try {
setData(await fetcherRef.current());
} catch (err) {
const message = getErrorMessage(err, errorToast || "Request failed");
setError(message);
if (errorToast) {
toast({
title: errorToast,
description: message,
variant: "destructive",
});
}
} finally {
setIsLoading(false);
}
}, [errorToast]);
useEffect(() => {
if (enabled) void run();
}, [enabled, run]);Sources: server/dashboard/src/hooks/use-api-query.ts:31-53
Sources: server/dashboard/src/hooks/use-api-query.ts:31-55, server/dashboard/src/app/root/dashboard/api-keys/page.tsx:41-47, server/dashboard/src/app/root/dashboard/configuration/page.tsx:41-47
Options and Return Signature Reference
| Property / Parameter | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Controls whether the query executes automatically on initial component mount. |
errorToast | string | undefined | Title string passed to the centralized notification toast if the request fails. |
initialData | T | undefined | Fallback data value assigned before the initial fetch completes. |
data | T | undefined | initialData | The successfully resolved payload returned by the fetcher function. |
isLoading | boolean | enabled | Boolean flag tracking whether an asynchronous fetch operation is currently in flight. |
error | string | "" | Formatted error message string populated when a fetch exception occurs. |
refetch | () => Promise<void> | — | Manual trigger function to re-run the asynchronous fetcher on demand. |
interface UseApiQueryOptions<T> {
enabled?: boolean;
errorToast?: string;
initialData?: T;
}
interface UseApiQueryResult<T> {
data: T | undefined;
isLoading: boolean;
error: string;
refetch: () => Promise<void>;
}
export function useApiQuery<T>(
fetcher: () => Promise<T>,
options: UseApiQueryOptions<T> = {},
): UseApiQueryResult<T> {Sources: server/dashboard/src/hooks/use-api-query.ts:5-21
return { data, isLoading, error, refetch: run };Sources: server/dashboard/src/hooks/use-api-query.ts:55-55
Note
If errorToast is omitted from UseApiQueryOptions, failure messages update the local error state variable without dispatching an intrusive toast notification to the user interface.
Sources: server/dashboard/src/hooks/use-api-query.ts:37-45
Redux Toolkit State and Navigation
Redux Store Configuration and Root Reducer
The application state container is established using Redux Toolkit's configureStore helper, importing a consolidated rootReducer that merges all feature slices. Currently, the store manages a single slice under the layout key, and exports standard TypeScript definitions for RootState and AppDispatch.
import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./rootReducer";
const store = configureStore({
reducer: rootReducer,
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;Sources: server/dashboard/src/store/store.ts:1-10
import { combineReducers } from "redux";
import { layoutReducer } from "./reducers/layoutReducer";
const rootReducer = combineReducers({
layout: layoutReducer,
});
export default rootReducer;Sources: server/dashboard/src/store/rootReducer.ts:1-8
| Export Name | Return Type / Definition | Purpose |
|---|---|---|
store | Return type of configureStore | The central Redux store instance wrapping rootReducer. |
RootState | ReturnType<typeof store.getState> | Global state type used by selectors across components. |
AppDispatch | typeof store.dispatch | Dispatch type for triggering slice actions. |
Sources: server/dashboard/src/store/store.ts:8-10
Note
rootReducer uses Redux's combineReducers function, making it easy to append new feature slices alongside layout as the dashboard architecture expands.
Sources: server/dashboard/src/store/rootReducer.ts:1-6
Collapsible Sidebar Navigation and Layout State
The sidebar navigation layout relies on the isSidebarCollapsed boolean flag retrieved from state.layout.isSidebarCollapsed via Redux's useSelector. Depending on this state, components like MainNav and NavWrapper dynamically adapt their width, hide or display text labels, and toggle collapsible trigger groups.
const pathname = usePathname();
const isSidebarCollapsed = useSelector(
(state: RootState) => state.layout.isSidebarCollapsed,
);
const [isCloudOpen, setIsCloudOpen] = React.useState(true);
Sources: server/dashboard/src/app/root/dashboard/components/main-nav.tsx:43-48
The navigation structure is divided into three primary sections: Activity, Cloud Features (wrapped in a local Collapsible component controlled by isCloudOpen), and Account. When the sidebar is collapsed, group labels are hidden, and menu items center their icons with tooltip text enabled.
<div className="flex flex-col gap-0">
{!isSidebarCollapsed && (
<SidebarGroupLabel className="mb-0">
ACTIVITY
</SidebarGroupLabel>
)}
{[
{
title: "Requests",
url: "/dashboard/requests",
icon: Activity,
active: pathname === "/dashboard/requests",
},
{
title: "Memories",
url: "/dashboard/memories",
icon: GalleryVerticalEnd,
active: pathname === "/dashboard/memories",
},
{
title: "Entities",
url: "/dashboard/entities",
icon: Users,
active: pathname === "/dashboard/entities",
},
].map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
asChild
collapsed={isSidebarCollapsed}
active={item.active}
tooltip={isSidebarCollapsed ? item.title : undefined}
>
<Link
href={item.url}
className={cn(
"flex items-center w-full",
isSidebarCollapsed
? "justify-center mx-auto"
: "gap-1.5",
)}
>
<item.icon className="size-4 shrink-0" />
{!isSidebarCollapsed && <span>{item.title}</span>}
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</div>
{isSidebarCollapsed && (
<div className="h-[1px] w-full bg-memBorder-primary my-2" />
)}
<Collapsible
open={isCloudOpen}
onOpenChange={setIsCloudOpen}
className="flex flex-col gap-0"
>
{!isSidebarCollapsed && (
<CollapsibleTrigger asChild>
<SidebarGroupLabel className="cursor-pointer mb-0">
CLOUD FEATURES
<ChevronDown
className={cn(
"size-3 transition-transform duration-200",
isCloudOpen ? "" : "-rotate-90",
)}
/>
</SidebarGroupLabel>
</CollapsibleTrigger>
)}
<CollapsibleContent className="flex flex-col gap-0">
{[
{
title: "Categories",
url: "/dashboard/categories",
icon: Tags,
},
{Sources: server/dashboard/src/app/root/dashboard/components/main-nav.tsx:59-242
export default function NavWrapper() {
const dispatch = useDispatch();
const isSidebarCollapsed = useSelector(
(state: RootState) => state.layout.isSidebarCollapsed,
);
const { user, logout } = useAuth();
const instanceName = process.env.NEXT_PUBLIC_INSTANCE_NAME || "Mem0";
const handleToggle = useCallback(() => {
dispatch(toggleSidebar());
}, [dispatch]);
Sources: server/dashboard/src/app/root/dashboard/components/nav-wrapper.tsx:31-43
Warning
Toggling the sidebar state updates both the fixed container width and the adjacent content offset calculations using constants like COLLAPSED_SIDEBAR_WIDTH and SIDEBAR_WIDTH.
Sources: server/dashboard/src/app/root/dashboard/components/nav-wrapper.tsx:46-51, server/dashboard/src/app/root/dashboard/components/nav-wrapper.tsx:136-141
First-Run Setup Wizard Flow
Multi-Step Wizard and Account Registration
The first-run setup wizard is orchestrated by SetupPage, which guides uninitialized server instances through a sequential five-step flow. The wizard tracks the active step index via local state and references an array of step titles defining the initialization lifecycle.
const STEP_TITLES = [
"Create your admin account",
"Review provider configuration",
"Your API key",
"Tell us your use case",
"Test your setup",
];Sources: server/dashboard/src/app/setup/page.tsx:45-51
| Step Index | Title | Primary Action / Handler |
|---|---|---|
0 | Create your admin account | handleStep1 validates email format, confirms password matching, enforces a minimum length of 8 characters, and invokes register(name, email, password). |
1 | Review provider configuration | handleStep2 checks if configuration fields are dirty, builds provider payloads, and posts updates to MEMORY_ENDPOINTS.CONFIGURE. |
2 | Your API key | handleStep3 requests a new API key from API_KEY_ENDPOINTS.BASE using the specified key label. |
3 | Tell us your use case | Captures user use case selection from preset options and custom instructions. |
4 | Test your setup | Executes a validation test message against the configured backend. |
Sources: server/dashboard/src/app/setup/page.tsx:45-51, server/dashboard/src/app/setup/page.tsx:157-186, server/dashboard/src/app/setup/page.tsx:188-235, server/dashboard/src/app/setup/page.tsx:237-246
Warning
Step 1 registration enforces strict client-side validation: email must satisfy isValidEmail, password must equal confirmPassword, and password.length must be at least 8 characters before submitting credentials to the authentication provider.
Sources: server/dashboard/src/app/setup/page.tsx:160-173
Provider Configuration and Credentials
When the wizard advances to step index 1, useEffect triggers an asynchronous configuration loader. This routine fetches the effective server configuration and bundled providers concurrently using MEMORY_ENDPOINTS.CONFIGURE and MEMORY_ENDPOINTS.CONFIGURE_PROVIDERS.
const loadConfig = async () => {
try {
const [configRes, providersRes] = await Promise.all([
api.get(MEMORY_ENDPOINTS.CONFIGURE),
api.get<BundledProviders>(MEMORY_ENDPOINTS.CONFIGURE_PROVIDERS),
]);Sources: server/dashboard/src/app/setup/page.tsx:112-117
The configuration loader extracts provider identifiers and model names for both Language Model (llm) and embedder subsystems, storing both current state values and initial baselines to detect subsequent modifications.
const llmProv = config?.llm?.provider || "";
const llmMod = config?.llm?.config?.model || "";
const embProv = config?.embedder?.provider || "";
const embMod = config?.embedder?.config?.model || "";
setLlmProvider(llmProv);
setLlmModel(llmMod);
setEmbedderProvider(embProv);
setEmbedderModel(embMod);
setInitialLlmProvider(llmProv);
setInitialLlmModel(llmMod);
setInitialEmbedderProvider(embProv);
setInitialEmbedderModel(embMod);
setServerHasLlmKey(!!config?.llm?.config?.api_key);
setProviders(providersRes.data);Sources: server/dashboard/src/app/setup/page.tsx:124-138
const USE_CASE_PRESETS = [
"Personal assistant",
"Coding agent",
"Customer support",
"Research",
"Therapy / journaling",
];Sources: server/dashboard/src/app/setup/page.tsx:55-61
Tip
If no changes are detected when submitting step 2 (dirty evaluates to false), the wizard bypasses the API save request and immediately advances to step 2.
Sources: server/dashboard/src/app/setup/page.tsx:192-202
Related
On This Page
OverviewApplication Layout and Provider HierarchyRoot Layout Structure and Font IntegrationTheme Injection and Provider CompositionClient Layout and Sidebar GeometryClient-Side Authentication Session ManagementAuthProvider Context and Session LifecycleAPI Token Handlers and Cookie RoutesRoute Protection and Edge MiddlewareMiddleware Execution and Path InterceptionSetup Status Verification and RoutingToken Verification and Protected NavigationAxios Client and Token InterceptionCentralized API Client Setup and Bearer Token InjectionResponse Interception and Token Refresh FlowStreaming Request HandlingData Fetching Hook LifecycleThe useApiQuery Execution ModelCall-Chain Execution WalkthroughOptions and Return Signature ReferenceRedux Toolkit State and NavigationRedux Store Configuration and Root ReducerCollapsible Sidebar Navigation and Layout StateFirst-Run Setup Wizard FlowMulti-Step Wizard and Account RegistrationProvider Configuration and CredentialsRelated