I have set out to develop a program that enables rendering of any client-side code base server-side using webpack. The end result is Proceed to “Hello, World”. tl;dr; https://github.com/gajus/isomorphic-webpack The past 6 months I have been working on a project with a requirement to serve the content on the server-side. The unnamed project is part of a website in list. The website itself extensively utilises (ESI) to assemble the page content “at the edges of the Internet”. The requirement to serve the content server-side comes from utilising the ESI. Alexa Top 100 Global Site Edge Side Includes This was a unique challenge: I was working as part of a larger frontend team; the code will need to be maintained by the frontend team. Therefore, my focus has been to use frameworks known to frontend teams and avoid backend specific frameworks as much as possible. I have proceeded to develop the application using React and webpack. I have had the application up and running, but there was one problem–none of the existing isomorphic-rendering solutions worked out of the box with the code base. The existing solutions required obscure configuration, ran multiple node processes (making it a pain to containerise the application), and didn’t work with all of the webpack loaders (e.g. ). style-loader I have set out to develop one program to address all of the above. I have called it . The rest of this post introduces to how it works and how to use it. https://github.com/gajus/isomorphic-webpack “Hello, World!” Lets start with the “Hello, World” and build on that example. Our example is a React application. It uses to render and append the resulting DOM to the element. react-dom ReactElement #app /src/app/index.js import React from 'react';import ReactDOM from 'react-dom'; const app = <div>Hello, World!</div>; ReactDOM.render(app, document.getElementById('app')); is configured to use to load the JavaScript files. webpack babel-loader /src/webpack.config.js import path from 'path'; export default {context: __dirname,entry: {app: [path.resolve(__dirname, './app')]},module: {loaders: [{include: path.resolve(__dirname, './app'),loader: 'babel-loader',test: /\.js$/}]},output: {filename: '[name].js',path: path.resolve(__dirname, './dist')}}; A server-side script is using to compile the application and to server the contents. webpack express /src/bin/server.js Note: is not a dependency of . Here it is used only to serve client-side application. webpack-dev-middleware isomorphic-webpack import express from 'express';import webpack from 'webpack';import webpackDevMiddleware from 'webpack-dev-middleware';import webpackConfiguration from '../webpack.configuration'; const compiler = webpack(webpackConfiguration); const app = express(); app.use(webpackDevMiddleware(compiler, {noInfo: false,publicPath: '/static',quiet: false,stats: 'minimal'})); app.get('/', (req, res) => {res.send(`<!doctype html><html><head></head><body><div id='app'></div> <script src='/static/app.js'></script> </body> </html>`);}); app.listen(8000); This isn’t an isomorphic application. Making an HTTP request simply responds with the string hard-coded in the server-side script. $ curl http://127.0.0.1:8000 <!doctype html><html><head></head><body><div id='app'></div> <script src='/static/app.js'></script> </body></html> An isomorphic application would evaluate the React application code and respond with the rendered application. If you want to just checkout the code, use the following commands: $ git clone :gajus/isomorphic-webpack-demo.git$ cd isomorphic-webpack-demo$ git reset --hard f66783c89040c0fc19a19df961cbb2633f27348d$ npm install$ npm start git@github.com Isomorphic “Hello, World!” What does it take to make the above application isomorphic? The following changes need to be made to our code base: Install isomorphic-webpack Setup using the webpack configuration isomorphic-webpack Export the application as a module. Use to render the application. [react-dom/server](https://facebook.github.io/react/docs/react-dom-server.html) [renderToString](https://facebook.github.io/react/docs/react-dom-server.html#rendertostring) Here is how that changes our example application: needs to export the application: /src/app/index.js import React from 'react';import ReactDOM from 'react-dom'; const app = <div>Hello, World!</div>; if (typeof ISOMORPHIC_WEBPACK === 'undefined') {ReactDOM.render(app, document.getElementById('app'));} export default app; is a constant used to differentiate between Node.js and browser environment. Presence of the constant indicates that it is a Node.js environment. [ISOMORPHIC_WEBPACK](https://github.com/gajus/isomorphic-webpack#how-to-differentiate-between-nodejs-and-browser-environment) The server-side script needs to initialise the compiler and use to render the contents of the application: createIsomorphicWebpack react-dom/server renderToString import express from 'express';import webpack from 'webpack';import webpackDevMiddleware from 'webpack-dev-middleware';import {renderToString} from 'react-dom/server';import {createIsomorphicWebpack} from 'isomorphic-webpack';import webpackConfiguration from '../webpack.configuration'; const compiler = webpack(webpackConfiguration); createIsomorphicWebpack(webpackConfiguration); const app = express(); app.use(webpackDevMiddleware(compiler, {noInfo: false,publicPath: '/static',quiet: false,stats: 'minimal'})); const renderFullPage = (body) => {return `<!doctype html><html><head></head><body><div id='app'>${body}</div> <script src='/static/app.js'></script> </body> </html>`;}; app.get('/', (req, res) => {const appBody = renderToString(require('../app').default); res.send(renderFullPage(appBody));}); app.listen(8000); overrides Node.js module resolution system, i.e. all calls that refer to resources that are part of the bundle will be handled by . createIsomorphicWebpack require() webpack isomorphic-webpack This made our application isomorphic. Making an HTTP request responds with the rendered React application: $ curl http://127.0.0.1:8000/ <!doctype html><html><head></head><body><div id='app'><div data-reactroot="" data-reactid="1" data-react-checksum="1607472067">Hello, World!</div></div> <script src='/static/app.js'></script></body></html> Free isomorphism. If you want to just checkout the code, use the following commands: $ git reset --hard 4fb6c11d488405a7c9b7f5a7cda4abec2396be00$ npm install$ npm start Using Webpack loaders Loaders allow you to preprocess files as you or “load” them. [..] Loaders can transform files from a different language like, CoffeeScript to JavaScript, or inline images as data URLs. Loaders even allow you to do things like require() css files right in your JavaScript! require() – https://webpack.github.io/docs/loaders.html For the purpose of this demonstration, I am going to show how to use with . style-loader css-loader First, we need to update our webpack configuration. /src/webpack.config.js import path from 'path'; export default {context: __dirname,entry: {app: [path.resolve(__dirname, './app')]},module: {loaders: [{include: path.resolve(__dirname, './app'),loader: 'babel-loader',test: /\.js$/},{loaders: [{loader: 'style-loader',query: {sourceMap: 1}},{loader: 'css-loader',query: {importLoaders: 1,localIdentName: '[path]___[name]___[local]',modules: 1}}],test: /\.css$/}]},output: {filename: '[name].js',path: path.resolve(__dirname, './dist')}}; Note: There is nothing specific in the above configuration. I am including the configuration only for completeness of the example. isomorphic-webpack Next, create a style sheet. /src/app/style.css .greetings {color: #f00;} Update the application to use the style sheet: import React from 'react';import ReactDOM from 'react-dom';import style from './style.css'; const app = <div className={style.greetings}>Hello, World!</div>; if (typeof ISOMORPHIC_WEBPACK === 'undefined') {ReactDOM.render(app, document.getElementById('app'));} export default app; Finally, restart the application and make an HTTP request. $ curl http://127.0.0.1:8000/ <!doctype html><html><head></head><body><div id='app'><div class="app-___style___greetings" data-reactroot="" data-reactid="1" data-react-checksum="72097819">Hello, World!</div></div> <script src='/static/app.js'></script> </body></html> As you see, the server responds with the evaluated value of the attribute, . class app-___style___greetings If it feels like you haven’t learned anything new in this section, then thats because there isn’t anything specific. There are no specific changes to the configuration or the application. That is a truly universal code base. isomorphic-webpack isomorphic-webpack You just won all the loaders! If you want to just checkout the code, use the following commands: $ git reset --hard 90d6e2708719a1727f2f8afd06f8b47432707b88$ npm install$ npm start Routes This section describes an experimental implementation. I have not tested this in production. Proceed with caution. It is pretty cool, though. documentation already includes a section about . You could follow that path… (but it requires to write server-side specific code) or you could trick into thinking that the script is running in a browser and avoid making any changes to your application. [react-router](https://github.com/ReactTraining/react-router) server rendering react-router By default, the does not evaluate scripts in directory. This is done for performance reasons: few scripts depend on the browser environment. However, (and ) do depend on browser environment. createIsomorphicWebpack node_modules react-router history I am going to tell to evaluate (and ) as if it is running in a browser. This is done using configuration. createIsomorphicWebpack react-router history nodeExternalsWhitelist createIsomorphicWebpack(webpackConfiguration, {nodeExternalsWhitelist: [/^react\-router/,/^history/]}); Now and packages are included in the webpack bundle and will be executed using the faux browser environment. react-router history However, we aren’t done yet. We need to tell what is the URL when evaluating the code. Bundle code can be evaluated using function ( is a property of the result), e.g. window evalCode evalCode createIsomorphicWebpack const {evalCode} = createIsomorphicWebpack(webpackConfiguration, {nodeExternalsWhitelist: [/^react\-router/,/^history/]}); app.get('/*', (req, res) => {evalCode(req.protocol + '://' + req.get('host') + req.originalUrl);const appBody = renderToString(require('../app').default);res.send(renderFullPage(appBody));}); Now, lets make some requests: $ curl http://127.0.0.1:8000/hello-world <!doctype html><html><head></head><body><div id='app'><div class="app-___style___greetings" data-reactroot="" data-reactid="1" data-react-checksum="72097819">Hello, World!</div></div> <script src='/static/app.js'></script></body></html> $ curl http://127.0.0.1:8000/hello-magic <!doctype html><html><head></head><body><div id='app'><div data-reactroot="" data-reactid="1" data-react-checksum="1580012444">Hello, Magic!</div></div> <script src='/static/app.js'></script></body></html> It takes a magician to know a magician. If you want to just checkout the code, use the following commands: $ git reset --hard 2959593fe217abada30d6ebe2c510e07a477c76b$ npm install$ npm start Conclusion There are already many articles that discuss the pros and cons of server-side rendering (e.g. ). In my specific case, I needed server-side rendering to enable Edge Side Includes (ESI). I have achieved this by first writing the client-side application and then using to render the application server-side. You’re Missing the Point of Server-Side Rendered JavaScript Apps isomorphic-webpack Evaluate the pros and cons of server-side rendering and if the pros outweigh the cons, then consider using isomorphic-webpack to make your application render server-side. Where to go next? Pull the https://github.com/gajus/isomorphic-webpack-demo Read the . There are some useful tips such as how to stall the HTTP request handling while has not completed a compilation. FAQ of the [isomorphic-webpack](https://github.com/gajus/isomorphic-webpack#isomorphic-webpack-faq) isomorphic-webpack