# How Modly Manages Frontend Domains Using Zustand Stores: Architecture & Implementation

> Discover how Modly manages frontend domains with a centralized Zustand navigation store. Learn about its architecture and reactive domain switching implementation.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: architecture
- Published: 2026-08-21

---

**Modly uses a centralized Zustand navigation store in [`src/shared/stores/navStore.ts`](https://github.com/lightningpixel/modly/blob/main/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](https://github.com/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`](https://github.com/lightningpixel/modly/blob/main/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 domain
- **`navigate`** – An action function with signature `(page: Page) => void` that updates the `currentPage` value

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:

```typescript
// 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:

```tsx
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:

```tsx
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:

```tsx
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`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/workflowsStore.ts)) – Contains workflow definitions and execution state used exclusively when the *Workflows* domain is active
- **`extensionsStore`** ([`src/shared/stores/extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/extensionsStore.ts)) – Manages the extension registry displayed within the *Models* domain
- **`appStore`** ([`src/shared/stores/appStore.ts`](https://github.com/lightningpixel/modly/blob/main/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:

1. **Extend the Page type** in [`src/shared/stores/navStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/navStore.ts) to include the new domain identifier
2. **Create a new view component** in `src/areas/[new-domain]/` 
3. **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:

```typescript
// 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`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/navStore.ts), tracking the active domain via a `currentPage` string union and `navigate` action
- **Reactive updates** propagate through Zustand's subscription model, causing components using `useNavStore` to 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 `Page` union 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`](https://github.com/lightningpixel/modly/blob/main/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`](https://github.com/lightningpixel/modly/blob/main/navStore.ts) manages only which domain is visible, dedicated stores like [`workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/workflowsStore.ts), [`extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/extensionsStore.ts), and [`appStore.ts`](https://github.com/lightningpixel/modly/blob/main/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`](https://github.com/lightningpixel/modly/blob/main/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.