Load different css files based on environment with NextJS
Asked Answered
H

2

6

I'm building a frontend app using NextJS v.13 and this will be a generic frontend codebase that will be used by multiple sites.

I want to have:

  • button.site1.css
  • button.site2.css

And when I build the codebase for site1 I want to somehow tell the app to use button.site1.css when building.

Basically I want to achieve the following:

.env.local

HOST_NAME=site1

About.js

import styles from `./Button.${process.env.HOST_NAME}.scss`; // This doesn't work. "Imports must be string literals"

const About = () => {
    <div>
      <h1 className={styles.h1}">About Page</h1>
    </div>
  )
}
Hypallage answered 26/1, 2023 at 12:29 Comment(0)
S
1

Although @Felix Eklöf has recommended a very nice approach by programmatically renaming files. Here's something more simple, suitable, and convenient.

Just import both the styles in your component and depending on the variable from .env file use the one that is needed. Next.js automatically tree-shakes extra classes that are not used, so you don't have to worry about performance in production or making production have large CSS.

import styles1 from "your-styles1.module.scss";
import styles2 from "your-styles2.module.scss";

const styles = process.env.HOSTNAME === "host1" ? styles1 : styles2;

Much straightforward and easier to implement. Right?

Update

If you are looking for conditionally adding global styles. Use link inside next/head instead. First, put your styles inside public directory. Then, withing your _app.jsx

// do not import global styles that are scopped to specific HOST, 

export default function(...) {
    const styleSheetLink = getStyleSheetLink(process.env.HOSTNAME) // some logic to detect appropreate stylesheet.
...
    return (
        <>
            <Head>
                 <link href={styleSheetLink} .../>
            </Head>
            ...
        </>
    )
...
}

Update

To enable CSS Optimization, you need to install critters and enable CSS Optimization.

// next.config.js
...
   experimental: { optimizeCss: true }
...
}

And

yarn add critters
Starstarboard answered 4/2, 2023 at 18:15 Comment(9)
Wow. What a great approach. And so simple - and solves all my headaches. This also allows me to have styles get updated whenever I save in the editor, which I completely lost with the other answer. I will change to this approach when I can and see how it plays out. But of course the drawback is that I will have one import statement per site that I have. Which in reality is not an issue as I will have less than 5 sites. So I'd consider that a minor drawback in this case. Thank you!Hypallage
Thanks, you may consider up voting or accepting this answer so that others can find it more easilyStarstarboard
You may consider using themed css aa in tailwind for a little better experienceStarstarboard
I just tried out your solution and changed accepted answer to yours, as I think it has more benefits and less drawbacks than the other solution. I also awarded you the bounty points. Thanks again!Hypallage
One comment though: Tree shaking of unused classes did not work. After researching this a bit it doesn't seem that there even is support for this mechanism. Where have you read that nextjs has tree shaking support for CSS modules? I did an experiment with your solution and could in fact see that unused CSS classes were still shipped to production builds. So a drawback of this solution is that the CSS shipped to the client will be X times larger than it needs to be, where X is the number of sites.Hypallage
This is experimental feature in Next.js 13. You have to enable CSS Optimization in next.configStarstarboard
Just updated the answer. You need to also install crittersStarstarboard
I added the experimental flag and installed critters as you suggested. I ran yarn build again but still the unused css classes from all my sites css files are shipped to production. Do I need to configure critters somehow? I'm on nextjs version 13.1.1 .Hypallage
Please try updating to the latest version of Next.js. I am using this configuration, and it is able to strip off unused classes. I am using Next 13.1.6Starstarboard
T
1

Here's one option, not sure if it will fit your problem entirely.

So, you have button.site1.css, button.site2.css etc.

  1. Add the env variable HOST_NAME=site1 as you suggested.

  2. Write a java/bash- script that copies either button.site1.css or button.site2.css based on HOST_NAME into a common name like just button.css.

  3. In your react components you import button.css instead.

