Architecture decision ADR-002

Use Next.js Hybrid SSR/SSG as Frontend and Backend-for-Frontend (BFF)

Accepted · 2026-05-10

Historical decision text is preserved. Accepted describes the decision, not deployment of every capability discussed. Consult the Architecture and Infrastructure tabs for the verified running system.

Date: 2026-05-10
Status: Accepted
Deciders: Daniel Meza


Context and Problem Statement

Urza's Wallet's primary acquisition channel is organic search. The platform must serve product comparison pages, category hubs, filter pages, and editorial content that are fully crawlable, fast to load (LCP (Largest Contentful Paint) < 2.5 s), and updated frequently as financial rates change. At the same time, the platform requires a logged-in dashboard with personalized saved comparisons and alerts — a use case better served by client-side rendering.

We need a frontend framework and rendering strategy that:

  1. Serves SEO (Search Engine Optimization)-optimized, static or server-rendered pages for public content.
  2. Provides rich, interactive client-side views for authenticated users.
  3. Handles the OIDC (OpenID Connect) session cookie flow securely without exposing tokens to the browser.
  4. Aggregates multiple API calls into page-level payloads server-side (BFF pattern) to reduce client round trips.
  5. Integrates cleanly with Railway's deployment model (single Docker container per service).

Decision Drivers


Considered Options

  1. Next.js with App Router (hybrid SSG/ISR/SSR/CSR — Client-Side Rendering: server sends a minimal shell and JS; the browser fetches data and renders UI; best for highly interactive SPA-like areas, weaker initial SEO) — single framework handling all rendering strategies and the BFF layer.
  2. React SPA (Vite) + separate Express BFF — full client-side rendering, separate backend for session management and API aggregation.
  3. Remix — server-rendered React framework with a different routing and data-loading model.
  4. Astro with React islands — static-first framework, excellent for editorial content but less suitable for interactive dashboard and comparison flows.
  5. Next.js Pages Router (hybrid, classic) — same framework but older Pages Router rather than App Router.

Decision Outcome

Option 1: Next.js 14 with App Router is adopted.

The rendering strategy is assigned per page type:

Page Type Strategy Revalidation Reason
Category hubs, filter pages, issuer pages SSG + ISR revalidate: 3600 High SEO value; CDN-cached; ISR refreshes rates hourly
Product detail pages SSG + ISR revalidate: 14400 Highest conversion value; pre-rendered for LCP; ISR on rate change
Editorial / guide pages SSG On-demand only Content changes rarely; full CDN caching; no personalization
Comparison pages SSR Per-request Dynamic product set; fresh data required; canonical control
User dashboard CSR (React Server Components shell + client components) — Authenticated, personalized, not indexed
Admin UI CSR — Internal, role-gated, not indexed

The Next.js app also serves as the BFF through Next.js Route Handlers and Server Actions:

On-demand ISR revalidation is triggered by the Data Ingestion Service via POST /api/revalidate with an internal HMAC (Hash-based Message Authentication Code)-signed token after a successful product data write. This ensures that product detail and category pages reflect updated rates within seconds of ingestion completing, without waiting for the TTL (Time to Live)-based revalidation window.


Positive Consequences

Negative Consequences / Trade-offs


Pros and Cons of the Options

Option SEO BFF Integration Ops Overhead Railway Fit
Next.js App Router (chosen) Excellent Native (RSC/Route Handlers) Low (single service) First-class
React SPA + Express BFF Poor (CSR only) Separate service Medium (2 services) Good
Remix Excellent Native (loaders) Low Good
Astro + React islands Excellent Limited Low Good
Next.js Pages Router Excellent Good (getServerSideProps) Low First-class

Remix was the strongest alternative. Its loader pattern is cleaner than Next.js App Router's server components for data-heavy pages. However, Next.js has a larger ecosystem, better Clerk integration, and more Railway deploy examples. The App Router's RSC model aligns better with the planned progressive enhancement of the dashboard in Phase 2.


References