Skip to content

ADR-006: React + Vite vs Next.js

Status: ✅ Adopted Date: 2024-07 Deciders: Frontend Team, CTO Related: Frontend Architecture

Context

We needed a frontend framework for building the Noumaris web application. Key requirements:

  • Fast development for MVP timeline
  • Modern tooling (hot reload, TypeScript support)
  • Good performance for production
  • Simple deployment (static hosting if possible)
  • Team expertise (small team, learning curve matters)
  • Authentication via Keycloak (OAuth2/OIDC)

Decision

Selected: React 18 + Vite

Frontend stack:

  • React 18 for UI framework
  • Vite for build tooling
  • React Router v7 for routing
  • TailwindCSS for styling
  • Radix UI for accessible components
  • React Query for data fetching

Rationale

Alternatives Considered

Option 1: Next.js

Pros:

  • Server-side rendering (SSR)
  • File-based routing
  • API routes (backend in same repo)
  • Image optimization
  • Great DX
  • Huge community

Cons:

  • Deployment complexity - Need Node.js server
  • Overkill for SPA - We don't need SSR (authenticated app)
  • Vendor lock-in - Vercel-specific features
  • Heavier builds - More complex build process
  • API routes unused - We have separate FastAPI backend
  • More to learn - Next.js conventions on top of React

Verdict: ❌ Rejected - Too complex for our needs

Option 2: Vue.js

Pros:

  • Simpler than React
  • Good documentation
  • Single-file components

Cons:

  • Smaller ecosystem - Fewer component libraries
  • Team expertise - Team knows React better
  • Healthcare libs - Fewer HIPAA/medical UI components
  • Job market - Harder to hire Vue developers

Verdict: ❌ Rejected - Team expertise matters

Option 3: Svelte/SvelteKit

Pros:

  • Excellent performance
  • Less boilerplate
  • Compiler-based (small bundle)

Cons:

  • Smaller ecosystem - Fewer libraries
  • Team expertise - Nobody knows Svelte
  • Risky - Less battle-tested
  • Component libraries - Limited options

Verdict: ❌ Rejected - Too risky for production app

Option 4: React + Create React App (CRA)

Pros:

  • Official React tooling
  • Zero config
  • Familiar to team

Cons:

  • Slow - Webpack is slower than Vite
  • Outdated - CRA development slowed
  • Heavy - Many unused features
  • Config locked - Need to eject for customization

Verdict: ❌ Rejected - Vite is superior

Option 5: React + Vite (SELECTED)

Pros:

  • Lightning fast - HMR in <50ms
  • Modern tooling - ESM-based, native TypeScript
  • Simple deployment - Static build → CDN
  • Lightweight - Only what you need
  • Great DX - Instant server start
  • React ecosystem - All React libs work
  • No vendor lock-in - Standard React
  • Easy to learn - Just React + build tool

Cons:

  • No SSR (we don't need it)
  • Need separate routing library
  • More manual setup than Next.js

Verdict:SELECTED

Consequences

Positive

  1. Fast Development: HMR updates in <50ms vs 3-5s with Webpack
  2. Simple Deployment: Build → upload to Cloudflare Pages → done
  3. Small Bundle: 320KB gzipped (vs ~800KB with Next.js)
  4. No Server Needed: Static files, scales infinitely
  5. Full Control: No framework magic, just React
  6. Fast Builds: Production build in ~15s vs ~60s with Next.js
  7. Team Productivity: Zero time learning framework-specific patterns

Negative

  1. No SSR: Can't server-render for SEO (not needed for auth-required app)
  2. Manual Routing: Had to set up React Router ourselves
  3. No API Routes: Backend is separate (but we wanted that anyway)
  4. Image Optimization: Manual (use CDN instead)

Mitigations

  1. SEO: Marketing site (landing page) uses Astro for SSR
  2. Routing: React Router v7 is excellent, took 30 mins to set up
  3. API: Separate backend is better for scaling anyway
  4. Images: Use Cloudflare Images for optimization

Implementation

Build Configuration

typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    outDir: 'dist',
    sourcemap: false,
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom', 'react-router-dom'],
          'ui-vendor': ['@radix-ui/react-*'],
        },
      },
    },
  },
  server: {
    port: 5173,
    strictPort: true,
  },
});

Deployment

bash
# Build for production
npm run build

# Output: dist/
# Upload to: Cloudflare Pages, Netlify, Vercel, or any static host

# Deployment time: <1 minute
# vs Next.js deployment: ~5 minutes (need Node.js server)

Performance Comparison

Development (time to see changes):

  • Vite: 30-50ms
  • Webpack (CRA): 2-5 seconds
  • Winner: Vite (100x faster)

Production Build:

  • Vite: 15 seconds
  • Next.js: 60 seconds
  • Winner: Vite (4x faster)

Bundle Size:

  • Vite + React: 320KB (gzipped)
  • Next.js: ~800KB (gzipped, includes Next.js runtime)
  • Winner: Vite (2.5x smaller)

Real-World Impact

Developer Experience

Hot Module Replacement:

  • Change component → see update instantly
  • No page refresh needed
  • State preserved during updates

Build Times:

  • Development server starts in 300ms (vs 30s with CRA)
  • Production build: 15s for entire app
  • Developers save ~1 hour/day waiting for builds

Deployment Simplicity

Vite Deployment:

bash
npm run build
npx wrangler pages publish dist
# Done in 30 seconds

Next.js Deployment (if we used it):

bash
npm run build
# Configure Node.js server
# Set up environment variables
# Configure serverless functions
# Deploy to Vercel/server
# Done in 5-10 minutes

Cost Savings

Hosting Costs:

  • Vite (static): $0/month (Cloudflare Pages free tier)
  • Next.js (SSR): $20-50/month (need Node.js hosting)
  • Savings: $240-600/year

Architecture Decisions

Why Not SSR?

Our app is authentication-required - all routes need login. SSR benefits:

  1. SEO - Not needed (search engines can't index auth-required pages)
  2. First Paint - Not critical (users authenticate first anyway)
  3. Social Sharing - Not needed (clinical docs are private)

Conclusion: SSR overhead not worth the complexity.

Client-Side Routing

We use React Router v7 with:

  • Protected routes - Redirect to login if not authenticated
  • Code splitting - Lazy load admin dashboard
  • Nested layouts - AdminLayout wraps admin routes

State Management

  • React Query - Server state (API data)
  • React Context - Global UI state (auth, theme)
  • Local state - Component-specific (useState)

No need for Redux - React Query handles 90% of state management needs.

When to Reconsider

Consider switching to Next.js if:

  • Public pages needed - Marketing content that needs SEO
  • Complex routing - File-based routing becomes beneficial
  • API routes desired - Want backend in same repo
  • Team grows large - More opinions on Next.js patterns

Future Enhancements

  • [ ] Add service worker for offline support
  • [ ] Implement PWA for mobile install
  • [ ] Add bundle analysis to CI/CD
  • [ ] Set up Lighthouse CI for performance monitoring

Lessons Learned

  1. Vite is amazing: 100x faster HMR worth it alone
  2. SSR often unnecessary: Authenticated apps don't need SSR
  3. Simple is better: React + Vite easier to understand than Next.js
  4. Static hosting rocks: Infinite scale, zero cost
  5. Build times matter: Fast builds = happy developers

References

Internal documentation for Noumaris platform