Skip to main content

Island Architecture Framework Design and Implementation

Comprehensive low-level design for a modular island architecture framework enabling isolated component rendering and state management in modern web applications.

Island architecture represents a modern approach to web application design where independent components (islands) can be rendered and managed in isolation while maintaining seamless integration within a larger application ecosystem.

1. Problem Statement & Requirements

Clarifying questions

  1. What is the target scope of the island architecture framework? Should it support full application islands, micro-frontend islands, or just isolated component islands within a larger app?
  2. What browsers and devices should we support? Should we target IE11, mobile-first, or just modern browsers?
  3. What authentication and authorization patterns are expected? Will islands need to handle their own auth, or will they inherit from a parent app?
  4. What is the expected data flow pattern? Should islands communicate via events, callbacks, or a shared state management system?
  5. What level of accessibility is required? Should islands be fully keyboard navigable and screen reader compatible out-of-the-box?
  6. Are there any i18n requirements? Should the framework support internationalization or assume English-only?
  7. What design system or component library are we building against? Should islands integrate with existing design tokens and components?

Functional requirements

  • Island discovery and registration: Framework must provide a mechanism for dynamically discovering and registering islands within an application
  • Island lifecycle management: Support for mounting, updating, and unmounting islands with proper cleanup
  • Island communication: Enable communication between islands (parent-child, sibling, or broadcast patterns)
  • Island configuration: Allow runtime configuration of islands (props, theme, locale, etc.)
  • Island isolation: Ensure that islands are isolated from each other and the parent application's global state
  • Island hydration: Support client-side hydration of islands that were server-rendered
  • Island bundling: Provide build-time tooling to package islands independently for optimal loading

Non-functional requirements

  • Performance: Islands must not cause performance degradation to the parent application or other islands
  • Scalability: Framework should support hundreds of islands with minimal overhead
  • Maintainability: Islands should be easily testable, debuggable, and maintainable in isolation
  • Accessibility: All islands must meet WCAG 2.1 AA standards
  • Security: Islands must not have access to parent application's sensitive data or DOM manipulation
  • Bundle size: Each island should be under 50KB (compressed) to minimize loading times
  • Type safety: Full TypeScript support for all island interfaces and configurations

Constraints and assumptions

  • Islands will be built using React 18+ with hooks and context API
  • Framework assumes a modern JavaScript environment (ES2020+) with support for ES modules
  • Islands will be isolated in their own bundles and loaded on demand
  • Parent application provides the core rendering context (theme, i18n provider, etc.)
  • Islands will not have direct DOM access to the parent application's DOM tree
  • Framework assumes that islands are self-contained and do not depend on global CSS or JS

Out of scope

  • Server-side rendering: Isolated islands will not be rendered server-side in this framework version
  • Cross-island state sharing: Direct global state management between islands is out of scope (will be handled by parent app or external store)
  • Island-to-island routing: Navigation between islands is not part of this framework's scope
  • Island versioning: Framework does not handle version compatibility between islands and the parent application
  • Island hot reloading: Runtime code updates or hot module replacement is not supported in this design
  • Island testing utilities: Framework does not provide built-in testing tools or mocks for islands

2. Component Architecture

graph TD
    A[IslandFramework] --> B[IslandContainer]
    A --> C[IslandRenderer]
    B --> D[IslandProvider]
    B --> E[IslandRegistry]
    C --> F[IslandComponent]
    C --> G[IslandSlot]
    D --> H[IslandContext]
    E --> I[IslandConfig]
    F --> J[IslandProps]
    G --> K[SlotProps]
    H --> L[IslandState]

Component Tree Structure

The Island Framework follows a hierarchical component structure where islands are self-contained units that can be composed and orchestrated:

  • IslandFramework (Root): Main orchestrator that manages the entire island ecosystem
  • IslandContainer: Wraps individual islands and handles their lifecycle management
  • IslandRenderer: Responsible for rendering islands with proper context and configuration
  • IslandProvider: Context provider that supplies island-specific configuration and state
  • IslandRegistry: Central registry for all available islands in the application
  • IslandComponent: The actual island implementation that can be rendered independently
  • IslandSlot: Placeholder for content to be injected into islands
  • IslandContext: Provides runtime context and configuration to islands

Smart vs. Dumb Components

Smart Components (Container/Provider):

  • IslandContainer: Manages island lifecycle, handles state transitions, orchestrates rendering
  • IslandProvider: Provides context and configuration to child islands
  • IslandRegistry: Centralized management of available islands and their configurations

Dumb Components (Presentational):

  • IslandComponent: Pure presentation layer that renders island content
  • IslandSlot: Simple placeholder component for content injection
  • IslandRenderer: Renders islands with proper configuration and context

TypeScript Props Contracts

// IslandComponent props contract
interface IslandProps {
  id: string;
  name: string;
  config?: IslandConfig;
  children?: React.ReactNode;
  onMount?: (id: string) => void;
  onUnmount?: (id: string) => void;
  onError?: (error: Error, id: string) => void;
}

// IslandSlot props contract
interface SlotProps {
  name: string;
  fallback?: React.ReactNode;
  children?: React.ReactNode;
}

// IslandRegistry props contract
interface IslandRegistryProps {
  islands: Record<string, IslandConfig>;
  onIslandRegistered?: (id: string) => void;
  onIslandUnregistered?: (id: string) => void;
}

// IslandContext contract
interface IslandContextType {
  islands: Record<string, IslandConfig>;
  registerIsland: (id: string, config: IslandConfig) => void;
  unregisterIsland: (id: string) => void;
  getIslandConfig: (id: string) => IslandConfig | undefined;
  updateIslandConfig: (id: string, config: Partial<IslandConfig>) => void;
  isIslandActive: (id: string) => boolean;
}

// IslandConfig type
interface IslandConfig {
  id: string;
  name: string;
  component: React.ComponentType<any>;
  dependencies?: string[];
  context?: Record<string, any>;
  mountOptions?: {
    hydrate?: boolean;
    defer?: boolean;
    preload?: boolean;
  };
  lifecycle?: {
    onMount?: () => void;
    onUnmount?: () => void;
    onError?: (error: Error) => void;
  };
}

UI State Machine

stateDiagram-v2
    [*] --> Idle
    Idle --> Loading : MountRequested
    Loading --> Active : LoadSuccess
    Loading --> Error : LoadFailure
    Active --> Unloading : UnmountRequested
    Unloading --> Idle : UnloadSuccess
    Error --> Loading : RetryRequested
    Error --> Idle : Dismissed
    Active --> Error : RuntimeFailure

Component Responsibilities

  • IslandFramework: Main orchestrator that initializes the island ecosystem and manages global state
  • IslandContainer: Handles individual island lifecycle management, including mounting/unmounting and error handling
  • IslandRenderer: Renders islands with proper configuration, context, and error boundaries
  • IslandProvider: Provides runtime context and configuration to islands through React Context
  • IslandRegistry: Central registry that maintains all available islands and their configurations, enabling dynamic island composition
  • IslandComponent: The actual implementation of an island that can be rendered independently with proper props
  • IslandSlot: Placeholder component that allows content to be injected into islands for composition

Reusability Decisions

The framework prioritizes:

  1. IslandComponent - Highly reusable across different parts of the application
  2. IslandSlot - Generic placeholder that can be used in any island composition scenario
  3. IslandRegistry - Centralized management that can be reused across multiple applications or micro-frontend setups
  4. IslandContext - Provides consistent context management that can be extended for various use cases

Component Lifecycle Considerations

  1. Mounting: Islands can be mounted either eagerly or deferred based on configuration
  2. Hydration: Support for both client-side and server-side hydration patterns
  3. Unloading: Graceful cleanup of resources when islands are removed from the DOM
  4. Error Handling: Isolated error boundaries per island to prevent cascading failures
  5. Configuration Updates: Dynamic updates to island configuration without full re-rendering
// Example IslandComponent implementation
const IslandComponent: React.FC<IslandProps> = ({ 
  id, 
  name, 
  config,
  children,
  onMount,
  onUnmount,
  onError
}) => {
  const [isMounted, setIsMounted] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  
  useEffect(() => {
    if (onMount) onMount(id);
    setIsMounted(true);
    
    return () => {
      if (onUnmount) onUnmount(id);
      setIsMounted(false);
    };
  }, [id, onMount, onUnmount]);
  
  if (error && onError) {
    onError(error, id);
  }
  
  return (
    <div 
      data-island-id={id} 
      data-island-name={name}
      className={`island-${name}`}
    >
      {children}
    </div>
  );
};

3. State Management Design

Local state vs. global state decisions

For an island architecture framework, state management follows a hybrid approach:

  • Local state: Component-specific state managed with React's useState/useReducer, ideal for UI-only concerns like form inputs, toggle states, and component visibility
  • Global state: Shared across islands using a centralized store for cross-island communication and shared context like user preferences, theme settings, and navigation state
  • Island-local state: Each island maintains its own isolated state that doesn't interfere with other islands

