Skip to content

Architecture Guide

Deep dive into Constellation's system design, patterns, and architectural decisions.

System Architecture

High-Level Overview

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚               User Browser (Client)                     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Next.js 15 + React 19 Single Page Application         โ”‚
โ”‚  โ”œโ”€ Client Components (Interactive)                     โ”‚
โ”‚  โ”œโ”€ Server Components (SSR/SSG)                         โ”‚
โ”‚  โ””โ”€ API Routes                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                 โ”‚
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚            โ”‚                        โ”‚
    โ–ผ            โ–ผ                        โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ API    โ”‚   โ”‚ SSE      โ”‚   โ”‚ WebSocket        โ”‚
โ”‚ Server โ”‚   โ”‚ Service  โ”‚   โ”‚ (Hocuspocus)     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค   โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค   โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚:8001   โ”‚   โ”‚:8002     โ”‚   โ”‚:8003             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
    โ”‚
    โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Backend Services       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ€ข Business Logic        โ”‚
โ”‚ โ€ข Database              โ”‚
โ”‚ โ€ข Authentication        โ”‚
โ”‚ โ€ข File Storage          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Layered Architecture

1. Presentation Layer

Location: src/app/ and src/components/

Components organized by domain:

  • Pages (src/app/) - Route-based page components
  • Features (src/features/) - Feature-specific layouts
  • Components (src/components/) - Reusable UI components
  • Plate UI (src/components/plate-ui/) - Editor plugins

Responsibility: - Render UI - Handle user interactions - Display data

2. Business Logic Layer

Location: src/hooks/, src/features/

  • Custom Hooks - State management and side effects
  • Feature Logic - Feature-specific business rules
  • Context Providers - Global state

Responsibility: - Process user actions - Manage component state - Coordinate feature workflows

3. Data Access Layer

Location: src/services/, src/lib/

  • API Client - HTTP communication
  • Services - Domain-specific API calls
  • Utilities - Helper functions

Responsibility: - Communicate with backend - Format data for frontend - Handle errors


Component Architecture

Component Types

Components
โ”œโ”€โ”€ Page Components (src/app/)
โ”‚   โ””โ”€ Full page layouts with layout.tsx
โ”‚
โ”œโ”€โ”€ Feature Components (src/features/)
โ”‚   โ””โ”€ Feature-specific containers
โ”‚
โ”œโ”€โ”€ Smart Components (src/components/**/)
โ”‚   โ””โ”€ Connected to services/hooks
โ”‚
โ”œโ”€โ”€ Presentational Components (src/components/ui/)
โ”‚   โ””โ”€ Pure UI, no logic
โ”‚
โ””โ”€โ”€ Layout Components (src/components/common/)
    โ””โ”€ Navigation, header, footer

Component Hierarchy

App (Root Layout)
โ”œโ”€โ”€ Navigation
โ”œโ”€โ”€ Providers (Auth, i18n, Theme)
โ”œโ”€โ”€ Routes
โ”‚   โ”œโ”€โ”€ Homepage
โ”‚   โ”œโ”€โ”€ Dashboard
โ”‚   โ”‚   โ”œโ”€โ”€ Workspace List
โ”‚   โ”‚   โ”œโ”€โ”€ Recent Items
โ”‚   โ”‚   โ””โ”€โ”€ Analytics
โ”‚   โ”œโ”€โ”€ Workspace
โ”‚   โ”‚   โ”œโ”€โ”€ Editor
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Toolbar
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ PlateEditor
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ Sidebar
โ”‚   โ”‚   โ”œโ”€โ”€ Graph Viewer
โ”‚   โ”‚   โ””โ”€โ”€ Collaborators
โ”‚   โ”œโ”€โ”€ Admin Panel
โ”‚   โ”‚   โ”œโ”€โ”€ User Management
โ”‚   โ”‚   โ”œโ”€โ”€ Content Moderation
โ”‚   โ”‚   โ””โ”€โ”€ System Settings
โ”‚   โ””โ”€โ”€ Profile Settings
โ””โ”€โ”€ Footer

Data Flow Patterns

1. Server Components (SSR)

For pages that don't need interactivity:

