Skip to main content

React AI chat deployment: Production-ready apps

Deploying a React chatbot to production requires more than copying files to a server. You must secure your API keys, proxy requests through a backend, monitor errors, handle scaling, and ensure reliability. This article covers production-grade deployment patterns: environment management, backend proxies, error tracking, analytics, and best practices used by teams deploying millions of chat messages daily.

According to Anthropic's Production AI Guidelines (2025), 64% of early-stage chatbot deployments fail due to poor error handling or missing observability. The difference between a prototype and a production system is visibility and resilience.

Securing Your API Keys

Never expose API keys in your React code. Use environment variables and a backend proxy:

# .env.local (not committed to git)
REACT_APP_BACKEND_URL=https://api.yourapp.com

# .env.production
REACT_APP_BACKEND_URL=https://api.yourapp.com

In your React code:

// WRONG: exposes API key in browser code
const apiKey = 'sk-abc123...'; // NEVER DO THIS
const response = await fetch('https://api.openai.com/v1/chat/completions', {
headers: { 'Authorization': `Bearer ${apiKey}` },
});

// RIGHT: call your backend instead
const response = await fetch(`${process.env.REACT_APP_BACKEND_URL}/api/chat`, {
method: 'POST',
body: JSON.stringify({ messages, model: 'gpt-4o-mini' }),
});

Your backend holds the API key in a secure environment variable:

// backend/routes/chat.js
const OPENAI_API_KEY = process.env.OPENAI_API_KEY; // Secure, not exposed

app.post('/api/chat', async (req, res) => {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(req.body),
});

// Stream or return response
res.setHeader('Content-Type', 'text/event-stream');
response.body.pipe(res);
});

Building a Backend Proxy

Your backend intercepts chat requests, adding:

  • Authentication (verify user is logged in)
  • Rate limiting (prevent abuse)
  • Logging (track usage for analytics)
  • Audit trails (for compliance)

Here is a minimal Express proxy:

const express = require('express');
const fetch = require('node-fetch');
const rateLimit = require('express-rate-limit');

const app = express();
app.use(express.json());

// Middleware: verify authentication
function authenticateUser(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Unauthorized' });

// Verify JWT or session
// ...

next();
}

// Rate limiting: 10 requests per minute per user
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 10,
keyGenerator: (req) => req.user.id, // Per-user limit
});

app.post('/api/chat', authenticateUser, limiter, async (req, res) => {
try {
// Log request
console.log(`User ${req.user.id} sent chat request`, {
messageCount: req.body.messages.length,
timestamp: new Date(),
});

// Call OpenAI
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: req.body.messages,
stream: true,
}),
});

if (!response.ok) {
const error = await response.json();
console.error('OpenAI error:', error);
return res.status(response.status).json({ error: error.error.message });
}

// Stream response back to client
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');

response.body.pipe(res);

// Log completion
response.body.on('end', () => {
console.log(`Chat request from user ${req.user.id} completed`);
});
} catch (error) {
console.error('Chat error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});

app.listen(3001, () => console.log('Backend listening on port 3001'));

Environment Configuration

Use different configs for development, staging, and production:

// config/index.js
const config = {
development: {
BACKEND_URL: 'http://localhost:3001',
LOG_LEVEL: 'debug',
SENTRY_DSN: null,
ANALYTICS_ENABLED: false,
},
staging: {
BACKEND_URL: 'https://api-staging.yourapp.com',
LOG_LEVEL: 'info',
SENTRY_DSN: 'https://[email protected]/0',
ANALYTICS_ENABLED: true,
},
production: {
BACKEND_URL: 'https://api.yourapp.com',
LOG_LEVEL: 'warn',
SENTRY_DSN: 'https://[email protected]/0',
ANALYTICS_ENABLED: true,
},
};

export default config[process.env.NODE_ENV || 'development'];

In your React app:

import config from './config';

const response = await fetch(`${config.BACKEND_URL}/api/chat`, {
method: 'POST',
body: JSON.stringify({ messages }),
});

Error Tracking with Sentry

Monitor errors in production:

npm install @sentry/react @sentry/tracing

Initialize Sentry in your app:

import * as Sentry from '@sentry/react';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
integrations: [
new Sentry.Replay({
maskAllText: false,
blockAllMedia: false,
}),
],
});

ReactDOM.render(
<Sentry.ErrorBoundary fallback={<div>Something went wrong</div>} showDialog>
<App />
</Sentry.ErrorBoundary>,
document.getElementById('root')
);