State management approach

Zustand is selected as the primary state management solution for the following reasons:

  1. Lightweight: No provider wrappers or complex setup
  2. Simple API: Easy to understand and maintain for developers
  3. TypeScript native: Excellent type inference and support
  4. Island-friendly: Each island can create its own store without global pollution
  5. Middleware support: Enables logging, persistence, and debugging capabilities

State shape and normalization strategy

// Normalized state structure for island framework
interface IslandState {
  islands: Record<string, Island>;
  sharedContext: SharedContext;
  userPreferences: UserPreferences;
  navigation: NavigationState;
}

interface Island {
  id: string;
  name: string;
  status: 'idle' | 'loading' | 'error' | 'ready';
  data: any;
  metadata: {
    dependencies: string[];
    lifecycle: IslandLifecycle;
  };
}

interface SharedContext {
  theme: 'light' | 'dark';
  locale: string;
  user: User | null;
}

interface UserPreferences {
  notifications: boolean;
  autoRefresh: boolean;
  fontSize: 'small' | 'medium' | 'large';
}

interface NavigationState {
  currentPath: string;
  breadcrumbs: Breadcrumb[];
  history: string[];
}

Server state vs. client state separation

Server state (fetched data) is managed separately from client state:

  • Server state: Managed with React Query for caching, refetching, and optimistic updates
  • Client state: UI state, user preferences, and island-specific runtime data
  • Hybrid approach: Islands can choose to use React Query for server state or local state for client-only concerns

Cache invalidation strategy

  1. Time-based: Automatic cache expiration (5-10 minutes for most data)
  2. Event-driven: Invalidate when user actions occur (e.g., form submission, navigation)
  3. Manual invalidation: API endpoints for explicit cache clearing
  4. Stale-while-revalidate: Allow stale data while fetching fresh data in background

Optimistic updates design

Optimistic updates are implemented for user actions that modify data:

  • Immediate UI feedback: Update UI before server response
  • Rollback on error: Revert to previous state if server request fails
  • Conflict resolution: Handle concurrent updates gracefully

Critical-path hook implementation

// IslandStore.ts - Core state management for island framework
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { devtools } from 'zustand/mmiddleware';

interface IslandStoreState {
  islands: Record<string, Island>;
  sharedContext: SharedContext;
  userPreferences: UserPreferences;
  navigation: NavigationState;
  activeIslandId: string | null;
  
  // Actions
  registerIsland: (island: Island) => void;
  unregisterIsland: (id: string) => void;
  updateIslandData: (id: string, data: any) => void;
  setActiveIsland: (id: string | null) => void;
  updateSharedContext: (context: Partial<SharedContext>) => void;
  updateUserPreferences: (prefs: Partial<UserPreferences>) => void;
  navigateTo: (path: string) => void;
  
  // Async actions with optimistic updates
  submitForm: (islandId: string, formData: any) => Promise<void>;
}

interface Island {
  id: string;
  name: string;
  status: 'idle' | 'loading' | 'error' | 'ready';
  data: any;
  metadata: {
    dependencies: string[];
    lifecycle: IslandLifecycle;
  };
}

interface IslandLifecycle {
  mountedAt?: Date;
  lastUpdated?: Date;
  error?: string;
}

interface SharedContext {
  theme: 'light' | 'dark';
  locale: string;
  user: User | null;
}

interface UserPreferences {
  notifications: boolean;
  autoRefresh: boolean;
  fontSize: 'small' | 'medium' | 'large';
}

interface NavigationState {
  currentPath: string;
  breadcrumbs: Breadcrumb[];
  history: string[];
}

interface Breadcrumb {
  label: string;
  path: string;
}

interface User {
  id: string;
  name: string;
  email: string;
}

const useIslandStore = create<IslandStoreState>()(
  devtools(
    persist(
      (set, get) => ({
        islands: {},
        sharedContext: {
          theme: 'light',
          locale: 'en-US',
          user: null,
        },
        userPreferences: {
          notifications: true,
          autoRefresh: true,
          fontSize: 'medium',
        },
        navigation: {
          currentPath: '/',
          breadcrumbs: [],
          history: [],
        },
        activeIslandId: null,
        
        registerIsland: (island) => {
          set((state) => ({
            islands: {
              ...state.islands,
              [island.id]: { ...island, status: 'ready' },
            },
          }));
        },
        
        unregisterIsland: (id) => {
          set((state) => {
            const newIslands = { ...state.islands };
            delete newIslands[id];
            return {
              islands: newIslands,
              activeIslandId: state.activeIslandId === id ? null : state.activeIslandId,
            };
          });
        },
        
        updateIslandData: (id, data) => {
          set((state) => ({
            islands: {
              ...state.islands,
              [id]: {
                ...state.islands[id],
                data,
                status: 'ready',
              },
            },
          }));
        },
        
        setActiveIsland: (id) => {
          set({ activeIslandId: id });
        },
        
        updateSharedContext: (context) => {
          set((state) => ({
            sharedContext: { ...state.sharedContext, ...context },
          }));
        },
        
        updateUserPreferences: (prefs) => {
          set((state) => ({
            userPreferences: { ...state.userPreferences, ...prefs },
          }));
        },
        
        navigateTo: (path) => {
          set((state) => {
            const newHistory = [...state.navigation.history, state.navigation.currentPath];
            return {
              navigation: {
                ...state.navigation,
                currentPath: path,
                history: newHistory,
              },
            };
          });
        },
        
        submitForm: async (islandId, formData) => {
          // Optimistic update
          set((state) => ({
            islands: {
              ...state.islands,
              [islandId]: {
                ...state.islands[islandId],
                status: 'loading',
              },
            },
          }));
          
          try {
            // Simulate API call
            await new Promise(resolve => setTimeout(resolve, 1000));
            
            // Update with real data after successful submission
            set((state) => ({
              islands: {
                ...state.islands,
                [islandId]: {
                  ...state.islands[islandId],
                  data: formData,
                  status: 'ready',
                },
              },
            }));
          } catch (error) {
            // Rollback on error
            set((state) => ({
              islands: {
                ...state.islands,
                [islandId]: {
                  ...state.islands[islandId],
                  status: 'error',
                  metadata: {
                    ...state.islands[islandId].metadata,
                    error: (error as Error).message,
                  },
                },
              },
            }));
          }
        },
      }),
      {
        name: 'island-framework-storage',
        partialize: (state) => ({
          sharedContext: state.sharedContext,
          userPreferences: state.userPreferences,
        }),
      }
    )
  )
);

// Custom hook for island-specific state access
const useIslandState = (islandId: string) => {
  const island = useIslandStore((state) => state.islands[islandId]);
  const updateData = useIslandStore((state) => state.updateIslandData);
  const setActive = useIslandStore((state) => state.setActiveIsland);
  
  return {
    island,
    updateData,
    setActive,
  };
};

// Custom hook for shared context
const useSharedContext = () => {
  const context = useIslandStore((state) => state.sharedContext);
  const update = useIslandStore((state) => state.updateSharedContext);
  
  return {
    context,
    update,
  };
};

export { useIslandStore, useIslandState, useSharedContext };

The critical-path hook implementation provides:

  1. Type-safe state management with full TypeScript support
  2. Island lifecycle tracking including status management and error handling
  3. Optimistic updates for user interactions with proper rollback on failure
  4. Shared context management for cross-island communication
  5. Persistence layer to maintain state across sessions
  6. Devtools integration for debugging and monitoring
  7. Modular design allowing each island to manage its own state while sharing global context

4. Routing & Navigation

Route Structure and Hierarchy

The island architecture framework will implement a nested route structure that supports both static and dynamic content islands. The routing system will be organized into three main levels:

  1. Public Routes: / (landing), /login, /signup
  2. Protected Routes: /dashboard, /profile, /settings
  3. Island Routes: /islands/:islandId, /islands/:islandId/:componentId
graph TD
    A[Root] --> B[Public Routes]
    A --> C[Protected Routes]
    A --> D[Island Routes]
    
    B --> B1[/]
    B --> B2[/login]
    B --> B3[/signup]
    
    C --> C1[/dashboard]
    C --> C2[/profile]
    C --> C3[/settings]
    
    D --> D1[/islands/:id]
    D --> D2[/islands/:id/:componentId]

Code Splitting Strategy

Route-based code splitting will be implemented using React.lazy and Suspense:

  • Public routes: Split into separate bundles to reduce initial load
  • Protected routes: Bundle with authentication context and shared components
  • Island routes: Dynamic code splitting per island component to enable progressive loading
// Route configuration with lazy loading
const routes = [
  {
    path: '/',
    element: React.lazy(() => import('pages/LandingPage')),
    lazy: true
  },
  {
    path: '/login',
    element: React.lazy(() => import('pages/LoginPage')),
    lazy: true
  },
  {
    path: '/dashboard',
    element: React.lazy(() => import('pages/DashboardPage')),
    lazy: true,
    requiresAuth: true
  },
  {
    path: '/islands/:id',
    element: React.lazy(() => import('components/IslandContainer')),
    lazy: true,
    requiresAuth: true
  }
];

