Skip to content

Building a Production-Ready DevOps Pipeline for a Next.js Dealership Platform

Dealership websites have unique challenges: customer data security, inventory performance, compliance requirements, and zero-tolerance for downtime. We built a modern DevOps pipeline for a Next.js dealership platform featuring 9 GitHub Actions workflows with smart filtering, comprehensive testing (Jest + Playwright), Lighthouse CI performance gates, Sentry monitoring, and Vercel deployment — all while maintaining sub-2-second load times and 90%+ accessibility scores.


Automotive dealerships operate in a highly regulated environment with unique technical requirements:

  • Customer Data: Credit applications, soft credit pulls, personal information requiring FCRA compliance
  • Performance: Users abandon slow sites — critical when competing for car buyers
  • Accessibility: WCAG compliance isn’t optional when serving diverse demographics
  • Security: VIN validation, payment calculators, and financial tools must be bulletproof
  • SEO: Dealerships live or die by local search rankings

Traditional “ship fast and fix later” doesn’t work when handling credit applications and customer financial data. We needed DevOps practices that balance velocity with safety.

Who benefits: Dealerships, automotive SaaS platforms, and anyone building customer-facing applications with strict compliance and performance requirements.


When we started, our deployment process looked like this:

Terminal window
# Manual deployment checklist (error-prone)
1. Run tests locally (maybe)
2. Check accessibility manually (occasionally)
3. Deploy to Vercel
4. Hope nothing breaks in production
5. Discover issues when customers report them

Pain points we faced:

  • Inconsistent quality: Different developers had different testing habits
  • Performance regression: No automated performance tracking — discovered slow pages after deployment
  • Security gaps: Dependencies updated manually, vulnerabilities slipped through
  • Accessibility debt: WCAG compliance checked sporadically, issues accumulated
  • Design drift: Tailwind and shadcn/ui patterns inconsistent across PRs
  • Customer impact: Credit application forms breaking = lost revenue

The metrics that scared us:

  • 18-minute manual QA checklist per PR (when done thoroughly)
  • Performance regressions caught in production 4x in one month
  • High-severity vulnerability sat unnoticed for 2 weeks
  • Accessibility issues found during compliance audit

We needed automation that could scale with our team while maintaining dealership-grade reliability.


We designed a multi-layered CI/CD pipeline with 9 specialized GitHub Actions workflows, each handling a specific concern:

┌─────────────────────────────────────────────────────────┐
│ Pull Request Opened │
└────────────────────┬────────────────────────────────────┘
┌────────────┴────────────┐
│ │
┌────▼─────┐ ┌─────▼──────┐
│ Required │ │ Advisory │
│ Checks │ │ Checks │
└────┬─────┘ └─────┬──────┘
│ │
┌───────▼────────┐ ┌────────▼─────────┐
│ • PR Checks │ │ • Lighthouse CI │
│ • Security │ │ • Claude Review │
│ • Design │ │ │
│ • Test Suite │ │ │
└───────┬────────┘ └──────────────────┘
Merge Ready
Vercel Deploy
Cache Warming

The foundation of quality control:

.github/workflows/pr-checks.yml
name: PR Quality Checks
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
test-build-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9.15.9
- name: Run Jest Tests
run: pnpm test
- name: TypeScript Check
run: npx tsc --noEmit
- name: Build Verification
run: pnpm build

Why it works: Fast feedback loop (2-3 minutes), catches 80% of issues before human review.

2. Security Review with Smart Filtering (Required*)

Section titled “2. Security Review with Smart Filtering (Required*)”

Here’s where we got creative. Running security scans on every PR created noise:

