Type something to search...
Next.js Using Sass

Next.js Using Sass

This project runs on Tailwind CSS 4, which is worth saying upfront since it means everything in this article describes a path this specific codebase doesn't take — but Sass remains one of the built-in, first-class styling options Next.js supports, and there are entirely legitimate reasons a team would reach for it over a utility-first framework: an existing Sass codebase migrating to Next.js with years of mixins and variable systems already built out, a design system that was already built around Sass's nesting and computation model, or simply a team whose CSS conventions predate Tailwind's rise and see no compelling reason to churn them. This article covers the actual mechanics of using Sass in a Next.js App Router project, plus the practical context around when it's genuinely still the right call.

Getting it running

Next.js has built-in support for Sass — you install the compiler, and both .scss and .sass files start working immediately, no additional webpack or Turbopack configuration required:

npm install --save-dev sass

Note this installs as a dev dependency, not a runtime one — Sass compiles at build time into plain CSS, so nothing about the sass package itself ships to the browser or needs to exist in your production node_modules.

Two syntaxes, one clear recommendation

Sass genuinely supports two different syntaxes, and this trips up newcomers occasionally because they look like entirely different languages at a glance rather than two dialects of the same one:

.scss uses SCSS syntax — a strict superset of ordinary CSS, meaning any valid CSS file is already valid SCSS. You write braces and semicolons exactly like CSS, with Sass's extra features (variables, nesting, mixins) layered on top of syntax you already know.

.sass uses the Indented Syntax — no braces, no semicolons, meaning purely off significant whitespace, closer in feel to something like Python or YAML than to CSS.

Unless you have a specific, existing reason to prefer the Indented Syntax — a team that already writes Sass this way, say — start with .scss. Being a superset of CSS you already know means there's genuinely nothing new to learn beyond Sass's actual features, whereas .sass requires learning an entirely separate whitespace-significant syntax on top of learning Sass itself.

CSS Modules work exactly the same way, with Sass

Component-scoped styles work through the identical CSS Modules pattern you'd use with plain CSS — just with a .module.scss (or .module.sass) extension instead:

// app/blog/blog.module.scss
.post {
  padding: 1rem;

  &:hover {
    background: #f5f5f5;
  }
}
// app/blog/page.tsx
import styles from "./blog.module.scss";

export default function BlogPage() {
  return <article className={styles.post}>...</article>;
}

Everything you'd expect from CSS Modules elsewhere in Next.js applies identically here — class names are scoped locally to the component by default, so .post in one module.scss file can't accidentally collide with an unrelated .post class somewhere else in the app. Sass's own features (nesting, in this example, via the &:hover selector) work exactly as normal inside a module file; the module scoping and the Sass compilation are two entirely independent, orthogonal things happening to the same file.

Configuring Sass's own options

If you need to tune how the Sass compiler itself behaves — beyond simply "compile my files" — sassOptions in next.config.js is the surface for that:

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  sassOptions: {
    additionalData: `$var: red;`,
  },
};

export default nextConfig;

additionalData is genuinely one of the more useful options here in practice — anything you put in that string gets prepended to every Sass file Next.js compiles, which is the standard pattern for making shared variables or mixins available everywhere without manually @import-ing them at the top of every single file. A design system with a handful of core color and spacing variables used across dozens of components is exactly the case this is built for — define them once in additionalData, and every .scss file in the project can reference them with no per-file import boilerplate.

Choosing which Sass implementation actually runs

By default, Next.js uses the sass package (Dart Sass, the reference implementation) to do the actual compilation. If you specifically need sass-embedded instead — typically reached for because of its meaningfully faster compilation performance on larger stylesheets, since it runs the Dart Sass compiler as a native binary rather than through a JS wrapper — that's an explicit opt-in:

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  sassOptions: {
    implementation: "sass-embedded",
  },
};

export default nextConfig;

Worth trying if your build times are being noticeably dragged down by Sass compilation specifically on a large stylesheet codebase — this is close to a free performance win in that specific situation, at the cost of one additional dependency to install and keep in sync with your next.config setting.

Passing Sass variables into your JavaScript

This is a genuinely underused feature worth knowing about even if you're not currently reaching for it: Sass variables can be exported from a CSS Module file and consumed directly as plain JavaScript values, which is a clean way to keep one single source of truth for a design token that both your stylesheets and your component logic need to agree on.

// app/variables.module.scss
$primary-color: #64ff00;

:export {
  primaryColor: $primary-color;
}
// app/page.tsx
import variables from "./variables.module.scss";

export default function Page() {
  return <h1 style={{ color: variables.primaryColor }}>Hello, Next.js!</h1>;
}

The :export block is the actual mechanism doing the work here — it's not Sass-specific syntax exactly, but a CSS Modules convention that Sass (and the underlying loader) understands and honors, turning whatever variables you list inside it into a plain JS object importable like any other module. This is worth reaching for specifically when a value needs to be consistent across both your CSS and your component logic — a breakpoint value used both in a media query and in a matchMedia call, say, or a brand color referenced both in a stylesheet and passed as a prop to a charting library that doesn't read CSS custom properties at all.

When Sass is genuinely still the right call — and when it isn't

Given this specific project runs Tailwind 4, it's worth being honest and specific about the actual trade-offs here, rather than treating this as a purely neutral "here's how, use whichever" article.

Sass earns its place when you have substantial existing investment in it already — a mature design system built around Sass mixins and computed values, a team fluent in and productive with Sass-first workflows, or component libraries you depend on that ship Sass source rather than compiled CSS or a utility-class API. It also genuinely still shines for deeply computational stylesheet logic — Sass's @function, loops, and math operations remain more expressive for that specific kind of work than composing utility classes, even in a Tailwind-first project that might reach for Sass only in a narrow, specific place where that expressiveness earns its keep.

Tailwind (what this project actually uses) tends to win for greenfield projects without existing Sass investment, for teams that want styling co-located directly with markup rather than separated into distinct stylesheet files, and for design-system consistency enforced by a constrained, curated set of utility classes rather than open-ended custom CSS that different contributors can drift into writing differently over time.

They are not mutually exclusive, worth remembering — Next.js has no problem running Tailwind for the majority of an application's styling while using Sass modules for one specific, narrow case (a complex data-visualization component with genuinely computed, non-trivial CSS logic, say) where Sass's expressiveness is a clear, deliberate win. There's no framework-level rule forcing an all-or-nothing choice between the two; the decision is really about which tool fits which specific piece of styling work, project by project, component by component.

Key Takeaways

TaskHow
Enable Sassnpm install --save-dev sass — no other config needed
Which extension to pick.scss by default (CSS superset); .sass only with a specific reason
Component-scoped styles.module.scss / .module.sass, same CSS Modules pattern as plain CSS
Share variables across every filesassOptions.additionalData in next.config.js
Faster compiles on large stylesheetssassOptions.implementation: 'sass-embedded'
Share a value between CSS and JS:export block in a .module.scss file

Sass support in Next.js is genuinely zero-friction — install one package, and .scss/.sass files, including scoped modules, just work, with no bundler configuration of your own required. Whether it's the right choice for a given project is really a question about existing investment and team fluency, not a technical limitation on either side — and as this project's own Tailwind setup shows, there's nothing stopping the two approaches from coexisting deliberately in the same codebase where each one genuinely fits best.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper