ca648ccf8b
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> Agent-Logs-Url: https://github.com/christianlouis/pop_puller_to_gmail/sessions/dd261f53-4891-437e-bd71-5561ece62d7d
5.7 KiB
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:
- User authentication (email/password + Google OAuth)
- Mail account management dashboard (CRUD operations)
- Processing run history and statistics
- Settings and subscription management
- 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:
- App Router: File-system based routing with layout nesting simplifies page organization
- TypeScript First: Strong typing catches API integration errors at compile time
- API Route Proxy: Next.js Route Handlers allow proxying backend requests at runtime — eliminating CORS issues and enabling
BACKEND_URLto be set at container runtime (not build time) - SSR + Static: Supports both server-rendered pages (authenticated dashboard) and static pages (marketing landing page)
- Tailwind CSS: Utility-first CSS eliminates the need for a separate CSS framework and enables rapid UI development
- Large Ecosystem: Extensive component libraries, excellent documentation, widely used
- Docker Friendly:
next startserves 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_URLat 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)
localStoragetoken storage is vulnerable to XSS (future: migrate tohttpOnlycookies)
Neutral
- ESLint with
eslint-config-nextenforces Next.js-specific rules npm ciused in CI to ensure deterministic installs frompackage-lock.json
Related Decisions
- See ADR-007 for JWT tokens consumed by the frontend
- See ADR-003 for the FastAPI backend the frontend proxies to