Vibecode production qa validator
AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 1,987+ agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.
npx -y skills add sickn33/agentic-awesome-skills --skill vibecode-production-qa-validatorAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
13-phase production QA for fullstack Next.js apps: build verification, SEO tags, OG images, favicon, route regression, API auth, page speed, lazy load, vulnerability scan, UI/UX cards, error boundaries, database, secure rendering, and cleanup.
SKILL.md
17.5 KB, as published. Nobody here has run it
Production QA Validator
Run phases in order. Fix failures before moving to next.
When to Use
- Use before shipping or promoting a fullstack Next.js app to production.
- Use after large UI, SEO, auth, API, database, or dependency changes need a concrete launch-readiness pass.
- Use when you need a compact command-driven checklist for build, route, metadata, performance, security, and cleanup checks.
export PROD_URL="https://yourdomain.com"
export QA_AUTH_HEADER="" # optional: "Bearer eyJ..."
export PAGESPEED_API_KEY="" # optional: for auto PageSpeed API
Consolidated Runner
qa:all() { qa:code && qa:build && qa:routes / /about /contact /privacy /terms /faq /sitemap.xml /robots.txt /api/health && qa:seo && qa:api /api/health /api/tools && qa:git && qa:smoke; }
qa:full() { qa:all && qa:auth && qa:auth:cookies && qa:lazyload && qa:heavyload && qa:vulns && qa:cleanup && qa:ux:cards && qa:ux:boundaries && qa:ux:animation && qa:database && qa:secure; }
Phase 1: Code Integrity
-
npx tsc --noEmit -
npx eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 0 -
npm test -- --runInBand --passWithNoTests
qa:code() { npx tsc --noEmit && npx eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 0 && npm test -- --runInBand --passWithNoTests; }
Phase 2: Build Verification
-
npm run buildsucceeds - SEO pages show
○/●notλ - Build log has no errors
qa:build() { local log; log="$(mktemp "${TMPDIR:-/tmp}/qa-build.XXXXXX.log")" || return 1; set -o pipefail; npm run build 2>&1 | tee "$log"; local rc=$?; set +o pipefail; [ "$rc" -eq 0 ] && ! grep -qi "error\|failed" "$log"; local ok=$?; rm -f "$log"; return "$ok"; }
| Symbol | Meaning |
|---|---|
○ | Static |
● | SSG |
λ | Dynamic/serverless |
⊕ | Partial prerender |
Phase 3: API Session & Authentication
- Auth endpoints respond (login, session, logout)
- Protected routes return 401/403
- Session cookie: HttpOnly + Secure + SameSite
- Cookie not expired, Path/Domain correct
- No rate limiting bypass
qa:auth() {
local F=0
for ep in /api/auth/login /api/auth/session /api/auth/logout; do
curl -so /dev/null -w "%{http_code}" "$PROD_URL$ep" | grep -q "200\|401" || { echo " ✗ $ep unreachable"; ((F++)); }
done
curl -so /dev/null -w "%{http_code}" "$PROD_URL/api/protected" | grep -q "401\|403" || echo " ⚠ Protected route not denying unauthenticated"
return $F
}
qa:auth:cookies() {
for ep in /api/auth/session /api/auth/login; do
curl -sI "$PROD_URL$ep" | grep -i "^set-cookie:" | while IFS= read -r c; do
echo " $ep: $(echo "$c" | cut -d= -f1)"
echo "$c" | grep -qi "HttpOnly" || echo " ✗ Missing HttpOnly"
echo "$c" | grep -qi "Secure" || echo " ✗ Missing Secure"
echo "$c" | grep -qi "SameSite" || echo " ⚠ Missing SameSite"
done
done
}
Phase 4: Route Regression
- Core pages, sitemap, robots.txt all 200
- URLs use kebab-case, no duplicate slugs
- robots.txt allows indexing
- Sitemap XML valid, all URLs resolve 200
qa:routes() { local F=0; for p; do local C=$(curl -so /dev/null -w "%{http_code}" "$PROD_URL$p"); echo "$C $p"; [ "$C" = "200" ] || ((F++)); done; return $F; }
qa:robots() { curl -s "$PROD_URL/robots.txt" | grep -qi "Disallow: /$" && echo " ✗ Blocks all crawlers" || echo " ✓ OK"; }
qa:sitemap() { curl -s "$PROD_URL/sitemap.xml" | python3 -c "import sys,xml.etree.ElementTree as ET; ET.parse(sys.stdin); print('✓ Valid XML')"; }
Phase 5: SEO — Tags, Images, Favicon, Slugs
-
<title>30–60 chars, unique per page -
<meta name="description">in raw HTML - og:title matches
<title>, og:url matches canonical - og:image ≥ 1200×630px, absolute URL, loads 200
- twitter:card = summary_large_image
- Canonical self-referencing, no duplicates
-
/favicon.ico200, apple-touch-icon present -
hreflangtags if multilingual - JSON-LD structured data present
- Slugs: kebab-case, < 80 chars, no stop words
qa:seo() {
local H=$(curl -s "$PROD_URL"); local F=0
for t in "og:title" "og:description" "og:image" "twitter:card" "canonical" "description"; do echo "$H" | grep -qi "$t" || { echo " ✗ $t"; ((F++)); }; done
echo "$H" | grep -qi "<title>" || { echo " ✗ <title>"; ((F++)); }
local T=$(echo "$H" | grep -oP '<title>\K[^<]+'); local L=${#T}; [ $L -ge 30 -a $L -le 60 ] || echo " ⚠ Title ${L}chars (target 30-60)"
curl -so /dev/null -w "%{http_code}" "$PROD_URL/favicon.ico" | grep -q 200 || echo " ⚠ No favicon.ico"
return $F
}
qa:seo:ogimage() {
local I=$(curl -s "$PROD_URL" | grep -oP 'og:image" content="\K[^"]+'); [[ "$I" =~ ^http ]] || I="$PROD_URL$I"
curl -so /dev/null -w "%{http_code}" "$I" | grep -q 200 || { echo " ✗ og:image returns non-200"; return 1; }
command -v identify &>/dev/null && curl -s "$I" | identify -format "%wx%h" - 2>/dev/null | grep -qP "12\d{2}x6\d{2}" && echo " ✓ ≥ 1200x630" || echo " ⚠ Install imagemagick to check dimensions"
}
Phase 6: API Route Behavior
- Correct status codes + Content-Type
- Errors return consistent JSON
{ error, message } - Response times < 200ms
- CORS headers correct (if cross-origin)
qa:api() {
for p; do
local R=$(curl -so /dev/null -w "%{http_code} %{content_type}" "$PROD_URL$p")
echo " $p → $R"
done
local E=$(curl -s "$PROD_URL/api/nonexistent")
echo "$E" | python3 -c "import sys,json; d=json.load(sys.stdin); assert 'error' in d; print('✓ Consistent errors')" 2>/dev/null || echo " ⚠ Inconsistent error shape"
}
Phase 7: Git Hygiene
- No secrets/credentials in diff
- No
.next/node_modulesstaged - Commit:
type(scope): message
qa:git() {
local S=$(git diff HEAD 2>/dev/null | grep -i "password\|secret\|api_key\|localhost:3000" | grep "^+")
[ -n "$S" ] && { echo " ✗ Secrets in diff!"; echo "$S"; return 1; } || echo " ✓ No secrets"
local A=$(git status --short 2>/dev/null | grep -E "\.next|node_modules" | head -3)
[ -n "$A" ] && echo " ⚠ Build artifacts:" && echo "$A" || echo " ✓ No artifacts"
}
Phase 8: Post-Deployment Smoke Test
- Homepage 200, key pages 200
- OG image loads 200
- No console errors (manual)
- Auth flow works (manual)
qa:smoke() {
curl -sI "$PROD_URL" | head -1 | grep -q "200" && echo " ✓ Homepage" || echo " ✗ Homepage"
curl -sI "$PROD_URL/sitemap.xml" | head -1 | grep -q "200" && echo " ✓ Sitemap" || echo " ✗ Sitemap"
}
Phase 9: Page Speed, Lazy Load & Bundles
- Lighthouse ≥ 90 (Perf, A11y, SEO)
- FCP < 2.5s, LCP < 4.0s, CLS < 0.1
- Images lazy-loaded (
loading="lazy"), WebP/AVIF - Dynamic imports for heavy components
- Largest JS chunk < 200KB gzipped
-
font-display: swap, no FOIT - Total page weight < 1MB
qa:lazyload() {
local N=$(grep -r "loading=" app/ --include="*.tsx" 2>/dev/null | grep -c "lazy" || true)
echo " Lazy images: $N"
grep -rn "next/dynamic\|dynamic((" app/ --include="*.tsx" 2>/dev/null | head -5 | grep . || echo " ⚠ No dynamic imports"
}
qa:heavyload() {
ls -lhS .next/static/chunks/*.js 2>/dev/null | head -5
local W=$(curl -so /dev/null -w "%{size_download}" "$PROD_URL" 2>/dev/null || echo 0)
echo " HTML weight: ~$((W/1024))KB"
echo " ⚠ Run 'npx lighthouse $PROD_URL --view' for full weight analysis"
}
# PageSpeed: open "https://pagespeed.web.dev/?url=$PROD_URL"
Phase 10: Cleanup & Vulnerability Scan
-
npm prune,depcheck— no unused deps - No console.log/debugger in staged code
-
npm audit— zero critical/high vulnerabilities - No eval/new Function/document.write
- TODOs resolved
qa:vulns() {
npm audit 2>/dev/null | grep -E "critical|high" | grep . && echo " ✗ Vulnerabilities!" || echo " ✓ No critical/high vulns"
npm outdated 2>/dev/null | head -5 | grep . || echo " ✓ All up to date"
local D=$(grep -rn "eval(\|new Function(\|document.write(" app/ src/ --include="*.ts" --include="*.tsx" 2>/dev/null | head -5) # security-allowlist: defensive source scan
[ -n "$D" ] && echo " ⚠ Dangerous patterns:" && echo "$D" || echo " ✓ No dangerous patterns"
}
qa:cleanup() {
local D=$(git diff --cached 2>/dev/null | grep "^+" | grep -i "console\.log\|debugger" | head -5)
[ -n "$D" ] && echo " ✗ Debug artifacts:" && echo "$D" || echo " ✓ No debug artifacts"
local T=$(git diff --cached 2>/dev/null | grep "^+" | grep -i "TODO\|FIXME\|HACK" | head -5)
[ -n "$T" ] && echo " ⚠ TODOs remain:" && echo "$T"
}
Phase 11: UI/UX — Cards, Animation, Error Boundaries
- Cards: equal height grid, no overlap, text ellipsis, responsive (1→2→3 col)
- No horizontal scroll at any viewport (320–1440px)
- Images: consistent
aspect-ratio+object-fit: cover - Touch targets ≥ 44×44px
- Animations use
transform+opacityonly (not layout props) -
prefers-reduced-motionrespected - Error boundaries at root + route level (
app/error.tsx,app/global-error.tsx) -
app/not-found.tsxandapp/loading.tsxexist - All client fetches show loading + error + empty states
- Buttons: hover, focus-visible, active, disabled, loading states
- Forms disable submit on click (no double-submit)
qa:ux:cards() {
local E=$(grep -rn "text-overflow\|line-clamp\|truncate" app/ --include="*.css" --include="*.tsx" 2>/dev/null | head -3)
[ -n "$E" ] && echo " ✓ Text overflow handling" || echo " ⚠ No text overflow handling"
local A=$(grep -rn "aspect-\|object-fit" app/ --include="*.css" --include="*.tsx" 2>/dev/null | head -3)
[ -n "$A" ] && echo " ✓ aspect-ratio/object-fit used" || echo " ⚠ No aspect-ratio set"
}
qa:ux:boundaries() {
for f in app/error.tsx app/global-error.tsx app/not-found.tsx app/loading.tsx; do
[ -f "$f" ] && echo " ✓ $f" || echo " ⚠ Missing $f"
done
}
qa:ux:animation() {
local A=$(grep -rn "animation.*width\|transition.*height\|@keyframes.*top\|@keyframes.*margin" app/ --include="*.css" --include="*.tsx" 2>/dev/null | head -5)
[ -n "$A" ] && echo " ⚠ Layout-triggering animations:" && echo "$A" || echo " ✓ No layout-triggering animations"
local P=$(grep -r "@media.*prefers-reduced-motion" app/ --include="*.css" --include="*.tsx" 2>/dev/null | head -3)
[ -n "$P" ] && echo " ✓ prefers-reduced-motion found in CSS" || echo " ⚠ No prefers-reduced-motion in CSS"
}
Phase 12: Database & Data Layer
- Connection pool configured (no starvation)
- Schema in sync with migrations
- Indexes on all queried columns, no N+1
- No hardcoded DB credentials in source
- No raw SQL injection risk
- No sensitive data leaked in API responses
- Migrations are idempotent
qa:database() {
local H=$(grep -rn "postgres://\|mysql://\|mongodb://" app/ src/ --include="*.ts" --include="*.tsx" 2>/dev/null | grep -v ".env" | head -5)
[ -n "$H" ] && { echo " ✗ Hardcoded DB URL:"; echo "$H"; } || echo " ✓ No hardcoded DB URLs"
local R=$(grep -rn "\$queryRaw\|\.raw(" app/ src/ --include="*.ts" --include="*.tsx" 2>/dev/null | head -5)
[ -n "$R" ] && echo " ⚠ Raw SQL:" && echo "$R" || echo " ✓ No raw SQL"
local N=$(grep -rn "\.findMany\|\.findUnique" app/ src/ --include="*.ts" --include="*.tsx" 2>/dev/null | grep -v "include:" | head -5)
[ -n "$N" ] && echo " ⚠ Possible N+1:" && echo "$N" || echo " ✓ No N+1 patterns"
}
qa:db:migrations() {
[ -d "prisma/migrations" ] && echo " ✓ Prisma: $(ls prisma/migrations 2>/dev/null | wc -l) migrations" || echo " - No prisma migrations dir"
local M=$(ls db/migrations/*.sql 2>/dev/null | head -5); [ -n "$M" ] && echo " ✓ SQL migrations:" && echo "$M" || echo " - No SQL migration files"
}
Phase 13: Secure Data Rendering
- No secrets/tokens in client source or localStorage
- No
dangerouslySetInnerHTMLwithout DOMPurify - API errors don't leak stack traces
- Internal IDs use UUIDs not auto-increment
- User emails masked in UI
- NEXT_PUBLIC_ vars contain no secrets
qa:secure() {
local S=$(git grep -n "api_key\|API_KEY\|secret_key\|PRIVATE_KEY" -- ':!*.env*' ':!*test*' 2>/dev/null | head -5)
[ -n "$S" ] && echo " ✗ Secrets in source:" && echo "$S" || echo " ✓ No hardcoded secrets"
local D=$(grep -rn "dangerouslySetInnerHTML" app/ src/ --include="*.tsx" 2>/dev/null | head -5)
[ -n "$D" ] && echo " ⚠ XSS risk — use DOMPurify:" && echo "$D" || echo " ✓ No dangerouslySetInnerHTML"
local T=$(grep -rn "localStorage\|sessionStorage" app/ src/ --include="*.ts" --include="*.tsx" 2>/dev/null | grep -i "token\|jwt\|secret" | head -5)
[ -n "$T" ] && echo " ⚠ Tokens in storage — use httpOnly cookies:" && echo "$T" || echo " ✓ No tokens in storage"
curl -s "$PROD_URL/api/nonexistent" 2>/dev/null | grep -qi "stack\|Error:" && echo " ✗ Stack trace leak" || echo " ✓ No stack leak"
}
Pre-Commit Hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
npx tsc --noEmit || exit 1
npx eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 0 || exit 1
EOF
chmod +x .git/hooks/pre-commit
CI/CD (GitHub Actions)
name: QA
on: [push, pull_request]
jobs:
qa:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx tsc --noEmit
- run: npx eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 0
- run: npm test -- --runInBand --passWithNoTests
- run: npm run build
Best Practices
| ✅ Do | ❌ Don't |
|---|---|
| Run full 13-phase flow before deploy | Skip typecheck or lint |
Set PROD_URL in profile/.envrc | Hardcode URLs in scripts |
| OG images ≥ 1200×630 | Use small OG images |
Animate with transform+opacity | Animate width/height/top |
| Show loading/error/empty states | Leave users on blank screens |
prefers-reduced-motion for animations | Force motion on all users |
| HttpOnly + Secure cookies for tokens | localStorage for auth tokens |
| Error boundaries at all levels | White screen on crash |
| Database indexes + include/populate | N+1 queries in loops |
npm audit before deploy | Deploy with known vulns |
Common Pitfalls
| Problem | Solution |
|---|---|
| OG tags missing in raw HTML | Use export const metadata in Next.js |
Disallow: / in robots.txt | Blocks all crawlers — use specific paths |
| Cards different heights in grid | Use display: grid with equal-height rows, not flex |
| Text overflows card | Add text-overflow: ellipsis + overflow: hidden |
| Animation jank | Animate transform not width/height |
| Form submits twice | Disable button on first click |
| Console errors in prod | Add no-console ESLint rule |
| DB connection timeout | Add connection pooling (PgBouncer/Prisma Accelerate) |
| Sensitive data in API | Strip passwordHash/secret in response transformer |
| App crashes on error | Add app/error.tsx error boundary |
| Large JS bundles | Dynamic import heavy components, analyze with next/bundle-analyzer |
| Images load slowly | Add loading="lazy", use WebP/AVIF, resize to display size |
Security Notes
- All
qa:*functions are read-only (tsc, lint, test, build, curl, grep) PROD_URLandQA_AUTH_HEADERonly for environments you own- Basic secret scanning in
git diff— for prod, usetrufflehog/git-secrets - Auth tests with real credentials against prod is destructive — use staging
Limitations
- Passing all phases reduces risk but doesn't eliminate production bugs
- Some checks depend on project-specific tooling (Prisma, NextAuth, etc.)
- Manual UX testing still required for critical user journeys
- SEO checks verify raw HTML only — not social preview rendering
- Route checks verify status codes, not content correctness
Master Checklist
Phase 1: Code
-
tsc --noEmit,eslint,npm testpass
Phase 2: Build
-
npm run buildsucceeds, no errors, pages static
Phase 3: Auth
- Endpoints respond, protected routes denied, secure cookies
Phase 4: Routes
- All core pages 200, sitemap valid, robots.txt correct
Phase 5: SEO
- title, description, og:*, twitter:card, canonical, favicon, slugs
Phase 6: API
- Status, Content-Type, consistent errors, timing
Phase 7: Git
- No secrets, no artifacts, conventional commit
Phase 8: Smoke
- Homepage + key pages 200, og:image loads
Phase 9: Speed
- Lighthouse ≥ 90, lazy images, dynamic imports, font-display: swap
Phase 10: Clean
- No vulns, no debug artifacts, unused deps pruned
Phase 11: UI/UX
- Cards responsive, error boundaries, button states, reduced-motion
Phase 12: Database
- Indexes, no N+1, no hardcoded URLs, no sensitive leaks
Phase 13: Secure Rendering
- No secrets in client, no XSS, no stack leaks, UUIDs