
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Nuxt applications frequently hit performance ceilings in production not because of framework flaws, but because teams treat them as static sites rather than stateful Node.js services. To effectively scale and monitor Nuxt in production, you must shift from default development settings to explicit resource management, implementing multi-process clustering, tiered caching, and distributed tracing before traffic spikes occur. This guide covers the operational primitives required to keep Nuxt responsive and observable under real-world load, building on the four golden signals of monitoring that define reliable service delivery.
How do you configure PM2 to scale Nuxt in production?
The single most common bottleneck I see in Nuxt deployments is running a single Node.js process on multi-core servers. Nuxt 3 runs on Nitro, which is still bound by Node’s single-threaded event loop for server-side rendering. You must use a process manager to fork workers across available CPUs. PM2 remains the standard for this in 2026 due to its built-in clustering, log management, and zero-downtime reloads.
Create an ecosystem configuration file
Do not start Nuxt with node .output/server/index.mjs directly in production. Create an ecosystem.config.cjs at your project root:
module.exports = {
apps: [{
name: 'nuxt-prod',
script: '.output/server/index.mjs',
instances: 'max',
exec_mode: 'cluster',
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'production',
PORT: 3000,
NITRO_PRESET: 'node-server'
}
}]
}; Key parameters explained:
- instances: 'max' — Forks one worker per logical CPU core. On a 4-core VPS, this gives you 4x SSR throughput.
- exec_mode: 'cluster' — Uses Node’s native cluster module so all workers share the same port via round-robin.
- max_memory_restart: '1G' — Safety valve. If a worker leaks memory beyond 1GB, PM2 restarts only that worker, preventing total outage.
- watch: false — Never enable file watching in production; it adds filesystem overhead and can cause unexpected restarts during deployments.
Validate cluster health after deployment
After starting with pm2 start ecosystem.config.cjs, verify all workers are online and balanced:
pm2 list
pm2 monit In practice, if you see one worker consuming significantly more CPU or memory than others, investigate route-specific bottlenecks or middleware issues. Uneven distribution often points to blocking synchronous code in server routes or plugins. For teams managing multiple environments, integrating this into a GitOps workflow with ArgoCD ensures consistent process management across staging and production.
How do you implement tiered caching in Nuxt to reduce server load?
Scaling compute is expensive; reducing unnecessary computation is cheaper. Nuxt’s Nitro engine supports multiple storage backends for caching, but many teams never move beyond the default in-memory driver, which doesn’t survive restarts and isn’t shared across cluster workers. For any serious production workload, you need a shared external cache.
Configure Redis as the primary cache driver
In your nuxt.config.ts, define named storage mounts that point to Redis:
export default defineNuxtConfig({
nitro: {
storage: {
cache: {
driver: 'redis',
host: process.env.REDIS_HOST || '127.0.0.1',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
tls: process.env.REDIS_TLS === 'true'
},
session: {
driver: 'redis',
host: process.env.REDIS_HOST || '127.0.0.1',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
base: 'session:'
}
},
routeRules: {
'/api/products/': { cache: { maxAge: 300, staleMaxAge: 3600 } },
'/blog/': { cache: { maxAge: 600, staleWhileRevalidate: 86400 } },
'/dashboard/**': { ssr: false }
}
}
}); This configuration achieves three things simultaneously:
- Shared cache across workers — All PM2 cluster instances read/write the same Redis keys, eliminating redundant API calls and database queries.
- Stale-while-revalidate — Users get instant responses from stale cache while Nitro refreshes content in the background, dramatically improving perceived latency.
- Session persistence — Authentication state survives individual worker restarts and deployments.
Cache invalidation strategy
A common mistake is setting long TTLs without an invalidation mechanism. When underlying data changes, you must proactively purge affected keys. Use Nitro’s useStorage('cache').removeItem() in your mutation endpoints or webhook handlers. For high-traffic e-commerce sites serving Nepali markets where inventory changes frequently during festival seasons, consider tag-based invalidation patterns rather than individual key removal to avoid race conditions.
How do you instrument Nuxt with OpenTelemetry for distributed tracing?
Logs tell you what happened; traces tell you where time was spent. Without distributed tracing, debugging slow SSR renders in Nuxt is guesswork. OpenTelemetry (OTel) is now the de facto standard, and Nitro has native support for it. Proper instrumentation lets you see exactly whether latency comes from database queries, external APIs, or template rendering.
Install and configure the OTel SDK
Add the required packages and create an instrumentation entry point:
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http Create server/instrumentation.ts:
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces'
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': { enabled: true },
'@opentelemetry/instrumentation-redis': { enabled: true },
'@opentelemetry/instrumentation-pg': { enabled: true }
})
]
});
sdk.start(); Register this in nuxt.config.ts under nitro.imports or as a server plugin to ensure it loads before any route handlers. For a complete setup guide including collector configuration, refer to OpenTelemetry: The Observability Standard.
Add custom spans for business logic
Auto-instrumentation covers infrastructure, but your application logic needs manual spans to be meaningful:
import { trace } from '@opentelemetry/api';
export default defineEventHandler(async (event) => {
const tracer = trace.getTracer('nuxt-products');
return tracer.startActiveSpan('fetchProducts', async (span) => {
try {
const products = await db.query('SELECT * FROM products WHERE active = $1', [true]);
span.setAttribute('products.count', products.length);
return products;
} catch (error) {
span.recordException(error);
span.setStatus({ code: 2 }); // ERROR
throw error;
} finally {
span.end();
}
});
}); This level of detail transforms debugging from log-diving into visual timeline analysis. When combined with structured logging best practices, you can correlate traces with specific log entries instantly.
What metrics and SLOs should you track for Nuxt reliability?
Monitoring everything leads to alert fatigue. Focus on signals that directly reflect user experience and business outcomes. For Nuxt applications, four metric categories matter most:
| Metric Category | Specific Signal | Recommended SLO Target | Why It Matters |
|---|---|---|---|
| Latency | p95 SSR response time | < 800ms | Directly impacts Core Web Vitals and SEO rankings |
| Error Rate | 5xx responses / total requests | < 0.1% | Indicates broken functionality or infrastructure failure |
| Saturation | Worker memory usage % | < 80% sustained | Predicts OOM kills before they cause outages |
| Cache Efficiency | Cache hit ratio | > 85% | Low ratios indicate misconfigured TTLs or cache stampedes |
Define alerts based on error budgets, not thresholds
Static threshold alerts ("CPU > 90%") generate noise. Instead, implement SLO-based alerting that fires only when your error budget is being consumed faster than expected. A 99.9% availability SLO allows 43 minutes of downtime per month. Alert when burn rate indicates you’ll exhaust that budget within days, not when a momentary spike occurs. This approach aligns engineering incentives with business reliability targets and reduces 3AM pages for non-issues. Teams adopting this pattern alongside SLO-driven alerting principles typically see alert volume drop by 60–80% while catching genuine incidents earlier.
Scale and Monitor Nuxt in Production: Next Steps
Reliable Nuxt operations come from treating the framework as a production Node.js service, not a static site generator. Implement PM2 clustering first—it’s the highest-ROI change for immediate throughput gains. Then add Redis-backed caching to protect your origin during traffic spikes. Finally, instrument with OpenTelemetry and define SLOs before you need them, not during an incident. These patterns have proven effective across dozens of production deployments I’ve architected, from Kathmandu-based startups to global platforms. If your team needs help designing a production-grade Nuxt infrastructure or establishing observability baselines, reach out to discuss your specific requirements.