Skip to content

Latest commit

 

History

History
225 lines (163 loc) · 7.34 KB

File metadata and controls

225 lines (163 loc) · 7.34 KB

Architecture

High-level decisions and rationale for Sovereign React applications.


Philosophy

Core Principle: Every feature works entirely in the browser with zero server dependency.

Why:

  • Privacy: User data never leaves their device
  • Speed: No network latency, instant responses
  • Simplicity: No backend to maintain, deploy, or scale
  • Reliability: No server downtime, always available
  • Cost: Free static hosting (Vercel, Netlify, GitHub Pages)

This doesn't mean you can never have a backend. It means the architecture doesn't assume one. Features work without it. If you add a backend later, it's progressive enhancement -- not a prerequisite.


Stack Decisions

Vite + React (NOT Next.js)

Why Vite:

  • Blazingly fast dev server (native ESM, no bundling in dev)
  • Automatic code splitting -- every React.lazy(() => import(...)) becomes its own chunk with zero config. No webpack splitChunks tuning, no manual optimization. Add a feature, get a chunk.
  • Simple mental model (just JavaScript, no framework magic)
  • Optimized production builds (Rollup-based, tree-shaken)
  • Minimal configuration needed

This is the foundation of the entire scaling story. You write one lazy import per feature, and Vite ensures that page's code is only downloaded when the user navigates to it. At 200 features, the main bundle is the same size as at 2.

Why NOT Next.js:

  • SSR provides zero benefit for client-side features
  • Adds framework complexity (getStaticProps, getServerSideProps, API routes, server components)
  • Larger bundle size and more boilerplate
  • The App Router's mental model is designed for server-rendered apps

When to reconsider: If you need SEO for every page, server-side data fetching, or authentication at the edge, Next.js is the right choice. Sovereign React is for apps where the browser is the runtime.

SEO escape hatch: If you need basic SEO without Next.js:

  1. React Helmet for meta tags (included in the template)
  2. Vite's prerender plugin for static HTML generation
  3. Or migrate to Next.js later -- the feature-per-page pattern transfers cleanly

Tailwind CSS

Why:

  • Consistent design system out of the box
  • Mobile-first responsive utilities (sm:, md:, lg:)
  • No CSS file bloat (unused classes are purged)
  • Dark mode with a single dark: prefix
  • Customizable via tailwind.config.js

Pattern: Write styles directly in JSX. Minimize custom CSS files.

<button className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg shadow-md transition-colors">
  Submit
</button>

React Router v6

Why:

  • Standard solution for React SPAs
  • Supports lazy-loaded routes natively
  • URL-based navigation with browser history support
  • Nested routes if you need them later

URL convention: Lowercase, hyphenated slugs.

/dashboard          (good)
/file-processor     (good)
/Dashboard          (bad)
/file_processor     (bad)

Key pattern: Routes are auto-generated from the route manifest -- you don't manually add <Route> elements. See Routing & Registry.


State Management: React Hooks Only

Decision: No Redux, Zustand, MobX, or any external state library.

Why this works:

  • Features are self-contained (minimal shared state)
  • Reduces bundle size and cognitive overhead
  • React hooks (useState, useContext, useReducer) handle every case

When features need shared state:

  • Theme preference (light/dark) -- React Context
  • User preferences -- localStorage + custom hook
  • Cross-feature communication -- custom events (rare)

When this breaks down: If your features frequently share complex state (e.g., a collaborative editor, real-time dashboard with cross-widget updates), you may need a state library. But for feature-per-page architectures, you almost certainly don't.


Data Storage

localStorage as Primary Storage

Why:

  • No backend required
  • Persists across browser sessions
  • Synchronous API (simpler than IndexedDB)
  • 5-10MB quota (sufficient for settings, history, preferences)

Namespacing convention:

// Good - clear ownership, no collisions
'myapp:dashboard:filters'
'myapp:settings:theme'
'myapp:file-processor:recent'

// Bad - collision risk with other libraries
'filters'
'theme'
'recent'

When NOT to use localStorage:

  • Sensitive data (passwords, tokens, API keys)
  • Large files (use File API, IndexedDB, or OPFS)
  • Cross-device sync (localStorage is device-specific)

See State Management for the full localStorage hook API.


Component Architecture

Feature Structure

Every feature follows the same three-layer pattern:

src/pages/features/MyFeaturePage.jsx     # Page: layout + composition
src/hooks/useMyFeature.js                # Hook: all logic, state, handlers
src/components/features/MyFeature/       # Subcomponents (if needed)

Why:

  • Logic is reusable (swap the UI without touching logic)
  • Logic is testable (test the hook with renderHook, no UI rendering needed)
  • Clear separation of concerns
  • Features stay self-contained

Example:

// useCounter.js -- all logic
export function useCounter(initialValue = 0) {
  const [count, setCount] = useLocalStorage('myapp:counter:value', initialValue);
  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);
  const reset = () => setCount(initialValue);
  return { count, increment, decrement, reset };
}

// CounterPage.jsx -- just UI
export default function CounterPage() {
  const { count, increment, decrement, reset } = useCounter();
  return (
    <FeatureLayout title="Counter">
      <Card>
        <div className="text-4xl font-bold text-center">{count}</div>
        <div className="flex gap-2 mt-4">
          <Button onClick={decrement}>-</Button>
          <Button onClick={reset} variant="secondary">Reset</Button>
          <Button onClick={increment}>+</Button>
        </div>
      </Card>
    </FeatureLayout>
  );
}

Shared Components

Before building custom UI, check src/components/shared/. If a pattern exists, use it. If you build something used in 3+ features, extract it to shared.

The template includes 30 production-ready components (54 at production scale). See Components Reference.


Security

Even without a backend, client-side apps have security concerns:

  • XSS prevention: Use DOMPurify for any dangerouslySetInnerHTML with user content
  • Crypto: Use Web Crypto API (crypto.subtle), never custom algorithms or Math.random() for security
  • CSP headers: Configure Content Security Policy in your hosting config
  • Input validation: Validate at system boundaries (user input, file uploads)

See Security for detailed patterns.


Deployment

Static Hosting (Recommended)

The build output is a static dist/ folder deployable anywhere:

  • Vercel -- Auto-deploy on push, preview deployments, global CDN, free tier
  • Netlify -- Similar to Vercel, slightly different DX
  • Cloudflare Pages -- Fast edge network, generous free tier
  • GitHub Pages -- Free for public repos
npm run build    # produces dist/
# Upload dist/ to any static host

SPA Routing

Since all routing is client-side, configure your host to serve index.html for all paths. Most static hosts handle this automatically. For custom servers, add a catch-all redirect.