Video files are massive, but one of the most popular–and essential–parts of how people use the internet today. According to
Statista
, video content accounted for over 80% of global internet traffic in 2023. For developers, mastering Node.js video compression is essential to optimize bandwidth, accelerate uploads, and enhance user experiences.
In this article, I’ll guide you through compressing videos with Node.js, utilizing FFmpeg for efficient local processing, automating workflows, and scaling video management with Cloudinary’s robust transformation APIs.
Effective video compression, especially with Node.js, is vital for the performance, cost-effectiveness, and user experience of today’s web applications. Here’s why:
Faster Load Times
: Compressed videos reduce page load times, improving performance and user retention.
Bandwidth Savings
: Smaller files lower hosting costs and data usage, crucial as videos drive over 80% of internet traffic (as mentioned before).
Smooth Playback
: Proper compression ensures seamless streaming, even on mobile networks.
Scalable Workflows
: Tools like FFmpeg and Cloudinary streamline processing for high-volume video uploads.
Device Compatibility
: Compression enables format and resolution optimization for diverse devices.
By using Node.js video compression, developers can balance quality and efficiency, delivering fast, scalable, and user-friendly video experiences.
Setting Up a Node.js Environment for Video Processing
Before we dive into compressing videos using Node.js, let’s first make sure our development environment is all set up. We’ll be using Node.js to write and run our video compression scripts, so the first step is to install Node if you haven’t already.
Head over to the
official Node.js website
and download the latest LTS version. For this tutorial, you’ll need at least Node.js v20.15.1 and npm v10.8.3. Once installed, open your terminal or command prompt and run:
node -v
npm -v
This will confirm that both Node and npm are properly installed.
Next, you need to install FFmpeg. So, head over to the
official FFmpeg website
and download and install FFmpeg using the instructions on the website. Once installed, open up your terminal and type the following command to check if FFmpeg was installed correctly:
ffmpeg -version
You should see an output that confirms the version of FFmpeg installed. This means everything is set up correctly so far.
Now, let’s initialize a new Node.js project where we’ll build our compression script. Create a new directory and run the following command inside it:
npm init -y
This generates a basic
package.json
file that will manage our dependencies.
Finally, install the
fluent-ffmpeg
library using the following command:
npm install fluent-ffmpeg
At this point, we have everything we need to start compressing videos from our Node.js app.
Using FFmpeg with Node.js for Video Compression
With everything ready, we can create a basic video compression function using FFmpeg through the
fluent-ffmpeg
library.
So, start by opening up your project directory in the editor of your choice. Next, create a new JS file and start by importing the required modules:
Here,
fluent-ffmpeg
gives us an easy interface to FFmpeg’s complex command-line tools, while
path
helps us safely build file paths across different operating systems.
Next, let’s define a simple function that takes an input video path and compresses it to an output path:
function compressVideo(inputPath, outputPath) {
ffmpeg(inputPath)
.outputOptions([
'-vcodec libx264', // Use H.264 codec for video compression
'-crf 28', // Set quality: lower CRF = higher quality
'-preset fast' // Trade-off between compression speed and efficiency
.on('end', () => {
console.log('Compression complete.');
.on('error', err => {
console.error('Error:', err.message);
.save(outputPath);
In this code, we start with ffmpeg(inputPath), which loads the video we want to compress. The .outputOptions method lets us define how the compression should work. For now, we use the H.264 codec for compatibility, set a CRF value of 28 to reduce file size while keeping decent quality, and apply the fast preset to speed things up. The .on callback runs when the compression finishes, and .on('error', ... helps catch and log any errors. Finally, .save(outputPath) tells FFmpeg where to write the compressed video.
Now all you need to do is call this function with paths to your original and compressed videos:
Once the script runs, you’ll get a smaller video file in the destination path, helping you save both storage and upload time.
Automating Video Compression in Server-Side Workflows
When you’re dealing with just a handful of video files, running compression manually might be fine. But once your app starts accepting user uploads at scale, you need a reliable and automated server-side workflow to handle the compression efficiently, and without introducing bottlenecks.
A practical approach here is to hook into your backend’s upload handling logic (whether it’s built with Express, NestJS, or something else) and invoke FFmpeg compression automatically after a video is uploaded to your server. You can queue up jobs using task schedulers like BullMQ or Agenda, so that compression happens asynchronously, keeping your API responses fast and responsive.
So let’s take a look at how you can automate video compression using Express, Multer for handling uploads, and fluent-ffmpeg for compression.
Let’s begin by installing the above libraries using npm:
npm install express multer fluent-ffmpeg
Next, open up a new JS file and start by importing the required packages:
Here, Multer saves each uploaded file into the uploads folder and gives it a unique filename so nothing gets overwritten.
Now let’s build the core of our app. Here, we will build a POST route that will accept a file upload, compress the video using FFmpeg, and then send the compressed version back to the client:
This route listens for uploads at /upload. When a file is uploaded under the field name "video", Multer saves it to disk and makes it available through req.file. We grab the file’s path and also define a new path for the compressed output, which will exist inside the compressed/ directory.
Next, we use fluent-ffmpeg to run the compression:
Once FFmpeg finishes compressing the video, we send it back to the client and clean up the original upload. This ensures the user gets the compressed file right away and your server doesn’t fill up with temporary files:
Once FFmpeg finishes, the .on('end') callback fires. Here, we send the compressed video back to the client using res.download(), and then we delete the original uploaded file to free up space with fs.unlinkSync(inputPath).
If there’s an error during processing, we catch it and return a clean error response:
Now, when you run this server and POST a video to /upload, it will compress the file and return the optimized version immediately, without you having to run any extra scripts.
In real production apps, you’d likely go beyond this by queuing compression jobs (with something like BullMQ) or uploading directly to cloud storage like Amazon S3. But if you’re looking for a fully managed, scalable alternative with zero backend setup, that’s exactly where Cloudinary comes in. It handles uploads, compresses videos automatically, and delivers them globally—all from a single URL or API call. We’ll look at how that works next.
Node.js Video Compression At Scale Through Cloudinary
Manually compressing videos with FFmpeg works great when you’re just getting started. But as your app grows and users begin uploading more and more videos, managing that processing on your own servers can quickly become a headache. It adds complexity, increases processing time, and demands more infrastructure to handle the load, especially if you’re dealing with large files or high traffic.
Cloudinary is a powerful media platform that handles not just storage, but also compression, transformation, optimization, and delivery–all through a simple, developer-friendly API. Instead of writing complex scripts or spinning up video processing servers, you can offload all of that to Cloudinary’s global infrastructure and focus on building your app.
Want to get a headstart on integrating Cloudinary into your project? Check out our documentation for more information.
To get started, you’ll first need to create a new project directory and install the Cloudinary Node.js SDK using npm:
npm install cloudinary
Next, head over to Cloudinary and log in to your account. If you don’t have one already, you can sign up for a free account. Next, head over to the Programmable Media Dashboard tab and click on the Go to API Keys button. Here, you will find your API credentials. Copy these as we will need them.
Now open up a JS file in your project directory and start by configuring the Cloudinary API in your Node.js environment:
Remember to replace the your-cloud-name, your-api-key, and your-api-secret with your own credentials.
Finally, we need a video to compare our transformations and deliveries. Thankfully, Cloudinary comes with a whole host of assets that we can use, so for now, we will be using sea-turtle from the Cloudinary demo cloud.
Now you’re ready to upload videos and apply transformations like compression, resolution resizing, and format conversion–all in the cloud, no FFmpeg required.
Applying Video Transformations Using Cloudinary’s API
Cloudinary makes video compression feel like magic. You can apply transformations like resolution resizing, quality optimization, or format conversion during upload, all with a few lines of code:
Here, we call cloudinary.uploader.upload() and pass the local path to the video we want to upload. The resource_type: 'video' flag tells Cloudinary that this is a video file (not an image), while the public_id is the name we want to give the uploaded video on Cloudinary. The real magic happens inside the eager array. This is where we define our transformation settings:
The width: 720 resizes the video to 720 pixels wide, which is great for web playback.
quality: 'auto' tells Cloudinary to automatically optimize compression while preserving visual quality. This uses AI and perceptual metrics to make smart decisions about bitrate.
format: 'mp4' ensures the output is encoded in a widely supported, streamable format.
When the upload completes, the .then() block logs the URL of the optimized video. The result is a ready-to-stream, optimized video URL that you can serve to users. Here is what the output looks like:
Cloudinary handles all the backend heavy lifting–transcoding, compressing, and caching your video globally.
Retrieving and Delivering Compressed Videos
Once your videos are uploaded, delivering them to end-users is just as smooth. Cloudinary gives you a dynamic URL builder that lets you transform videos in real-time:
In this snippet, we’re generating a URL that returns a version of sample.mp4 scaled down to 480 pixels in width and compressed using Cloudinary’s auto quality mode. This is perfect for delivering mobile-friendly versions of videos that load quickly without sacrificing too much visual quality. The transformation happens in real time, and Cloudinary caches the result, so future requests are served instantly.
Now let’s say you want to deliver a square-cropped version of the same video, which is very useful for social media previews or in-app thumbnails. You can do that like this:
Here we resize the video to exactly 480×480 pixels and use crop: 'fill' to fill the frame, automatically centering the important part of the content with gravity: 'auto'. It’s then compressed and delivered as an MP4.
You can also strip the audio track from the video entirely if you want to deliver a muted version. This can be useful for silent previews or compliance in certain environments:
Cloudinary’s transformation URLs give you full control over how videos are presented–whether you want them resized, cropped, trimmed, muted, converted to another format, or optimized in real-time. And since the CDN caches each transformed version, they load almost instantly after the first request, with no extra load on your servers.
With these tools, you can build highly dynamic and responsive video experiences with no need to pre-process or store multiple versions of your assets. Whether you’re optimizing for speed, user experience, or bandwidth, Cloudinary handles the heavy lifting–and scales effortlessly with your app.
Working with Different Video Formats and Codecs
Let’s be honest, MP4 gets all the love. But if you’re aiming for peak performance, especially across different browsers, bandwidth constraints, and use cases, understanding and leveraging different video formats and codecs becomes a must.
Each format has its trade-offs. For instance, MP4 (using H.264 compression) is widely compatible but not the most efficient in compression. WebM and AV1, on the other hand, offer superior compression ratios and great for modern browsers.
With Cloudinary, switching between formats and codecs doesn’t require re-encoding videos manually. You can simply upload once and deliver in multiple formats, depending on your audience’s needs and devices.
Converting Between Formats During Compression
Let’s start with format conversion during the upload process. Suppose you want to convert a video to WebM format and apply automatic compression while uploading. Cloudinary makes it super easy:
// Upload and convert format during upload:
cloudinary.uploader.upload('path/to/your/video.mp4', {
resource_type: 'video',
eager: [
{ format: 'webm', quality: 'auto' } // Convert to webm with auto compression
}).then(result => {
console.log('WebM URL:', result.eager[0].secure_url);
Here, Cloudinary takes your source video and transforms it into WebM with the format parameter. We also set the quality parameter to auto to ensure a great balance between file size and visual quality. Cloudinary generates and caches the transformed file immediately after upload, making it ready to serve as soon as needed.
If you’d rather convert the format on the fly, without pre-processing during upload, Cloudinary makes that easy too:
// On the fly
const convertedUrl = cloudinary.url('sample.mp4', {
resource_type: 'video',
format: 'webm', // Convert to .webm format
transformation: [
{ quality: 'auto' }
console.log('WebM video:', convertedUrl);
This code dynamically transforms the original MP4 to a compressed WebM file at request time. It’s incredibly useful when you need to serve different formats to different browsers (for example, MP4 for iOS and WebM for Chrome), all from the same uploaded asset.
Adjusting Resolution, Bitrate, and Frame Rate
Sometimes, format isn’t the only concern–you might need to optimize videos for mobile viewing, reduce frame rates for smoother delivery, or apply bandwidth-conscious compression. Different use cases require different optimization strategies. You might want high-resolution for desktop, low-bandwidth streams for mobile, or capped frame rates for smoother delivery. Cloudinary lets you control all of this through simple transformation parameters.
Here’s an example that combines multiple video optimizations in one go:
// Upload and convert format during upload:
const transformedUrl = cloudinary.url('sample', {
resource_type: 'video',
transformation: [
{ width: 720, crop: 'scale' }, // Resize to 720p
{ quality: 'auto:eco' }, // Smart compression (can be 'auto', 'auto:eco', etc.)
{ video_codec: 'auto' }, // Optimal codec
{ fps: "24" } // Set max frame rate to 24fps
format: 'mp4'
console.log('Optimized video URL:', transformedUrl);
In this transformation, the video is scaled down to 720 pixels in width, a common resolution for desktop and tablet screens. The auto:eco setting for the quality parameter applies a smart compression preset optimized for good visual clarity. By setting the codec to auto, Cloudinary selects the most efficient codec for the device and browser requesting the video. We cap the frame rate at 24fps, a common standard for cinematic playback that also reduces file size.
You can also bake all of these settings directly into the upload using eager transformations, like this:
Here we upload a video and generate an AVIF version, resized to a maximum of 1080 pixels wide using crop: 'limit' to avoid upscaling smaller videos. The result is compressed to 25fps and served in AVIF–a next-gen format known for its superior compression ratios.
With all of these tools at your fingertips, you can adapt your videos to any device, network condition, or user experience goal, without ever manually reprocessing your files. Cloudinary handles the transformations, caching, and delivery seamlessly behind the scenes.
Deliver Efficient Videos, Every Time
Video compression is a non-negotiable aspect of modern web development, and in this article, we’ve explored practical ways to achieve it using Node.js video compression techniques. We covered setting up a Node.js environment with FFmpeg to compress videos manually or automatically, offering developers precise control over the process. For handling large-scale workflows Cloudinary’s scalable platform simplifies video management, from upload to delivery, with powerful transformation APIs.
While FFmpeg excels for local processing, Cloudinary automates compression, format selection, and delivery, ensuring videos load quickly and look great everywhere. Users demand instant playback without buffering, and these tools make that possible with minimal effort.
Ready to streamline your video uploads and enhance user experience? Start using Cloudinary today!
In my experience, here are tips that can help you better speed up uploads and optimize Node.js video compression workflows:
Pre-validate video metadata before compression Extract metadata (e.g., duration, resolution, codec) using ffprobe before compression. Skip or reroute unsupported formats and avoid wasting compute on already optimized files.
Use -movflags +faststart for better streaming When outputting MP4s, add this FFmpeg flag to move the moov atom to the start of the file, allowing browsers to start playback before the file is fully downloaded.
Throttle concurrent FFmpeg processes On high-traffic servers, use a job queue with process limits (e.g., using BullMQ or p-queue) to prevent FFmpeg from saturating CPU and crashing your Node app.
Segment and compress in chunks for large files For extremely large uploads, segment them using -segment_time and compress each chunk independently. This avoids memory spikes and enables progressive upload or parallel processing.
Generate thumbnails during compression Use ffmpeg.screenshots() or -vf "thumbnail" to extract poster frames mid-transcode. Displaying these improves perceived performance and helps with media previews.
Detect and skip silent compressions If the CRF-based output results in negligible size savings, skip re-compression. Check input/output file sizes before finalizing writes to conserve CPU and disk.
Cache results by hashing input content Generate a content hash (e.g., SHA-256) of the input video and cache the compression result. If the same video is uploaded again, instantly return the cached version.
Use lossless codecs for critical workflows For source material used in further processing (e.g., editing or transcoding), offer a lossless version using -crf 0 and store in a separate pipeline to preserve quality.
Profile FFmpeg performance with benchmarking Time FFmpeg executions under different presets (ultrafast, medium, veryslow) and CRF values to find the best balance of speed vs. file size for your use case.
Integrate Cloudinary’s upload presets for consistent UX Define reusable presets in Cloudinary with specific codecs, bitrates, and transformations. This ensures consistency across uploads and decouples config from code changes.