High-level decisions and rationale for Sovereign React applications.
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.
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 webpacksplitChunkstuning, 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:
- React Helmet for meta tags (included in the template)
- Vite's
prerenderplugin for static HTML generation - Or migrate to Next.js later -- the feature-per-page pattern transfers cleanly
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>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.
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.
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.
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>
);
}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.
Even without a backend, client-side apps have security concerns:
- XSS prevention: Use DOMPurify for any
dangerouslySetInnerHTMLwith user content - Crypto: Use Web Crypto API (
crypto.subtle), never custom algorithms orMath.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.
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 hostSince 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.