Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 176% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 86% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 61% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 238% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 119% | 0% |
Run phases in order. Fix failures before moving to next.
bashexport PROD_URL="https://yourdomain.com" export QA_AUTH_HEADER="" # optional: "Bearer eyJ..." export PAGESPEED_API_KEY="" # optional: for auto PageSpeed API
bashqa: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; }
npx tsc --noEmitnpx eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 0npm test -- --runInBand --passWithNoTestsbashqa:code() { npx tsc --noEmit && npx eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 0 && npm test -- --runInBand --passWithNoTests; }
npm run build succeeds○/● not λbashqa: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 |
bashqa: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 }
bashqa: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')"; }
<title> 30–60 chars, unique per page<meta name="description"> in raw HTML<title>, og:url matches canonical/favicon.ico 200, apple-touch-icon presenthreflang tags if multilingualbashqa: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" }
{ error, message }bashqa: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" }
.next/node_modules stagedtype(scope): messagebashqa: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" }
bashqa: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" }
loading="lazy"), WebP/AVIFfont-display: swap, no FOITbashqa: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"
npm prune, depcheck — no unused depsnpm audit — zero critical/high vulnerabilitiesbashqa: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" }
aspect-ratio + object-fit: covertransform+opacity only (not layout props)prefers-reduced-motion respectedapp/error.tsx, app/global-error.tsx)app/not-found.tsx and app/loading.tsx existbashqa: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" }
bashqa: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" }
dangerouslySetInnerHTML without DOMPurifybashqa: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" }
bashcat > .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
yamlname: 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
| ✅ 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 |
| 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 |
qa:* functions are read-only (tsc, lint, test, build, curl, grep)PROD_URL and QA_AUTH_HEADER only for environments you owngit diff — for prod, use trufflehog/git-secretstsc --noEmit, eslint, npm test passnpm run build succeeds, no errors, pages staticOther measured skills in the registry, with their headline benchmark lift.