# Smart filtering logic
- name: Determine if security scan needed
id: check
run: |
# Skip if PR only touches docs/styles
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
if echo "$CHANGED_FILES" | grep -qE '\.(ts|tsx|js|jsx|json); then
echo "should_run=true" >> $GITHUB_OUTPUT
else
echo "should_run=false" >> $GITHUB_OUTPUT
fi
- name: Security Audit
if: steps.check.outputs.should_run == 'true'
run: |
pnpm audit --audit-level=high --production
# Fail on critical or high vulnerabilities
if [ $? -ne 0 ]; then exit 1; fi

Key innovation: We analyze the PR’s changed files and skip security scans for documentation-only or style-only changes. Reduces CI runtime by 40% without sacrificing safety.

Dealership branding consistency matters:

- name: Check Tailwind/shadcn Usage
run: |
# Ensure shadcn components used correctly
node scripts/validate-design-patterns.js
# Verify no hardcoded colors (use Tailwind theme)
! grep -r "bg-#[0-9a-fA-F]" src/components/
# WCAG color contrast validation
node scripts/validate-color-contrast.js

Performance gates that prevent regression:

lighthouserc.js
module.exports = {
ci: {
collect: {
numberOfRuns: 3,
url: ['http://localhost:3000', 'http://localhost:3000/inventory']
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.7 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }]
}
}
}
};

Critical insight: We set accessibility and SEO as required (0.9 minimum) but performance as advisory (0.7). Why? Accessibility regressions are unacceptable; performance can sometimes be traded for features with conscious decision-making.

Two-tier testing strategy:

Unit Tests (Jest): 100% coverage on critical utilities

__tests__/lib/utils.test.ts
describe('VIN Validator', () => {
it('rejects invalid VIN checksums', () => {
expect(validateVIN('1HGBH41JXMN109186')).toBe(false); // Bad checksum
});
it('accepts valid VIN', () => {
expect(validateVIN('1HGBH41JXMN109187')).toBe(true);
});
});

E2E Tests (Playwright): 33+ tests covering critical user journeys

e2e/credit-application.spec.ts
test('credit application submission with soft pull consent', async ({ page }) => {
await page.goto('/financing/credit-application');
// Fill form
await page.fill('[name="firstName"]', 'John');
await page.fill('[name="ssn"]', '123-45-6789');
// Verify soft pull disclosure
await expect(page.locator('text=soft credit inquiry')).toBeVisible();
// Submit
await page.click('button:has-text("Submit Application")');
// Verify Turnstile CAPTCHA triggered
await expect(page.locator('.cf-turnstile')).toBeVisible();
});

6. Automated Theme Cleanup (Manual Dispatch)

Section titled “6. Automated Theme Cleanup (Manual Dispatch)”

Dealerships often have 20+ color themes. Unused CSS bloats bundles:

scripts/cleanup-themes.js
const themes = ['red', 'blue', 'green', /* ... 17 more */];
const activeTheme = process.env.DEALERSHIP_THEME;
// Remove unused theme CSS
themes.filter(t => t !== activeTheme).forEach(theme => {
const themeFile = `src/styles/themes/${theme}.css`;
if (fs.existsSync(themeFile)) {
fs.unlinkSync(themeFile);
console.log(`Removed unused theme: ${theme}`);
}
});

Triggered manually via GitHub Actions dispatch before production builds.

7. Sentry Integration for Production Monitoring

Section titled “7. Sentry Integration for Production Monitoring”
src/lib/sentry.ts
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.VERCEL_ENV || 'development',
// Session Replay for debugging
replaysSessionSampleRate: process.env.NODE_ENV === 'production' ? 0.01 : 0.1,
// Performance monitoring
tracesSampleRate: 0.1,
beforeSend(event) {
// Scrub sensitive data
if (event.request?.data?.ssn) {
event.request.data.ssn = '[Redacted]';
}
return event;
}
});

Critical: We automatically redact PII (SSN, credit card numbers) before sending to Sentry.


Our security approach has 4 layers:

Layer 1: Dependency Scanning

- name: Audit Dependencies
run: pnpm audit --audit-level=high --production

Layer 2: Static Code Analysis

