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: atoms → molecules → organisms → pages. |
| 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:
.protofiles 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.