Authentication guards will be implemented using React Router's navigation hooks:

// AuthGuard component that wraps protected routes
const AuthGuard: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const { isAuthenticated, loading } = useAuth();
  
  if (loading) return <LoadingSpinner />;
  if (!isAuthenticated) return <Navigate to="/login" replace />;
  
  return children;
};

// Route configuration with guard
const ProtectedRoute: React.FC<{ 
  element: React.ReactNode; 
  path: string 
}> = ({ element, path }) => (
  <Route 
    path={path} 
    element={
      <AuthGuard>
        {element}
      </AuthGuard>
    } 
  />
);

Deep Linking Support and URL Parameter Design

Island architecture will support deep linking through a structured URL pattern:

/islands/:islandId/:componentId?params=...&filters=...

URL parameters will be normalized and validated:

// URL parameter validation and normalization
const validateIslandParams = (params: Record<string, string>) => {
  const validated = {
    islandId: params.islandId?.match(/^[a-zA-Z0-9_-]+$/) ? params.islandId : null,
    componentId: params.componentId?.match(/^[a-zA-Z0-9_-]+$/) ? params.componentId : null,
    filters: parseFilters(params.filters),
    sort: params.sort || 'default'
  };
  
  return validated;
};

// URL parameter serialization
const serializeIslandParams = (params: IslandRouteParams) => {
  const searchParams = new URLSearchParams();
  
  if (params.filters) {
    searchParams.append('filters', JSON.stringify(params.filters));
  }
  
  if (params.sort) {
    searchParams.append('sort', params.sort);
  }
  
  return searchParams.toString();
};

Browser History Management and Scroll Restoration

Scroll restoration will be handled using React Router's built-in scroll restoration:

// Custom scroll restoration implementation
const ScrollToTop: React.FC = () => {
  const location = useLocation();
  
  useEffect(() => {
    window.scrollTo(0, 0);
  }, [location.pathname]);
  
  return null;
};

// Route configuration with scroll restoration
const AppRouter: React.FC = () => (
  <BrowserRouter>
    <ScrollToTop />
    <Routes>
      <Route path="/" element={<LandingPage />} />
      <Route path="/dashboard" element={<DashboardPage />} />
      <Route path="/islands/:id" element={<IslandContainer />} />
    </Routes>
  </BrowserRouter>
);

The scroll restoration will be enhanced with custom behavior for island components to maintain their scroll position when navigating between different components within the same island.

Route Configuration Example

// Complete route configuration with island support
const routeConfig: RouteConfig[] = [
  {
    path: '/',
    element: <LandingPage />,
    lazy: true,
    title: 'Home'
  },
  {
    path: '/login',
    element: <LoginPage />,
    lazy: true,
    title: 'Login'
  },
  {
    path: '/dashboard',
    element: <DashboardPage />,
    lazy: true,
    requiresAuth: true,
    title: 'Dashboard'
  },
  {
    path: '/islands/:islandId',
    element: <IslandContainer />,
    lazy: true,
    requiresAuth: true,
    title: 'Island View'
  },
  {
    path: '/islands/:islandId/:componentId',
    element: <IslandComponentView />,
    lazy: true,
    requiresAuth: true,
    title: 'Island Component'
  }
];

This routing strategy ensures that the island architecture can scale efficiently while maintaining proper navigation semantics and user experience. The nested structure supports both application-level navigation and component-level navigation within islands, enabling granular loading and caching strategies.

5. API Integration Layer

API Client Design Pattern

The island architecture framework will utilize a centralized Axios instance with interceptor support for consistent request/response handling. The client will be built as a modular, extensible system that supports multiple API endpoints and authentication strategies.

// api/client.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';

class ApiClient {
  private client: AxiosInstance;
  
  constructor(baseURL: string, timeout = 10000) {
    this.client = axios.create({
      baseURL,
      timeout,
      headers: {
        'Content-Type': 'application/json',
      },
    });
    
    this.setupInterceptors();
  }
  
  private setupInterceptors() {
    // Request interceptor for auth token injection
    this.client.interceptors.request.use(
      (config) => {
        const token = localStorage.getItem('authToken');
        if (token) {
          config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
      },
      (error) => Promise.reject(error)
    );
    
    // Response interceptor for error handling and normalization
    this.client.interceptors.response.use(
      (response) => response,
      (error) => {
        if (error.response?.status === 401) {
          // Handle auth errors
          localStorage.removeItem('authToken');
          window.location.href = '/login';
        }
        return Promise.reject(error);
      }
    );
  }
  
  public get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return this.client.get<T>(url, config);
  }
  
  public post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return this.client.post<T>(url, data, config);
  }
  
  public put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return this.client.put<T>(url, data, config);
  }
  
  public delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return this.client.delete<T>(url, config);
  }
}

export const apiClient = new ApiClient(process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000/api');

Request/Response Interceptors

The interceptor system handles:

  • Authentication token injection
  • Response normalization (consistent error format)
  • Automatic retry logic for transient errors
  • Error transformation for better UX

Error Handling and Retry Strategy

// api/errorHandler.ts
import { AxiosError } from 'axios';

interface ApiError {
  status: number;
  message: string;
  code?: string;
}

export class ApiError extends Error {
  constructor(
    public status: number,
    message: string,
    public code?: string
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

export const handleApiError = (error: AxiosError): ApiError => {
  if (error.response) {
    return new ApiError(
      error.response.status,
      error.response.data?.message || 'API Error',
      error.response.data?.code
    );
  }
  
  if (error.request) {
    return new ApiError(503, 'Network Error - Please check your connection');
  }
  
  return new ApiError(500, 'Unknown Error occurred');
};

// Retry logic with exponential backoff
export const retryRequest = async <T>(
  fn: () => Promise<T>,
  retries = 3,
  delay = 1000
): Promise<T> => {
  for (let i = 0; i <= retries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === retries) throw error;
      
      // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
    }
  }
  throw new Error('Retry limit exceeded');
};

Loading, Error, and Empty States Design

The framework provides a consistent pattern for handling different API states:

// components/ApiStateHandler.tsx
import React from 'react';

type ApiState<T> = 
  | { status: 'loading' }
  | { status: 'error'; error: Error }
  | { status: 'success'; data: T }
  | { status: 'empty' };

interface ApiStateHandlerProps<T> {
  state: ApiState<T>;
  loadingComponent?: React.ReactNode;
  errorComponent?: (error: Error) => React.ReactNode;
  emptyComponent?: React.ReactNode;
  successComponent: (data: T) => React.ReactNode;
}

export const ApiStateHandler = <T,>({
  state,
  loadingComponent,
  errorComponent,
  emptyComponent,
  successComponent,
}: ApiStateHandlerProps<T>) => {
  switch (state.status) {
    case 'loading':
      return loadingComponent || <div>Loading...</div>;
      
    case 'error':
      return errorComponent 
        ? errorComponent(state.error) 
        : <div>Error: {state.error.message}</div>;
        
    case 'empty':
      return emptyComponent || <div>No data available</div>;
      
    case 'success':
      return successComponent(state.data);
  }
};

Data Fetching Strategy

The framework supports multiple data fetching strategies based on use case:

  1. CSR (Client-Side Rendering) - For dynamic, user-interaction driven content
  2. SSR (Server-Side Rendering) - For SEO and initial page load performance
  3. ISR (Incremental Static Regeneration) - For content that needs periodic updates
// hooks/useIslandData.ts
import { useState, useEffect } from 'react';
import { apiClient } from '../api/client';

interface IslandData<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

export const useIslandData = <T>(endpoint: string, options?: { 
  staleTime?: number; 
  retry?: boolean;
}): IslandData<T> => {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await apiClient.get<T>(endpoint);
        setData(response.data);
      } catch (err) {
        setError(err instanceof Error ? err : new Error('Unknown error'));
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [endpoint]);

  return { data, loading, error };
};

API Response Transformation Layer

// api/transformers.ts
interface RawUser {
  id: string;
  name: string;
  email: string;
  createdAt: string;
}

interface User {
  id: string;
  name: string;
  email: string;
  joinedAt: Date;
}

export const transformUser = (rawUser: RawUser): User => ({
  id: rawUser.id,
  name: rawUser.name,
  email: rawUser.email,
  joinedAt: new Date(rawUser.createdAt),
});

// Generic transformer for collections
export const transformCollection = <T, U>(
  rawItems: T[],
  transformer: (item: T) => U
): U[] => {
  return rawItems.map(transformer);
};

Sequence Diagram for Primary Data Flow

