CSS Modules and React

Avatar of Robin Rendle
Robin Rendle on (Updated on )

In this final post of our series on CSS Modules, I’ll be taking a look at how to make a static React site with the thanks of Webpack. This static site will have two templates: a homepage and an about page with a couple of React components to explain how it works in practice.

Article Series:

  1. What are CSS Modules and why do we need them?
  2. Getting Started with CSS Modules
  3. React + CSS Modules = 😍 (You are here!)

In the previous post we set up a quick project with Webpack that showed how dependencies can be imported into a file and how a build process can be used to make a unique class name that is generated in both CSS and HTML. The following example relies heavily on that tutorial so it’s definitely worth working through those previous examples first. Also this post assumes that you’re familiar with the basics of React.

In the previous demo, there were problems with the codebase when we concluded. We depended on JavaScript to render our markup and it wasn’t entirely clear how we should structure a project. In this post we’ll be looking at a more realistic example whereby we try to make a few components with our new Webpack knowledge.

To catch up, you can check out the css-modules-react repo I’ve made which is just a demo project that gets us up to where the last demo left off. From there you can continue with the tutorial below.

Webpack’s Static Site Generator

To generate static markup we’ll need to install a plugin for Webpack that helps us generate static markup:

npm i -D static-site-generator-webpack-plugin

Now we need to add our plugin into webpack.config.js and add our routes. Routes would be like / for the homepage or /about for the about page. Routes tell the plugin which static files to create.

var StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');
var locals = {
  routes: [
    '/',
  ]
};

Since we want to deliver static markup, and we’d prefer to avoid server side code at this point, we can use our StaticSiteGeneratorPlugin. As the docs for this plugin mentions, it provides:

a series of paths to be rendered, and a matching set of index.html files will be rendered in your output directory by executing your own custom, webpack-compiled render function.

If that sounds spooky hard, not to worry! Still in our webpack.config.js, we can now update our module.exports object:

module.exports = {
  entry:  {
    'main': './src/',
  },
  output: {
    path: 'build',
    filename: 'bundle.js',
    libraryTarget: 'umd' // this is super important
  },
  // ...
}

We set the libraryTarget because that’s a requirement for nodejs and the static site plugin to work properly. We also add a path so that everything will be generated into our /build directory.

Still inside our webpack.config.js file we need to add the StaticSiteGeneratorPlugin at the bottom, like so, passing in the routes we want to generate:

plugins: [
  new ExtractTextPlugin('styles.css'),
  new StaticSiteGeneratorPlugin('main', locals.routes),
]

Our complete webpack.config.js should now look like this:

var ExtractTextPlugin = require('extract-text-webpack-plugin');
var StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin')
var locals = {
  routes: [
    '/',
  ]
}

module.exports = {
  entry: './src',
  output: {
    path: 'build',
    filename: 'bundle.js',
    libraryTarget: 'umd' // this is super important
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        include: __dirname + '/src',
      },
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract('css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'),
        include: __dirname + '/src'
      }
    ],
  },
  plugins: [
    new StaticSiteGeneratorPlugin('main', locals.routes),
    new ExtractTextPlugin("styles.css"),
  ]
};

In our empty src/index.js file we can add the following:

// Exported static site renderer:
module.exports = function render(locals, callback) {
  callback(null, 'Hello!');
};

For now we just want to print Hello! onto the homepage of our site. Eventually we’ll grow that up into a more realistic site.

In our package.json, which we discussed in the previous tutorial, we already have the basic command, webpack, which we can run with:

npm start

And if we check out our build directory then we should find an index.html file with our content. Sweet! We can confirm that the Static Site plugin is working. Now to test that this all works we can head back into our webpack.config.js and update our routes:

var locals = {
  routes: [
    '/',
    '/about'
  ]
};

By rerunning our npm start command, we’ve made a new file: build/about/index.html. However, this will have “Hello!” just like build/index.html because we’re sending the same content to both files. To fix that we’lll need to use a router, but first, we’ll need to get React set up.

Before we do that we should move our routes into a separate file just to keep things nice and tidy. So in ./data.js we can write:

module.exports = {
  routes: [
    '/',
    '/about'
  ]
}

Then we’ll require that data in webpack.config.js and remove our locals variable:

var data = require('./data.js');

