Architecture
For developers and operators self-hosting Linktrap — how the app, database, auth, and billing connect.
Day-to-day link creation does not require reading this page. If you only use the website, start with Getting started.
Runtime stack
- Framework: Next.js 16 App Router (React 19), Tailwind 4.
- Database: Turso (libSQL) via
@libsql/client+ Drizzle ORM (db/schema.ts). - Auth: Clerk (
middleware.ts+ session on API routes). - Billing: Stripe Checkout, Customer Portal, webhooks.
- Optional: OpenAI + Vercel Blob for AI-generated preview text/images in the wizard.
Request flows
Marketing & app UI
/— Server-rendered landing./create— Client wizard (dynamic import, no SSR); requires sign-in via middleware./dashboard— Workspace links and Pro tooling.
Short link resolution
GET /s/[slug] loads the link row, checks isActive and expiresAt, classifies User-Agent via lib/crawler-uas.ts:
- Preview bot: returns HTML with
og:title,og:description, optionalog:image,Vary: User-Agent. - Human: 302 to destination with UTM query params merged in; click logged asynchronously to
link_clicks.
Middleware
Clerk protects private routes. Optional rewrite: verified custom host + path /slug → internal /s/slug (see Custom domains).
Data model (conceptual)
- users — maps Clerk user id to internal id.
- workspaces — plan (
free/pro), Stripe customer/subscription fields. - workspace_members — user ↔ workspace (owner role today).
- links — slug, destination, bot meta, workspace, UTM, expiry, active flag.
- link_clicks — per-click analytics (referrer, country).
- api_keys — hashed keys for
/api/v1(Pro). - custom_domains — hostname → workspace (verified flag).
- stripe_events_processed — webhook idempotency.
- rate_limit_buckets — per-fingerprint minute windows.
Details: Database schema.