2026-07-15
Turning Comments On: The Last Mile Is Configuration
A week ago I built a comment system for this blog: a client component, an API route, a Neon Postgres database, and Cloudflare Turnstile to keep bots out. The code was merged, deployed, and working. It was also completely unusable.
Under every post, visitors saw this instead of a form:
Comments aren't configured yet — add a Turnstile site key and database connection to enable them.
That was by design. The feature was built to degrade gracefully when three environment variables are missing:
DATABASE_URL, the Postgres connection stringNEXT_PUBLIC_TURNSTILE_SITE_KEY, the public bot-check keyTURNSTILE_SECRET_KEY, the private key the server uses to verify submissions
None of them existed. The feature shipped dark. Turning it on was its own small unit of work, and it involved no code at all.
What enabling actually took
A real database. I created a Neon Postgres database through Vercel's Marketplace integration and connected it to the project. Vercel injects DATABASE_URL (and a pile of related variables) automatically. The comments table itself needed no setup step, because the API route creates it on first request with CREATE TABLE IF NOT EXISTS.
One gotcha here: connecting the database in the dashboard only attached it to the Production and Preview environments. Local development pulls from the Development environment, which had nothing. The fix was reconnecting the database with all three environments selected. Easy once you see it, invisible until you do.
Real Turnstile keys. In the Cloudflare dashboard I added a Turnstile widget with two hostnames: the production domain and localhost, so the same real widget works in local testing. Cloudflare hands you a site key and a secret key, and those became the two remaining environment variables in Vercel, set for all three environments.
Pulling it all down locally. One command syncs local dev with what Vercel has:
vercel env pull .env.local
Verification before calling it done. The checks, in order:
GET /api/comments?slug=welcomereturned{"comments":[]}. That one request proved the database connection worked and created the table.- The post page rendered the actual comment form instead of the "not configured" notice, proving the site key was being read.
- A
POSTwith a fake Turnstile token came back403 CAPTCHA verification failed. That's the server-side verification doing its job against Cloudflare with the real secret key. - A real comment, posted in the browser, passing the real challenge.
One deploy note. Any variable with a NEXT_PUBLIC_ prefix gets baked into the JavaScript bundle at build time. Setting it in Vercel does nothing for the deployment that's already live. Production needs one fresh deploy after the keys exist, and then comments are on.
The PM angle
For a week, this feature was "done" by every definition a developer usually cares about. Code complete, reviewed, merged, deployed, write-up published. And for that same week, its actual value to a visitor was zero, because done and live are different milestones.
The gap between them was not glamorous work: provisioning a database, generating keys, setting variables in three environments, verifying each link in the chain. Nobody writes user stories for this, and that's exactly why it slips. On client projects I learned to treat enablement as its own line item with its own checklist, separate from the build. Environments are a good example of why. The database connected fine, but only to two of the three environments, and nothing failed loudly. It just quietly wouldn't have worked on the next developer machine.
If your definition of done stops at "merged," you will keep shipping features that technically exist and practically don't. The last mile is configuration, and it deserves a place on the plan.
Comments
Loading comments...