The first template we launched at peal.dev was a disaster. Not a catastrophic, server-down disaster — more like a slow-burn embarrassment. Broken meta tags. A demo link that 404'd. A README that referenced a database we'd renamed three commits before launch. The kind of stuff that makes you want to close the laptop and go work at a bakery.
After a few of those humbling experiences, we sat down and built an actual launch playbook. Not a vibe. Not "let's eyeball it." A real checklist with real consequences if you skip steps. This is that playbook.
Phase 1: The Week Before — Template Hardening
The biggest mistake early on was treating launch day as the moment we finished building. Wrong. Launch day is when strangers start poking at your thing, and strangers are ruthless. They'll find the env variable you forgot to document, the mobile breakpoint that turns your beautiful nav into abstract art, the button that does nothing because you wired up the onClick to a console.log you never removed.
So now, one week before any launch, we freeze the feature work and switch to hardening mode. That means:
- Clone the repo fresh into a new directory and follow the README from scratch — if you get stuck, the README is wrong, not you
- Set up the demo environment with a clean database and real (test mode) Stripe/auth credentials, not the ones from dev
- Go through every page on mobile. Not in DevTools, on an actual phone. Stefan owns a 2019 iPhone SE specifically for this purpose
- Run a Lighthouse audit on the demo and fix anything below 85. Not because users check Lighthouse scores, but because the underlying issues matter
- Check every external link in the docs — GitHub, npm packages, third-party service docs
We also do what we call a "cold start test" — nuke the node_modules, clear the .next cache, and time how long it takes to get from zero to running dev server. If it takes more than 3 minutes with a fresh setup, something's wrong with the dependency tree.
# Cold start test script — we literally run this every pre-launch
rm -rf node_modules .next
time pnpm install
time pnpm build
# Expected output:
# pnpm install: under 45s
# pnpm build: under 90s
# If either is way over, investigate before launchPhase 2: Environment Variables Are Always the Problem
No joke, 60% of the support messages we've gotten in our first six months were some variation of "it doesn't work" caused by misconfigured environment variables. The user copies the .env.example, fills in two of the eight values, and then wonders why auth is broken.
So now every template ships with a startup validation script. It runs before the dev server starts and screams if required variables are missing or obviously wrong:
// lib/env-check.ts — runs in next.config.ts at startup
const requiredEnvVars = [
{ key: 'DATABASE_URL', validate: (v: string) => v.startsWith('postgres') },
{ key: 'NEXTAUTH_SECRET', validate: (v: string) => v.length >= 32 },
{ key: 'NEXTAUTH_URL', validate: (v: string) => v.startsWith('http') },
{ key: 'STRIPE_SECRET_KEY', validate: (v: string) => v.startsWith('sk_') },
{ key: 'STRIPE_WEBHOOK_SECRET', validate: (v: string) => v.startsWith('whsec_') },
] as const
export function validateEnv() {
const errors: string[] = []
for (const { key, validate } of requiredEnvVars) {
const value = process.env[key]
if (!value) {
errors.push(`Missing: ${key}`)
} else if (!validate(value)) {
errors.push(`Invalid format: ${key} (check your .env.example)`)
}
}
if (errors.length > 0) {
console.error('\n❌ Environment variable issues found:')
errors.forEach(e => console.error(` ${e}`))
console.error('\nCheck the README setup section.\n')
process.exit(1)
}
}
// In next.config.ts:
// import { validateEnv } from './lib/env-check'
// validateEnv() // Only in non-production or handle accordinglyThis alone cut our setup-related support volume by half. People see the error, they fix the variable, it works. Revolutionary.
Phase 3: The Demo Is a Product, Not an Afterthought
This took us embarrassingly long to learn: the demo is the most important page of the launch. Most people will never read your README. They'll click the demo link, spend 90 seconds clicking around, and decide whether to buy based entirely on that. The demo is your pitch.
So we treat it like one. The demo environment gets:
- Real-looking seed data — no 'Test User 1' or 'Lorem ipsum'. Actual fake names, real-looking email addresses, plausible numbers
- A demo account with credentials visible on the login page (email: demo@example.com / password: demo1234 — yes, we know)
- A read-only Stripe test mode with actual products set up, not just placeholders
- A banner explaining it's a demo and data resets every 24 hours (we have a cron that does this)
// app/api/cron/reset-demo/route.ts
// Vercel cron job — runs daily at 3am UTC
import { db } from '@/lib/db'
import { seedDemoData } from '@/lib/seed-demo'
export async function GET(request: Request) {
const authHeader = request.headers.get('authorization')
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('Unauthorized', { status: 401 })
}
// Wipe demo tenant data only
await db.transaction(async (tx) => {
await tx.delete(orders).where(eq(orders.tenantId, DEMO_TENANT_ID))
await tx.delete(users).where(eq(users.tenantId, DEMO_TENANT_ID))
// Re-seed with fresh data
await seedDemoData(tx, DEMO_TENANT_ID)
})
return Response.json({ reset: true, at: new Date().toISOString() })
}The daily reset matters more than you'd think. Without it, within 48 hours some guy has renamed all your demo users to SpongeBob characters and changed the product images to memes. This is a universal law.
Phase 4: Launch Day Morning — The 2-Hour Window
We do launches at 9am Romania time. That's 2am Pacific, which means by the time the US wakes up and starts upvoting on Hacker News or sharing on Twitter, we've had a few hours to catch any immediate fires. We learned this after launching something at 3pm our time (6am Pacific) and being asleep when the first bug reports came in.
The morning routine, T-minus 2 hours to launch:
- Deploy the demo one final time from a clean branch — not your local, the actual tagged release
- Do one complete user journey: sign up, configure, use the main feature, hit the billing page, sign out
- Check that the demo banner is showing and the demo credentials work
- Verify the buy link goes to the right Gumroad/Lemon Squeezy page and the price is correct (yes, we've launched with wrong prices)
- Set up a shared Slack/Discord channel between both of us and keep it open all day
The buy link having the wrong price is a special kind of pain. You either honor it and eat the difference, or you email people who already bought and explain the mistake. Both options are bad. Check the price.
Phase 5: The Post-Launch Watch Window
For the first 4 hours after launch, one of us is always watching three things: the error monitoring dashboard (we use Sentry), the demo environment logs, and whatever channel people are talking about the template in. Stefan handles the technical fires, Robert handles the communication. This split works because when you're debugging a production issue, the last thing you want is to also be crafting a polite response to someone on Twitter.
We have a severity triage that we made up but actually follow:
- P0 — Demo is broken or down. Drop everything. Fix in under 30 minutes or put up a maintenance page.
- P1 — Core user journey broken (can't sign up, can't buy). Fix same day, communicate status.
- P2 — Feature broken but workaround exists. Fix within 48 hours, update docs with workaround.
- P3 — Cosmetic issue, typo, minor confusion. Goes in the queue, fixed in next patch.
The reason we wrote this down is that without it, everything feels like a P0 when you're stressed and the Sentry alerts are piling up. Having a framework forces you to slow down and actually triage instead of panic-fixing the first thing you see.
The Checklist We Actually Use
Here's the complete pre-launch checklist in a format you can actually use. We keep this as a GitHub issue template and create a new one for every launch:
## Pre-Launch Checklist — [Template Name] v[X.Y.Z]
### T-7 days
- [ ] Fresh clone + README setup completed successfully
- [ ] All env vars documented in .env.example with comments
- [ ] Env validation script runs and catches missing vars
- [ ] Mobile tested on real device (iOS + Android)
- [ ] Lighthouse score >85 on demo
- [ ] All external links in docs verified
- [ ] Cold start test: install <45s, build <90s
### T-3 days
- [ ] Demo environment deployed from release branch
- [ ] Seed data looks real (no Lorem ipsum, no Test User 1)
- [ ] Demo credentials work: [email] / [password]
- [ ] Daily reset cron is active and tested
- [ ] Demo banner showing
- [ ] Stripe test mode products configured correctly
### T-1 day
- [ ] Buy link tested end-to-end (actually click "buy")
- [ ] Price on sales page matches actual product price
- [ ] OG image renders correctly (use opengraph.xyz to check)
- [ ] Meta title and description set
- [ ] Changelog / release notes written
### Launch morning
- [ ] Final deploy from tagged release
- [ ] Full user journey walkthrough
- [ ] Sentry alerts configured and tested
- [ ] War room channel open
- [ ] Both of us awake and at computers (obvious but worth saying)
### T+4 hours
- [ ] Error rate normal (<0.1% of requests)
- [ ] No P0 or P1 issues open
- [ ] First user feedback collectedYou can adapt this for your own projects — it's not specific to templates. The bones work for any web app launch.
What We've Stopped Caring About
Launch playbooks can get bloated fast. You start adding steps every time something goes wrong, and eventually you're spending more time on the checklist than the product. So here's what we deliberately cut and why.
We don't do a separate staging environment anymore. We deploy to Vercel preview URLs during development and that's enough. The full production deploy is the launch. Maintaining a separate staging environment for a two-person team was burning time we don't have.
We don't do load testing. Our templates are Next.js on Vercel — autoscaling handles it. If a template got enough traffic to actually cause load issues, that would be a wonderful problem to have and we'd deal with it then.
We don't A/B test launch copy. We write what we'd want to read, ship it, and update based on feedback. Two-person team. Choose your battles.
A launch playbook should reduce anxiety, not create more. If your checklist takes longer than a workday to complete, you've added too much. Cut it until it hurts a little.
Every template we ship at peal.dev goes through this exact process — it's baked into how we build, not bolted on at the end. If you ever buy one and notice the README is unusually clear or the demo data looks suspiciously realistic, now you know why.
The most important thing this playbook does isn't catch bugs — we could catch most of those without it. What it does is make launch day boring. And boring launches are the best launches. Nobody wants to be the one refreshing Sentry at midnight because you forgot to handle a missing environment variable that 40% of your users are hitting.
Build the checklist. Run the checklist. Then go touch grass, because the launch is going to be fine.
