BISO Sites

Architecture Overview

High-level architecture of the BISO Sites monorepo including system design, data flow, and key architectural decisions.

Architecture Overview

This document provides a high-level overview of the BISO Sites monorepo architecture, explaining how all the pieces fit together and why certain architectural decisions were made.

System Architecture

BISO SItes is built as a monorepo containing multiple applications that share common packages. The architecture follows these key principles:

  1. Separation of Concerns - Clear boundaries between applications and packages
  2. Code Reusability - Shared packages eliminate duplication
  3. Type Safety - Full TypeScript coverage across all code
  4. Framework Agnostic Packages - Packages don't depend on Next.js runtime
  5. Scalability - Can add new apps and packages easily

High-Level Diagram

Layer Architecture

The system is organized into distinct layers:

Presentation Layer (Apps)

Responsibility: User interface and user interactions

  • Web App - Public-facing UI for end users
  • Admin App - Management interface for content creators
  • Docs App - Documentation for developers

Technologies:

  • Next.js 15 App Router
  • React 19
  • Server Components & Server Actions
  • next-intl for i18n

Business Logic Layer (Packages)

Responsibility: Reusable business logic and utilities

  • @repo/api - Appwrite API wrappers
  • @repo/payment - Payment processing logic
  • @repo/editor - Page builder configuration

Key Principle: Framework-agnostic, testable, pure TypeScript

Presentation Components Layer (@repo/ui)

Responsibility: Reusable UI components

  • shadcn/ui components
  • Custom composite components
  • Theme provider
  • Styling utilities

Technology: React + Tailwind CSS

Infrastructure Layer (Backend)

Responsibility: Data persistence, authentication, file storage

  • Appwrite - Primary backend

    • Database (NoSQL/Document)
    • Authentication & Authorization
    • File Storage
    • Real-time subscriptions
  • External Services

    • Vipps MobilePay (payments)
    • OpenAI API (AI features in admin)

Architectural Patterns

Monorepo Pattern

Why Monorepo?

Benefits:

  • Share code between apps without publishing packages
  • Single version of dependencies across all apps
  • Atomic commits across multiple apps
  • Consistent tooling and configuration
  • Easy to refactor across boundaries

Challenges:

  • Larger repository size
  • Need proper build orchestration (solved with Turborepo)

Implementation:

  • Turborepo for build orchestration
  • Bun workspaces for dependency management
  • Shared tsconfig and eslint configs

Dependency Injection Pattern

Used in the payment package to keep it framework-agnostic.

Before (❌ Tight Coupling):

// packages/payment/vipps.ts
import { createSessionClient } from '@repo/api/server'; // Next.js dependency!

export async function createCheckout(params) {
  const { db } = await createSessionClient(); // Importing Next.js runtime
  // ... logic
}

After (✅ Dependency Injection):

// packages/payment/vipps.ts
// No Next.js imports - framework agnostic!

export async function createCheckout(params, db) {
  // db is passed as parameter
  // ... logic
}

Benefits:

  • Package can be used in any Node.js environment
  • Easy to test (mock the db)
  • Clear dependencies
  • Follows SOLID principles

See Dependency Injection for full details.

Server-First Architecture

Leveraging Next.js App Router's server-first approach:

Server Components (Default):

  • Fast initial page load
  • SEO friendly
  • Direct database access
  • Reduced client bundle size

Client Components (When Needed):

  • Interactivity (onClick, useState, etc.)
  • Browser APIs
  • Third-party libraries that need window/document

Server Actions:

  • Type-safe mutations
  • No API routes needed for simple operations
  • Progressive enhancement

Internationalization (i18n)

Pattern: Route-based localization

// URL structure
/en/about  -> English
/no/about  -> Norwegian

// Message files
messages/
├── en/
│   └── about.json
└── no/
    └── about.json

Benefits:

  • SEO friendly (different URLs per language)
  • Easy to add new languages
  • Type-safe translations with next-intl

Component Composition

UI components follow atomic design principles:

// Atoms
<Button />
<Input />

// Molecules  
<SearchBar />
<NavItem />

// Organisms
<Navigation />
<HeroSection />

// Templates (Layouts)
<DashboardLayout />

// Pages
<HomePage />

Data Flow

Read Operations (Queries)

Write Operations (Mutations)

Payment Flow

Authentication Flow

Build & Deploy Pipeline

Key Features:

  • Incremental builds - Only rebuild what changed
  • Remote caching - Share build cache across team
  • Parallel execution - Build multiple apps simultaneously
  • Task dependencies - Ensure packages build before apps

Technology Stack Summary

Frontend

TechnologyVersionPurpose
Next.js15.xReact framework
React19.xUI library
TypeScript5.9.xType safety
Tailwind CSS4.xStyling
shadcn/uiLatestComponent library

Backend

TechnologyPurpose
AppwriteDatabase, Auth, Storage
Node.jsRuntime
BunPackage manager

Build Tools

ToolPurpose
TurborepoBuild orchestration
BunPackage management
ESLintCode linting
PrettierCode formatting

Development

ToolPurpose
TypeScriptType checking
Next.js DevHot reload
FumadocsDocumentation

Design Decisions

Why Turborepo?

Decision: Use Turborepo over Nx or Lerna

Reasoning:

  • Simple configuration
  • Great caching
  • Works perfectly with Next.js
  • Fast builds
  • Good DX

Why Bun?

Decision: Use Bun instead of npm/yarn/pnpm

Reasoning:

  • 10-20x faster installs
  • Compatible with npm packages
  • Built-in TypeScript support
  • Modern and actively maintained

Why Appwrite?

Decision: Use Appwrite over Firebase/Supabase

Reasoning:

  • Self-hostable (data ownership)
  • Open source
  • Great TypeScript support
  • All-in-one solution (DB, Auth, Storage)
  • No vendor lock-in

Why Next.js App Router?

Decision: Use App Router over Pages Router

Reasoning:

  • Server Components (better performance)
  • Simplified data fetching
  • Server Actions (no API routes needed)
  • Better file-based routing
  • Future of Next.js

Why Dependency Injection?

Decision: Use DI pattern in packages

Reasoning:

  • Framework-agnostic packages
  • Easier testing
  • Clear dependencies
  • SOLID principles
  • Reusable in different contexts

Security Considerations

Authentication

  • Session-based auth with Appwrite
  • HTTPOnly cookies
  • CSRF protection
  • Role-based access control (RBAC)

API Security

  • Server-side API keys (never exposed to client)
  • Session verification on all protected routes
  • Rate limiting (Appwrite built-in)

Payment Security

  • PCI compliance via Vipps
  • No card data stored locally
  • Webhook signature verification
  • HTTPS only

Environment Variables

  • Secrets in .env.local (gitignored)
  • Public vars prefixed with NEXT_PUBLIC_
  • Validation at runtime

Performance Optimization

Build Time

  • Turborepo caching
  • Incremental builds
  • Parallel execution
  • Remote cache sharing

Runtime

  • Server Components (less JS to client)
  • Static generation where possible
  • Image optimization (next/image)
  • Code splitting
  • Font optimization

Bundle Size

  • Shared packages reduce duplication
  • Tree shaking
  • Dynamic imports for large components
  • Minimal client-side JavaScript

Scalability

Horizontal Scaling

  • Stateless Next.js apps
  • Session state in Appwrite
  • Can run multiple instances

Vertical Scaling

  • Appwrite scales independently
  • Database sharding possible
  • CDN for static assets

Future Considerations

  • Microservices if needed
  • Message queue for async tasks
  • Separate API server
  • CDN integration

Next Steps

ℹ️
Dive Deeper

Now that you understand the overall architecture: