Files
gh-christianlouis-inboxconv…/docs/adr/008-nextjs-frontend.md
T

5.7 KiB

ADR 008: Use Next.js for Frontend

Status: Accepted
Date: 2026-02-10
Deciders: Development Team

Context

The SaaS platform requires a web frontend that provides:

  1. User authentication (email/password + Google OAuth)
  2. Mail account management dashboard (CRUD operations)
  3. Processing run history and statistics
  4. Settings and subscription management
  5. Responsive design for mobile and desktop

Technical requirements:

  • Type-safe API integration with the FastAPI backend
  • Server-side rendering (SSR) for SEO and initial load performance
  • Static export capability for CDN hosting
  • Easy Docker containerization
  • Modern development tooling (TypeScript, ESLint, hot-reload)

Decision

We will use Next.js 14+ (App Router) with TypeScript and Tailwind CSS for the frontend.

Alternatives Considered

1. React (Create React App / Vite SPA)

  • Pros: Simpler build setup, no SSR complexity, large ecosystem
  • Cons: Client-side rendering only (SEO limitations), no built-in routing, additional setup for SSR

2. Vue.js / Nuxt.js

  • Pros: Excellent developer experience, reactive by default, SSR via Nuxt
  • Cons: Smaller ecosystem than React, team less familiar, different component model

3. Svelte / SvelteKit

  • Pros: Very small bundle size, simple reactive model, SSR via SvelteKit
  • Cons: Smaller community, fewer UI component libraries, less mature ecosystem

4. Angular

  • Pros: Full framework (routing, forms, HTTP), TypeScript-first
  • Cons: Heavy boilerplate, steep learning curve, over-engineered for a dashboard application

5. Server-Side Templates (Jinja2 / FastAPI with Jinja)

  • Pros: Simplest deployment (single backend), no separate frontend build
  • Cons: No reactive UI, poor UX for dynamic dashboards, hard to test independently

Rationale

Next.js was chosen because:

  1. App Router: File-system based routing with layout nesting simplifies page organization
  2. TypeScript First: Strong typing catches API integration errors at compile time
  3. API Route Proxy: Next.js Route Handlers allow proxying backend requests at runtime — eliminating CORS issues and enabling BACKEND_URL to be set at container runtime (not build time)
  4. SSR + Static: Supports both server-rendered pages (authenticated dashboard) and static pages (marketing landing page)
  5. Tailwind CSS: Utility-first CSS eliminates the need for a separate CSS framework and enables rapid UI development
  6. Large Ecosystem: Extensive component libraries, excellent documentation, widely used
  7. Docker Friendly: next start serves the production build, easily containerized

Implementation Architecture

Frontend Proxy Pattern

The frontend proxies all API calls through a Next.js Route Handler to avoid CORS and build-time URL issues:

Browser → Next.js Server (/api/v1/*) → FastAPI Backend (BACKEND_URL/api/v1/*)
// src/app/api/v1/[...path]/route.ts
const BACKEND_URL = process.env.BACKEND_URL ?? "http://localhost:8000";

export async function GET(req: NextRequest, { params }: { params: { path: string[] } }) {
  const path = params.path.join("/");
  const url = `${BACKEND_URL}/api/v1/${path}${req.nextUrl.search}`;
  return fetch(url, { headers: req.headers });
}

This means BACKEND_URL is a runtime environment variable (set in Docker Compose / Kubernetes), not a build-time variable. The Axios client uses a relative base URL:

// src/lib/api.ts
const api = axios.create({ baseURL: "/api/v1" });

Project Structure

frontend/src/
├── app/                    # Next.js App Router pages
│   ├── page.tsx            # Landing page
│   ├── login/              # Authentication pages
│   ├── dashboard/          # Main dashboard
│   ├── accounts/           # Mail account management
│   ├── settings/           # User settings
│   └── api/v1/[...path]/   # Backend proxy Route Handler
├── components/             # Reusable UI components
│   ├── AddMailAccountModal.tsx
│   ├── DashboardLayout.tsx
│   └── AuthGuard.tsx
├── lib/
│   └── api.ts              # Typed Axios API client
├── store/
│   └── authStore.ts        # Zustand authentication state
└── instrumentation.ts      # Server startup hook (logs BACKEND_URL)

Authentication State

Authentication state is managed via Zustand store, with JWT tokens persisted in localStorage. The AuthGuard component protects all dashboard routes.

Consequences

Positive

  • TypeScript catches API contract mismatches at compile time
  • Proxy pattern eliminates CORS configuration and build-time URL embedding
  • App Router layouts reduce boilerplate for authenticated vs public pages
  • Tailwind CSS enables rapid UI iteration without writing custom CSS
  • Server instrumentation hook logs BACKEND_URL at startup for easy debugging

Negative

  • Next.js adds complexity vs a plain React SPA (SSR concepts, server/client component boundary)
  • Node.js 20+ required at runtime (not just build time)
  • Separate Docker container needed (backend and frontend are distinct services)
  • localStorage token storage is vulnerable to XSS (future: migrate to httpOnly cookies)

Neutral

  • ESLint with eslint-config-next enforces Next.js-specific rules
  • npm ci used in CI to ensure deterministic installs from package-lock.json
  • See ADR-007 for JWT tokens consumed by the frontend
  • See ADR-003 for the FastAPI backend the frontend proxies to

References