Public-facing marketing, blog, and documentation site for the ResQ platform. Built with Next.js 16 (App Router), deployed to Cloudflare Workers via OpenNext, and optimized for Core Web Vitals and edge performance.
Content is authored in Sanity (with MDX fallbacks for content/posts and content/changelog). The site also runs a double opt-in newsletter (Resend), Turnstile-guarded forms, edge rate limiting (Upstash), and first-party analytics (PostHog + GA4).
Locales: English, Spanish, Chinese, Hindi, Arabic (RTL)
flowchart LR
Browser -->|HTTPS| CF["Cloudflare CDN"]
CF -->|Static assets| Assets["Static HTML/CSS/JS"]
CF -->|Dynamic routes| Worker["Cloudflare Worker"]
Worker -->|Locale routing| MW["next-intl Middleware"]
MW --> SSR["Server Components"]
SSR --> UI["React 19 UI"]
Worker -->|/api/*| Hono["Hono utility surface"]
Hono --> Health["/api/health"]
Hono --> Revalidate["/api/revalidate/research"]
Hono --> Newsletter["/api/newsletter/confirm · unsubscribe"]
Hono --> Webhook["/api/webhooks/resend"]
Worker -->|/api/v1/*| Elysia["Elysia v1 API"]
Elysia --> Status["/api/v1/status · /views"]
Elysia --> NL["/api/v1/newsletter"]
Worker -->|/api/og · /api/md| Edge["OG images · markdown-for-agents"]
flowchart TD
Push["git push main"] --> CI
subgraph CI["CI (parallel)"]
Build["Build"]
Test["bun test"]
Lint["biome lint"]
end
CI -->|all pass| Deploy
subgraph Deploy["Deploy"]
ON["opennextjs-cloudflare build"] --> Wrangler["wrangler deploy"]
end
Wrangler --> Live["resq.software"]
graph TD
subgraph App["src/app/"]
Pages["[locale]/(marketing)/*"]
API["api/[[...route]]"]
end
subgraph Features["src/features/"]
Sections["marketing/sections/"]
Components["marketing/components/"]
end
subgraph Core["Core"]
Hooks["src/hooks/"]
Lib["src/lib/"]
Utils["src/utils/"]
Config["src/config/"]
Actions["src/actions/"]
I18n["src/i18n/"]
end
subgraph Content["content/"]
Posts["posts/*.mdx"]
Changelog["changelog/*.mdx"]
end
Pages --> Sections
Pages --> Components
Sections --> Hooks
Components --> Lib
API --> APISurf["Hono /api/* + Elysia /api/v1/*"]
Pages --> Content
| Route | Description |
|---|---|
/[locale] |
Homepage — hero, metrics, problem statement, platform pillars, use cases |
/[locale]/platform |
Platform overview |
/[locale]/protocol |
Protocol details |
/[locale]/pricing |
Pricing — civilian tiers published; defense/allied route to "Request a briefing" |
/[locale]/research |
Research teaser; consumes index.json from research.resq.software |
/[locale]/developers · /[locale]/developers/[slug] |
Developer landing and doc pages |
/[locale]/contact |
Contact form (name, email, org, message) — Turnstile-guarded |
/[locale]/blog · /[locale]/blog/[slug] |
Blog listing and individual posts (Sanity + MDX) |
/[locale]/blog/featured · /[locale]/blog/category/[category] · /[locale]/blog/authors/[id] |
Blog facets |
/[locale]/blog/archive · /[locale]/blog/archive/[year] · /[locale]/blog/archive/[year]/[month] |
Blog date archives |
/[locale]/changelog · /[locale]/changelog/[slug] |
Product changelog and entries |
/[locale]/changelog/archive/* |
Changelog date archives |
/[locale]/legal/* |
Privacy, terms, cookies, data processing, acceptable use, security |
/studio |
Embedded Sanity Studio (content editing) |
/api/health |
Health check (Hono): health, health/detailed, health/ready, health/live |
/api/revalidate/research |
Bearer-auth publish hook called by research.resq.software after deploy |
/api/newsletter/confirm · /api/newsletter/unsubscribe |
Double opt-in confirm and RFC 8058 one-click unsubscribe |
/api/webhooks/resend |
Svix-signed Resend webhook → suppression on bounce/complaint |
/api/og |
Dynamic OG image generation (@vercel/og) |
/api/md/* |
Markdown-for-Agents fallback (serves text/markdown of pages) |
/api/v1/* |
Elysia business surface — status, views, newsletter — see src/app/api/v1/[[...route]]/ |
git clone https://github.com/resq-software/landing.git
cd landing
bun install
bun dev # http://localhost:3000| Command | Description |
|---|---|
bun dev |
Dev server (Turbopack) |
bun build |
Production build |
bun build:cf |
Build the Cloudflare Worker bundle (opennextjs-cloudflare) |
bun preview |
Build + run the worker locally via wrangler dev |
bun test |
Unit tests (bun:test) |
bun test:coverage |
Unit tests with coverage |
bun cy:open |
Cypress E2E (interactive) |
bun cy:run |
Cypress E2E (headless) |
bun test:e2e:critical |
Critical-path E2E specs (pre-push gate) |
bun lint |
Biome lint check |
bun format |
Biome auto-format |
bun check |
Biome lint + format |
bun knip |
Find unused files, deps, and exports |
bun lhci |
Lighthouse CI |
bun size |
Bundle size budget check (size-limit) |
bun audit |
Dependency vulnerability audit |
10 test files, 91 tests covering utilities, hooks, email tokens, and API handlers.
bun test # run all
bun test:coverage # with coverage report| Test file | Coverage |
|---|---|
src/lib/utils.test.ts |
cn() class merging |
src/lib/helpers.test.ts |
URL, scroll, hash, stringify utilities |
src/lib/logger.test.ts |
Logger class with log levels |
src/lib/seo/meta.test.ts |
Metadata constructors |
src/lib/seo/speculation-schema.test.ts |
Schema.org graph builders |
src/lib/email/confirm-token.test.ts |
HMAC confirm/unsubscribe token signing |
src/lib/repo-hygiene.test.ts |
Repo hygiene / license-header checks |
src/hooks/use-in-view.test.ts |
IntersectionObserver hook |
src/app/api/v1/**/health/handlers.test.ts |
Health check handlers |
src/app/api/v1/**/status/handlers.test.ts |
Better Stack status with fetch mocking |
36 spec files covering homepage and navigation, blog (listing, search, authors, editorial, archives, post features), changelog, contact and newsletter forms, API health/v1/status, locale switching, legal and content pages, SEO/OG/sitemap, security headers, accessibility, and mobile nav. The homepage, navigation, contact-form, sitemap-robots, newsletter-inline, and security-headers specs run as the pre-push critical-path gate (bun test:e2e:critical).
bun cy:open # interactive mode
bun cy:run # headless CI modegraph LR
subgraph Cloudflare
Worker["Worker<br/>(Next.js SSR)"]
Assets["Static Assets<br/>(HTML, CSS, JS, fonts)"]
DNS["DNS<br/>resq.software"]
end
subgraph External
Sentry["Sentry<br/>(error tracking)"]
BetterStack["Better Stack<br/>(uptime monitoring)"]
end
DNS --> Worker
Worker --> Assets
Worker --> Sentry
Worker --> BetterStack
- Push to
maintriggers CI (build + test + lint) - CI passes triggers the deploy workflow
- OpenNext builds the Next.js app into a Cloudflare Worker bundle
- Wrangler deploys the worker + static assets to Cloudflare
- Static pages (blog, legal, homepage) are prerendered at build time and served from the assets cache
- Dynamic routes (API) are server-rendered by the Worker
The site uses @opennextjs/cloudflare with static-assets caching — no KV, R2, or D1 bindings required.
| Setting | Value | Why |
|---|---|---|
incrementalCache |
static-assets |
Serves prerendered pages from static assets |
queue |
direct |
No ISR revalidation needed |
enableCacheInterception |
false |
Reduces cold start overhead |
All variables are validated in src/env.ts via @t3-oss/env-nextjs + Zod. Every key is optional in development; several are required in production (noted below) and the features that depend on them degrade gracefully (log-only / disabled) when unset. Secrets live in Cloudflare Workers secrets, not wrangler.jsonc.
Core / observability
| Variable | Prod | Description |
|---|---|---|
NEXT_PUBLIC_APP_URL |
Required | Canonical site URL |
NEXT_PUBLIC_SENTRY_DSN |
No | Sentry DSN for client-side error tracking |
SENTRY_AUTH_TOKEN / SENTRY_ORG / SENTRY_PROJECT / SENTRY_URL |
No | Sentry source-map upload + project config |
BETTERSTACK_API_KEY |
No | Better Stack API key for uptime status |
Analytics
| Variable | Prod | Description |
|---|---|---|
NEXT_PUBLIC_POSTHOG_KEY |
No | PostHog project key (analytics disabled when unset) |
NEXT_PUBLIC_POSTHOG_HOST |
No | First-party reverse-proxy path (defaults to /ingest) |
NEXT_PUBLIC_GA4_ID |
No | GA4 Measurement ID (G-XXXXXXX) |
Forms / bot mitigation / rate limiting
| Variable | Prod | Description |
|---|---|---|
NEXT_PUBLIC_TURNSTILE_SITE_KEY / TURNSTILE_SECRET_KEY |
Recommended | Cloudflare Turnstile sitekey + secret (widget omitted when unset) |
UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN |
Recommended | Upstash Redis for cross-isolate rate limiting (falls back to per-isolate memory) |
RATE_LIMIT_KEY_PEPPER |
Required* | SHA-256 pepper for rate-limit keys (*required when Upstash is configured) |
CSRF_SECRET |
No | CSRF token secret for state-changing forms |
Newsletter (Resend, double opt-in)
| Variable | Prod | Description |
|---|---|---|
RESEND_API_KEY |
Required | Resend API key (Sending + Contacts); newsletter is log-only when unset |
RESEND_AUDIENCE_ID |
Required | Resend Audience/Segment id used as the subscriber list |
RESEND_FROM |
Required | Verified sender identity (e.g. ResQ <updates@send.resq.software>) |
RESEND_WEBHOOK_SECRET |
Required | Svix signing secret for /api/webhooks/resend |
NEWSLETTER_CONFIRM_SECRET |
Required | HMAC pepper for confirm/unsubscribe tokens (openssl rand -hex 32) |
NEWSLETTER_POSTAL |
No | Physical postal address for the commercial-broadcast email footer (CAN-SPAM) |
Content (Sanity) & research feed
| Variable | Prod | Description |
|---|---|---|
NEXT_PUBLIC_SANITY_PROJECT_ID / NEXT_PUBLIC_SANITY_DATASET |
Required | Sanity project + dataset for blog/changelog content |
SANITY_API_TOKEN |
No | Sanity read/write token (Studio, server fetches) |
SANITY_WEBHOOK_SECRET |
No | Verifies Sanity publish webhook → /api/v1/revalidate |
RESEARCH_FEED_URL |
Required | Absolute URL to the research subdomain feed (.../index.json) |
RESEARCH_REVALIDATE_TOKEN |
Required | 32-byte bearer token shared with the research publish hook |
ADMIN_API_TOKEN |
No | Bearer token for admin-only API operations |
| Secret | Description |
|---|---|
CLOUDFLARE_API_TOKEN |
Cloudflare API token with Workers Scripts Edit permission |
CLOUDFLARE_ACCOUNT_ID |
Cloudflare account ID |
Blog and changelog content is authored in Sanity (an embedded Studio is served at /studio, and src/lib/sanity/ holds the client, queries, and adapters). Publishing fires a webhook to /api/v1/revalidate. MDX files under content/ remain as a fallback / seed source.
MDX files in content/posts/ with frontmatter:
---
title: "Post Title"
date: "2026-01-15"
excerpt: "Short description"
tag: "Engineering"
tags: ["mesh", "protocols"]
author: "mike"
readTime: "8 min read"
---MDX files in content/changelog/ with dated entries.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, React 19) |
| Styling | Tailwind CSS v4 |
| Language | TypeScript 6 (strict) |
| Runtime | Bun (tooling/tests); workerd with nodejs_compat (production) |
| API | Hono (/api/* utility surface) + Elysia (/api/v1/* business surface) |
| i18n | next-intl (5 locales, RTL support) |
| CMS | Sanity (blog + changelog), MDX fallback |
| Forms | TanStack Form + next-safe-action + Zod |
| Bot mitigation | Cloudflare Turnstile |
| Rate limiting | Upstash Redis (per-isolate memory fallback) |
| Email / Newsletter | Resend (double opt-in, Svix webhooks) |
| UI Components | Radix UI + class-variance-authority |
| Linting | Biome |
| Unit Testing | bun:test |
| E2E Testing | Cypress |
| Hosting | Cloudflare Workers (via @opennextjs/cloudflare) |
| Error Tracking | Sentry |
| Analytics | PostHog + GA4 |
| Uptime | Better Stack |
- Fork and create a branch (
feat/,fix/,docs/) - Run
bun checkbefore committing - Follow Conventional Commits
- Submit a PR against
main— CI must pass