Frontend Architecture
Frontend Architecture Document
Project:
{{PROJECT_NAME}}Drop — Fintech Payment App Version:{{VERSION}}0.1.0 Date:{{DATE}}2026-02-23 Author:{{AUTHOR}}John (AI Director, ALAI) Status:Draft |In Review| ApprovedReviewers:{{REVIEWERS}}Alem Bašić (CEO)
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | Initial draft from source code analysis |
1. Purpose & Scope
This document defines the frontend architecture for {{PROJECT_NAME}}Drop., a fintech payment app for all residents of Norway/Scandinavia. It covers frameworkthe decisions,Next.js folderweb structure,application rendering strategy, routing, build configuration, performance targets,(src/drop-app/) and operationalthe considerations.static marketing site (landing/).
TODO:Intended readers: DefineFrontend scopeengineers, boundariesmobile engineers, architects, product managers.
In scope:
- Next.js App Router web application (
src/drop-app/) - Static HTML marketing landing page (
landing/index.html) - Expo React Native mobile app (
src/drop-mobile/) —whichdetailsclientinapplicationsmobile-architecture.md
Out of scope: Backend API (webHono app,v4), admininfrastructure, panel,CI/CD marketing site, etc.).pipelines.
2. Framework Choice & Rationale
| Criterion | Weight | Next.js 15 | Nuxt 3 | SvelteKit | Remix |
|---|---|---|---|---|---|
| Team expertise | 30% | ||||
| Ecosystem maturity | 25% | ||||
| Performance | 20% | ||||
| DX / tooling | 15% | ||||
| License / cost | 10% |
Selected Framework: {{FRAMEWORK}}Next.js 15 (App Router) + React 19
Rationale:
Next.js
TODO:wasWriteselected3-5 sentences explainingbecause thefinalentiredecision.team (AI-driven) operates in TypeScript + React. The App Router supports a hybrid rendering model essential for Drop: Server Components for the marketing landing page (fast, SEO-friendly), and Client Components for the authenticated payment flows (real-time, user-specific). Code sharing between the Next.js web app and the Expo mobile app is maximized — shared hooks, types, and validation logic. The Vercel deployment model provides zero-config CDN, edge middleware for auth protection, and ISR for marketing pages without managing infrastructure.
Node/runtime version:
Package manager: {{NODE_VERSION}}Node.js 20 LTS{{npm | yarn | pnpm | bun}}
3. Project Folder Structure
{{PROJECT_NAME}}/src/drop-app/
├── src/
│ ├── app/ # RouteNext.js segmentsApp Router (Appfile-based Router)routing)
or│ pages/│ ├── page.tsx # / — Marketing home (Server Component)
│ │ ├── layout.tsx # Root layout — fonts, PWA, cookie consent
│ │ ├── login/page.tsx # /login — Client Component
│ │ ├── register/page.tsx # /register — Multi-step onboarding
│ │ ├── dashboard/page.tsx # /dashboard — Auth required
│ │ ├── accounts/page.tsx # /accounts — Bank account linking
│ │ ├── transactions/page.tsx # /transactions — Transaction history
│ │ ├── scan/page.tsx # /scan — QR payment
│ │ ├── send/page.tsx # /send — Remittance flow
│ │ ├── profile/ # /profile and sub-routes
│ │ ├── notifications/page.tsx # /notifications
│ │ ├── cards/page.tsx # /cards — Feature-flagged (future)
│ │ ├── fees/page.tsx # /fees — Public fee disclosure
│ │ ├── privacy/page.tsx # /privacy — GDPR policy
│ │ ├── terms/page.tsx # /terms — Terms of service
│ │ ├── complaints/page.tsx # /complaints — Finansavtaleloven §3-53
│ │ ├── withdrawal/page.tsx # /withdrawal — Angrerettloven
│ │ └── api/ # API routes (BFF layer)
│ │ ├── auth/ # login, logout, me, register
│ │ ├── transactions/ # remittance, qr-payment
│ │ ├── recipients/ # CRUD recipients
│ │ ├── rates/ # Exchange rates
│ │ ├── cards/ # Feature-flagged
│ │ ├── merchants/ # Merchant dashboard
│ │ ├── settings/ # User preferences
│ │ ├── notifications/ # Push notifications
│ │ └── consents/ # Cookie consent
│ ├── components/
│ │ ├── ui/ # Primitiveshadcn/ui designprimitives system components(Radix-based)
│ │ ├── features/bottom-nav.tsx # Feature-scopedFixed compositebottom componentsnavigation (5 tabs)
│ │ ├── drop-logo.tsx # DropLogo, DropWordmark, DropLogoFull, DropAppIcon
│ │ ├── drop-icons.tsx # 9 custom domain-specific icons
│ │ ├── cookie-consent.tsx # GDPR cookie consent banner + modal
│ │ ├── pre-payment-disclosure.tsx # PSD2 pre-payment disclosure modal
│ │ └── layouts/pwa-register.tsx # PageService layoutWorker wrappers
│ ├── hooks/ # Shared custom hooks / composablesregistration
│ ├── lib/
# Pure utilities, third-party wrappers│ │ ├── stores/use-auth.ts # ClientuseAuth() state— (Zustandauth /hook Piniawith slices)redirect
│ │ ├── services/feature-flags.ts # APIFeature client,flag data-fetchingsystem layer(env-var based)
│ │ └── middleware/ # auth-middleware, error-handler, validation
│ └── middleware.ts # Next.js middleware (auth protection)
├── public/
│ ├── styles/sw.js # GlobalService CSS,worker theme(PWA)
tokens│ └── manifest.json # Web app manifest
├── landing/ # Static marketing site (separate from Next.js)
│ ├── types/index.html # TypeScriptMain interfaceslanding &page enums(~646 lines, pure HTML/CSS/JS)
│ └── constants/pages/ # App-wide12 constantssub-pages (priser, personvern, vilkar, etc.)
├── public/globals.css # StaticCSS assetscustom servedproperties at(brand root
├── tests/
│ ├── unit/
│ ├── integration/
│ └── e2e/
├── .env.exampletokens)
├── next.config.ts # (or framework config file)
└── package.json
TODO: Update tree to match actual project structure.
4. Rendering Strategy
| Page Type | Strategy | Rationale |
|---|---|---|
/ — Marketing |
||
/fees, /privacy, /terms, /complaints, /withdrawal |
||
/login, /register |
||
/dashboard, /accounts, /transactions |
||
/scan, /send |
||
/profile, /notifications, /cards |
Client Component (CSR) | Auth-gated, user-specific |
/api/** |
API Routes | BFF layer — cookie-to-bearer proxy |
Hydration approach: {{Partial hydration | Full hydrationhydration. |Server Islands}}Components used for the marketing page (page.tsx). All authenticated app pages are Client Components using "use client" directive.
TODO:Note: MapThe everymarketing top-levellanding routepage to(landing/index.html) is a renderingcompletely strategy.separate pure HTML/CSS/JS file served statically. It does not use the Next.js framework.
5. Routing Architecture
5.1 Route Organization
/ → HomeMarketing home (SSG)Server Component)
/app/login → App shellLogin (SSR, auth-required)Client)
/app/register → Registration / Onboarding (Client, 4-step)
/dashboard → Dashboard (Client, auth required)
/app/[resource]/[id]accounts → DynamicBank resourceaccounts detailvia AISP (Client, auth required)
/transactions → Transaction history (Client, auth required)
/scan → QR scanner (Client, auth required)
/send → Remittance flow (Client, auth required)
/profile → Profile (Client, auth required)
/profile/personal → Personal info — BankID-verified, read-only
/profile/security → Security settings — 2FA, active devices
/profile/notifications → Notification preferences
/profile/language → Language selection (nb, en, bs, sq)
/notifications → Notifications center (Client, auth required)
/cards → Card management (Client, auth required, feature-flagged)
/fees → Fee disclosure (Public)
/privacy → Privacy policy / GDPR (Public)
/terms → Terms of service (Public)
/complaints → Complaint form — Finansavtaleloven §3-53 (Public)
/withdrawal → Right of withdrawal — Angrerettloven (Public)
/api/** → API routes (BFF)BFF /auth/login— →cookie Loginauth (CSR)proxy /[...catchAll]to →Hono 404 handlerbackend)
5.2 Route Guards / Middleware
Auth is enforced at two levels:
Level 1 — useAuth() hook (per-page):
// src/lib/use-auth.ts
// Default: redirectIfUnauthenticated = true
const { user, loading } = useAuth();
// On 401: redirects to /login
Level 2 — Middleware (future):
Middleware execution order:
1. Request logging
2. Authentication check (JWT validationcookie / session lookup)validation)
3. Redirect unauthenticated → /auth/login
4. Role-basedFeature routeflag protectiongate 5.(returns Locale404 detectionif /flag redirectdisabled)
TODO:Protected routes: ListAll allroutes protectedunder route/dashboard, patterns/accounts, and/transactions, their/scan, required/send, roles./profile/**, /notifications, /cards.
Public routes: /, /login, /register, /fees, /privacy, /terms, /complaints, /withdrawal.
6. Build & Bundle Configuration
6.1 Key Config Options
| Option | Value | Reason |
|---|---|---|
| Output | standalone |
Docker-optimized deployment |
| Image optimization | next/image |
|
| Bundle analyzer | ANALYZE=true |
On-demand local analysis |
| Source maps | Production: hidden | |
| Compression | gzip + brotli | CDN- |
| PWA | Service Worker at /sw.js |
Offline capability (basic) |
6.2 Code Splitting Strategy
- Route-level splitting: Automatic per
page/routeApp Router segment - Component-level splitting:
dynamic()/defineAsyncComponent()formodals,heavywidgetscomponents (Dialog, QR scanner UI) Third-partyFeaturesplitting:flags:VendorCardschunkpageisolation for large deps (charts, editors)
TODO: Identify and document specificcomponents lazy-loaded components.
false)
7. Performance Budget
| Metric | Target | Tool |
|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | Lighthouse, CrUX |
| < 200ms | CrUX | |
| CLS (Cumulative Layout Shift) | < 0.1 | Lighthouse |
| TTFB (Time to First Byte) | < 600ms | WebPageTest |
| Total JS bundle (compressed) | < 200 KB | Bundlesize CI check |
| First page load (mobile 4G) | < 3s | WebPageTest |
| Lighthouse Performance Score | ≥ 90 | Lighthouse CI |
CI enforcement:Note: The marketing page () TODO:/linkis the primary SEO and performance target. The app pages (dashboard, send) accept slightly higher bundle sizes due to bundlesizeshadcn/ui config+ orRadix Lighthouse CI workflowdependencies.
8. Asset Management
8.1 Images
- Format: WebP primary, JPEG fallback, SVG for
vectorvectors (logos, icons) - Optimization:
Framework image component (next/image/withnuxt/image)defined CDN:{{CDN_PROVIDER}}— origin:domains{{STORAGE_BUCKET}}- Lazy loading: Native
loading="lazy"+viaframework componentnext/image
8.2 Fonts
Self-hostedFraunces (variable, 400-900) — loaded viain/public/fonts/next/font/googlelayout.tsx- DM Sans (
novariable,Google400-700)Fonts—runtimeloadedrequests)vianext/font/googleinlayout.tsx - Geist Mono — loaded via
next/font/googleinlayout.tsx - CSS variables:
--font-fraunces,--font-dm-sans,--font-geist-mono font-display: swapon all facesSubset(handledtobyused characters if possiblenext/font)
8.3 Icons
Library:Primary library:{{Lucidelucide-react| Heroicons | custom(inline SVGsprite}}via React components)- Custom icons:
components/drop-icons.tsx— 9 domain-specific icons (IconSendMoney, IconQrScan, IconVirtualCard, IconShield, IconFastTransfer, IconCorridors, IconWallet, IconHistory, IconTopUp) - Social auth icons: Inline SVG (BankID green, Vipps orange)
- Delivery: Inline SVG via component (no icon
font)font, no sprite)
TODO: Finalize CDN domain and storage bucket configuration.
9. Internationalization (i18n) Strategy
| Item | Decision |
|---|---|
| Library | |
|
|
| Default locale | (Norwegian Bokmål) |
| Supported locales | nb (launch), en, bs, sq (Phase 2) |
| Language selection | /profile/language — PATCH /api/settings with { |
| Translation format | |
| |
| RTL support |
TranslationPhase workflow:1: All UI text hardcoded in Norwegian Bokmål. Language setting stored in user preferences (/api/settings) but not yet applied to UI translation.
Developer2:TODO: extraction toolgeneratesJSON translationkeysfiles Translatorforfillsnb,missingen,keysbs,in Phrase/Lokalise/manual JSONCI validates no missing keys before deploy
Phase addsIntroduce keynext-intl or i18next with Englishper-locale string
sq.
10. Error Boundary Strategy
| Level | Scope | Behavior | |
|---|---|---|---|
Global error.tsx |
Full page crash | Show branded error |
|
useAuth() /login |
|||
| Form submission | Mutation failure | Inline error message (text-[#EF4444]) + retry |
|
| Data fetch | useEffect fetch failure |
Caught in .catch(), silent or empty state |
|
| Feature flag | Flag disabled | Returns 404 or "Feature not available" message |
Error reporting: {{Sentry | Datadog | custom}}
DSN: {{SENTRY_DSN}} (environment variableTBD — neverSentry hardcode)
TODO:planned Implementfor and test each boundary level.production.
11. Environment Configuration
| Variable | Dev | Staging | Prod | Description |
|---|---|---|---|---|
NEXT_PUBLIC_API_URL |
http://localhost: |
https:// |
https:// |
Backend API base URL (mobile uses this) |
NEXT_PUBLIC_APP_ENV |
development |
staging |
production |
Runtime environment flag |
NEXT_PUBLIC_FF_VIRTUAL_CARDS |
false |
false |
false |
Cards feature flag |
NEXT_PUBLIC_FF_PHYSICAL_CARDS |
false |
false |
false |
Physical cards flag |
NEXT_PUBLIC_FF_NOTIFICATIONS |
true |
true |
true |
Notifications feature flag |
NEXT_PUBLIC_FF_MERCHANT_DASHBOARD |
true |
true |
true |
Merchant dashboard flag |
SENTRY_DSN |
optional | required | required | Error reporting (server-side) |
| ||||
| |
Feature flag convention: NEXT_PUBLIC_FF_ + SCREAMING_SNAKE_CASE flag name.
Secrets management: All non-public secrets stored in Vaultwarden (). {{Vault / AWS Secrets Manager / Vercel env}}vault.basicconsulting.no.env.example:Never Must be kept up to date — CI validates no undocumented variables exist.committed.
12. Dependency Management Policy
| Rule | Detail |
|---|---|
| Allowed licenses | MIT, Apache-2.0, ISC, BSD-2, BSD-3 |
| Security audit | pnpm audit in CI — fail on high/critical |
| Update cadence | |
| Banned patterns | No moment.js (use date-fns), no lodash (use |
TODO:Key dependencies:
next@15,orreact@19Dependabot—withframeworkshadcn/uigrouping+rules.@radix-ui/*— component primitiveslucide-react— icon librarytailwindcss— stylingclass-variance-authority,clsx,tailwind-merge— class utilities
13. Architecture Diagram
graph TB
subgraph "Client Browser"Browser / Mobile"
Browser["User Browser"Browser (PWA)"]
Mobile["Expo Mobile App"]
end
subgraph "Frontend Application — {{FRAMEWORK}}"Next.js 15 App Router"
CDN["Vercel CDN Edge (Static Assets)Assets + Edge)"]
SSR[Marketing["SSRMarketing Page (Server /Component, Edge Runtime"SSG)"]
Pages[AppPages["RouteApp SegmentsPages (Client Components, CSR)"]
BFF["BFF API Routes (/api/*)\nCookie Pages"→ Bearer proxy"]
Middleware["Next.js Middleware\n(Auth guard)"]
Components["Component Tree"]
State["State Layer (Servershadcn/ui + Client)"Custom Components\nBottomNav, DropLogo, drop-icons"]
Services[AuthHook["APIuseAuth() ServiceHook\nGET Layer"/api/auth/me"]
FeatureFlags["Feature Flags\nenv-var based"]
end
subgraph "Backend"Backend API[— Hono v4"
HonoAPI["Hono REST /API\n/v1/* GraphQL— API"Bearer auth"]
Auth[OpenBanking["AuthOpen Service"Banking (PSD2)\nAISP + PISP"]
BankID["BankID OIDC\nAuthentication"]
end
Browser --> CDN
Browser --> SSRMarketing
SSRBrowser --> PagesMiddleware
PagesMiddleware --> AppPages
AppPages --> Components
ComponentsAppPages --> StateAuthHook
StateAppPages --> ServicesFeatureFlags
ServicesAuthHook --> APIBFF
SSRBFF --> AuthHonoAPI
HonoAPI --> OpenBanking
HonoAPI --> BankID
Mobile --> HonoAPI
TODO: Refine diagram to reflect actual deployed infrastructure (Vercel, AWS, self-hosted, etc.).
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | John (AI Director) | 2026-02-23 | |
| Tech Lead | |||
| Architect | |||
| Engineering Manager |