Modern SaaS web stack
a SaaS web stack drawn as cards: Next.js on Vercel serving the UI, Cloudflare in front of the Node.js API, Neon and Upstash for data, Stripe, Resend and Clerk as third-party services, Sentry and PostHog for observability.
logossi12 nodes7 groups12 edges
modern-web-stack.yamlOn GitHub
# yaml-language-server: $schema=../references/diagram.schema.json
title: Modern SaaS web stack
subtitle: Next.js on Vercel serves the UI and Cloudflare fronts the API; Neon and Upstash hold the data while Clerk, Stripe and Resend handle auth, payments and email
direction: down
# Product cards keep the managed services compact.
look: card
# One port per edge, so the checkout, webhook and email edges fan out of the API without crossing.
layout: { spacing: compact, merge: false }
legend:
solid: Synchronous request
dashed: Webhook
dotted: Errors and analytics
groups:
edge: { type: zone, label: Edge }
ui: { type: zone, label: Frontend }
app: { type: zone, label: Backend }
data: { type: zone, label: Data }
auth: { type: zone, label: Identity }
saas: { type: zone, label: Third-party services }
obs: { type: zone, label: Observability }
nodes:
web: { icon: generic:browser, label: Web, note: Browser }
mobile: { icon: generic:mobile, label: Mobile app, note: iOS and Android }
cloudflare: { icon: logos:cloudflare-icon, label: Cloudflare, note: CDN and WAF for the API, in: edge }
api: { icon: logos:nodejs-icon, label: API service, note: "Node.js, verifies Clerk JWT", in: app }
nextjs: { icon: logos:nextjs-icon, label: Next.js app, note: Hosted on Vercel, in: ui }
neon: { icon: logos:neon-icon, label: Neon, note: Serverless Postgres, in: data }
upstash: { icon: logos:upstash-icon, label: Upstash, note: Redis cache, in: data }
stripe: { icon: si:stripe, label: Stripe, note: Payments and billing, in: saas }
resend: { icon: logos:resend-icon, label: Resend, note: Transactional email, in: saas }
clerk: { icon: logos:clerk-icon, label: Clerk, note: Auth and sessions, in: auth }
sentry: { icon: logos:sentry-icon, label: Sentry, note: Error tracking, in: obs }
posthog: { icon: logos:posthog-icon, label: PostHog, note: Analytics and replay, in: obs }
edges:
# app.example.com is DNS-only to Vercel (Vercel advises against stacking a proxy in front of it).
- web -> nextjs: app.example.com
# The browser and the mobile app call the API through Cloudflare; the Next.js server fetches from it directly.
- web, mobile -> cloudflare: API calls
- cloudflare -> api: api.example.com
- nextjs -> api: server fetch
# Clients sign in against Clerk; the API only verifies the token it sends.
- web, mobile -> clerk: sign in
- api -> neon: SQL
- api -> upstash: REST
- api -> stripe: checkout
- api -> resend: send email
# Written in layout order with the arrow flipped, so the webhook does not loop around.
- { from: api, to: stripe, arrow: start, line: dashed, label: webhooks via api.example.com }
- api, nextjs ..> sentry: errors
- nextjs ..> posthog: eventsStart from this one
Copies the spec to diagram.yaml, then renders every format.
$ orrery init modern-web-stack$ orrery render diagram.yaml -f all --theme both