Enabling assets for Server-Side Rendering in Webpack

Notice: A lot has changed since this article was written. file-loader and url-loader support not-emitting-files themselves now, so fake-file-loader and fake-url-loader are deprecated. fake-style-loader can be replaced by using css-loader/locals as explained in fake-style-loader#3. So, do not use the fake module family anymore. It is recommended to use css-loader, file-loader and url-loader directly instead.

It’s still worth reading the article to familiarize yourself with the concepts.

Webpack is commonly used to build the client bundle of web applications. To improve perceived load times, web applications may render on the server. Ideally the code for the components used by the web app should be shared by the code generating the client and server bundle. Commonly, two separate Webpack configurations are used to achieve this, where each imported dependency of a component is interpreted differently depending on whether Webpack currently creates the client or server bundle.

When importing an image from a component, Webpack typically emits the image to the assets and resolves the import with the URL to the emitted file. This is also true for other assets like CSS files and fonts. If we’d now simply add the same configuration for the server bundle, Webpack would emit the files twice, possibly resulting in different URLs. This is more work and increases the build time unnecessarily. The markup generated during SSR could then possibly differ from the markup generated by the browser because of the different URLs, and React would complain.

Another exception is the style-loader, which attaches the imported style sheets to the DOM. When rendering on the server, there is no DOM so an exception occurs.

One existing solution is webpack-isomorphic-tools, but I found this very hard to set up although it seems to have a lot of power. I couldn’t get it to work properly and eventually gave up.

Solution

There is a flag called emitFile in file-loader. When enabled, file-loader will skip the emission of the asset and simply return the generated file name. Since url-loader uses file-loader behind the scenes, the same flag can be used there. So you’d use your regular file-loader for the client bundle and append emitFile=false in the server Webpack configuration. This solves the problem for static assets.

However, this does not yet work for styles.

Introducing fake-style-loader

fake-style-loader enables loading CSS modules on the server. It returns the names of the generated identifiers and further enables access to the underlying CSS so it can be served inline (or extracted to a file).

https://www.npmjs.com/package/fake-style-loader

From webpack’s style-loader documentation:

When using local scope CSS the module exports the generated identifiers
var style = require("style!css!./file.css");
style.placeholder1 === "z849f98ca812bc0d099a43e0f90184"

To enable loading CSS modules on the server, we don’t need the attach style-tags-to-DOM magic style-loader does, because there is no DOM. Instead, we simply need to know the names of the identifiers.

The intention is to use the regular style-loader for the client-side bundle and the fake-style-loader for the server bundle. Now there will only be one emitted file, but the exported URLs will be available to both bundles. Just like with file-loader and the url-loader. Here’s an example of two Webpack configurations:

// regular style-loader for client bundle
{ test: /\.css$/, loader: 'style!css?modules' },
// fake-loader for server bundle
{ test: /\.css$/, loader: 'fake-style!css?modules' }

Note: The fake-style-loader currently works in combination with the css-loader in modules mode only. It doesn’t make sense to use it in non-modules mode because there would be no return value.

fake-style-loader has another capability. It exports the CSS source code as the source property. This is useful for SSR, when you want to inline the initial CSS code.

var styles = require('./some-file.css')
console.log(styles)
// returns
// {
// // the regular classnames of css-modules
// heading: '.XYZABC123',
//
...
//
// // the special source
property containing the original CSS
//
source: '.heading { color: blue; }'
// }

To see all of this in action, check out my webapp project, specifically these files:

I hope fake-style-loader and the other modules with emission turned off are as helpful for you as they were for me. I’ve created the loader 4 months ago and put off writing about it for a long time. It still lacks documentation, but I hope by writing this article things clear up.

Further, webapp should be a good starting point.

Happy Server-Side Rendering :)

Note: Previously it was not possible to turn off asset emission in file-loader and url-loader. This was introduced only recently in #74. Back then I created fake-file-loader and fake-url-loader. These are considered deprecated now since it’s possible out of the box now :) It is also recommended to use css-loader/locals instead of fake-style-loader. See fake-style-loader#3 for more information.