A Website Launch Checklist Where Every Item Is a Command
Five commands, three greps, and the defects they caught on a site that already looked finished — including a licence page selling a tier that no longer existed.
A checklist you read is a checklist you skip. The useful version is executable: five commands that fail the pipeline, plus a short list of greps that fail a human. Every item below is on ours because it once caught something real — a nav menu whose five links all pointed at one URL, two products sharing a byte-identical cover image, a licence page still selling a tier that had been renamed months earlier.
This is the pre-deploy pass for a site that already exists and already looks right. It assumes the design is chosen and the content is written. What it checks is whether the thing you are about to publish is true: that its links resolve, its claims match its code, its images are the images you think they are, and its secrets are not in the bundle.
The five commands
Everything here runs on every push and every pull request, on Node 22, with a fifteen-minute ceiling:
| Command | Fails on | Why it earns a slot |
|---|---|---|
npm ci | A lockfile that does not match package.json | The one install that reproduces what CI and production get |
npm run lint | ESLint errors, including framework rules | Catches raw <a> to internal routes, missing keys, unsafe patterns |
npm run format:check | Unformatted files | Keeps diffs about content, not whitespace |
npm test | A failed invariant | Where the content checks live — see below |
npm run build | A type error or a page that cannot render | The only check that renders every page |
The build is the heaviest and the least negotiable. It type-checks every component and prerenders every route, which means a broken data reference in one of 111 product pages fails the pipeline instead of returning a 500 to whoever clicks it first. Our latest run prerendered 256 pages and left 8 routes dynamic — the API handlers, the auth callback and the account pages. If your build output shows routes you expected to be static rendering per request, fix that before launch; the four things that force a route dynamic are all small and all cheap to undo at this stage.
The tests that check content, not components
Most of what breaks a launch is not logic. It is a reference to something that no longer exists. So the test suite spends most of its assertions on the content graph:
- Every catalog product resolves its own files. If an entry names a screenshot, the file must exist under
public/. A renamed directory becomes a red test rather than a broken card. - Every blog post exists in three places. Metadata registry, MDX file, and an explicit loader entry. Miss one and the suite fails; a wildcard import would have turned that into a runtime 404.
- Every in-post link target exists. Posts declare the products and category hubs they point at, and those declarations are verified against the catalog. A retired product cannot leave a dead link in a published article.
- Every available product appears on at least one category hub. This one is the reason it exists: hand-listed hubs silently stopped covering new products, and 24 of them ended up on no hub at all.
The pattern generalises past this codebase. Anything you would otherwise verify by clicking — does this page link to that page, does this image exist, does this list still cover everything — is a test, and it costs about ten lines.
The greps that are not tests yet
Three sweeps run by hand before a deploy. Naming them here is also an admission: they should be CI steps, and they are not.
# 1. No placeholder links anywhere in the source.
grep -rn 'href="#"' src/
# 2. No trace of the source template's brand in the shipped site.
grep -rni '<original-vendor-name>' src/ public/
# 3. No wrong-domain internal links.
grep -rn 'https://.*\.example\.com' src/
The first is the one that matters most and the one people skip. A placeholder anchor renders as a link, passes every visual review, and does nothing when clicked. Our rule is zero of them in the tree — not "few", zero — because a threshold above zero cannot be checked automatically.
The second exists because this site started as a port. Anything you build from a template, a starter or an agency handover carries the previous owner's strings in places no design review looks: alt text, JSON-LD, manifest fields, comments in shipped CSS.
What ours actually caught
Not hypotheticals. These are the defects the passes above found on a site that looked finished:
| Found by | Defect |
|---|---|
| Link sweep | A products mega-menu whose five per-framework tiles all pointed at the same product URL |
| Link sweep | Three nav items pointing at pages that did not exist, all silently dumping users on /docs |
| Copy audit | A "V 2.3" version chip on a product whose catalog version was 1.0.0 |
| Copy audit | A GitHub button showing ... where a star count belonged, leaving the link with no accessible name |
| Image audit | Two products sharing a byte-identical cover (same md5) — one of them was showing the wrong product |
| Image audit | Two cover.png files that were actually JPEGs; format and extension disagreed |
| Cross-page read | /pricing selling Single $39 / All-Access $129 / Full Stack $249 while /license still described a "Studio ($149)" tier that no longer existed |
| Accessibility pass | A page with no h1; another skipping h1 → h3; a tag chip at 4.49:1 against a 4.5:1 requirement |
The pricing-versus-licence mismatch is the instructive one. No linter finds it. No test finds it, because both pages were internally consistent. It was found by reading the two pages side by side and asking whether a buyer comparing them would see the same offer twice. Budget an hour for that read; it is the highest-yield hour in the whole checklist.
Secrets, before anyone can fetch them
Two checks, both mechanical:
# Nothing secret-shaped in the git history.
gitleaks detect --config .gitleaks.toml
# Nothing secret-shaped in what the browser downloads.
grep -rl "$YOUR_SECRET_VALUE" .next/static
The second is the one people never run, and it takes a second. Build with your real environment, then grep the built client bundle for the value of each secret. We ran exactly that with marker values in place of the real ones: both public variables landed in one client chunk each, and all three server-only secrets landed in zero. The measurement, and the build-time-versus-run-time trap behind it, is its own post.
Gitleaks needs an allowlist or it will flag your own .env.example. Ours permits obvious placeholder shapes (re_xxxx…, your-api-key, changeme) only in files matching .env.{example,sample,template} — narrow enough that a real key in a real file still trips it.
Crawl, index and the claims you make to Google
The launch is where SEO mistakes get baked in, because they take weeks to surface and longer to unwind:
- Ship
robots.txtand a sitemap, and make the sitemap honest.lastModifiedmust be a real content date, never the build clock. Telling a search engine that 250 URLs changed on every deploy is a checkable false claim, and it costs the crawl scheduling that decides whether a page gets indexed at all. - Check the internal link graph, not just the page count. We put 38 product pages into "Discovered — currently not indexed" by generating pages that nothing linked to. The whole story is in the programmatic-SEO post; the short version is that a page with no inbound links is a page you told nobody about.
- Canonicals and metadata on every route. Including the ones added late — the pages that ship after the launch audit are exactly the pages the launch audit never saw.
- Decide what is indexable before it is live. Facet and filter URLs, staging subdomains, preview deployments. A
noindexyou leave on for months eventually stops being crawled at all, which means the day you remove it nothing happens.
Security headers belong in the same pass, and they are one config block: a content-security policy, X-Content-Type-Options, Referrer-Policy, X-Frame-Options and HSTS. Add preload to HSTS after launch, once every subdomain is confirmed HTTPS-only — it is hard to reverse.
Mistakes and how they show up
| Mistake | Symptom | Fix |
|---|---|---|
| Checklist as a document | Skipped under deadline | Make each item a command that fails the build |
| Testing components, not content | Green suite, broken links | Assert that referenced files and routes exist |
href="#" tolerated "for now" | Dead clicks nobody reports | Enforce zero, by grep, in the pipeline |
| Never grepping the client bundle | A secret ships and stays in a cached build | Grep .next/static for each secret value |
lastModified: new Date() | Sitemap claims everything changed; crawl budget wasted | Derive it from real content dates |
| Pages with no inbound links | "Discovered — currently not indexed" | Fix the link graph, not the sitemap |
| Launch-day accessibility check only | Pages added later skip the audit entirely | Re-run the sweep over every route, not the original set |
| Trusting file extensions | Format and extension disagree; some clients refuse the image | Verify by content, not by name |
| Two pages describing the same offer | Buyer sees two prices, trusts neither | Read the money pages side by side, by hand |
Frequently asked questions
How long should a pre-launch pass take? The automated part should be minutes, because it is CI. The human part — reading the money pages against each other, clicking through the primary nav, checking the images are of the right products — is a couple of hours for a site of this size. If it takes a day, most of that day belongs in a test.
Is a staging environment required? A preview deployment per pull request covers most of the value: a real URL, real headers, real build output, no shared state to corrupt. What a preview does not give you is production data or production DNS, which is why the first hour after go-live is part of the checklist too.
What about backups and rollback? For a statically-rendered marketing site the rollback is the previous deployment, and it is instant. What actually needs a backup plan is anything with a database behind it: know, before launch, how you would restore and how much data you would lose.
Do I need all of this for a five-page site? The commands, yes — they cost nothing once configured. The content-graph tests scale with how much content you have; at five pages, reading them is faster than testing them. The break-even is somewhere around the point where you stop being able to click every page in one sitting.
We bought a template. Does that change the list? It adds one item: sweep for the original vendor's branding and demo content everywhere, including metadata and image alt text. And it removes almost none of the others — a template ships a site, not a launch.
Templates that ship launch-ready
Every template below arrives with the parts this checklist tests: real routes rather than placeholder anchors, per-page metadata, a sitemap and robots file, and an accessible, measured baseline. The three here are the ones whose own subject matter is uptime, analytics and search visibility — which is what you are about to start caring about.
