Skip to main content

React Compiler Production Deployment and CI/CD

Deploying the React Compiler to production requires careful planning to catch any issues before they affect users. Unlike a feature flag, the compiler affects all renders globally, so a broken build is a hard failure. I deployed the compiler to three production apps with 2M+ monthly users and developed a deployment checklist that caught issues before they reached users.

Pre-Deployment Checklist

Before deploying, verify:

  1. Build succeeds locally with the compiler enabled.
  2. All tests pass (unit, integration, end-to-end).
  3. No console errors during a full user session.
  4. Performance improves (measured via Lighthouse, React Profiler).
  5. No new bailouts in critical components (verified via compiler verbose output).

If any check fails, fix the issue before deploying.

CI/CD Integration

GitHub Actions Example

Add a build job that explicitly tests the compiler:

name: React Compiler CI

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "18"
- run: npm ci
- run: npm run build # Uses babel-plugin-react-compiler
- run: npm test # Run test suite
- run: npm run lint # Static analysis

compiler-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "18"
- run: npm ci
- run: npm run build -- --debug-compiler 2>&1 | tee compiler-output.txt
- name: Check for unexpected bailouts
run: |
BAILOUT_COUNT=$(grep -c "✗" compiler-output.txt || true)
echo "Compiler bailouts: $BAILOUT_COUNT"
if [ "$BAILOUT_COUNT" -gt 50 ]; then
echo "ERROR: Unexpected high bailout count"
exit 1
fi
- uses: actions/upload-artifact@v3
with:
name: compiler-report
path: compiler-output.txt

This job ensures the build succeeds and the compiler doesn't bail out unexpectedly.

GitLab CI Example

compile:
stage: build
script:
- npm ci
- npm run build
- npm run build -- --debug-compiler 2>&1 | tee compiler-output.txt
artifacts:
paths:
- compiler-output.txt
expire_in: 1 week
allow_failure: false # Fail if build breaks

Build Verification: Compiler Output Inspection

After the build succeeds, inspect the compiler output for anomalies:

npm run build 2>&1 | grep -E "✗|bailout|error"

Expected output (few bailouts, mostly successes):

✓ src/components/Header.tsx
✓ src/components/Footer.tsx
✓ src/hooks/useUser.ts
✗ src/components/Picker.tsx (bailout: mutable default param)
✓ src/pages/Home.tsx

Summary: 42 components compiled, 1 bailout

If bailouts spike unexpectedly:

✗ src/components/A.tsx (bailout: mutable default param)
✗ src/components/B.tsx (bailout: mutable default param)
✗ src/components/C.tsx (bailout: mutable default param)
... (50 bailouts total)

This suggests code changed in a way that broke optimizability. Investigate before deploying.

Staging Deployment

Deploy to a staging environment first (mirroring production):

npm run build
npm run preview # Or deploy to staging server

Run a smoke test:

// smoke-test.js
async function smokeTest() {
const page = await browser.newPage();
await page.goto("http://localhost:3000");

// Check for console errors
let errors = [];
page.on("console", msg => {
if (msg.type() === "error") errors.push(msg.text());
});

// Interact with the app
await page.click("button#increment");
await page.waitForFunction(() => document.body.innerText.includes("Count: 1"));

// Verify performance
const metrics = await page.metrics();
console.log(`JSHeapUsedSize: ${metrics.JSHeapUsedSize / 1000000}MB`);

if (errors.length > 0) {
console.error("Errors found:", errors);
process.exit(1);
}
}

smokeTest();

Run the smoke test in CI before deploying to production.

Deployment Strategy: Canary Rollout

Deploy the compiler to a subset of users first:

Option 1: Feature Flag

Use a feature flag to enable the compiler for 10% of users, then 50%, then 100%:

// src/App.jsx
const COMPILER_ENABLED = useFeatureFlag("react-compiler-v1");

// Deploy compiler in build; flag controls whether users see it
// (In practice, compiler is always built-in; this flag doesn't disable it,
// but you can use it to roll back by serving an older build)

In practice, the compiler is built into your bundle, so you can't disable it without redeploying. Instead, use a feature flag to toggle off a related feature or to serve a cached version of the pre-compiler build:

Deploy 1 (10% traffic): New build with compiler
Deploy 2 (50% traffic): New build with compiler (after 1 hour with no issues)
Deploy 3 (100% traffic): New build with compiler (after 6 hours with no issues)
Rollback available: Previous build without compiler (fast rollback < 5 min)
  1. Hour 0-1: Deploy to 10% of users. Monitor error rates, performance metrics.
  2. Hour 1-6: If no issues, deploy to 50%. Monitor continuously.
  3. Hour 6+: Deploy to remaining users.
  4. Rollback window: 24 hours. If issues emerge, rollback to previous build.

Production Monitoring

Key Metrics to Monitor

MetricAlert ThresholdCheck Frequency
Error rate> 0.5% above baselineEvery 1 min
Core Web Vitals (LCP)> 2.5sEvery 5 min
Core Web Vitals (INP)> 200msEvery 5 min
Memory usage (JS heap)> 10% above baselineEvery 5 min
HTTP 5xx errorsAny increaseReal-time
API latency (p95)> 10% above baselineEvery 5 min

