New to Rust? Grab our free Rust for Beginners eBook Get it free →
node-sass in Node.js: What it is, How it works and Why you need to Migrate

node-sass was the go-to way to compile Sass to CSS inside Node.js for nearly a decade. In July 2024, the Sass team officially marked it end-of-life. This article covers what node-sass actually does, how to set it up in a Node.js project, the Node.js version compatibility table you need to avoid binding errors and the exact steps to migrate to Dart Sass, the current recommended replacement.
If you’ve hit the Error: Cannot find module './build/Release/node-sass.node' wall in your terminal, you’re in the right place.
What is node-sass?
node-sass is a Node.js binding (a JavaScript wrapper) around LibSass, a C++ implementation of the Sass compiler. Sass (Syntactically Awesome Stylesheets) extends CSS with variables, nesting, mixins and more.
How node-sass compiles Sass to CSS
When you run npm run compile-sass, node-sass calls LibSass under the hood. LibSass parses your .scss or .sass source, applies any @import rules, resolves variables and mixins, then outputs a plain .css file. The binding is what lets Node.js (which speaks JavaScript) call into C++ code.
Because the C++ side has to compile to a native binary for your exact OS and processor, node-sass ships pre-compiled binaries for a fixed list of Node.js versions. If your Node version isn’t on that list, the install falls back to compiling from source via node-gyp, which needs Python 2.7 and Visual Studio on Windows. That’s where most node-sass binding errors come from.
What node-sass does well
- Speed at compile time: LibSass is written in C++ so it compiles large Sass codebases fast.
- Source maps: It generates
.mapfiles that link compiled CSS back to the original.scsslines, making browser debugging straightforward. - CLI usage: The CLI flag
--output-style compressedminifies output directly, no extra tooling needed. - Caching: node-sass caches compiled results so partial rebuilds skip unchanged files.
- Cross-platform: Pre-compiled binaries ship for Windows, macOS and Linux, as long as your Node version is supported.
Installing node-sass
npm install node-sass --save-dev
This installs node-sass as a dev dependency. After install, you’ll see a postinstall script run that either downloads a pre-compiled binary or kicks off node-gyp to build one. A slow postinstall (sometimes over a minute on CI) is the main complaint developers have about node-sass.
Verify the install worked:
node -e "var sass = require('node-sass'); console.log(sass.info);"
node-sass in a Node.js + Express project
Here’s a minimal Express project that uses node-sass to compile a .scss file on startup. If you’re new to Express, the Express tutorial covers the basics.
Project structure
project/
├── public/
│ └── styles/
│ └── style.scss
├── views/
│ └── index.ejs
├── server.js
└── package.json
style.scss
$primary: #3498db;
$white: #ffffff;
body {
margin: 0;
padding: 0;
font-family: sans-serif;
background: $primary;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: $white;
border-radius: 4px;
h1 {
color: $primary;
}
}
server.js
const express = require('express');
const sass = require('node-sass');
const path = require('path');
const fs = require('fs');
const app = express();
const port = 3000;
// Compile Sass to CSS before serving
sass.render(
{
file: path.join(__dirname, 'public/styles/style.scss'),
outputStyle: 'compressed',
outFile: path.join(__dirname, 'public/styles/style.css'),
sourceMap: true,
},
function (err, result) {
if (err) {
console.error('Sass compile error:', err.message);
return;
}
fs.writeFileSync(path.join(__dirname, 'public/styles/style.css'), result.css);
console.log('Sass compiled successfully');
}
);
app.set('view engine', 'ejs');
app.use('/styles', express.static(path.join(__dirname, 'public/styles')));
app.get('/', (req, res) => {
res.render('index');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
package.json scripts
For a simpler CLI-based approach, add this to your package.json scripts:
{
"scripts": {
"compile-sass": "node-sass public/styles/style.scss public/styles/style.css",
"watch-sass": "node-sass --watch public/styles/style.scss public/styles/style.css"
}
}
Then run:
npm run compile-sass
# or keep it watching for changes:
npm run watch-sass
Using the sass.render API
node-sass exposes both a sync and async render method:
const sass = require('node-sass');
// Async (preferred)
sass.render(
{
file: './styles/main.scss',
outputStyle: 'expanded', // nested | expanded | compact | compressed
sourceMap: true,
outFile: './styles/main.css',
includePaths: ['./node_modules'], // lets @import resolve packages
},
function (err, result) {
if (err) {
console.error(err.line, err.message);
} else {
// result.css is a Buffer
console.log(result.css.toString());
}
}
);
// Sync
const result = sass.renderSync({
data: '$color: red; body { background: $color; }',
outputStyle: 'compressed',
});
console.log(result.css.toString());
node-sass API options reference
These are the options you’ll use day-to-day:
| Option | Type | Default | What it does |
|---|---|---|---|
file | String | — | Path to your .scss entry file |
data | String | — | Sass string to compile directly |
outputStyle | String | nested | Output format: nested, expanded, compact or compressed |
sourceMap | Boolean / String | false | Emit a .map file. Set to true or a file path |
outFile | String | — | Required when sourceMap is true |
includePaths | Array | [] | Directories LibSass searches for @import targets |
indentWidth | Number | 2 | Spaces per indent level in output |
precision | Integer | 5 | Decimal digits in output numbers |
node-sass Version Compatibility with Node.js
The biggest source of pain with node-sass is version mismatches. node-sass compiles a native binary for a particular Node.js ABI (Application Binary Interface). Mismatches produce the binding error you’ve probably seen.
Here’s the compatibility table:
| node-sass version | Supported Node.js versions |
|---|---|
| 9.x | 18, 20 |
| 8.x | 16, 18 |
| 7.x | 12, 14, 16 |
| 6.x | 10, 12, 14, 16 |
| 5.x | 10, 12 |
| 4.x | 6, 8, 10 |
If you’re on Node 20 or 22 with an older node-sass version, you’ll hit the binding error. The fix is either pinning your Node version with nvm or, better, migrating to Dart Sass.
node-sass is end-of-life: what that means
In July 2024, the Sass team archived the node-sass GitHub repository and marked the npm package deprecated. No new releases are planned. The reasons:
- LibSass feature lag: LibSass stopped receiving new Sass features in 2018. Modern CSS features like
@layer,color-mix()and recent@use/@forwardsyntax aren’t supported. - Node compatibility ceiling: Pre-compiled binaries don’t ship for new Node versions. Every Node release requires manual work to add support, and no one is doing that work.
- Build complexity: The
node-gypfallback requires Python 2.7 and platform build tools, which breaks in CI environments that don’t have them configured.
Frameworks have already moved on. The Create React App team dropped node-sass in CRA v5. Next.js has used Dart Sass since version 11. Angular CLI switched in Angular 12.
Migrating from node-sass to Dart Sass
Dart Sass (the sass npm package) is the current official Sass compiler. It’s pure JavaScript with zero native dependencies so it installs in seconds on any platform and any Node version.
Step 1: Remove node-sass
npm uninstall node-sass
Step 2: Install sass
npm install --save-dev sass
Step 3: Update your package.json scripts
node-sass:
"compile-sass": "node-sass src/style.scss dist/style.css --output-style compressed"
Dart Sass:
"compile-sass": "sass src/style.scss dist/style.css --style=compressed"
The flag changed from --output-style to --style. Everything else is the same.
Step 4: Update your JavaScript API calls
node-sass:
const sass = require('node-sass');
sass.render({ file: 'style.scss' }, callback);
Dart Sass:
const sass = require('sass');
// compile is synchronous by default in the JS API
const result = sass.compile('style.scss', { style: 'compressed' });
// or async:
const result = await sass.compileAsync('style.scss');
Step 5: Update @import to @use
@import is deprecated in Dart Sass. The replacement is @use for loading files and @forward for re-exporting them:
// Old (node-sass / @import)
@import 'variables';
@import 'mixins';
// New (Dart Sass / @use)
@use 'variables';
@use 'mixins';
The main practical difference is that @use namespaces imported members. $primary-color from a file _variables.scss becomes variables.$primary-color in the importing file. You can alias it with @use 'variables' as v; and then reference v.$primary-color.
Transparent replacement (alias trick)
If you have a project where many packages depend on node-sass by name and you can’t update them all, npm’s aliasing lets you point the name at Dart Sass:
npm install node-sass@npm:sass --save-dev --legacy-peer-deps
This makes any require('node-sass') resolve to the sass package instead, with no code changes. Use this as a stop-gap, not a permanent solution.
node-sass vs sass (Dart Sass): when to use which
| node-sass | sass (Dart Sass) | |
|---|---|---|
| Status | End-of-life | Active |
| Implementation | C++ (LibSass) | JavaScript (Dart compiled to JS) |
| Install speed | Slow (native compile or binary download) | Fast (pure JS) |
| Node version support | Fixed list, gaps on new versions | All versions |
| Modern Sass features | No (stuck at 2018) | Yes |
| Source maps | Yes | Yes |
@use / @forward | Partial | Full |
If you’re starting a new project, use Dart Sass. If you’re on an existing project still running node-sass, plan the migration. The node-sass command failed error you’ll hit as your Node version moves forward is the clearest sign it’s time.
Key Takeaways
- node-sass is a Node.js wrapper around LibSass (C++) that compiles
.scssfiles to.css. - It was marked end-of-life in July 2024 and the npm package is deprecated.
- Version mismatches between node-sass and Node.js cause the binding error. Check the compatibility table before installing.
- The
sasspackage (Dart Sass) is the official replacement. It’s pure JS, installs fast and works on any Node version. - Migration is mostly a flag rename (
--output-styleto--style) and an API swap (rendertocompile). @importis deprecated in Dart Sass. Replace it with@useand@forward.- Major frameworks (Next.js, Angular, CRA) have all switched to Dart Sass.
Frequently Asked Questions (FAQ)
What is node-sass?
node-sass is a Node.js library that wraps LibSass, the C++ Sass compiler, allowing you to compile .scss and .sass files to CSS inside a Node.js project.
Is node-sass deprecated?
Yes. The Sass team officially marked it end-of-life in July 2024 and archived the GitHub repository. No new versions will ship.
What should I use instead of node-sass?
Install the sass package (npm install --save-dev sass), which is Dart Sass. It’s the official Sass implementation and has full support for modern Sass features.
What is the difference between node-sass and sass?
node-sass wraps LibSass (C++) and requires native binaries. The sass package is Dart Sass compiled to pure JavaScript with no native dependencies, no binding errors and full support for new Sass features.
Why does node-sass fail on Node 20 or 22?
node-sass compiles to native binaries for a fixed list of Node.js versions. Node 20 and 22 are not on that list because the project is end-of-life, so the install falls back to node-gyp which typically fails. Switching to the sass package fixes this permanently.
Can I use node-sass with Express?
You can, but since node-sass is deprecated, new Express projects should use the sass package and compile via the CLI or the sass.compile() API. Existing setups should migrate before they upgrade Node.
Does Dart Sass support SCSS syntax?
Yes. Dart Sass supports both .scss (the CSS-superset syntax) and the older indented .sass syntax, same as node-sass did.
The Sass tooling space moved fast once LibSass fell behind. Dart Sass is maintained, has active releases and compiles faster in practice despite being pure JavaScript. The JIT in modern V8 closes the gap with C++. If you’re still running node-sass, the migration takes about 20 minutes and saves hours of CI debugging later.




