Skip to content

Trystpilot - System Architecture

Trystpilot — System Architecture

Version: 0.5.0 · Last updated: 2026-02-28 See also: design/architecture/ for Mermaid diagrams


Executive Summary

Trystpilot is a Next.js 15 / App Router SaaS platform deployed on Vercel with a PostgreSQL database. All compute runs serverless (Vercel Functions + Edge Middleware). The architecture is intentionally simple for MVP: a monorepo, no separate microservices, no GraphQL layer. Complexity is deferred until traffic forces it.


1. Domain & Subdomain Strategy

DomainPurpose
trystpilot.xyzPrimary — production
*.vercel.appPR preview deployments
localhost:3000Local development

Future subdomains (not yet provisioned):

SubdomainPurpose
api.trystpilot.xyzDedicated API domain (Phase 4)
admin.trystpilot.xyzAdmin panel (Phase 2)
blog.trystpilot.xyzBlog / SEO content (Phase 3)

2. Frontend Architecture

Stack: Next.js 15 (App Router), React 19, Tailwind CSS v4

Key Decisions

  • App Router only — no Pages Router. All routes in app/.
  • Server Components by default — client components ('use client') only where interactivity requires it (search dropdown, submit form, locale switcher).
  • No client-side routing library — Next.js Link/Router is sufficient.
  • i18n: Custom language pack provider; no next-intl dependency. Five packs: en, de, es, fr, pt. Selected by Accept-Language header + timezone heuristic in server component.
  • Analytics: Vercel Analytics (@vercel/analytics/next) in root layout. Cloudflare beacon injected via next/script afterInteractive strategy.

Component Hierarchy

app/layout.tsx ← analytics, locale, fonts
├── app/page.tsx ← Home (Hero, Search, Feeds)
├── app/profile/[alias]/ ← SEO profile (SSG candidate)
├── app/submit/ ← Client form
├── app/moderation/ ← Admin-gated server component
└── app/legal/* ← Static MDX or TSX pages

Performance Targets

MetricTarget
LCP< 2.5 s
CLS< 0.1
INP< 200 ms
Profile page TTFB< 200 ms (ISR cache hit)

3. Backend Architecture

Stack: Next.js App Router API Routes, Node.js 20, pg (PostgreSQL client)

All server logic lives in app/api/. Service libraries in app/lib/ are shared across API routes, middleware, and server components.

Service Library Responsibilities

LibraryFileResponsibility
Text filterlib/moderation/textFilter.tsRegex-based PII, threat, explicit content detection
Flagginglib/moderation/flagging.tsComposite risk score (0–1) per review
Reputation scorerlib/reputation/scorer.tsWeighted rating average with recency decay
Fingerprinterlib/anonymity/fingerprint.tsDeterministic hash from request signals
DB clientlib/db/client.tspg.Pool singleton, max 10 connections

API Design

All APIs follow REST conventions. No GraphQL. No tRPC.

  • GET endpoints are public (no auth).
  • POST /api/reviews requires valid hCaptcha token (planned).
  • PATCH /api/reviews/[id]/moderate requires ADMIN_SECRET header.
  • POST /api/profiles requires ADMIN_SECRET header.

Responses use standard HTTP status codes. Errors return { error: string }.


4. Database Architecture

Engine: PostgreSQL (Railway or Supabase) Client: pg with connection pool (max 10, idle timeout 30 s) Schema: lib/db/schema.sql

Connection Strategy

Single pg.Pool singleton instantiated once per serverless cold-start. Pool is shared across all API routes in the same function instance. Max connections set conservatively (10) to avoid exhausting Railway’s free-tier limits.

Warning: Serverless functions can spawn many parallel instances. If traffic is high, migrate to PgBouncer or use Supabase’s connection pooler before scaling.

Known Schema Issues

IssueFileFix
respect_rating not in scorerlib/reputation/scorer.ts:L18Add 0% now, plan weight redistribution
No migration toolinglib/db/schema.sqlAdd Flyway or db-migrate — Phase 1

5. Authentication Model

MVP uses a single ADMIN_SECRET environment variable checked in server components and API routes. There are no user accounts, sessions, or JWTs in the current implementation.

Known critical issue: The moderation dashboard fails open if ADMIN_SECRET is not set. Fix is M1-T2 in the ROADMAP.

Phase 2 target: NextAuth.js with credentials provider for a proper admin session model, with bcrypt-hashed admin credentials stored in the database.


6. Rate Limiting

Current: In-memory Map in middleware.tsnon-functional on serverless (resets per cold-start, no cross-instance sharing).

Target: Upstash Redis with @upstash/ratelimit. Sliding window, 5 requests per 60 s per IP on POST /submit. Environment vars: UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN.


7. Caching Strategy

SurfaceStrategyStatus
Profile pagesISR (revalidate: 300 s)Planned — Phase 2
Static legal / blogStatic generation
API responsesNo cache (dynamic)Current
Rate limit countersUpstash Redis (TTL-based)Planned — Phase 1

8. Security Hardening

ControlStatus
Security headers (CSP, HSTS, X-Frame-Options)✅ middleware.ts
SQL injection prevention (parameterised queries)
XSS prevention (React escaping)
CSRF protection⚠️ Not explicit — Add SameSite=Strict cookies + origin check
Admin route protection⚠️ Fails open — M1-T2
Rate limiting❌ M1-T3
CAPTCHA❌ M1-T4
Dependency scanning❌ Phase 1 CI
Secret scanning❌ Phase 1 CI

9. Observability

LayerToolStatus
Frontend analyticsVercel Analytics
CDN analyticsCloudflare
Error trackingNone (Sentry planned)
Server logsVercel Function logs✅ (basic)
Uptime monitoringNone (Checkly planned)
Database metricsRailway dashboard✅ (basic)

10. Scalability Path

Current (MVP) Phase 2 Phase 3+
───────────────── ──────────────── ─────────────────
Vercel Functions + ISR caching + Edge Functions
pg.Pool (10 conn) + PgBouncer pooler + Read replicas
In-memory rate limit + Upstash Redis + Multi-region
Single admin + Admin session auth + RBAC Moderators
No search index + Full-text search + Elasticsearch/Typesense