Further down that file we’ll update our StaticSiteGeneratorPlugin:

plugins: [
  new ExtractTextPlugin('styles.css'),
  new StaticSiteGeneratorPlugin('main', data.routes, data),
]

Installing React

We want to make lots of little bundles of HTML and CSS that we can then bundle into a template (like an About or Homepage). This can be done with react, and react-dom, which we’ll need to install:

npm i -D react react-dom babel-preset-react

Then we’ll need to update our .babelrc file:

{
  "presets": ["es2016", "react"]
}

Now in a new folder, /src/templates, we’ll need to make a Main.js file. This will be where all our markup resides and it’ll be where all the shared assets for our templates will live (like everything in the and our site’s <footer>:

import React from 'react'
import Head from '../components/Head'

export default class Main extends React.Component {
  render() {
    return (
          { /* This is where our content for various pages will go */ }
    )
  }
}

There are two things to note here: First, if you’re unfamiliar with the JSX syntax that React uses, then it’s helpful to know that the text inside the body element is a comment. You also might have noticed that odd element—that’s not a standard HTML element—it’s a React component and what we’re doing here is passing it data via its title attribute. Although, it’s not an attribute it’s what’s known in the React world as props.

Now we need to make a src/components/Head.js file, too:

import React from 'react'

export default class Head extends React.Component {
  render() {
    return (
 
    )
  }
}

We could put all that code from Head.js into Main.js, but it’s helpful to break our code up into smaller pieces: if we want a footer then we would make a new component with src/components/Footer.js and then import that into our Main.js file.

Now, in src/index.js, we can replace everything with our new React code:

import React from 'react'
import ReactDOMServer from 'react-dom/server'
import Main from './templates/Main.js'

module.exports = function render(locals, callback) {
  var html = ReactDOMServer.renderToStaticMarkup(React.createElement(Main, locals))
  callback(null, '' + html)
}

What this does is import all our markup from Main.js (which will subsequently import the Head React component) and then it’ll render all of this with React DOM. If we run npm start once more and check out `build/index.html` at this stage then we’ll find that React has added our Main.js React component, along with the Head component, and then it renders it all into static markup.

But that content is still being generated for both our About page and our Homepage. Let’s bring in our router to fix this.

Setting up our Router

We need to deliver certain bits of code to certain routes: on the About page we need content for the About page, and likewise on a Homepage, Blog or any other page we might want to have. In other words we need a bit of software to boss the content around: a router. And for this we can let react-router do all the heavy lifting for us.

Before we begin it’s worth noting that in this tutorial we’ll be using version 2.0 of React Router and there are a bevy of changes since the previous version.

First we need to install it, because React Router doesn’t come bundled with React by default so we’ll have to hop into the command line:

npm i -D react-router</code>

In the /src directory we can then make a routes.js file and add the following:

import React from 'react'
import {Route, Redirect} from 'react-router'
import Main from './templates/Main.js'
import Home from './templates/Home.js'
import About from './templates/About.js'

module.exports = (
  // Router code will go here
)

We want multiple pages: one for the homepage and another for the About page so we can quickly go ahead and make a src/templates/About.js file:

import React from 'react'

export default class About extends React.Component {
  render() {
    return (
      <div>
        <h1>About page</h1>
        <p>This is an about page</p>
      </div>
    )
  }
}

And a src/templates/Home.js file:

import React from 'react'

export default class Home extends React.Component {
  render() {
    return (
      <div>
        <h1>Home page</h1>
        <p>This is a home page</p>
      </div>
    )
  }
}

Now we can return to routes.js and inside module.exports:

<Route component={Main}>
  <Route path='/' component={Home}/>
  <Route path='/about' component={About}/>
</Route>

Our src/templates/Main.js file contains all of the surrounding markup (like the ). The `Home.js` and About.js React components can then be placed inside the element of Main.js.

Next we need a src/router.js file. This will effectively replace src/index.js so you can go ahead and delete that file and write the following in router.js:

import React from 'react'
import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
import {Router, RouterContext, match, createMemoryHistory} from 'react-router'
import Routes from './routes'
import Main from './templates/Main'

module.exports = function(locals, callback){
  const history = createMemoryHistory();
  const location = history.createLocation(locals.path);

  return match({
    routes: Routes,
    location: location
  }, function(error, redirectLocation, renderProps) {
    var html = ReactDOMServer.renderToStaticMarkup(
      <RouterContext {...renderProps} />
    );
    return callback(null, html);
  })
}

If you’re unfamiliar with what’s going on here then it’s best to take a look at Brad Westfall’s intro to React Router.

Because we’ve removed our index.js file and replaced it with our router we need to return to our webpack.config.js and fix the value for the entry key:

module.exports = {
  entry: './src/router',
  // other stuff...
}

And finally we just need to head over to src/templates/Main.js:

export default class Main extends React.Component {
  render() {
    return (
      <html>
        <Head title='React and CSS Modules' />
        <body>
          {this.props.children}
        </body>
      </html>
    )
  }
}

{this.props.children} is where all our code from the other templates will be placed. So now we can npm start once more and we should see two files being generated: `build/index.html` and build/about/index.html, each with their own respective content.

Reimplementing CSS Modules

Since the is the hello world of CSS, we’re going to create a Button module. And although I’ll be sticking with Webpack’s CSS loader and what I used in the previous tutorial, there are alternatives.

This is the sort of file structure that we’d like in this project:

/components
  /Button
    Button.js
    styles.css

We’ll then import this custom React component into one of our templates. To do that we can go ahead and make a new file: src/components/Button/Button.js:

import React from 'react'
import btn from './styles.css'

export default class CoolButton extends React.Component {
  render() {
    return (
      <button className={btn.red}>{this.props.text}</button>
    )
  }
}

As we learnt in the previous tutorial, the {btn.red} className is diving into the CSS from styles.css and finding the .red class, then Webpack will generate our gobbledygook CSS modules class name.

Now we can make some simple styles in src/components/Button/styles.css:

.red {
  font-size: 25px;
  background-color: red;
  color: white;
}

And finally we can add that Button component to a template page, like src/templates/Home.js:

import React from 'react'
import CoolButton from '../components/Button/Button'

export default class Home extends React.Component {
  render() {
    return (
      <div>
        <h1>Home page</h1>
        <p>This is a home page</p>
        <CoolButton text='A super cool button' />
      </div>
    )
  }
}

One more npm start and there we have it! A static React site where we can quickly add new templates, components and we have the added benefit of CSS Modules so that our classes now look like this:

You can find a complete version of the demo above in the React and CSS Modules repo. If you notice any errors in the code above then be sure to file an issue.

There are certainly ways in which we could improve this project, for one we could add Browsersync to our Webpack workflow so we don’t have to keep npm installing all the time. We could also add Sass, PostCSS and a number of loaders and plugins to help out, but in the sake of brevity I’ve decided to leave those out of the project for now.

Wrapping up

What have we accomplished here? Well, although this looks like an awful lot of work we now have a modular environment to write code. We could add as many components as we like:

/components
  Head.js
  /Button
    Button.js
    styles.css
  /Input
    Input.js
    style.css
  /Title
    Title.js
    style.css

Consequently, if we have a .large class inside the styles for our Heading component then it won’t conflict with the .large styles from our Button component. Also, we can still use global styles by importing a file such as `src/globals.css` into each component, or simply by adding a separate CSS file into the .

By making a static site with React we’ve lost a great deal of the magical properties that React gives us out of the box, including managing state, yet it’s still possible to serve two kinds of website with this system: you can make a static site as I’ve shown you above and then progressively enhance everything with React superpowers after the fact.

This workflow is neat and tidy but there are many instances when this combination of CSS Modules, React and Webpack would be complete overkill. Depending on the size and scope of the web project it would be borderline crazy to spend the time implementing this solution—if it was only a single web page, for instance.

However, if there are lots of people contributing CSS to the codebase everyday then it might be extraordinarily helpful if CSS Modules prevented any errors that are thanks to the cascade. But this might lead to designers having less access to the codebase because they must now learn how to write Javascript, too. There are also a lot of dependencies that have to be supported for this method to work correctly.

Does this mean that we’ll all be using CSS Modules in the near future? I don’t think so, because—as with all front-end techniques— the solution depends on the problem and not all problems are the same.

Article Series:

  1. What are CSS Modules and why do we need them?
  2. Getting Started with CSS Modules
  3. React + CSS Modules = 😍 (You are here!)