Node.js Strategies That Actually Hold Up in Production (2026)

Production Node.js in 2026 means running Node 22 LTS or 24, never blocking the event loop with CPU-heavy work, and offloading real computation to workerthreads. Use clusters or PM2 across cores, stream instead of buffering, and pick Bun only for new small projects, not rewrites.
Part of the complete web development guide.
On this page
- Pick the right runtime and stick with it
- Stop blocking the event loop
- Stream instead of buffering
- Caching is not optional
- Handle errors like they’re going to happen
- Think about deployment before you think about frameworks
- When to bring in help
- FAQ
- Is Node.js still a good choice for new projects in 2026?
- What’s the most common Node.js performance mistake?
- Do I need Redis if I’m running one Node instance?
Most Node.js advice online is written by people who have never had to wake up at 2am because a process ran out of memory. I’ve been building Node services for years, and the strategies that matter in 2026 are less about clever tricks and more about respecting how the runtime actually behaves under load. Here’s what I actually do, and what I tell people who ask.
Pick the right runtime and stick with it
Node 22 is the current LTS line, and Node 24 is the active release. If you’re still on Node 16 or 18, you’re missing meaningful improvements in the V8 engine, native fetch stability, and the built-in test runner that finally makes Jest optional for smaller projects.
The bigger decision in 2026 is whether Node is even the right tool. Bun and Deno have matured. For most HTTP APIs and anything touching the npm ecosystem, Node is still the safe bet. For scripts, CLIs, and greenfield services where you control every dependency, Bun’s startup time and built-in tooling are genuinely nice. I don’t rewrite working Node services to chase benchmarks. I do reach for Bun on new small projects.
Stop blocking the event loop
This is the single most common cause of “Node is slow” complaints. The event loop is single-threaded for your JavaScript. Anything CPU-heavy blocks every other request.
Bad:
app.get('/report', (req, res) => {
const data = hugeArray.map(row => expensiveTransform(row));
res.json(data);
});
Better, using the built-in worker pool via worker_threads for genuinely heavy work, or offloading to a separate service. For lighter CPU work, setImmediate and chunking help, but they don’t fix a fundamentally blocking operation.
import { Worker } from 'node:worker_threads';
function runReport(payload) {
return new Promise((resolve, reject) => {
const worker = new Worker('./report-worker.js', { workerData: payload });
worker.once('message', resolve);
worker.once('error', reject);
});
}
app.get('/report', async (req, res) => {
res.json(await runReport(req.query));
});
If you’re doing image processing, PDF generation, or heavy crypto, that work belongs in a worker or a separate process. Not on the main thread.
Stream instead of buffering
Reading a whole file into memory to send it is a habit that works fine in development and falls over in production. Node’s streams are the answer, and they’ve been stable for years.
import { createReadStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
app.get('/download/:file', async (req, res) => {
const path = resolveUpload(req.params.file);
res.setHeader('Content-Type', 'application/octet-stream');
await pipeline(createReadStream(path), res);
});
The same principle applies to proxying upstream responses, parsing large JSON, and handling uploads. If you’re calling await res.json() on a 200MB payload, you have a memory problem waiting to happen.
Caching is not optional
Every production Node service I’ve worked on eventually needed caching, and the team that added it early spent less time firefighting. The layers that matter:
- HTTP caching headers on anything static or semi-static.
Cache-Controlwith sensiblemax-ageandstale-while-revalidategets you a lot for free. - In-process caching for hot config or lookups that rarely change. A simple Map with a TTL is fine for small datasets.
- Redis or similar when you have multiple instances and need shared state. Don’t try to keep a shared cache in memory across a horizontally scaled service.
The mistake I see most often is caching the wrong thing. Cache reads, not writes. Cache stable data, not user-specific data unless you’re keying correctly. And always have a way to bust the cache without redeploying.
Handle errors like they’re going to happen
Unhandled promise rejections crash the process in modern Node. That’s a feature, not a bug, but it means you need to be deliberate.
process.on('unhandledRejection', (reason) => {
logger.fatal({ reason }, 'unhandled rejection');
process.exit(1);
});
process.on('uncaughtException', (err) => {
logger.fatal({ err }, 'uncaught exception');
process.exit(1);
});
Exiting on uncaught errors is correct. A process in an unknown state should die and be replaced by your orchestrator, not limp along serving corrupted responses. Pair this with a proper structured logger (pino is still my default) and you’ll actually be able to diagnose issues.
Think about deployment before you think about frameworks
Whether you’re deploying to a VPS, containers, or a serverless platform changes your architecture more than your choice of Express vs Fastify. Cold starts, connection pooling, and graceful shutdown all behave differently.
Graceful shutdown is the one people forget:
const server = app.listen(3000);
process.on('SIGTERM', () => {
server.close(() => {
db.end().then(() => process.exit(0));
});
});
Without this, in-flight requests get killed during deploys and your users see errors. It takes five minutes to add and saves real pain.
When to bring in help
If you’re running a business and Node is the thing keeping the lights on, the strategies above are the difference between a service that scales and one that needs constant babysitting. I do custom web development for clients around Commack and across Long Island, and most of the work is exactly this: taking an app that mostly works and making it hold up under real traffic. If you’re not sure whether your setup has these problems, that’s usually a sign it does.
FAQ
Is Node.js still a good choice for new projects in 2026?
Yes, for most web APIs and anything that benefits from the npm ecosystem. Bun and Deno are worth considering for smaller or greenfield projects, but Node’s stability and tooling are hard to beat for production services.
What’s the most common Node.js performance mistake?
Blocking the event loop with CPU-heavy synchronous work. If a single request can freeze your whole process, no amount of horizontal scaling will save you. Move that work to a worker or a separate service.
Do I need Redis if I’m running one Node instance?
Not necessarily. In-process caching works fine for a single instance. The moment you scale to two or more, you need shared state, and that’s when Redis or an equivalent starts earning its keep.