sequenceDiagram
    participant Client
    participant AuthInterceptor
    participant ApiClient
    participant Server
    participant ResponseTransformer
    
    Client->>AuthInterceptor: Make API request
    AuthInterceptor->>ApiClient: Inject auth token
    ApiClient->>Server: Send request with token
    Server-->>ApiClient: Return response
    ApiClient->>ResponseTransformer: Transform response
    ResponseTransformer-->>Client: Return transformed data
    
    Note over Client,ResponseTransformer: Error handling and retry logic included

The API integration layer provides a robust foundation for the island architecture with consistent error handling, proper state management, and extensible design patterns that support various data fetching strategies while maintaining type safety through TypeScript interfaces.

6. Performance Design

Core Web Vitals Targets

  • Largest Contentful Paint (LCP): <2.5s for 95% of users
  • Interaction to Next Paint (INP): <200ms for 90% of interactions
  • Cumulative Layout Shift (CLS): <0.1 for 95% of users

Bundle Size Budget per Route

  • Home/Overview: <200KB (including all dependencies)
  • Product Detail: <300KB
  • Analytics Dashboard: <500KB
  • Admin Panel: <800KB

Critical Rendering Path Optimization

// Critical CSS extraction and inlining strategy
const criticalCss = `
  .island-container { 
    display: flex; 
    min-height: 100vh;
  }
  .island-content { 
    flex: 1; 
    padding: 1rem;
  }
`;

// Preload critical resources
const preloadCriticalResources = () => {
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'style';
  link.href = '/critical.css';
  document.head.appendChild(link);
};

Image Optimization Strategy

  • Formats: WebP for modern browsers, JPEG fallback for legacy
  • Lazy loading: loading="lazy" attribute with IntersectionObserver polyfill
  • Responsive images: srcset with multiple sizes (320px, 768px, 1024px, 1920px)
  • Image compression: 85% quality for JPEGs, 90% for WebP

Font Loading Strategy

// Font loading with FOUT mitigation
const preloadFonts = () => {
  const fontStyles = [
    { family: 'Inter', weight: '400', style: 'normal' },
    { family: 'Inter', weight: '600', style: 'normal' }
  ];

  fontStyles.forEach(({ family, weight, style }) => {
    const link = document.createElement('link');
    link.rel = 'preload';
    link.as = 'font';
    link.crossOrigin = 'anonymous';
    link.href = `/fonts/${family}-${weight}-${style}.woff2`;
    document.head.appendChild(link);
  });
};

Memoization Strategy

// Memoize expensive computations in islands
const useIslandData = (islandId: string) => {
  const [data, setData] = useState<IslandData[]>([]);
  
  // Memoize data transformation
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      computedValue: expensiveCalculation(item.value)
    }));
  }, [data]);

  // Memoize event handlers
  const handleInteraction = useCallback((event: MouseEvent) => {
    // Handle interaction logic
  }, []);

  return { data: processedData, handleInteraction };
};

Virtualization for Large Lists