// src/app/dashboard/page.tsx
import { getUserData } from '@/src/services/userService';

export default async function DashboardPage() {
  const user = await getUserData();

  return (
    <div>
      <h1>Welcome, {user.name}</h1>
    </div>
  );
}

Benefits: - Faster initial load - No JavaScript sent to client - Direct database access

2. Client Components with Hooks

For interactive features:

// src/components/workspace/WorkspaceEditor.tsx
'use client';

import { useWorkspace } from '@/src/hooks/use-workspace';

export function WorkspaceEditor() {
  const { workspace, updateContent } = useWorkspace();

  return (
    <Editor
      content={workspace.content}
      onChange={updateContent}
    />
  );
}

Flow:

User Action โ†’ Hook State Update โ†’ Re-render โ†’ UI Update

3. API Route Pattern

For backend communication:

// src/app/api/workspace/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const data = await apiClient.get(`/workspace/${params.id}`);
  return NextResponse.json(data);
}

Request Flow:

Frontend โ†’ Next.js API Route โ†’ Backend API โ†’ Database โ†’ Response

4. Real-time Updates (SSE)

For live notifications:

'use client';

import { useSSE } from '@/src/hooks/use-sse';

export function NotificationCenter() {
  const { notifications } = useSSE('/events');

  return (
    <div>
      {notifications.map(notification => (
        <Notification key={notification.id} {...notification} />
      ))}
    </div>
  );
}

State Management Strategy

Local Component State

const [count, setCount] = useState(0);

Use for: Form inputs, toggle states, temporary UI state

Context API

// src/components/context/WorkspaceContext.tsx
export const WorkspaceContext = createContext();

export function WorkspaceProvider({ children }) {
  const [workspace, setWorkspace] = useState(null);

  return (
    <WorkspaceContext.Provider value={{ workspace, setWorkspace }}>
      {children}
    </WorkspaceContext.Provider>
  );
}

Use for: Global app state, authentication, user preferences

Custom Hooks

// src/hooks/use-workspace.ts
export function useWorkspace() {
  const [workspace, setWorkspace] = useState(null);

  useEffect(() => {
    fetchWorkspace();
  }, []);

  return { workspace, updateWorkspace: setWorkspace };
}

Use for: Reusable stateful logic shared across components

Server-side Caching

// src/lib/cache.ts
import { cache } from 'react';

export const getUserData = cache(async (userId: string) => {
  return await apiClient.get(`/users/${userId}`);
});

Use for: Expensive operations, database queries


Routing Architecture

App Router Structure

src/app/
โ”œโ”€โ”€ layout.tsx              # Root layout
โ”œโ”€โ”€ page.tsx                # Home page (/)
โ”œโ”€โ”€ (www)/                  # Public route group
โ”‚   โ”œโ”€โ”€ about/page.tsx      # /about
โ”‚   โ””โ”€โ”€ pricing/page.tsx    # /pricing
โ”œโ”€โ”€ (admin)/                # Protected admin routes
โ”‚   โ”œโ”€โ”€ layout.tsx          # Admin layout
โ”‚   โ”œโ”€โ”€ users/page.tsx      # /admin/users
โ”‚   โ””โ”€โ”€ settings/page.tsx   # /admin/settings
โ”œโ”€โ”€ dashboard/              # Dashboard route
โ”‚   โ”œโ”€โ”€ layout.tsx          # Dashboard layout
โ”‚   โ”œโ”€โ”€ page.tsx            # /dashboard
โ”‚   โ””โ”€โ”€ [workspaceId]/      # Dynamic route
โ”‚       โ””โ”€โ”€ page.tsx        # /dashboard/[workspaceId]
โ”œโ”€โ”€ workspace/              # Workspace route
โ”‚   โ”œโ”€โ”€ [id]/
โ”‚   โ”‚   โ”œโ”€โ”€ page.tsx        # /workspace/[id]
โ”‚   โ”‚   โ”œโ”€โ”€ editor/page.tsx # /workspace/[id]/editor
โ”‚   โ”‚   โ””โ”€โ”€ settings/page.tsx
โ”‚   โ””โ”€โ”€ ...
โ”œโ”€โ”€ api/                    # API routes
โ”‚   โ”œโ”€โ”€ workspace/
โ”‚   โ”‚   โ”œโ”€โ”€ route.ts        # /api/workspace
โ”‚   โ”‚   โ””โ”€โ”€ [id]/route.ts   # /api/workspace/[id]
โ”‚   โ””โ”€โ”€ ...
โ””โ”€โ”€ [404].tsx               # Not found

