4

I am trying to add CSS to my component built in React using SSR, but I am unable to do so.

Things I've looked at:

But in none the process is simple or clearly mentioned. The one which I tried a lot was isomorphic loader which seemed promising, but then it gave some vague errors after setting it in my CSS files:

Unexpected token (1:0) You may need an appropriate loader to handle this file type.

This is my boilerplate package - https://github.com/alexnm/react-ssr

How do I add styles to my React SSR code.

Update

const dev = process.env.NODE_ENV !== "production";
const path = require( "path" );
const { BundleAnalyzerPlugin } = require( "webpack-bundle-analyzer" );
const FriendlyErrorsWebpackPlugin = require( "friendly-errors-webpack-plugin" );

const plugins = [
    new FriendlyErrorsWebpackPlugin(),
];

if ( !dev ) {
    plugins.push( new BundleAnalyzerPlugin( {
        analyzerMode: "static",
        reportFilename: "webpack-report.html",
        openAnalyzer: false,
    } ) );
}

module.exports = {
    mode: dev ? "development" : "production",
    context: path.join( __dirname, "src" ),
    devtool: dev ? "none" : "source-map",
    entry: {
        app: "./client.js",
    },
    resolve: {
        modules: [
            path.resolve( "./src" ),
            "node_modules",
        ],
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /(node_modules|bower_components)/,
                loader: "babel-loader",
            },
        ],
    },
    output: {
        path: path.resolve( __dirname, "dist" ),
        filename: "[name].bundle.js",
    },
    plugins,
};
Rahul Singh
  • 19,030
  • 11
  • 64
  • 86

2 Answers2

5

Below configuration made CSS work
Packages installed:
babel-plugin-dynamic-import-node, babel-plugin-css-modules-transform, mini-css-extract-plugin, css-loader, style-loader

index.js

require( "babel-register" )( {
presets: [ "env" ],
plugins: [
    [
        "css-modules-transform",
        {
            camelCase: true,
            extensions: [ ".css", ".scss" ],
        }
    ],
    "dynamic-import-node"
],
} );
require( "./src/server" );

webpack.config.js

rules: [
        {
            test: /\.jsx?$/,
            exclude: /(node_modules|bower_components)/,
            loader: "babel-loader",
        },{
            test: /\.css$/,
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,
                },
                'css-loader'
            ],
        },
    ]

In webpack config, added following plugin

new MiniCssExtractPlugin({
    filename: "styles.css",
}),

In server.js, Added the following code inside head in htmlTemplate.

<link rel="stylesheet" type="text/css" href="./styles.css" />

Usage

import  "./../App.css";

<h2 className="wrapper">F1 2018 Season Calendar</h2>
Ajay Varghese
  • 741
  • 4
  • 14
  • Hey @AjayVarghese it works thanks great solution , saved my day. can you explain what happened here and how it worked .. as i just followed the step u mentioned but couldn't get any insights , it will be help full if you share some insights. also with this i am not able to use scss , i think i need `scss loader ` for it and also the module concept is not working , like css modules for react. if you could add that it will become a complete solution for others using this library .. thank you – Rahul Singh Jul 14 '19 at 15:13
  • For SSR, rendering happens twice, one in server and in client. In server, normal CSS loader won't work. css-modules-transform will handle this part, That's what I've written in index.js. This one is a very basic CSS setup. For sass or CSS-modules, you can add the config in webpack.config.js. Hope that will work. – Ajay Varghese Jul 14 '19 at 16:49
0

I've went crazy trying to setup it up myself following all the mentioned approaches with no luck. In my case I'm using css-loader for create css modules syntax and SSR for React modules that aren't really big.

As someone mentioned because webpack will run twice you'll end with different class names which will make pop up some errors on the console saying that your server and client markups are different. To mitigate that you can create your own loader.

The idea is to store the value of the output from css-loader from one of the webpack compilations and use that for the next one. It might not be for what a loader is designed for but it does the job.

 /**
 * @typedef {Object} LoaderOptions
 * @property {"client" | "server"} type
 */

const mapContent = new Map();

/**
 * @param {LoaderOptions} options
 * @returns boolean
 */
const optionsValidator = (options) => {
  return (
    typeof options === 'object' &&
    'type' in options &&
    typeof options.type === 'string' &&
    (options.type === 'client' || options.type === 'server')
  );
};

module.exports = function (source) {
  /**
    @type import('./node_modules/webpack/types').LoaderContext<LoaderOptions>
   */
  const loader = this;
  const options = loader.getOptions();
  const logger = loader.getLogger();

  if (!optionsValidator(options)) {
    logger.error('css-ssr-replacer-loader. Only valid props are "type" with values of "client" or "server"');
  }

  const isClient = options.type === 'client';

  if (isClient) {
    mapContent.set(loader.resourcePath, Buffer.from(source));
  }

  return isClient ? source : mapContent.get(loader.resourcePath);
};

And then on your webpack you need to add something like the following

const webpackConfig = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: 'howEverYouWantToCallTheLoader',
            options: {
              type: 'client' || 'server'
            }
          },
          {
            loader: 'css-loader',
            options: {
              modules: {
                exportLocalsConvention: 'camelCase',
                localIdentName: '[local]_[hash:base64:8]',
                mode: 'local'
              }
            }
          }
        ]
      }
    ]
  },
  resolveLoader: {
    alias: {
      howEverYouWantToCallTheLoader: path.resolve(__dirname, 'pathToYourLoader.js')
    }
  }
};
byverdu
  • 373
  • 7
  • 7