GaijinToSensei: an app in production
An app to learn Japanese, in production with real users.
- Next.js
- PostgreSQL (Neon)
- Prisma
- Upstash Redis
- Resend
- Cloudflare Workers
- Playwright
- Bash
Context
I am alone on the project. Claude Code agents write most of the code. My job is the frame around it: how code reaches production, how we know it works, and how we fix it.
The app runs on Cloudflare Workers (Next.js adapted by OpenNext), with a serverless PostgreSQL database on Neon (through Prisma), Redis on Upstash for rate limiting and Resend for emails.
Problem
Many changes a day, several agents in parallel, and a daily deployment quota on the original host.
Every release has to stay safe: a broken version must not stay online, and two deployments must not collide.
What I did
An agent pipeline that ships
- Several agents work in parallel, each on its own branch. They write code, test it (automated QA with Playwright), merge and deploy.
- Locks prevent conflicts between agents.
- Merges pile up on the main branch and ship in a single deployment, to stay within the platform quota.
Checked releases
- A script builds the exact export of a commit and uploads it as a new Worker version with no traffic, then checks the public pages on that version's address: status 200, title, canonical URL, full sitemap, robots.
- The version only gets traffic after these checks. The main branch only moves by fast-forward, never by force.
- After activation the site is checked again. If that fails, the script rolls back to the previous version on its own and sends an alert.
- A file lock stops two releases from running at the same time.
- Every 10 minutes, a scheduled job checks that production is not behind the main branch. If it is, it starts a safe release. Otherwise it only sends an alert.
Migration from Vercel to Cloudflare Workers
- On 5 November 2026, the free Vercel account went over its usage quota and its projects were shut down. I moved the app to Cloudflare Workers the same day, for $5 a month.
- In front of the app, a second Worker caches public pages for signed-out visitors, never for a session. Server response time (TTFB) went from 0.26-0.30 s to 0.07-0.09 s, home page LCP 1.4 s.
Monitoring and protections
- A health route checks the database, Redis, email sending and storage, with a timeout per service and a short cache so it does not overload them.
- Server errors are stored in the database and shown on a Health page in the admin tool.
- Sliding-window rate limits on login, chat, reports and support.
- Since the 29 October 2026 incident, database migrations are additive only. The script rejects any DROP, RENAME, TRUNCATE or ALTER COLUMN, and never applies a migration itself.
Result
- The app is in production, with real users.
- New features ship in batches, which fits the quota. A deployment that fails the checks does not stay online.
What I learned
- Guardrails matter more than speed. A fast agent without guardrails ends up breaking production.
- Any deployment step must be reversible without me.
- A simple health route, called often, tells more than a dashboard nobody looks at.