How Modly Manages Frontend Domains Using Zustand Stores: Architecture & Implementation
Modly uses a centralized Zustand navigation store in src/shared/stores/navStore.ts to track the active frontend domain via a currentPage state union type and a navigate action function, enabling reactive domain switching across components without requiring a complex routing library.
The open-source Modly application (lightningpixel/modly) splits its user interface into logical frontend domains including Generate, Workflows, Models, and Settings. By leveraging lightweight Zustand stores, the application maintains a global, reactive navigation system that controls domain visibility while isolating domain-specific data in separate, dedicated stores. This architecture simplifies state management and ensures a single source of truth for which UI section is currently active.
The Navigation Store Architecture
Modly centralizes all routing concerns in a single Zustand store responsible for tracking which frontend domain is visible. This approach eliminates prop drilling and allows any component to initiate domain changes or react to navigation events.
Centralizing Domain State in navStore.ts
The primary navigation logic lives in src/shared/stores/navStore.ts. According to the source code, this store is created using Zustand's create function and maintains a minimal state interface consisting of two properties:
currentPage– A string literal union type ('generate' | 'workflows' | 'models' | 'settings') representing the currently visible UI domainnavigate– An action function with signature(page: Page) => voidthat updates thecurrentPagevalue
When the navigate action is invoked (for example, navigate('workflows')), Zustand's set function updates the store state. Because Zustand employs reactive subscriptions, every component consuming the store via the useNavStore hook automatically re-renders with the new domain value.
The Page Type Union
The domain identifiers are strictly typed through a TypeScript union exported from the navigation store. The Page type restricts valid navigation targets to the four supported domains, providing compile-time safety when switching contexts:
// src/shared/stores/navStore.ts
export type Page = 'generate' | 'workflows' | 'models' | 'settings'
This type safety ensures that developers cannot navigate to non-existent domains, preventing runtime errors in the conditional rendering logic that depends on these string values.
How Domain Switching Works in Practice
Components interact with the navigation store through two primary patterns: reading the current domain to determine what to render, and calling the navigate function to change domains.
Reading the Current Domain
To determine which frontend domain is active, components select the currentPage state from the navigation store. This pattern enables headers, sidebars, and status indicators to reflect the current context:
import { useNavStore } from '@/shared/stores/navStore'
function Header() {
const currentPage = useNavStore(state => state.currentPage)
return <h1>Current: {currentPage}</h1>
}
Subscribing to specific state slices (rather than the entire store) ensures components only re-render when the domain actually changes, preserving performance.
Navigating Between Domains
Any UI element can trigger domain transitions by accessing the navigate action. Navigation controls, sidebar buttons, and programmatic redirects all use this same interface:
import { useNavStore } from '@/shared/stores/navStore'
function NavButton({ target }: { target: 'generate' | 'workflows' | 'models' | 'settings' }) {
const navigate = useNavStore(state => state.navigate)
return (
<button onClick={() => navigate(target)}>
Go to {target}
</button>
)
}
Because the store is global, domain switches can be initiated from deeply nested components without passing callbacks through intermediate layers.
Conditional Rendering Based on Active Domain
The main layout component uses the currentPage value to determine which domain-specific view to mount. This switch-based approach replaces traditional routing libraries for the primary application shell:
import { useNavStore } from '@/shared/stores/navStore'
import GenerateView from '@/areas/generate/GenerateView'
import WorkflowsView from '@/areas/workflows/WorkflowsView'
export default function MainContent() {
const page = useNavStore(state => state.currentPage)
switch (page) {
case 'generate': return <GenerateView />
case 'workflows': return <WorkflowsView />
case 'models': return <ModelsView />
case 'settings': return <SettingsView />
}
}
Each case mounts the corresponding component from the src/areas/* directory, ensuring domain-specific code is loaded only when needed.
Domain-Specific State Management
While navigation state remains centralized, Modly isolates data concerns by splitting domain-specific information into separate Zustand stores. This separation prevents state pollution and allows domains to manage complex data independently.
Isolating Workflows, Models, and Settings Data
Dedicated stores handle data for individual domains:
workflowsStore(src/shared/stores/workflowsStore.ts) – Contains workflow definitions and execution state used exclusively when the Workflows domain is activeextensionsStore(src/shared/stores/extensionsStore.ts) – Manages the extension registry displayed within the Models domainappStore(src/shared/stores/appStore.ts) – Stores global application configuration such as backend URLs and UI preferences that persist across all domains
This architecture ensures that heavy domain-specific data (such as workflow graphs or model metadata) does not linger in memory when the user switches to a different section, unless explicitly required by global state.
Global Application State
The appStore holds cross-cutting concerns that remain relevant regardless of the active frontend domain. Settings like API endpoints, theme preferences, and authentication tokens live here, making them accessible to both the Settings domain (which modifies them) and other domains (which consume them).
Extending the Domain Architecture
Adding a new frontend domain to Modly requires only three steps, demonstrating the scalability of this Zustand-based approach:
- Extend the Page type in
src/shared/stores/navStore.tsto include the new domain identifier - Create a new view component in
src/areas/[new-domain]/ - Add a case to the switch statement in the main layout component to render the new view when active
For example, to add an "Analytics" domain:
// src/shared/stores/navStore.ts
export type Page = 'generate' | 'workflows' | 'models' | 'settings' | 'analytics'
Components can then call navigate('analytics') to switch to the new domain, and the reactive store system will immediately update the UI.
Summary
- Centralized navigation lives in
src/shared/stores/navStore.ts, tracking the active domain via acurrentPagestring union andnavigateaction - Reactive updates propagate through Zustand's subscription model, causing components using
useNavStoreto re-render automatically when domains change - Separation of concerns is maintained by storing domain-specific data (workflows, extensions) in isolated stores while keeping navigation state global
- Type safety is enforced through the
Pageunion type, preventing invalid domain transitions at compile time - Simple extension model allows new frontend domains to be added by extending a type union and adding a switch case, without refactoring existing state logic
Frequently Asked Questions
How does Modly switch between different UI sections without React Router?
Modly replaces traditional routing libraries with a Zustand store that tracks the current domain as a simple string state. Components call the navigate action from navStore.ts to update this state, and the main layout uses a switch statement on currentPage to render the appropriate view from src/areas/*. This approach reduces bundle size and eliminates route-matching overhead for applications with discrete, non-URL-driven sections.
What is the purpose of the Page type union in navStore.ts?
The Page type union ('generate' | 'workflows' | 'models' | 'settings') acts as a strict contract that defines all valid frontend domains within the application. It provides TypeScript compile-time checking for the currentPage state and navigate function parameters, ensuring developers cannot accidentally navigate to undefined domains or typos in navigation logic.
How does Modly keep domain-specific data separate from navigation state?
While navStore.ts manages only which domain is visible, dedicated stores like workflowsStore.ts, extensionsStore.ts, and appStore.ts handle the actual data content for those domains. Components in the Workflows domain subscribe to workflowsStore for their data, while the navigation store simply controls their visibility. This prevents workflow data from affecting performance in the Settings domain and vice versa.
Can new frontend domains be added to Modly without refactoring existing stores?
Yes, new domains can be added by extending the Page type in navStore.ts, creating a corresponding view component in src/areas/, and adding a rendering case to the main layout switch statement. Existing stores remain untouched unless the new domain requires additional domain-specific state, in which case a new dedicated Zustand store following the existing pattern is created.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →