next.jssassturbomantine

SCSS Module Styles Only Work with next dev --turbo


I’m using Next.js 15 (with the app directory) and Mantine 7 in my project. I'm styling Mantine components using SCSS modules (*.module.scss), but the styles behave inconsistently depending on how I run the app.

Issue

When running the app with next dev --turbo, SCSS module styles are applied correctly to Mantine components. However, without --turbo (i.e., just next dev) or when starting from a build result (next buildnext start), the SCSS module styles are not applied at all.

My Configuration

I’ve followed the official Mantine documentation for using Sass with Next.js (https://mantine.dev/styles/sass/#usage-with-nextjs). postcss.config.cjs and _mantine.scss file already added on the root of the repo exactly same as what written in the docs.

next.config.mjs

import path from 'node:path';
import bundleAnalyzer from '@next/bundle-analyzer';

const withBundleAnalyzer = bundleAnalyzer({
  enabled: process.env.ANALYZE === 'true',
});

export default withBundleAnalyzer({
  reactStrictMode: false,
  eslint: {
    ignoreDuringBuilds: true,
  },
  experimental: {
    optimizePackageImports: ['@mantine/core', '@mantine/hooks'],
  },
  sassOptions: {
    implementation: 'sass-embedded',
    additionalData: `@use "${path.join(process.cwd(), '_mantine').replace(/\\/g, '/')}" as mantine;`,
  },
});

package.json

{
  "name": "my-repo",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "fast": "next dev --turbo",
    "build": "next build",
    "analyze": "ANALYZE=true next build",
    "start": "next start",
    "typecheck": "tsc --noEmit",
    "lint": "npm run eslint && npm run stylelint",
    "eslint": "next lint",
    "stylelint": "stylelint '**/*.css' --cache",
    "lint:fix": "npm run prettier:write && eslint . --fix",
    "jest": "jest",
    "jest:watch": "jest --watch",
    "prettier:check": "prettier --check \"**/*.{ts,tsx}\"",
    "prettier:write": "prettier --write \"**/*.{ts,tsx}\"",
    "test": "npm run prettier:check && npm run lint && npm run typecheck && npm run jest",
    "storybook": "storybook dev -p 6006",
    "storybook:build": "storybook build"
  },
  "dependencies": {
    "@emotion/react": "^11.14.0",
    "@mantine/core": "7.16.3",
    "@mantine/dates": "^7.16.1",
    "@mantine/form": "^7.16.1",
    "@mantine/hooks": "7.16.3",
    "@mantine/modals": "^7.16.3",
    "@mantine/notifications": "^7.16.1",
    "@next/bundle-analyzer": "^14.2.4",
    "@tabler/icons-react": "^3.6.0",
    "@tanstack/react-query": "^5.64.2",
    "@tanstack/react-query-devtools": "^5.64.2",
    "axios": "^1.7.9",
    "clsx": "^2.1.1",
    "currency.js": "^2.0.4",
    "dayjs": "^1.11.13",
    "i18next": "^24.2.1",
    "i18next-http-backend": "^3.0.2",
    "mantine-react-table": "2.0.0-beta.8",
    "material-icons": "^1.13.12",
    "next": "15.1.7",
    "next-auth": "^4.24.11",
    "next-i18next": "^15.4.1",
    "react": "18.3.1",
    "react-dom": "18.3.1",
    "react-i18next": "^15.4.0",
    "react-imask": "^7.6.1",
    "zod": "^3.24.1",
    "zod-validation-error": "^3.4.0"
  },
  "devDependencies": {
    "@babel/core": "^7.24.7",
    "@eslint/js": "^9.15.0",
    "@ianvs/prettier-plugin-sort-imports": "^4.3.1",
    "@storybook/nextjs": "^8.1.10",
    "@storybook/react": "^8.1.10",
    "@tanstack/eslint-plugin-query": "^5.64.2",
    "@testing-library/dom": "^10.1.0",
    "@testing-library/jest-dom": "^6.4.6",
    "@testing-library/react": "^16.0.0",
    "@testing-library/user-event": "^14.5.2",
    "@types/eslint-plugin-jsx-a11y": "^6",
    "@types/jest": "^29.5.12",
    "@types/node": "^20.14.8",
    "@types/react": "18.3.3",
    "babel-loader": "^9.1.3",
    "eslint": "^9.15.0",
    "eslint-config-mantine": "^4.0.3",
    "eslint-plugin-jsx-a11y": "^6.10.2",
    "eslint-plugin-react": "^7.37.2",
    "husky": "^9.1.7",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "postcss": "^8.4.38",
    "postcss-preset-mantine": "1.17.0",
    "postcss-simple-vars": "^7.0.1",
    "prettier": "^3.3.2",
    "sass": "^1.85.0",
    "sass-embedded": "^1.83.4",
    "storybook": "^8.1.10",
    "storybook-dark-mode": "^4.0.2",
    "stylelint": "^16.6.1",
    "stylelint-config-standard-scss": "^13.1.0",
    "ts-jest": "^29.1.5",
    "typescript": "5.5.2",
    "typescript-eslint": "^8.14.0"
  },
  "packageManager": "yarn@4.6.0"
}

