We automated Avo site deployments with a GitHub Actions CI/CD pipeline that catches TypeScript errors in 35 seconds and deploys to Vercel production in 90 seconds.
90 sec
Frontend deploy time (was 40-60 min)
85%
Build cache hit rate
34 sec
Build time with cache (was 91 sec)
~35%
Actions minutes saved (cancel-in-progress)
CHAPTER 01
Deploying Avo required coordinating two completely separate delivery surfaces with no shared toolchain. The frontend is a Next.js 15 application deployed to Vercel. The backend is a set of Rust binaries compiled against a 111-crate Cargo workspace and installed as systemd units on a Hetzner server. Changes to either surface affect the other: a new API route in Next.js may require a corresponding change to the Argus data model, and a Rust binary update may change the JSON schema that the Next.js API routes deserialize.
The initial workflow was entirely manual. With 111 Cargo crates in the workspace and 18 Next.js sites across the Avo Engine, a full manual cycle took 40 to 60 minutes and required active attention throughout. Three specific failure modes drove the push to automate: TypeScript errors propagated to Vercel as failed deployments with no warning in the development cycle; ESLint violations accumulated to 203 errors because there was no mandatory lint gate; deploying Rust service changes required SSHing into the server and manually running systemctl restart, which was error-prone.
CHAPTER 02
The GitHub Actions workflow for avo-site separates CI from deployment. nextjs-ci.yml runs on every push to any branch and every pull request, running lint, TypeScript check, and build in sequence. vercel-deploy.yml triggers only on merge to main (production) or on pull requests (preview). By keeping CI and deployment in separate workflow files, CI can be extended with new checks without touching the deployment logic.
The pnpm 9 lockfile is used as the cache key for both the dependency install and the .next/cache build cache. The cache key hashes pnpm-lock.yaml combined with all TypeScript and JavaScript source files. This means a lockfile change busts the dependency cache and the build cache together. Source file changes bust only the build cache. In practice, the build cache hit reduces pnpm build time from approximately 90 seconds to approximately 35 seconds.
ARCHITECTURE OVERVIEW
INGRESS
GitHub Actions
Vercel CLI 34 CLUSTER
pod-1
pod-2
pod-3
pod-4
pod-5
pod-6
STORAGE
pnpm 9
OBSERVABILITY
Next.js 15
CHAPTER 03
Vercel's native Git integration and the CLI-based workflow cannot both be active simultaneously. The CLI-based workflow was chosen for explicitness: every deployment is a visible GitHub Actions job with logs, timestamps, and a clear audit trail. The --prebuilt flag on vercel deploy skips a second build on Vercel's infrastructure. The artifact from vercel build is uploaded directly, reducing total deployment time from approximately 3 minutes to approximately 90 seconds and making the deployed artifact deterministic.
The Vercel configuration in vercel.json specifies per-route function settings. Routes with compute-heavy operations get extended timeouts: the audit generation route gets maxDuration=60 and memory=2048. The intelligence commentary route gets maxDuration=60 for the Anthropic API call. Standard API routes default to maxDuration=30.
One early production incident: the Vercel deployment used Preview environment variables by default when --prod was not specified. Two deployments went to Preview instead of Production, serving the site without STRIPE_WEBHOOK_SECRET and CLERK_WEBHOOK_SECRET. The fix was adding an explicit if: github.ref == 'refs/heads/main' condition before the production deploy command.
TECH STACK
CHAPTER 04
Before CI automation: TypeScript build errors reached production twice, caught only after user-reported broken pages. ESLint error count reached 203 before a forced audit. Manual deployment cycle time: 40 to 60 minutes. After CI automation: TypeScript errors are caught within 35 to 90 seconds of push. ESLint violations fail CI and block merge to main. Frontend deployment cycle time for a pure Next.js change: 90 seconds from merge to main to Vercel production deployment active. The concurrency group setting cancel-in-progress: true reduced GitHub Actions minute usage by approximately 35% during high-velocity development.
90 sec
Frontend deploy time (was 40-60 min)
85%
Build cache hit rate
34 sec
Build time with cache (was 91 sec)
~35%
Actions minutes saved (cancel-in-progress)
CHAPTER 05
DECISION · 01
The separation of CI and deployment into distinct workflow files was the single most valuable structural decision. During the launch sprint, CI ran on feature branches continuously while deployment ran only on main, preventing dozens of failed or partial deployments per day from polluting the Vercel deployment history.
DECISION · 02
The decision to use pnpm over npm was motivated by lockfile determinism and workspace support for the multi-site Avo Engine structure. pnpm's content-addressable store reduced disk usage on the CI runner by approximately 40% for repeated installs.
DECISION · 03
The Rust service deployment should be automated. The current model requires a human to SSH to the server and restart services after a binary update, introducing a window of inconsistency. A simple GitHub Actions job that SSHes to the server, pulls the latest commit, runs cargo build, and restarts affected services would close this gap.
START A PROJECT
We build fast. Most projects ship in under two weeks. Start with a free 30-minute discovery call.
Start a ProjectWe discovered 209,033 regime keys with no TTL and fixed them in a single SCAN pass, then cut the regime endpoint latency 13x by eliminating per-request key scans.
209,033 Keys without TTL (found)
Read case study →
InfrastructureWe built a 63-line Node.js proxy that gives Vercel serverless functions read-only access to a private ClickHouse instance with zero database exposure.
12ms Proxy overhead (end-to-end)
Read case study →
InfrastructureWe added a lock-free AtomicUsize round-robin proxy pool to argus-common, giving all 23 downloader binaries IP rotation without duplication or mutex contention.
180/min Download throughput (proxy)
Read case study →