5

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>
  )
}
Mayank Kumar Chaudhari
  • 16,027
  • 10
  • 55
  • 122
Weblurk
  • 6,562
  • 18
  • 64
  • 120

2 Answers2

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.

Felix Eklöf
  • 3,253
  • 2
  • 10
  • 27
  • Interesting approach. Will try it when I get a chance, but what do you mean by 5. copying the files manually? Copy them where? – Weblurk Jan 30 '23 at 13:29
  • 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 exist – Felix Eklöf Jan 30 '23 at 13:31
  • Ah yes, of course. – Weblurk Jan 30 '23 at 13:34
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
Mayank Kumar Chaudhari
  • 16,027
  • 10
  • 55
  • 122
  • 1
    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! – Weblurk Feb 06 '23 at 07:49
  • Thanks, you may consider up voting or accepting this answer so that others can find it more easily – Mayank Kumar Chaudhari Feb 06 '23 at 08:26
  • You may consider using themed css aa in tailwind for a little better experience – Mayank Kumar Chaudhari Feb 06 '23 at 08:31
  • 1
    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! – Weblurk Feb 06 '23 at 08:56
  • 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. – Weblurk Feb 06 '23 at 09:09
  • This is experimental feature in Next.js 13. You have to enable CSS Optimization in next.config – Mayank Kumar Chaudhari Feb 06 '23 at 11:33
  • Just updated the answer. You need to also install critters – Mayank Kumar Chaudhari Feb 06 '23 at 11:53
  • 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 . – Weblurk Feb 06 '23 at 12:14
  • 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.6 – Mayank Kumar Chaudhari Feb 06 '23 at 13:48