import styles from `./button.css`;
  1. In package.json add that script in prebuild.
{
  "name": "npm-scripts-example",
  "version": "1.0.0",
  "description": "npm scripts example",
  "scripts": {
    "prebuild": "node copycss.js",
    "build": "next build",
  }
}
  1. You also have to copy any of the CSS files manually when you're editing so you don't get errors in the editors. Or just run the script you've written locally with env vars.

EDIT

I was unable to try it earlier, but here's and example of copy script. I tried it in a fresh next.js project and it worked.

./copy-css.js

const fs = require('fs')

const site = process.env.HOST_NAME

fs.copyFileSync(`./styles/button.${site}.css`, `./styles/button.css`)

I'm guessing you have more files than just button, then you could put all site-specific CSS files in a separate folder and search it in the copy-css.js script and run the copyFileSync on each file.

Torpid answered 30/1, 2023 at 8:56 Comment(3)
Interesting approach. Will try it when I get a chance, but what do you mean by 5. copying the files manually? Copy them where?Hypallage
I meant you won't have button.css in your project, you'll only have button.site1.css etc. So you have to copy any of the .site.css files and rename it button.css in your dev environment. Otherwise vs code will show import errors as it doesn't existKulturkampf
Ah yes, of course.Hypallage
S
1

Although @Felix Eklöf has recommended a very nice approach by programmatically renaming files. Here's something more simple, suitable, and convenient.

Just import both the styles in your component and depending on the variable from .env file use the one that is needed. Next.js automatically tree-shakes extra classes that are not used, so you don't have to worry about performance in production or making production have large CSS.

import styles1 from "your-styles1.module.scss";
import styles2 from "your-styles2.module.scss";

const styles = process.env.HOSTNAME === "host1" ? styles1 : styles2;

Much straightforward and easier to implement. Right?

Update

If you are looking for conditionally adding global styles. Use link inside next/head instead. First, put your styles inside public directory. Then, withing your _app.jsx

// do not import global styles that are scopped to specific HOST, 

export default function(...) {
    const styleSheetLink = getStyleSheetLink(process.env.HOSTNAME) // some logic to detect appropreate stylesheet.
...
    return (
        <>
            <Head>
                 <link href={styleSheetLink} .../>
            </Head>
            ...
        </>
    )
...
}

Update

To enable CSS Optimization, you need to install critters and enable CSS Optimization.

// next.config.js
...
   experimental: { optimizeCss: true }
...
}

And

yarn add critters
Starstarboard answered 4/2, 2023 at 18:15 Comment(9)
Wow. What a great approach. And so simple - and solves all my headaches. This also allows me to have styles get updated whenever I save in the editor, which I completely lost with the other answer. I will change to this approach when I can and see how it plays out. But of course the drawback is that I will have one import statement per site that I have. Which in reality is not an issue as I will have less than 5 sites. So I'd consider that a minor drawback in this case. Thank you!Hypallage
Thanks, you may consider up voting or accepting this answer so that others can find it more easilyStarstarboard
You may consider using themed css aa in tailwind for a little better experienceStarstarboard
I just tried out your solution and changed accepted answer to yours, as I think it has more benefits and less drawbacks than the other solution. I also awarded you the bounty points. Thanks again!Hypallage
One comment though: Tree shaking of unused classes did not work. After researching this a bit it doesn't seem that there even is support for this mechanism. Where have you read that nextjs has tree shaking support for CSS modules? I did an experiment with your solution and could in fact see that unused CSS classes were still shipped to production builds. So a drawback of this solution is that the CSS shipped to the client will be X times larger than it needs to be, where X is the number of sites.Hypallage
This is experimental feature in Next.js 13. You have to enable CSS Optimization in next.configStarstarboard
Just updated the answer. You need to also install crittersStarstarboard
I added the experimental flag and installed critters as you suggested. I ran yarn build again but still the unused css classes from all my sites css files are shipped to production. Do I need to configure critters somehow? I'm on nextjs version 13.1.1 .Hypallage
Please try updating to the latest version of Next.js. I am using this configuration, and it is able to strip off unused classes. I am using Next 13.1.6Starstarboard

© 2022 - 2024 — McMap. All rights reserved.