What I've Did

Removing implementation: sass-embedded in next.config.mjs do succeed overwrite some scss style module in some components, but still mostly not (especially for font-size, color padding, and many more). Example like this:

footer.module.scss

.footerLink { 
  color: var(--mantine-color-neutral-4) !important; // if i added important it applied 
  transition: color 0.2s ease; // applied 
  font-size: 16px; // not applied 

  &:hover {
    color: var(--mantine-color-primary-5); // not applied 
  }
}

.listRoot { 
  column-count: 1; // applied 
  column-gap: 42px; // applied 

  @media (min-width: mantine.$mantine-breakpoint-sm) {
    column-count: 2; // applied 
  }
}

.listText { 
  line-height: 13.81px; // not applied 
}

Question

Why do SCSS module styles only work properly when running the app with next dev --turbo? How can I ensure the styles also work with next dev or next build?

Any insights or guidance would be appreciated! Let me know if more details are needed.


Solution

  • Solution

    Importing mantine core css file before my custom style fixed the problem.

    Like this:

    AppProvider.tsx

    'use client'
    
    ...
    import '@mantine/core/styles.css'
    import alertClasses from './styles/Alert.module.scss'
    // other style modules
    
    const theme = createTheme({
        spacing: { '2lg':'24px'},
        components: {
             Alert: Alert.extend({
                defaultProps: {
                    classNames: alertClasses,
                    color: 'primary',
                },
            }),
        },
        ...
    })
    
    const AppProvider = ({ children }: Props) => {
        return (
                <MantineProvider theme={theme}>
                    <ModalsProvider>
                        <Notifications position="top-right" autoClose={5000} />
                        <AppLayout>{children} </AppLayout>
                    </ModalsProvider>
                </MantineProvider>
        )
    }
    export default AppProvider
    
    

    Previous State

    Previously I imported mantine core css file in root layout.tsx and it makes my custom style not working, the mantine core css style overwrited my custom style.

    Like this:

    layout.tsx

    ...
    import '@mantine/core/styles.css'
    
    export default async function RootLayout({ children }: { children: any }) {
        return (
            <html lang="en" {...mantineHtmlProps}>
                <head>
                    <ColorSchemeScript />
                    <link rel="shortcut icon" href="/favicon.svg" />
                    <meta
                        name="viewport"
                        content="minimum-scale=1, initial-scale=1, width=device-width, user-scalable=no"
                    />
                </head>
                <body>
                    <AppProvider>{children}</AppProvider>
                </body>
            </html>
        )
    }
    
    

    AppProvider.tsx

    'use client'
    
    ...
    import alertClasses from './styles/Alert.module.scss'
    // other style modules
    
    const theme = createTheme({
        spacing: { '2lg':'24px'},
        components: {
             Alert: Alert.extend({
                defaultProps: {
                    classNames: alertClasses,
                    color: 'primary',
                },
            }),
        },
        ...
    })