- name: Static Analysis
run: |
# Check for common vulnerabilities
grep -r "dangerouslySetInnerHTML" src/ && exit 1
grep -r "eval(" src/ && exit 1
grep -r "process.env" src/components/ && exit 1 # No env vars in client components

Layer 3: Input Validation

// All forms use Zod schemas
const creditAppSchema = z.object({
ssn: z.string().regex(/^\d{3}-\d{2}-\d{4}$/),
income: z.number().min(0).max(1000000),
vin: z.string().refine(validateVINChecksum)
});

Layer 4: CAPTCHA Protection

// Cloudflare Turnstile on all forms
<Turnstile
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
onSuccess={handleCaptchaSuccess}
/>

Our build process optimizes aggressively:

next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384]
},
webpack: (config) => {
// Chunk splitting
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
maxSize: 200000 // 200KB vendor chunks
},
common: {
minChunks: 2,
maxSize: 150000 // 150KB common chunks
}
}
};
return config;
},
// Use SWC minification (faster than Terser)
swcMinify: true
};

Post-deploy cache warming:

scripts/warm-image-cache.js
// Pre-warm inventory images after deployment
const inventoryVehicles = await fetch('/api/inventory').then(r => r.json());
for (const vehicle of inventoryVehicles) {
await fetch(vehicle.imageUrl); // Trigger CDN cache
await sleep(100); // Throttle
}

Three-tier environment setup:

EnvironmentBranchDeploy TriggerFeatures
Developmentfeature/*Auto (preview)Mock APIs, verbose logging, hot reload
StagingdevelopAutoReal APIs, Sentry sampling, real CAPTCHA
ProductionmainAuto (after merge)Optimized builds, cache warming, monitoring
Terminal window
# Environment variable structure
.env.local # Local development (gitignored)
.env.development # Shared dev config
.env.production # Production config
.env.example # Template for new developers

Vercel integration:

{
"env": {
"NEXT_PUBLIC_SENTRY_DSN": "@sentry-dsn",
"DEALERSHIP_ID": "@dealership-id",
"ENCRYPTION_KEY": "@encryption-key"
},
"build": {
"env": {
"NEXT_TELEMETRY_DISABLED": "1"
}
}
}

MetricBefore AutomationAfter ImplementationImprovement
Manual QA Time18 min/PR3 min/PR83% reduction
Production Incidents4/month0.5/month87% reduction
Time to Detect IssuesHours-DaysMinutes95%+ faster
Security VulnerabilitiesDiscovered manuallyBlocked in CI100% prevention
Performance Regressions4 in 1 month0 in 6 monthsEliminated
Accessibility Compliance78% average93% average+15 points
Deploy ConfidenceLow (manual checklist)High (automated gates)Immeasurable

Incident Example: A developer introduced a dependency with a critical vulnerability. Before our pipeline:

  • Vulnerability sat unnoticed for 2 weeks
  • Discovered during manual security review
  • Emergency patch required

After our pipeline:

  • PR blocked automatically within 2 minutes
  • Developer notified immediately
  • Fixed before merge

Performance Example: Lighthouse CI caught a 3rd-party script causing 800ms JavaScript execution delay:

Lighthouse CI Failed
❌ Total Blocking Time: 1,247ms (threshold: 300ms)
Blocking Script: widget.somevendor.com/analytics.js (847ms)
Recommendation: Load async or defer

This would have impacted thousands of users. Caught in PR review instead.

  • Security audit service: $5k/year → $0 (automated)
  • Manual QA hours: 72 hrs/month → 12 hrs/month (83% reduction)
  • Incident response: 16 hrs/month → 2 hrs/month (87% reduction)

ROI: ~$85k annually in labor + avoided incidents.


Smart filtering reduced noise: Not every PR needs every check. Documentation-only changes don’t need security scans.

Advisory vs. Required distinction: Making some checks advisory (Lighthouse) vs. required (accessibility) gave us flexibility without sacrificing standards.

Invest in E2E tests for critical flows: Our 33 Playwright tests cover credit applications, contact forms, and soft pull flows — the revenue-generating paths. Worth every hour of development.

Automate the boring stuff: Theme cleanup, cache warming, dependency audits — automate everything that doesn’t require human judgment.

Too many required checks initially: We started with 12 required checks. PRs took 15+ minutes. Developers got coffee while waiting. We consolidated to 4 core workflows.

Lighthouse CI flakiness: Performance scores varied ±5 points between runs. Solution: Run 3 times, take median score.

Over-aggressive security scanning: Flagged false positives constantly. Solution: Whitelist known-safe patterns, focus on high/critical only.

Sentry noise in development: Local errors polluted production dashboard. Solution: Separate DSNs per environment, sample aggressively (1% prod, 10% staging).

🔄 Start with E2E tests earlier: We added Playwright 6 months in. Wish we’d started Day 1.

🔄 Document the “why” for each workflow: New developers asked “why do we have 9 workflows?” A lot. Now we have this article.

🔄 Invest in faster CI runners: GitHub’s default runners are slow. Self-hosted or GitHub’s 4-core runners would cut CI time in half.

🔄 Build a “CI dashboard”: Tracking workflow trends over time would help optimize further.


Smart automation beats blind automation — Use changed file detection to skip unnecessary checks

Required vs. Advisory matters — Not all quality gates should block merges; some should inform decisions

Security must be layered — Dependency scanning + static analysis + input validation + CAPTCHA = defense in depth

Performance is a feature — Lighthouse CI gates prevent regressions; treat performance like any other requirement

E2E tests for revenue paths — Focus automation on flows that impact business (credit apps, contact forms, inventory search)

Monitor production relentlessly — Sentry with session replay catches what testing misses

Optimize for developer experience — Fast CI, clear failures, automatic fixes where possible


  • CI/CD: GitHub Actions
  • Testing: Jest 30.1.3, Playwright 1.55.1
  • Monitoring: Sentry 10.15.0
  • Performance: Lighthouse CI, Vercel Analytics
  • Security: npm audit, Cloudflare Turnstile
  • Package Management: pnpm 9.15.9

Questions for the community:

  • How do you balance CI speed vs. thoroughness? We settled on 9 workflows with smart filtering — what’s your approach?
  • What’s your strategy for E2E test maintenance? Our 33 Playwright tests are growing — concerned about flakiness at scale.
  • How do you handle performance testing for dynamic content (inventory changes daily)?

Facing similar challenges?

This architecture works for dealership websites, but the patterns apply to any compliance-heavy, customer-facing application:

  • Healthcare portals (HIPAA compliance)
  • Financial services (PCI-DSS)
  • E-commerce platforms (PCI + performance-critical)
  • SaaS products with enterprise security requirements

WorkflowTriggerPurposeStatusAvg Runtime
PR ChecksPR open/syncJest tests, TypeScript, buildRequired2-3 min
Security ReviewPR open/sync (smart filter)Dependency audit, static analysisRequired*1-2 min
Design ReviewPR open/sync (smart filter)Tailwind/shadcn compliance, WCAGRequired*1 min
Lighthouse CIPR open/syncPerformance, SEO, accessibility metricsAdvisory3-4 min
Claude AI ReviewPR comment triggerIntelligent code analysisOptional2-3 min
Test SuitePost-merge to mainFinal validation (Jest + Playwright)Required5-7 min
Cleanup ThemesManual dispatchRemove unused theme CSSManual1 min
Sentry ReleaseProduction deployCreate Sentry release, upload source mapsAuto1 min
Cache WarmingPost-deploy hookPre-warm inventory images in CDNAuto2-3 min

Total PR Review Time: ~7-10 minutes (down from 18 minutes manual)


Last updated: 2025-10-21 Reading time: ~12 minutes Technical level: Intermediate to Advanced