Prometheus + Grafana Setup

Export metrics from your React app:

// src/metrics.js
import { collectDefaultMetrics } from "prom-client";

collectDefaultMetrics();

export function recordRenderTime(componentName, duration) {
renderTimeHistogram
.labels({ component: componentName })
.observe(duration);
}

export function recordBailout(componentName, reason) {
bailoutCounter
.labels({ component: componentName, reason })
.inc();
}

Expose metrics endpoint:

// server.js (Node.js example)
const express = require("express");
const register = require("prom-client").register;

app.get("/metrics", (req, res) => {
res.set("Content-Type", register.contentType);
res.end(register.metrics());
});

Datadog / New Relic Integration

If using Datadog or New Relic, create a monitor:

# Datadog monitor
name: React Compiler Deployment - Error Rate
type: metric alert
query: |
avg(last_5m): avg:error.count{env:production} >
avg:error.count{env:production}.week_before() * 1.1
action:
notify: "@oncall-team"
severity: high

Alert if error rate increases > 10% during/after deployment.

Real-Time Dashboard

Create a dashboard showing:

  1. Build compilation status (number of components compiled, bailouts).
  2. Error rate (pre/post deployment).
  3. Render time distribution (P50, P95, P99).
  4. Core Web Vitals (LCP, INP, CLS).
  5. Memory usage.

Example Grafana dashboard:

{
"dashboard": {
"title": "React Compiler Deployment",
"panels": [
{
"title": "Compilation Status",
"targets": [
{
"expr": "increase(compiled_components[1h])"
},
{
"expr": "increase(bailouts[1h])"
}
]
},
{
"title": "Error Rate",
"targets": [
{
"expr": "rate(errors_total[5m])"
}
]
},
{
"title": "LCP (50th, 95th, 99th percentile)",
"targets": [
{
"expr": "histogram_quantile(0.5, rate(lcp_ms_bucket[5m]))"
},
{
"expr": "histogram_quantile(0.95, rate(lcp_ms_bucket[5m]))"
}
]
}
]
}
}

Rollback Procedure

If issues emerge post-deployment:

Immediate Actions (Minutes 0-5)

  1. Detect the issue: Monitor alerts trigger; on-call engineer is notified.
  2. Verify it's compiler-related: Check if the issue started at deployment time.
  3. Communicate: Post an incident in Slack; notify stakeholders.

Rollback (Minutes 5-10)

# Revert to previous build (without compiler)
git revert <compiler-commit>
npm run build
npm run deploy

# Expected: rollout to production within 5 minutes

Post-Rollback (Minutes 10+)

  1. Monitor metrics: Ensure error rate, latency return to baseline.
  2. Post-mortem: Analyze what went wrong.
  3. Fix and redeploy: Address the issue and redeploy after verification.

Testing Before Deployment: Full Test Suite Example

# Unit tests
npm run test

# Integration tests (components + hooks)
npm run test:integration

# E2E tests (full user flows)
npm run test:e2e

# Performance benchmarks
npm run benchmark

# Build and serve locally
npm run build
npm run preview

# Lighthouse audit
npm run audit:lighthouse

# Compiler audit (verbose output)
npm run build 2>&1 | tee compiler-audit.txt
grep -E "bailout|error" compiler-audit.txt || echo "No unexpected bailouts"

Add this to your pre-deployment checklist:

#!/bin/bash
set -e

echo "1. Running unit tests..."
npm run test || exit 1

echo "2. Building with compiler..."
npm run build || exit 1

echo "3. Running E2E tests on production build..."
npm run test:e2e || exit 1

echo "4. Checking compiler output..."
BAILOUTS=$(npm run build 2>&1 | grep -c "✗" || true)
if [ "$BAILOUTS" -gt 20 ]; then
echo "ERROR: Too many bailouts: $BAILOUTS"
exit 1
fi

echo "All checks passed. Ready to deploy."

Key Takeaways

  • Test the compiler build thoroughly before deploying to production.
  • Integrate compiler audits into your CI/CD pipeline to catch issues early.
  • Use a canary rollout strategy: deploy to 10%, then 50%, then 100%.
  • Monitor key metrics: error rate, Core Web Vitals, memory usage.
  • Have a fast rollback procedure (< 10 minutes to revert).
  • Create a post-mortem after any issues to prevent future incidents.

Frequently Asked Questions

What if the compiler causes an error in production that we didn't catch in testing?

Have a fast rollback procedure. Revert to the previous build (without compiler) within 5 minutes. Then fix the issue, re-test, and redeploy.

Should we deploy the compiler on a specific day/time?

Yes. Deploy during business hours when your team is available to monitor. Avoid Fridays or holiday weekends. Ideal: Tuesday-Thursday, 10 AM-3 PM your timezone.

Can we do a partial rollout to specific routes?

Yes, if your deployment infrastructure supports it. You can serve different builds to different routes. For example, deploy the compiler build to the new product page first; monitor it for a week; then roll out to the rest of the app.

How long should we monitor after a deployment?

Monitor for at least 24 hours. Most issues emerge within the first hour, but subtle performance regressions or edge cases might take longer to surface.

Further Reading