Architecture Overview
gh-org-tool is a single deployable unit — a Next.js application with no separate backend service. All business logic, API surface, and frontend rendering live in one codebase. The entire system runs on Vercel.
System layers
Technology stack
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js App Router | 16.2.6 |
| UI runtime | React | 19.2.3 |
| Package manager | pnpm | 10.33.1 |
| Styling | Tailwind CSS | 4 |
| Components | shadcn/ui (new-york style) | — |
| Server state | TanStack Query | 5.x |
| Client state | Zustand | 5 stores |
| Database | Supabase (Postgres) | — |
| Cache | Upstash Redis (optional) | — |
| Auth | GitHub App OAuth + App installations | — |
| GitHub SDK | Octokit | — |
| Deployment | Vercel | — |
| Tests | Vitest + v8 coverage | — |
| Docs | Nextra v4 | — |
| Docs search | Pagefind | — |
Directory structure
app/ Next.js App Router
[organization]/ Org-scoped pages
organization/ team-management, role-management, mass-invite, overview (stub)
leaderboards/ Contributor, /r, /r/[repository], /team, /scoring
analytics/ Overview, /contributor, /repository, /heatmap, /timeline
account/ User account page
api/
auth/ OAuth: start, callback, session, logout
install/ App install: start, callback, complete, status, webhook
organizations/ Lists, detail, mass-invite, bulk-role, teams/bulk-add
github/graphql/ Allowlisted GraphQL proxy
[organization]/ 18 org-scoped routes (leaderboard, analytics, repository, contributor, org summary, installation access, teams, contributors)
debug/ presets, recompute, computed-scores, preset-by-id
docs/ Nextra catch-all MDX renderer
components/
auth/ ProtectedRoute, AuthErrorBanner
layout/ Header, Footer, Sidebar, OrganizationLayoutClient
organization/ MassInvite, TeamManagement, BulkRoleManagement, InstallationAccessBanner
leaderboards/ ContributorLeaderboard, RepositoryLeaderboard, TeamLeaderboard, ScoringRules, charts
analytics/ OrganizationAnalytics, ContributorProfileView, ContributionHeatmap, ActivityTimeline
shared/charts/ Recharts wrappers, MiniSparkline (SVG)
providers/ QueryProvider, TanStackDevtools
ui/ shadcn/ui re-exports
hooks/
queries/ 27 useQuery hooks
mutations/ 8 useMutation hooks
query-keys.ts Central query key factory
lib/
auth/ OAuth, session, crypto, token-refresh, installation-service, webhook
github/ Octokit factories, fetch-graphql (31KB — ingest core), graphqlProxy, operations/registry, queries/
leaderboard/ Pipeline, ingest, score, serve, cache layers, scope resolution
scoring/ Engine, aggregate, normalize, rules, diminishing
supabase/ Typed repositories (leaderboard-db, signals, ingest-state, repo-cache, team-cache, install, org, user, server)
cache/ redis.ts (Upstash REST wrapper)
api/ server.ts (auth guards), client.ts (axios)
env/ index.ts (4 env groups, lazy proxies)
constants/ time.ts (all time constants)
schemas/ Zod API request schemas
stores/ 5 Zustand stores
content/ Nextra MDX docs served at /docs
types/ TypeScript interfaces (db, api, scoring, auth, installations, github)
supabase/ migrations/ (001-011), config.tomlDesign decisions
Single deployable unit
There is no separate API server, worker process, or microservice. Every endpoint is a Next.js route handler. Background jobs (ingest, score computation) run as after() callbacks from route handlers. This simplifies deployment to zero-config Vercel push, at the cost of being tied to Vercel’s function execution model.
Two GitHub auth modes
The application operates with two different GitHub credentials depending on the operation:
- User OAuth token — obtained during sign-in; used for the GraphQL proxy and listing orgs. Scoped to what the signed-in user can see.
- Installation token — obtained per-GitHub-App-installation via
getInstallationOctokit(). Used for leaderboard ingest, mass-invite, bulk-role, and team operations. Accesses all repos the App is installed on, regardless of who is signed in.
This is intentional: ingestion should not be limited to what any individual user can see.
Repository pattern for data access
All database access goes through typed repositories in lib/supabase/. Route handlers never construct raw Supabase queries directly. This keeps query logic testable and co-located with the schema types in types/db/.
TanStack Query configuration
The frontend uses TanStack Query with staleTime: 30000 (30 seconds) and refetchOnWindowFocus: false. This means data is considered fresh for 30 seconds and will not re-fetch when the user switches back to the tab. Explicit refetch is triggered by user actions (e.g. manual sync button).
Leaderboard queries override staleTime to 24 hours — far longer than the default, because data is precomputed and served from cache layers.
Five Zustand stores
| Store | File | Responsibility |
|---|---|---|
auth-store | stores/auth-store.ts | Session object, loading state, errors |
workspace-store | stores/workspace-store.ts | Active org slug, analytics date range selectors |
leaderboard-store | stores/leaderboard-store.ts | Leaderboard view state (sort, filters) |
url-params-store | stores/url-params-store.ts | Persists URL params (timeRange, presetId) across org nav |
analytics-store | stores/analytics-store.ts | Selected contributor, repo, date range for analytics |
No store uses localStorage persistence. All state is ephemeral in-memory — lost on page reload. Session state is recovered by re-fetching GET /api/auth/session.
Four environment groups
Environment variables are split into four lazy-initialized proxies in lib/env/index.ts: appEnv, authEnv, supabaseEnv, cacheEnv. Validation is skipped in production (NODE_ENV === 'production') — missing vars fail at runtime, not build time.
No infrastructure-as-code
There is no Docker, Kubernetes, docker-compose, Terraform, or vercel.json. Deployment is a direct git push to the Vercel-connected repository. Environment variables are managed through the Vercel dashboard.