Skip to content

Architecture

The web platform is a Next.js 14 App Router application that acts as a typed orchestration layer in front of the Grepsr gRPC microservices. It owns three things: presentation, the user session/authorization context, and the translation of browser requests into backend gRPC calls. It does not own business data — that lives in the backend services it calls.

Layered View

flowchart TD
    subgraph Browser["Browser (Client Components)"]
        UI[React / Ant Design UI]
        Hooks["hooks/apiHooks
(React Query)"] Store["Zustand store
(client state)"] UI --> Hooks UI --> Store end subgraph Edge["Next.js Edge"] MW["middleware.ts
(session gating)"] end subgraph Server["Next.js Server (Node runtime)"] Routes["app/api/*
route handlers"] Grpc["utils/grpc.ts
nice-grpc clients"] Cerbos["utils/cerbos.ts
policy checks"] Routes --> Cerbos Routes --> Grpc end subgraph Backend["Grepsr Backend (Kubernetes / EKS)"] MS["gRPC Microservices
projects, accounts, delivery,
scheduler, data-quality, ..."] end Hooks -->|"Axios (REST/JSON)"| Routes UI -. navigation .-> MW MW -. allow/redirect .-> UI Grpc -->|"Protocol Buffers"| MS

The key boundary is the Axios → API route hop. Browser code never speaks gRPC directly; it makes REST/JSON calls to Next.js route handlers under app/api/*, and those handlers translate the request into a strongly-typed gRPC call using the generated proto clients. This keeps gRPC credentials and cluster addressing entirely server-side.

App Router Structure

The application uses the App Router (app/). The root app/layout.tsx is a client component that mounts the global provider tree and bootstraps the user session; feature areas are nested route segments beneath it.

app/
├── layout.tsx            # Root layout — provider tree + session bootstrap
├── page.tsx              # Redirects to /projects
├── account/              # Unauthenticated routes (login, signup, reset, invite)
├── api/                  # Route handlers — the gRPC/REST boundary (60+ routes)
├── projects/             # Projects, reports, data-schema, crawler-logs
│   ├── [id]/reports/[rid]/...
│   └── crawler-logs/
├── quality/              # Data-quality rules dashboard
├── reporting/            # Cross-project reporting
├── teams/                # Team & membership management
├── api-keys/             # Programmatic API key management
├── messages/             # In-app messaging
└── admin/                # Internal admin (accounts, billing, proxies, roles, services)

Routing conventions:

  • Dynamic segments use [id] / [rid] for resource identifiers.
  • Route handlers (app/api/.../route.ts) are the only place backend gRPC calls originate.
  • Layouts at each level compose feature-specific chrome on top of the root provider tree.

Provider Tree

Cross-cutting concerns are supplied by React context providers composed in app/layout.tsx. The order matters — outer providers are available to everything they wrap.

flowchart TD
    A[MediaContextProvider] --> B[FeatureFlagProvider]
    B --> C[ThemeProvider]
    C --> D[QueryProvider]
    D --> E[Toasterv2Provider]
    E --> F[ToasterProvider]
    F --> G[AnalyticsProvider]
    G --> H[CaslProvider]
    H --> I["children (routes)"]
Provider Responsibility
MediaContextProvider Responsive breakpoints (SSR-safe media queries).
FeatureFlagProvider Flagsmith feature flags; supplies useFlags/useFeature.
ThemeProvider Light/dark mode via next-themes.
QueryProvider TanStack React Query client + DevTools — all server state.
Toasterv2Provider / ToasterProvider Notification/toast surfaces (v2 + legacy).
AnalyticsProvider Amplitude analytics + session replay.
CaslProvider Transforms the logged-in user into a CASL Ability for UI gating.

Session bootstrap lives in the root layout: on mount it calls setUserData() (hydrate the auth store from cookies) and, once a user is present and not impersonating, getUserSettings().

Folder Responsibilities

The codebase follows a consistent layered, domain-oriented organization. Each backend domain typically has a matching quartet: a .proto definition, a service, a hook, and a type file.

Layer Folder Role
Presentation components/ Atomic-design UI: atomsmoleculesorganismspages.
Data binding hooks/apiHooks/ One hook per operation; wraps a service in useQuery/useMutation.
API surface services/ Functions that issue Axios calls to app/api/*; one module per domain.
Transport (client) apis/ Configured Axios instance with auth + error interceptors.
Transport (server) utils/grpc.ts nice-grpc client factory + microservice registry.
Contracts proto/proto-types/ Protobuf definitions compiled to TS clients/types.
Client state store/ Zustand slices (app, auth, pageRef, crawlerLogs).
Cross-cutting providers/ Context providers (see above).
Authorization lib/ability.ts, utils/cerbos.ts CASL rules (client) + Cerbos client (server).
Glue helpers/, utils/, constants/, hoc/ Pure utilities, domain helpers, guards.

Design Principles

  • Type safety end to end. Protocol Buffers are the contract: .proto files compile to TypeScript (yarn proto), so request/response shapes are shared between the API routes and the backend services. See Build & Deployment.
  • Thin client, server-mediated backend. The browser only ever sees REST/JSON; gRPC addressing, TLS, and credentials are confined to the server runtime.
  • Separation of server and client state. React Query owns anything fetched from the backend; Zustand owns ephemeral UI/session state. The two are never conflated. See Data Flow & State.
  • Two-layer authorization. CASL gates the UI for responsiveness; Cerbos is the authoritative server-side check. See Authentication & Authorization.
  • Consistent feature wiring. Every feature follows type → service → hook → component, which makes the codebase highly navigable once the pattern is learned.