Skip to main content

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 | Approved Reviewers: {{REVIEWERS}}Alem Bašić (CEO)

Document History

Version Date Author Changes
0.1 {{DATE}}2026-02-23 {{AUTHOR}}John 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/)whichdetails clientin applicationsmobile-architecture.md
  • are
covered

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% {{SCORE}}9 {{SCORE}}5 {{SCORE}}4 {{SCORE}}6
Ecosystem maturity 25% {{SCORE}}9 {{SCORE}}7 {{SCORE}}6 {{SCORE}}7
Performance 20% {{SCORE}}9 {{SCORE}}8 {{SCORE}}9 {{SCORE}}8
DX / tooling 15% {{SCORE}}9 {{SCORE}}7 {{SCORE}}7 {{SCORE}}7
License / cost 10% {{SCORE}}10 {{SCORE}}10 {{SCORE}}10 {{SCORE}}10

Selected Framework: {{FRAMEWORK}}Next.js 15 (App Router) + React 19

Rationale:

Next.js

TODO:was Writeselected 3-5 sentences explainingbecause the finalentire decision.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: {{NODE_VERSION}}Node.js 20 LTS Package manager: {{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 / landinghome SSGServer Component (SSG) Maximum caching,SEO-critical, no authauth, dependencystatic content
Dashboard/fees, /privacy, app/terms, views/complaints, /withdrawal SSRClient Component (CSR) FreshPublic data,pages, auth-gatedno auth required
Listing/login, pages/register ISRClient Component (revalidate: 60s)CSR) BalanceForm freshnessstate, vsclient-side performancevalidation
User-specific/dashboard, views/accounts, /transactions CSRClient Component (CSR) Dynamic,Auth-gated, personalizedpersonalized, real-time data
API/scan, routes/send ServerClient Component (CSR) ThinMulti-step flows, camera access, form state
/profile, /notifications, /cardsClient Component (CSR)Auth-gated, user-specific
/api/**API RoutesBFF 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/loginApp shellLogin (SSR, auth-required)Client)
/app/register                   → Registration / Onboarding (Client, 4-step)
/dashboard                  → Dashboard (Client, auth required)
/app/[resource]/[id]accountsDynamicBank 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

server-level
Option Value Reason
Output standalone Docker-optimized deployment
Image optimization Enablednext/image Next/ImageWebP withconversion, definedlazy domainsloading
Bundle analyzer ANALYZE=true On-demand local analysis
Source maps Production: hidden Security — uploadUpload to Sentry only
Compression gzip + brotli CDN-level,level notcompression
PWAService Worker at /sw.jsOffline capability (basic)

6.2 Code Splitting Strategy

  • Route-level splitting: Automatic per page/routeApp Router segment
  • Component-level splitting: dynamic() / defineAsyncComponent() for modals, heavy widgetscomponents (Dialog, QR scanner UI)
  • Third-partyFeature splitting:flags: VendorCards chunkpage isolation for large deps (charts, editors)

TODO: Identify and document specificcomponents lazy-loaded components.

(all flags default to false)

7. Performance Budget

Metric Target Tool
LCP (Largest Contentful Paint) < 2.5s Lighthouse, CrUX
FID / INP (Interaction to Next Paint) < 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 /with nuxt/image)
  • defined
  • CDN: {{CDN_PROVIDER}} — origin: {{STORAGE_BUCKET}}domains
  • Lazy loading: Native loading="lazy" +via framework componentnext/image

8.2 Fonts

  • Self-hostedFraunces (variable, 400-900) — loaded via /public/fonts/next/font/google in layout.tsx
  • DM Sans (novariable, Google400-700) Fonts runtimeloaded requests)via next/font/google in layout.tsx
  • Geist Mono — loaded via next/font/google in layout.tsx
  • CSS variables: --font-fraunces, --font-dm-sans, --font-geist-mono
  • font-display: swap on all faces
  • Subset(handled toby used characters if possiblenext/font)

8.3 Icons

  • Library:Primary library: {{Lucidelucide-react | Heroicons | custom(inline SVG sprite}}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

None(Phase1Norwegian
Item Decision
Library `{{next-intl
Locale routing /[locale]/path prefixonly)
Default locale {{en}}nb (Norwegian Bokmål)
Supported locales nb (launch), en, bs, sq (Phase 2)
Language selection/profile/language — PATCH /api/settings with {{en, nb,language: de}string }
Translation format JSONTBD key-value (perrequires locale,i18n perlibrary namespace)
Translation storagesrc/messages/[locale]/[namespace].json
PluralizationICU message formatselection
RTL support `{{YesNo

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.

    Phase

  1. Developer2: addsIntroduce keynext-intl or i18next with Englishper-locale string
  2. TODO: extraction tool generatesJSON translation keys
  3. files
  4. Translatorfor fillsnb, missingen, keysbs, in Phrase/Lokalise/manual JSON
  5. CI validates no missing keys before deploy
sq.


10. Error Boundary Strategy

error
Level Scope Behavior
Global error.tsx Full page crash Show branded error page, report to Sentrypage
Layout boundarySection crashIsolate — rest of page remains usable
Async componentData fetchAuth failure SkeletonAPI 401 useAuth() stateredirects UIto /login
Form submission Mutation failure Inline error message (text-[#EF4444]) + retry
Data fetchuseEffect fetch failureCaught in .catch(), silent or empty state
Feature flagFlag disabledReturns 404 or "Feature not available" message

Error reporting: {{Sentry | Datadog | custom}} DSN: {{SENTRY_DSN}} (environment variableTBDneverSentry hardcode)

integration

TODO:planned Implementfor and test each boundary level.production.


11. Environment Configuration

Variable Dev Staging Prod Description
NEXT_PUBLIC_API_URL http://localhost:40003000/api https://api-drop-app-staging.{{DOMAIN}}vercel.app/api https://api.{{DOMAIN}}drop-app.vercel.app/api Backend API base URL (mobile uses this)
NEXT_PUBLIC_APP_ENV development staging production Runtime environment flag
NEXT_PUBLIC_FF_VIRTUAL_CARDSfalsefalsefalseCards feature flag
NEXT_PUBLIC_FF_PHYSICAL_CARDSfalsefalsefalsePhysical cards flag
NEXT_PUBLIC_FF_NOTIFICATIONStruetruetrueNotifications feature flag
NEXT_PUBLIC_FF_MERCHANT_DASHBOARDtruetruetrueMerchant dashboard flag
SENTRY_DSN optional required required Error reporting (server-side)
NEXT_PUBLIC_SENTRY_DSNoptionalrequiredrequiredError reporting (client-side)
ANALYZEtrue/falseEnable bundle analyzer

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
Approval required for new depsPR must include: bundle size impact, license check, last release date
Allowed licenses MIT, Apache-2.0, ISC, BSD-2, BSD-3
Security audit npm audit / pnpm audit in CI — fail on high/critical
Update cadence Minor/patch:Monthly monthlyRenovate automatedPRs PRfor (Renovate);minor/patch; Major:major = manual + reviewed
Banned patterns No moment.js (use date-fns), no lodash (use native / lodash-es)native)

TODO:Key dependencies:

Configure
    Renovate
  • next@15, orreact@19 Dependabot withframework
  • appropriate
  • shadcn/ui grouping+ rules.

    @radix-ui/* — component primitives
  • lucide-react — icon library
  • tailwindcss — styling
  • class-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