Route Protection

// src/components/context/ProtectedRoute.tsx
import { redirect } from 'next/navigation';
import { isAdmin } from '@/src/lib/auth';

export async function ProtectedAdminRoute({ children }) {
  const admin = await isAdmin();

  if (!admin) {
    redirect('/');
  }

  return children;
}

Error Handling Strategy

Global Error Boundary

// src/app/error.tsx
'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div className="p-4">
      <h1>Something went wrong!</h1>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

API Error Handling

// src/lib/api.ts
class APIClient {
  async request(url: string, options: RequestInit) {
    try {
      const response = await fetch(url, options);

      if (!response.ok) {
        const error = await response.json();
        throw new APIError(error.message, response.status);
      }

      return response.json();
    } catch (error) {
      logger.error('API request failed', error);
      throw error;
    }
  }
}

User-facing Error Display

// src/components/common/ErrorNotification.tsx
'use client';

import { useEffect, useState } from 'react';
import { useError } from '@/src/hooks/use-error';

export function ErrorNotification() {
  const { error, clearError } = useError();
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    if (error) setVisible(true);
  }, [error]);

  if (!visible) return null;

  return (
    <div className="fixed top-4 right-4 bg-red-500 text-white p-4 rounded">
      {error?.message}
      <button onClick={() => {
        setVisible(false);
        clearError();
      }}>
        Dismiss
      </button>
    </div>
  );
}

Async Patterns

Server Components Async

// src/app/workspace/[id]/page.tsx
export default async function WorkspacePage({
  params: { id }
}: {
  params: { id: string }
}) {
  const workspace = await fetchWorkspace(id);

  return (
    <WorkspaceDetail workspace={workspace} />
  );
}

Client Components with useEffect

// src/components/WorkspaceViewer.tsx
'use client';

import { useEffect, useState } from 'react';

export function WorkspaceViewer({ id }: { id: string }) {
  const [workspace, setWorkspace] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    (async () => {
      try {
        const data = await apiClient.get(`/workspace/${id}`);
        setWorkspace(data);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    })();
  }, [id]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <WorkspaceDetail workspace={workspace} />;
}

Performance Optimization

Code Splitting

// Automatic route-based splitting
// Each route gets its own bundle

// Manual component splitting
import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => <div>Loading...</div>,
});

Memoization

// Memoize expensive component
import { memo } from 'react';

export const MemoizedList = memo(function List({ items }) {
  return items.map(item => <Item key={item.id} {...item} />);
});

Image Optimization

// Use Next.js Image component
import Image from 'next/image';

export function OptimizedImage() {
  return (
    <Image
      src="/image.jpg"
      alt="Description"
      width={800}
      height={600}
      priority // For above-fold images
    />
  );
}

Dependency Injection Pattern

// src/lib/dependencies.ts
export const dependencies = {
  apiClient: new APIClient(),
  authService: new AuthService(),
  notificationService: new NotificationService(),
};

// Usage in hooks
export function useWorkspace() {
  const api = dependencies.apiClient;
  // ...
}

Testing Architecture

Unit Tests

// Test individual functions
describe('formatDate', () => {
  it('should format date correctly', () => {
    expect(formatDate(new Date('2024-01-15'))).toBe('Jan 15, 2024');
  });
});

Component Tests

// Test component behavior
describe('WorkspaceCard', () => {
  it('should render workspace name', () => {
    render(<WorkspaceCard workspace={{ name: 'My Workspace' }} />);
    expect(screen.getByText('My Workspace')).toBeInTheDocument();
  });
});

Integration Tests

// Test feature workflows
describe('Workspace Creation Flow', () => {
  it('should create new workspace', async () => {
    // Mock API, render component, simulate user action, assert result
  });
});


Last Updated: January 2026