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:
- What are CSS Modules and why do we need them?
- Getting Started with CSS Modules
- 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 install
ing 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:
- What are CSS Modules and why do we need them?
- Getting Started with CSS Modules
- React + CSS Modules = 😍 (You are here!)
Great series. If you’re using CSS Modules with React, I highly recommend taking a look at react-css-modules.
Very nice walkthrough. If you want to go even further (PostCSS, Redux, etc) I like this starter project: https://github.com/koistya/react-static-boilerplate
Hi, the best thing i can do is to recommand you http://www.smartranking.fr they are the best at this.
If you write CSS Modules in this style, ES CSS Modules may also be useful! https://github.com/jacobp100/es-css-modules
Awesome article! Just what I was looking for :D
Well expained, thanks a lot!
Static file generation makes deployments easy since you only need to upload the files and be done with it, no hassle with git hooks or specialised deploys depending on the host/setup you have. I’m getting to quite like that approach, for a lot of cases this is plentiful.
Static file generation could be quite useful for my universal-dev-toolkit. I already have hashed files, browsersync, (s)css-modules, server-side rendering and a bunch of other goodies in there so adding this shouldn’t be too hard!
This is pretty cool! I’ll be sure to take a closer look at this project.
Thanks so much for this series of articles/tutorials Robin, I spotted a couple of issues with this latest one which have hindered me a little though. Just thought I should highlight them:
Your code example for the
.babelrc
file above has"presets": ["es2016", "react"]
however previously in part 2 we were using"presets": ["es2015"]
so it causes errors if you are following on from part 2.Also, there’s a typo in your
router.js
example above:should be:
(took me a while to figure that one out!) Anyways, hopefully you can update those when you get a chance.
Thanks!
Thanks Chuck! I’ve gone ahead and fixed the post now.
Yo! Thanks for such a gentle introduction to CSS Modules. Going through the basics was great, though I still feel like a Part IV is definitely missing… wink ;)
Taking into account more complex scenarios like CSS specificity examples would be tremendously helpful. Also, don’t forget to update your
.babelrc
sample code on this part, which still contains a reference toes2016
instead ofes2015
!