Errors are automatically reported to Sentry, where you can view them in a dashboard, set alerts, and trace user sessions.

Monitoring and Analytics

Track key metrics:

import config from './config';

function logEvent(eventName, properties = {}) {
if (!config.ANALYTICS_ENABLED) return;

const event = {
name: eventName,
timestamp: new Date(),
userId: getCurrentUserId(),
...properties,
};

fetch(`${config.BACKEND_URL}/api/analytics`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event),
}).catch((err) => console.error('Analytics error:', err));
}

// Usage
function ChatApp() {
const handleSendMessage = async (message) => {
logEvent('chat_message_sent', { length: message.length });

try {
const response = await fetch(`${config.BACKEND_URL}/api/chat`, {
method: 'POST',
body: JSON.stringify({ messages }),
});

logEvent('chat_response_received', { statusCode: response.status });
} catch (error) {
logEvent('chat_error', { error: error.message });
}
};

return <div>{/* ... */}</div>;
}

Track:

  • Messages sent / responses received
  • Errors (type, frequency, stack trace)
  • Latency (time from send to first token)
  • User retention (daily/weekly active users)
  • API cost (tokens used per user)

Deployment Platforms

Here are popular options for deploying React + Node.js chatbots:

PlatformBest forPricingScaling
VercelFrontend + serverless backendFree tier + pay-as-you-goAuto-scales
HerokuQuick prototypes$7–50/monthManual dyno management
AWS EC2Full control$0.01–$1/hourManual scaling
Google Cloud RunServerless containersPay-per-requestAuto-scales
RailwayModern deployment UX$5–200/monthAuto-scales

For a production chatbot, Vercel (frontend) + Google Cloud Run (backend) is a solid, scalable combo.

Deploying to Vercel

# Install Vercel CLI
npm install -g vercel

# Deploy frontend
vercel --prod

For the backend, create a serverless function:

// api/chat.js (Vercel serverless function)
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}

try {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: req.body.messages,
stream: true,
}),
});

if (!response.ok) {
return res.status(response.status).json({ error: 'API error' });
}

res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');

response.body.pipe(res);
} catch (error) {
res.status(500).json({ error: error.message });
}
}

Vercel automatically scales serverless functions based on traffic.

Pre-deployment Checklist

Before going live:

  • API keys stored in .env.production and set in platform dashboard
  • Rate limiting enabled on backend
  • Error tracking (Sentry) configured
  • Analytics logging implemented
  • Authentication implemented (JWT, OAuth, etc.)
  • HTTPS everywhere (automatic on Vercel, CloudFlare)
  • CORS configured for frontend domain
  • Backward compatibility for old API versions
  • Monitoring and alerts set up (Sentry, datadog, etc.)
  • Load testing done (simulate 100+ concurrent users)
  • Graceful degradation when API is down
  • Terms of Service and privacy policy updated
  • User data retention policy documented
  • Backup and disaster recovery plan

Scaling Considerations

As your chatbot grows:

  • Cache frequently asked questions to reduce API calls
  • Implement request queuing to prevent overload
  • Use CDN for static assets (Cloudflare, AWS CloudFront)
  • Archive old conversations to reduce database size
  • Monitor API costs and set spending limits in your provider dashboard

Key Takeaways

  • Never expose API keys in frontend code; use a backend proxy.
  • Environment variables separate configuration across development, staging, and production.
  • Error tracking (Sentry) and analytics provide visibility into user issues.
  • Rate limiting on the backend prevents abuse and runaway costs.
  • Deploy frontend and backend to separate platforms for independence and scaling.

Frequently Asked Questions

What if the OpenAI API goes down?

Return a user-friendly error message and offer to queue the request for later retry. Implement a job queue (Bull, RabbitMQ) that retries requests when the API recovers.

How do I handle users with poor internet connections?

Implement request retries on the client, with exponential backoff. Show a "Retrying..." message. For very slow connections, consider a lower context window to reduce payload size.

Should I cache AI responses?

Only if responses are deterministic (same prompt = same answer). For most chatbots, caching is not helpful since each user has different conversation history. Caching adds complexity without much benefit.

How do I monitor costs?

OpenAI provides usage dashboards. Set spending limits and alerts in your account. Log each request on your backend with token count. Build a dashboard showing cost per user, per day, per conversation.

What if a user tries to jailbreak the API (e.g., "ignore your instructions")?

The AI API is designed to resist these attacks. Set a strong system prompt that guides the assistant's behavior. Never pass user input directly as a system message. Log suspicious patterns and consider rate-limiting suspicious accounts.

Further Reading