// React Virtual implementation for large data sets
const VirtualizedList = ({ items }: { items: Item[] }) => {
  const { outerRef, innerRef, items: virtualItems } = useVirtual({
    size: items.length,
    parentRef: outerRef,
    estimateSize: () => 50,
    overscan: 5
  });

  return (
    <div ref={outerRef} className="virtual-container">
      <div ref={innerRef} style={{ height: `${items.length * 50}px` }}>
        {virtualItems.map((virtualItem) => (
          <div
            key={virtualItem.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`
            }}
          >
            <IslandComponent item={items[virtualItem.index]} />
          </div>
        ))}
      </div>
    </div>
  );
};

Prefetching and Preloading Strategy

// Route-based prefetching with IntersectionObserver
const useRoutePrefetch = (route: string) => {
  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          // Prefetch route data
          prefetchRouteData(route);
        }
      });
    });

    const target = document.querySelector(`[data-prefetch="${route}"]`);
    if (target) {
      observer.observe(target);
    }

    return () => observer.disconnect();
  }, [route]);
};

// Prefetch data for upcoming islands
const prefetchIslandData = (islandId: string) => {
  const prefetchUrl = `/api/islands/${islandId}/data`;
  
  // Use native browser preloading
  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = prefetchUrl;
  document.head.appendChild(link);
};

7. Error Handling & Resilience

Error Boundary Placement Strategy

Error boundaries should be strategically placed to provide the best user experience while maintaining system resilience. The placement follows a hierarchical approach:

  1. Application-level boundary - Catches unhandled errors in the root component
  2. Feature-level boundaries - Wrap major feature components (e.g., dashboard, profile, settings)
  3. Component-level boundaries - Wrap individual components that may fail (e.g., charts, tables)
  4. Critical path boundaries - Protect core user flows (e.g., login, form submission)
graph TD
    A[App Root] --> B[ErrorBoundary]
    B --> C[Dashboard Feature]
    B --> D[Profile Feature]
    B --> E[Settings Feature]
    C --> F[DashboardLayout]
    F --> G[ChartComponent]
    F --> H[TableComponent]
    G --> I[ErrorBoundary]
    H --> J[ErrorBoundary]

Global Error Handler Design

A centralized error handling system that captures and reports errors across the application:

// ErrorHandlerContext.tsx
import React, { createContext, useContext, useEffect } from 'react';

interface ErrorContextType {
  handleError: (error: Error, errorInfo?: React.ErrorInfo) => void;
  clearError: () => void;
}

const ErrorContext = createContext<ErrorContextType | undefined>(undefined);

export const useErrorHandling = () => {
  const context = useContext(ErrorContext);
  if (!context) {
    throw new Error('useErrorHandling must be used within an ErrorProvider');
  }
  return context;
};

export const ErrorProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const handleError = (error: Error, errorInfo?: React.ErrorInfo) => {
    // Log to console for debugging
    console.error('Unhandled error:', error, errorInfo);
    
    // Send to monitoring service (Sentry, etc.)
    if (window.Sentry) {
      window.Sentry.captureException(error, {
        contexts: { react: errorInfo }
      });
    }
    
    // Track in analytics
    trackError(error, errorInfo);
  };

  const clearError = () => {
    // Clear any displayed error messages
  };

  return (
    <ErrorContext.Provider value={{ handleError, clearError }}>
      {children}
    </ErrorContext.Provider>
  );
};

Offline / Network Failure Handling

Implement comprehensive offline detection and graceful degradation:

// OfflineHandler.ts
import { useEffect, useState } from 'react';

export const useOfflineDetection = () => {
  const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);
  
  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return isOnline;
};

// OfflineAwareComponent.tsx
interface OfflineAwareProps {
  fallbackComponent?: React.ReactNode;
}

export const OfflineAware: React.FC<OfflineAwareProps> = ({ 
  fallbackComponent, 
  children 
}) => {
  const isOnline = useOfflineDetection();
  
  if (!isOnline && fallbackComponent) {
    return <div className="offline-fallback">{fallbackComponent}</div>;
  }
  
  return <>{children}</>;
};

Graceful Degradation Patterns

Implement fallback strategies for various failure scenarios:

// FallbackStrategy.tsx
import React, { useState, useEffect } from 'react';

interface FallbackProps {
  error?: Error;
  retry?: () => void;
  fallbackComponent?: React.ReactNode;
}

export const FallbackStrategy: React.FC<FallbackProps> = ({ 
  error, 
  retry, 
  fallbackComponent 
}) => {
  const [showFallback, setShowFallback] = useState(false);
  
  useEffect(() => {
    if (error) {
      setShowFallback(true);
    }
  }, [error]);

  if (!showFallback) return null;

  return (
    <div className="fallback-container">
      {fallbackComponent || (
        <div className="fallback-content">
          <h2>Something went wrong</h2>
          <p>We're working to fix this issue.</p>
          {retry && (
            <button onClick={retry} className="retry-button">
              Try Again
            </button>
          )}
        </div>
      )}
    </div>
  );
};

User Feedback on Errors

Implement comprehensive error communication:

// ToastErrorDisplay.tsx
import React, { useEffect } from 'react';
import { useToast } from './useToast';

interface ToastErrorProps {
  error: Error;
  title?: string;
}

export const ToastErrorDisplay: React.FC<ToastErrorProps> = ({ 
  error, 
  title = "An error occurred" 
}) => {
  const { addToast } = useToast();
  
  useEffect(() => {
    addToast({
      id: Date.now().toString(),
      type: 'error',
      title,
      message: error.message,
      duration: 5000,
      dismissible: true
    });
  }, [error, title, addToast]);

  return null;
};

// useToast.ts
import { useState } from 'react';

interface Toast {
  id: string;
  type: 'success' | 'error' | 'warning' | 'info';
  title: string;
  message?: string;
  duration?: number;
  dismissible?: boolean;
}

export const useToast = () => {
  const [toasts, setToasts] = useState<Toast[]>([]);
  
  const addToast = (toast: Omit<Toast, 'id'>) => {
    const newToast = { ...toast, id: Date.now().toString() };
    setToasts(prev => [...prev, newToast]);
    
    if (toast.duration) {
      setTimeout(() => {
        removeToast(newToast.id);
      }, toast.duration);
    }
  };
  
  const removeToast = (id: string) => {
    setToasts(prev => prev.filter(toast => toast.id !== id));
  };
  
  return { toasts, addToast, removeToast };
};

Error Boundary Implementation

// ErrorBoundary.tsx
import React from 'react';

interface ErrorBoundaryState {
  hasError: boolean;
  error?: Error;
  errorInfo?: React.ErrorInfo;
}

class ErrorBoundary extends React.Component<
  { children: React.ReactNode; fallback?: React.ReactNode },
  ErrorBoundaryState
> {
  constructor(props: { children: React.ReactNode; fallback?: React.ReactNode }) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // Log error to monitoring service
    console.error('ErrorBoundary caught:', error, errorInfo);
    
    // Send to Sentry or similar
    if (window.Sentry) {
      window.Sentry.captureException(error, { contexts: { react: errorInfo } });
    }
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="error-boundary">
          <h2>Something went wrong</h2>
          <p>We're sorry, but something went wrong loading this content.</p>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

Retry Logic Implementation

// useRetry.ts
import { useState, useCallback } from 'react';

interface RetryConfig {
  maxRetries?: number;
  delay?: number;
  backoffMultiplier?: number;
}

export const useRetry = <T>(
  asyncFunction: () => Promise<T>,
  config: RetryConfig = {}
) => {
  const {
    maxRetries = 3,
    delay = 1000,
    backoffMultiplier = 2
  } = config;

  const [retryCount, setRetryCount] = useState(0);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const [data, setData] = useState<T | null>(null);

  const executeWithRetry = useCallback(async () => {
    setIsLoading(true);
    setError(null);
    
    let currentDelay = delay;
    
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        const result = await asyncFunction();
        setData(result);
        setRetryCount(0);
        return result;
      } catch (err) {
        if (attempt === maxRetries) {
          setError(err as Error);
          throw err;
        }
        
        // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, currentDelay));
        currentDelay *= backoffMultiplier;
        setRetryCount(attempt + 1);
      }
    }
  }, [asyncFunction, maxRetries, delay, backoffMultiplier]);

  return {
    data,
    isLoading,
    error,
    retryCount,
    execute: executeWithRetry
  };
};

Error Recovery Patterns

For critical operations, implement recovery strategies:

// RecoveryManager.ts
export class RecoveryManager {
  private static readonly RECOVERY_STRATEGIES = {
    NETWORK_FAILURE: 'network-failure',
    SERVER_ERROR: 'server-error',
    CLIENT_ERROR: 'client-error'
  };

  static handleRecovery(error: Error, strategy: string) {
    switch (strategy) {
      case this.RECOVERY_STRATEGIES.NETWORK_FAILURE:
        return this.handleNetworkFailure(error);
      case this.RECOVERY_STRATEGIES.SERVER_ERROR:
        return this.handleServerError(error);
      case this.RECOVERY_STRATEGIES.CLIENT_ERROR:
        return this.handleClientError(error);
      default:
        return this.handleGenericError(error);
    }
  }

  private static handleNetworkFailure(error: Error) {
    // Show offline message and enable retry
    return {
      type: 'offline',
      message: 'No internet connection. Please check your network.',
      retryable: true
    };
  }

  private static handleServerError(error: Error) {
    // Show maintenance message or retry with exponential backoff
    return {
      type: 'server-error',
      message: 'Server is temporarily unavailable. Please try again later.',
      retryable: true
    };
  }

  private static handleClientError(error: Error) {
    // Show user-friendly error or fallback content
    return {
      type: 'client-error',
      message: 'We encountered an issue processing your request.',
      retryable: false
    };
  }

  private static handleGenericError(error: Error) {
    // Fallback to basic error handling
    return {
      type: 'generic',
      message: 'An unexpected error occurred.',
      retryable: true
    };
  }
}

Performance Considerations

Error handling should not impact performance:

  • Minimize error logging in production
  • Use debounced error reporting for frequent errors
  • Implement error deduplication to prevent spam
  • Avoid heavy operations in error boundaries

Testing Strategy for Error Handling

Unit tests should cover:

  1. Basic error boundary behavior
  2. Retry logic with different failure scenarios
  3. Offline state handling
  4. Error recovery strategies
  5. Toast display and dismissal

The error handling system should be designed to provide users with clear feedback while maintaining application stability and performance.

8. Testing Strategy

Unit Testing Approach

Component-level testing:

  • Target 85%+ code coverage for all components
  • Test all prop combinations and edge cases
  • Verify accessibility attributes are properly applied
  • Mock external dependencies (API calls, timers, etc.)
  • Test custom hooks in isolation using React Testing Library

Utility function testing:

  • Test all utility functions with boundary conditions
  • Validate data transformation logic (e.g., date formatting, currency conversion)
  • Test error handling paths in utility functions

Testing framework: Jest + React Testing Library for component testing, Vitest for utilities (if preferred by team)

Integration Testing Strategy

Component integration tests:

  • Test component composition and data flow between parent-child components
  • Validate proper state propagation in multi-level component hierarchies
  • Test interaction flows between multiple components (e.g., form submission, modal close)

API integration tests:

  • Test data fetching and rendering in connected components
  • Validate proper loading states and error handling for API failures
  • Test optimistic updates and cache invalidation scenarios

E2E Testing Scope

Testing tool: Playwright (preferred over Cypress for better TypeScript support and performance)

Critical user flows to test:

  • User authentication and session management
  • Core feature workflows (e.g., creating/editing/deleting items in an island)
  • Data persistence and synchronization across sessions
  • Responsive behavior on different device sizes

Testing coverage:

  • 100% of authenticated user journeys (login → feature usage → logout)
  • 80% of public user flows (landing page, guest access, etc.)
  • Cross-browser compatibility testing (Chrome, Firefox, Safari)

Visual Regression Testing

Tool: Percy or Chromatic for visual regression detection

Scope:

  • All component variants and state changes (hover, focus, error states)
  • Responsive breakpoints (mobile, tablet, desktop)
  • Dark/light mode variants
  • RTL layout variations

Accessibility Testing Automation

Tool: axe-core with automated testing in CI pipeline

Testing scope:

  • WCAG 2.1 AA compliance for all interactive components
  • Keyboard navigation coverage (Tab, Shift+Tab, Enter, Space)
  • Screen reader compatibility testing with NVDA and VoiceOver
  • Focus order validation across component hierarchies

Test Data Strategy

Test data generation:

  • Use factories (e.g., Faker.js) for realistic test data
  • Implement fixtures for common test scenarios
  • Create minimal test data sets that cover all code paths

Test data isolation:

  • Use unique identifiers for test entities to prevent cross-contamination
  • Implement test database cleanup strategy (in-memory DB or transaction rollback)
  • Ensure deterministic test results through proper state management

Data generation patterns:

// Example factory pattern for test data
const createMockIsland = (overrides = {}) => ({
  id: faker.string.uuid(),
  title: faker.lorem.sentence(),
  description: faker.lorem.paragraph(),
  status: faker.helpers.arrayElement(['active', 'draft', 'archived']),
  createdAt: faker.date.recent(),
  ...overrides
});

9. Accessibility (A11y)

WCAG 2.1 AA compliance checklist for this feature

The island architecture framework must meet WCAG 2.1 AA standards across all islands and their constituent components:

  • Text Contrast: All text must have a contrast ratio of at least 4.5:1 against its background (minimum 3:1 for large text)
  • Keyboard Navigation: All interactive elements must be fully operable via keyboard (Tab, Enter, Space)
  • Focus Management: Clear visual focus indicators and proper focus order throughout all interactive elements
  • Screen Reader Support: ARIA labels and roles must be properly implemented for dynamic content changes
  • Alternative Text: All non-text content must have appropriate alt text or ARIA labels
  • Error Identification: Form errors must be programmatically associated with their input fields
  • Time-based Content: Any time limits must be adjustable or extendable by users

Keyboard navigation support

All islands must support full keyboard navigation:

  • Tab Navigation: Logical tab order that follows visual hierarchy and content flow
  • Enter/Space Activation: Buttons, links, and form controls must activate with Enter or Space key
  • Escape Handling: Modal islands must close with Escape key when appropriate
  • Arrow Navigation: List navigation within islands using arrow keys (up/down for menus, left/right for carousels)
  • Form Navigation: Tab navigation between form fields with proper error handling and validation

Focus management strategy

The framework implements robust focus management:

// Focus trap implementation for modal islands
const useFocusTrap = (islandRef: React.RefObject<HTMLElement>, isOpen: boolean) => {
  useEffect(() => {
    if (!isOpen || !islandRef.current) return;

    const focusableElements = islandRef.current.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    
    const firstElement = focusableElements[0] as HTMLElement;
    const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;

    const handleTabKey = (e: KeyboardEvent) => {
      if (e.key === 'Tab') {
        if (e.shiftKey && document.activeElement === firstElement) {
          lastElement.focus();
          e.preventDefault();
        } else if (!e.shiftKey && document.activeElement === lastElement) {
          firstElement.focus();
          e.preventDefault();
        }
      }
    };

    islandRef.current.addEventListener('keydown', handleTabKey);
    
    // Focus first element on open
    firstElement.focus();

    return () => {
      islandRef.current?.removeEventListener('keydown', handleTabKey);
    };
  }, [isOpen]);
};

ARIA roles and attributes plan

Each island component implements appropriate ARIA semantics:

// Example IslandCard component with proper ARIA attributes
interface IslandCardProps {
  title: string;
  description: string;
  ariaLabel?: string;
  isAccessible?: boolean;
}

const IslandCard: React.FC<IslandCardProps> = ({ 
  title, 
  description, 
  ariaLabel,
  isAccessible = true
}) => {
  return (
    <div 
      role="article"
      aria-label={ariaLabel || title}
      tabIndex={isAccessible ? 0 : -1}
      className="island-card"
    >
      <h2 
        role="heading" 
        aria-level={2}
        className="island-card__title"
      >
        {title}
      </h2>
      <p className="island-card__description">
        {description}
      </p>
    </div>
  );
};

Screen reader compatibility

The framework ensures screen reader compatibility through:

  • Semantic HTML: Use of proper HTML5 elements (header, main, section, article)
  • Dynamic Content Updates: Proper announcement of content changes using aria-live regions
  • Landmark Regions: Clear ARIA landmarks for navigation and content organization
  • Live Region Implementation:
// Live region for status updates in islands
const StatusAnnouncer: React.FC<{ message: string }> = ({ message }) => {
  return (
    <div 
      aria-live="polite" 
      aria-atomic="true"
      className="sr-only"
    >
      {message}
    </div>
  );
};
  • Keyboard Focus Indicators: Custom focus styles that are visible to screen reader users
  • Form Field Association: Proper htmlFor and aria-describedby relationships for all form elements

Additional A11y considerations

  • Color Blindness Support: Color contrast testing and alternative visual indicators
  • Zoom Compatibility: Islands must remain fully functional when zoomed to 200%
  • Reduced Motion Support: CSS prefers-reduced-motion media query support for animations
  • Alternative Input Methods: Support for voice control and switch access
  • Error Handling: Clear error messages with proper ARIA attributes for screen readers

The framework provides accessibility as a core architectural principle, ensuring that islands can be used by people with disabilities without compromising functionality or user experience.

10. Security Considerations

XSS Prevention Strategy

Content Sanitization: All user-generated content must be sanitized before rendering to prevent script injection. Implement a robust HTML sanitizer using libraries like DOMPurify:

// sanitize.ts
import DOMPurify from 'dompurify';

export const sanitizeHTML = (html: string): string => {
  return DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li'],
    ALLOWED_ATTR: ['class'],
    KEEP_CONTENT: true,
  });
};

Input Validation: All user inputs must be validated on both frontend and backend. Implement strict validation rules with proper sanitization:

// inputValidation.ts
export const validateUserInput = (input: string): boolean => {
  // Reject inputs that contain script tags or dangerous patterns
  const dangerousPatterns = [
    /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
    /javascript:/gi,
    /vbscript:/gi,
    /on\w+\s*=\s*["'][^"']*["']/gi,
  ];

  return !dangerousPatterns.some(pattern => pattern.test(input));
};

CSRF Protection

Token Management: Implement automatic CSRF token injection in all API requests:

// apiClient.ts
class APIClient {
  private csrfToken: string | null = null;

  async getCSRFToken(): Promise<string> {
    const response = await fetch('/api/csrf-token', {
      method: 'GET',
      credentials: 'include',
    });
    const { token } = await response.json();
    this.csrfToken = token;
    return token;
  }

  async request<T>(url: string, options: RequestInit = {}): Promise<T> {
    const headers = {
      ...options.headers,
      'X-CSRF-Token': this.csrfToken || (await this.getCSRFToken()),
    };

    return fetch(url, { ...options, headers });
  }
}

Content Security Policy (CSP) Headers

CSP Implementation: Configure strict CSP headers to prevent XSS and data injection attacks:

// csp.ts
export const CSP_POLICY = `
  default-src 'self';
  script-src 'self' 'unsafe-inline' 'unsafe-eval';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: blob:;
  font-src 'self' data:;
  connect-src 'self' wss:;
  frame-ancestors 'none';
  base-uri 'self';
`;

// In server middleware
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy', CSP_POLICY);
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  next();
});

Sensitive Data Handling in Frontend State

Secure State Management: Never store sensitive data like auth tokens or user credentials in React state or localStorage:

// secureState.ts
class SecureStateManager {
  private storage = new Map<string, any>();
  
  setSecureData(key: string, data: any): void {
    // Store in memory only - never persist to disk
    this.storage.set(key, data);
  }
  
  getSecureData(key: string): any {
    return this.storage.get(key);
  }
  
  removeSecureData(key: string): void {
    this.storage.delete(key);
  }
}

Input Sanitization Approach

Comprehensive Input Handling: Implement a layered input sanitization strategy:

// inputSanitizer.ts
export class InputSanitizer {
  static sanitizeText(input: string): string {
    // Remove control characters
    let sanitized = input.replace(/[\x00-\x1F\x7F]/g, '');
    
    // Escape HTML entities
    sanitized = sanitized
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#x27;');
    
    // Limit length to prevent abuse
    return sanitized.substring(0, 1000);
  }
  
  static sanitizeURL(input: string): string {
    try {
      const url = new URL(input);
      // Only allow http/https protocols
      if (!['http:', 'https:'].includes(url.protocol)) {
        throw new Error('Invalid protocol');
      }
      return url.toString();
    } catch (e) {
      return '';
    }
  }
}

Security Headers Implementation

Security Header Configuration: Implement security headers at the application level:

// securityHeaders.ts
export const SECURITY_HEADERS = {
  'X-Content-Type-Options': 'nosniff',
  'X-Frame-Options': 'DENY',
  'X-XSS-Protection': '1; mode=block',
  'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
  'Referrer-Policy': 'strict-origin-when-cross-origin',
  'Permissions-Policy': 'geolocation=(), microphone=()',
};

Authentication Token Security

Secure Token Handling: Implement secure token storage and handling:

// authTokens.ts
class AuthTokenManager {
  private static readonly TOKEN_KEY = 'auth_token';
  
  static setToken(token: string): void {
    // Store in secure HTTP-only cookie (if available) or in-memory
    if (typeof window !== 'undefined') {
      // In-memory storage for client-side apps
      sessionStorage.setItem(this.TOKEN_KEY, token);
    }
  }
  
  static getToken(): string | null {
    if (typeof window !== 'undefined') {
      return sessionStorage.getItem(this.TOKEN_KEY);
    }
    return null;
  }
  
  static clearToken(): void {
    if (typeof window !== 'undefined') {
      sessionStorage.removeItem(this.TOKEN_KEY);
    }
  }
}

Security Testing Considerations

Vulnerability Scanning: Integrate security scanning into CI/CD pipeline:

  1. Static Analysis: Use tools like ESLint with security plugins to detect potential vulnerabilities
  2. Dependency Scanning: Regularly scan npm dependencies for known vulnerabilities using npm audit or Snyk
  3. Input Validation Testing: Unit tests that specifically validate XSS prevention mechanisms

Security Best Practices Implementation

Runtime Security Checks: Implement runtime security checks:

// runtimeSecurity.ts
export const RuntimeSecurity = {
  validateOrigin(): boolean {
    // Check that the app is running in a legitimate context
    return window.location.origin === process.env.REACT_APP_BASE_URL;
  },
  
  validateRequestIntegrity(request: any): boolean {
    // Verify request integrity and prevent tampering
    return request.timestamp && Date.now() - request.timestamp < 300000; // 5 min timeout
  },
};

Risk Mitigation

Threat Modeling: Regular security reviews to identify potential attack vectors:

  1. API Security: All API endpoints must implement proper authentication and authorization
  2. Data Exposure: Never expose sensitive user data in client-side state or logs
  3. Cross-Origin Security: Implement proper CORS policies and validate all cross-origin requests

This security framework ensures that the island architecture is resilient against common web vulnerabilities while maintaining a secure development environment.

11. Internationalization (i18n)

Translation Key Structure and Naming Convention

Following BEM-style naming with feature-scoped keys to prevent collisions:

// Translation key structure example
{
  "auth": {
    "login": {
      "title": "Login to Your Account",
      "email_label": "Email Address",
      "password_label": "Password",
      "submit_button": "Sign In"
    },
    "signup": {
      "title": "Create New Account",
      "name_label": "Full Name",
      "confirm_password_label": "Confirm Password"
    }
  },
  "dashboard": {
    "welcome_message": "Welcome back, {{name}}!",
    "stats": {
      "revenue": "Revenue",
      "users": "Active Users"
    }
  },
  "common": {
    "actions": {
      "save": "Save",
      "cancel": "Cancel",
      "delete": "Delete"
    },
    "errors": {
      "network_error": "Network error occurred. Please try again.",
      "validation_error": "Please check your input"
    }
  }
}

RTL Support Considerations

RTL support handled through CSS classes and direction properties:

/* CSS for RTL layout */
[dir="rtl"] .container {
  text-align: right;
  direction: rtl;
}

[dir="rtl"] .button-group {
  flex-direction: row-reverse;
}

Date, Number, Currency Formatting Strategy

Using Intl API for locale-aware formatting:

// Format utilities
export const formatDate = (date: Date, locale: string): string => {
  return new Intl.DateTimeFormat(locale, {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }).format(date);
};

export const formatCurrency = (amount: number, locale: string, currency: string): string => {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency
  }).format(amount);
};

export const formatNumber = (num: number, locale: string): string => {
  return new Intl.NumberFormat(locale).format(num);
};

Locale Detection and Switching Strategy

Automatic detection based on browser language with manual override:

// Locale detection and switching logic
const detectLocale = (): string => {
  const storedLocale = localStorage.getItem('locale');
  if (storedLocale) return storedLocale;
  
  const browserLang = navigator.language.split('-')[0];
  const supportedLocales = ['en', 'es', 'fr', 'de'];
  
  return supportedLocales.includes(browserLang) ? browserLang : 'en';
};

const switchLocale = (newLocale: string): void => {
  localStorage.setItem('locale', newLocale);
  document.documentElement.lang = newLocale;
  // Trigger re-render of all i18n components
  window.location.reload();
};

i18n Provider Implementation

// i18n context and provider
import { createContext, useContext, useEffect, useState } from 'react';

interface I18nContextType {
  locale: string;
  messages: Record<string, any>;
  formatMessage: (key: string, params?: Record<string, any>) => string;
  switchLocale: (locale: string) => void;
}

const I18nContext = createContext<I18nContextType | undefined>(undefined);

export const useI18n = (): I18nContextType => {
  const context = useContext(I18nContext);
  if (!context) {
    throw new Error('useI18n must be used within an I18nProvider');
  }
  return context;
};

export const I18nProvider: React.FC<{ 
  messages: Record<string, any>; 
  children: React.ReactNode;
}> = ({ messages, children }) => {
  const [locale, setLocale] = useState<string>(detectLocale());
  
  const formatMessage = (key: string, params?: Record<string, any>): string => {
    const message = getNestedProperty(messages, key);
    if (!message) return key;
    
    if (params) {
      return message.replace(/\{\{(\w+)\}\}/g, (_, param) => params[param] || '');
    }
    
    return message;
  };

  const switchLocale = (newLocale: string) => {
    setLocale(newLocale);
    localStorage.setItem('locale', newLocale);
    document.documentElement.lang = newLocale;
  };

  return (
    <I18nContext.Provider value={{ locale, messages, formatMessage, switchLocale }}>
      {children}
    </I18nContext.Provider>
  );
};

// Helper to get nested object property
const getNestedProperty = (obj: any, path: string): any => {
  return path.split('.').reduce((current, key) => current?.[key], obj);
};

Translation Loading Strategy

Lazy loading of translation files with fallback:

// Translation loader with caching
const translationCache = new Map<string, any>();

export const loadTranslations = async (locale: string): Promise<Record<string, any>> => {
  if (translationCache.has(locale)) {
    return translationCache.get(locale)!;
  }

  try {
    const response = await fetch(`/locales/${locale}.json`);
    const translations = await response.json();
    translationCache.set(locale, translations);
    return translations;
  } catch (error) {
    console.warn(`Failed to load translations for ${locale}, falling back to en`);
    return await loadTranslations('en');
  }
};

i18n Component Integration

// i18n-aware component example
interface TranslatedTextProps {
  id: string;
  params?: Record<string, any>;
}

export const TranslatedText: React.FC<TranslatedTextProps> = ({ 
  id, 
  params 
}) => {
  const { formatMessage } = useI18n();
  const translated = formatMessage(id, params);
  
  return <span>{translated}</span>;
};

// Usage in component
const WelcomeComponent: React.FC = () => {
  return (
    <div>
      <TranslatedText id="dashboard.welcome_message" params={{ name: "John" }} />
    </div>
  );
};

i18n Testing Strategy

Test coverage includes:

  • Translation key existence validation
  • Parameter interpolation correctness
  • RTL layout rendering verification
  • Locale switching behavior testing

i18n Performance Considerations

  • Translation files are loaded on-demand per locale
  • Caching strategy prevents redundant fetches
  • Bundle size impact minimized through code splitting
  • No runtime translation parsing (avoiding regex overhead)

12. Monitoring & Observability

Real User Monitoring (RUM) Metrics to Track

The island architecture framework will implement comprehensive RUM tracking to monitor user experience and system performance:

  • First Contentful Paint (FCP): Target <2.5s for 95% of users
  • Largest Contentful Paint (LCP): Target <2.5s for 95% of users
  • First Input Delay (FID): Target <100ms for 90% of users
  • Cumulative Layout Shift (CLS): Target <0.1 for 90% of users
  • Time to Interactive (TTI): Target <5s for 90% of users
  • Page Load Time: Target <3s for 95% of users
  • Island Render Time: Track individual island load times and cumulative render duration
  • User Session Duration: Monitor average session length and engagement patterns

Error Tracking Integration

Sentry Integration will be implemented as the primary error tracking solution:

// Error tracking service implementation
class ErrorTracker {
  private sentryClient: Sentry.Client;

  init(dsn: string, environment: string) {
    this.sentryClient = Sentry.init({
      dsn,
      environment,
      release: `island-architecture@${process.env.REACT_APP_VERSION}`,
      tracesSampleRate: 1.0,
      // Capture user context for debugging
      integrations: [
        new Sentry.Integrations.BrowserTracing(),
        new Sentry.Integrations.Replay({
          maskAllText: false,
          blockAllMedia: true
        })
      ]
    });
  }

  captureError(error: Error, context?: Record<string, any>) {
    Sentry.captureException(error, {
      contexts: {
        island: context
      }
    });
  }

  captureMessage(message: string, level: 'error' | 'warning' | 'info' = 'error') {
    Sentry.captureMessage(message, level);
  }
}

Custom Events and User Journey Tracking

Island Interaction Events will be tracked to understand user behavior:

// Event tracking system for island interactions
interface IslandEvent {
  type: 'island_mount' | 'island_render' | 'island_error' | 'island_data_fetch';
  islandId: string;
  timestamp: number;
  duration?: number;
  error?: string;
  dataFetched?: boolean;
}

class IslandEventTracker {
  private events: IslandEvent[] = [];
  
  track(event: Omit<IslandEvent, 'timestamp'>) {
    const fullEvent: IslandEvent = {
      ...event,
      timestamp: Date.now()
    };
    
    this.events.push(fullEvent);
    
    // Send to analytics backend
    this.sendToAnalytics(fullEvent);
  }

  private sendToAnalytics(event: IslandEvent) {
    // Send to backend or analytics service
    fetch('/api/analytics/events', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(event)
    });
  }
}

Performance Monitoring and Regression Detection

Performance Baseline Tracking will be implemented to detect regressions:

// Performance monitoring service
class PerformanceMonitor {
  private baselineMetrics: Record<string, number> = {};
  
  async measureIslandPerformance(islandId: string): Promise<void> {
    const start = performance.now();
    
    try {
      // Track render time
      await this.waitForIslandRender(islandId);
      const end = performance.now();
      const duration = end - start;
      
      // Store metric for regression detection
      this.recordMetric(islandId, duration);
      
      // Alert on regressions > 20%
      if (this.hasRegression(islandId, duration)) {
        this.alertOnRegression(islandId, duration);
      }
    } catch (error) {
      console.error(`Performance tracking failed for island ${islandId}`, error);
    }
  }

  private recordMetric(islandId: string, duration: number) {
    this.baselineMetrics[islandId] = duration;
  }

  private hasRegression(islandId: string, currentDuration: number): boolean {
    const baseline = this.baselineMetrics[islandId];
    if (!baseline) return false;
    
    const regression = (currentDuration - baseline) / baseline;
    return regression > 0.2; // 20% threshold
  }
}

Observability Dashboard Components

The framework will provide observability dashboards with:

  1. Island Health Dashboard: Real-time status of all islands, render times, error rates
  2. Performance Trends: Historical performance metrics with regression alerts
  3. User Journey Maps: End-to-end user flow tracking across islands
  4. Error Heatmaps: Visual representation of error distribution by island and user segment

Integration Points

  • Sentry for error tracking and performance monitoring
  • Custom Analytics API for user journey tracking and custom metrics
  • Prometheus/Grafana integration for infrastructure-level observability (optional)
  • Log aggregation services for detailed logging and debugging

Alerting Strategy

  • Critical Alerts: Island rendering failures, >50% render time increase
  • Warning Alerts: Performance degradation >20%, error rate >5%
  • Info Alerts: New island deployments, user session patterns

Data Privacy Considerations

All monitoring data will be:

  • Anonymized where possible
  • Compliant with GDPR/CCPA requirements
  • Configurable to allow user opt-out
  • Sent over HTTPS only

Metrics Collection Frequency

  • Real-time metrics: Every 5 seconds for active islands
  • Batched reporting: Every minute for aggregated statistics
  • Error reporting: Immediate on error occurrence
  • User session tracking: Session-based aggregation

This observability framework will provide comprehensive insights into the island architecture's performance and reliability while maintaining user privacy and system security standards.

13. File & Folder Structure

Proposed Directory Structure

/src
├── app/
│   ├── layout.tsx
│   ├── page.tsx
│   └── (routes)/
│       ├── dashboard/
│       │   ├── page.tsx
│       │   └── components/
│       ├── profile/
│       │   ├── page.tsx
│       │   └── components/
│       └── settings/
│           ├── page.tsx
│           └── components/
├── features/
│   ├── auth/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── services/
│   │   └── types/
│   ├── ui/
│   │   ├── components/
│   │   ├── hooks/
│   │   └── utils/
│   └── shared/
│       ├── components/
│       ├── hooks/
│       └── utils/
├── lib/
│   ├── api/
│   ├── hooks/
│   └── utils/
├── types/
│   ├── index.ts
│   └── shared.ts
├── styles/
│   └── globals.css
└── components/
    ├── base/
    └── ui/

Module Boundaries and Import Rules

  1. Feature isolation: Each feature (auth, dashboard, profile) is self-contained with its own components, hooks, and services
  2. Shared modules: Common UI components and utilities live in /features/shared/ to avoid duplication
  3. No cross-feature imports: Features cannot directly import from other features - they must use shared modules or pass data through parent components
  4. Barrel exports: Each feature has a index.ts that re-exports key components and types for clean imports

Shared vs Feature-Specific Code Separation

Shared code (features/shared/):

  • Base UI components (buttons, modals, forms)
  • Common hooks (useApi, useAuth)
  • Utility functions (formatDate, validateEmail)

Feature-specific code:

  • Route-specific components and logic
  • Feature-level services and API clients
  • Component-specific styling and animations

This structure enables:

  • Clear ownership boundaries between teams
  • Independent feature development and testing
  • Easy code reuse across features
  • Scalable architecture for large teams
  • Isolated builds and deployments when needed

The feature-based organization reduces cognitive load for developers working on specific features while maintaining clear separation of concerns and enabling proper code reuse patterns.

14. Dependencies & Third-Party Libraries

Key Libraries Selected with Justification

React (18.2+) - Core framework for component-based UI development with concurrent rendering capabilities. Essential for building modern, performant React applications.

TypeScript (5.0+) - Static type checking for catching runtime errors early and improving developer experience with better IDE support and refactoring capabilities.

React Router DOM (6.15+) - Declarative routing solution with excellent support for nested routes, lazy loading, and client-side navigation patterns.

Zustand (4.3+) - Lightweight state management solution that provides a simple API for managing global state without the complexity of Redux, ideal for island architecture's component isolation needs.

React Query (5.0+) - Production-ready data fetching and state management solution that handles caching, background updates, and server-state synchronization with minimal boilerplate.

Tailwind CSS (3.3+) - Utility-first CSS framework that enables rapid UI development with consistent design system and excellent customization capabilities.

Framer Motion (10.12+) - Production-ready animation library that provides declarative animations and gesture support, essential for creating engaging user experiences in island components.

Axios (1.4+) - Robust HTTP client with request/response interceptors, automatic JSON parsing, and comprehensive error handling capabilities.

date-fns (2.30+) - Modern date utility library with modular design and zero dependencies, perfect for internationalization and timezone handling in island components.

lodash (4.17+) - Utility library for common functional programming patterns and data manipulation operations, with tree-shaking support to minimize bundle size.

Alternatives Considered and Rejected

Redux Toolkit - While powerful, introduces unnecessary complexity for island architecture's component-isolated state management. Zustand provides similar functionality with less boilerplate and better developer experience.

Jotai - Offers fine-grained state management but introduces additional complexity for the typical use cases in island components. Zustand's simpler API aligns better with the architectural goals.

Emotion - Alternative CSS-in-JS solution that provides similar functionality to styled-components but with less community adoption and ecosystem maturity.

Next.js - While excellent for SSR, this framework is designed for monolithic applications. Island architecture emphasizes component reusability and independent deployment, making standalone React more appropriate.

Licensing and Bundle Size Impact Assessment

React + ReactDOM: MIT license, ~150KB minified. Core dependency with minimal impact on bundle size.

TypeScript: MIT license, no runtime impact. Development dependency only.

React Router DOM: MIT license, ~10KB minified. Essential for navigation in island components.

Zustand: MIT license, ~3KB minified. Lightweight state management with minimal overhead.

React Query: MIT license, ~15KB minified. Critical for data fetching and caching in island architectures.

Tailwind CSS: MIT license, ~10KB minified. Bundle size impact is minimal due to utility-first approach and tree-shaking.

Framer Motion: MIT license, ~20KB minified. Animation support with performance optimizations.

Axios: MIT license, ~15KB minified. Essential for HTTP client functionality with minimal impact.

date-fns: MIT license, ~5KB minified. Modular approach allows for tree-shaking of unused functions.

lodash: MIT license, ~25KB minified. Bundle size impact is manageable with selective imports.

Bundle Size Budgets and Optimization Strategy

  • Core Island Framework: ~150KB (including React, Router, Query, Zustand)
  • UI Components: ~50KB (Tailwind, Framer Motion, Icons)
  • Data Layer: ~30KB (Axios, date-fns, Lodash utilities)
  • Total Bundle Budget: ~230KB for initial load (excluding feature-specific code)

All libraries support tree-shaking and modular imports to minimize final bundle size. Critical path dependencies are prioritized for preloading, while optional features are lazy-loaded to reduce initial payload.

15. Open Questions, Risks & Trade-offs

Decisions that need product/engineering alignment

Feature boundary definition: How granular should island boundaries be? Should islands be defined at component level, feature level, or route level? The answer will impact the complexity of the framework and developer adoption.

Island rehydration strategy: Should islands be rehydrated on mount or pre-rendered server-side? This decision affects SSR support, bundle size, and initial render performance.

Island communication model: How should islands communicate with each other? Should we support direct parent-child communication, event bus patterns, or global state management?

Known trade-offs made in this design

Complexity vs. flexibility: The island architecture introduces additional complexity through inter-island communication and lifecycle management, but provides better component isolation and reusability.

Bundle size vs. performance: While islands enable code splitting, they may increase overall bundle size due to framework overhead and multiple entry points.

Developer experience vs. tooling: The framework requires additional tooling for island discovery, registration, and bundling, which may increase initial setup complexity.

Risks and mitigations

Framework adoption risk: Teams may resist adopting a new framework pattern, especially if they're comfortable with existing component architectures. Mitigation: Provide clear migration paths and demonstrate tangible benefits through proof-of-concepts.

Performance regression risk: Islands may introduce performance overhead due to additional abstraction layers and communication mechanisms. Mitigation: Implement performance monitoring and provide optimization guidelines.

Testing complexity risk: Isolated islands may be harder to test in isolation, requiring more sophisticated testing infrastructure. Mitigation: Provide comprehensive testing utilities and documentation for island-specific testing patterns.

Technical debt acknowledged

Framework maintenance burden: The island framework will require ongoing maintenance, documentation updates, and version compatibility management as React evolves.

API stability risk: Early design decisions around island lifecycle methods and communication patterns may need refactoring as the framework matures.

Tooling dependency risk: The framework relies on build-time tooling and bundler configurations that may become outdated or incompatible with future toolchains.

Performance monitoring gap: Without comprehensive performance tracking built into the framework, teams may miss identifying performance bottlenecks in island implementations.

Cross-island dependency management: The framework currently lacks sophisticated dependency resolution for islands that may need to coordinate their loading order or dependencies, which could lead to runtime errors in complex scenarios.

Was this article helpful?

Topics in this article

More from Frontend LLD

  • Frontend HLD

    Online Food Delivery Platform Frontend Architecture

    Comprehensive high-level design for a scalable food delivery frontend with real-time order tracking and restaurant discovery features.

  • Frontend HLD

    Frontend Code Splitting and Lazy Loading Strategy Design

    Comprehensive high-level design for implementing efficient code splitting and lazy loading in modern web applications to optimize performance and user experience.

  • Frontend HLD

    Fast Initial Page Load Application Design

    Comprehensive frontend high-level design for optimizing application startup performance with code splitting, caching, and preloading strategies

Stay in the Loop

Get curated DSA patterns, coding problems, frontend and backend LLD/HLD, and AI/ML System Design insights delivered straight to your inbox. No spam, just focused interview and engineering content.