Adding React to a Node Application with Jade or Pug

Justin Dickow
3 min readNov 1, 2019

--

It was very easy to miss React after working with it for the last few years when we had to go back to an older code base of a product still being used and add some new features. The code base in question is a Node JS application that does server-side rendering using Jade (not even Pug).

After performing the customary Google searches, “add react to node jade pug”, “react server side rendering”, etc — it became pretty obvious that most out there had written about adding React to their code base without really explaining why anything had to be done the way they did it. This made it difficult to adapt their instructions to the particular situation we had.

I’ll explain the architecture we were working within, the questions we had to answer, and how we answered those questions in order to get React working within our application.

The first question was “how are we going to include React and ReactDOM?”. There are a couple of different options, such as including an external source like

<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>

Our application was already using webpack to build a transpiled JavaScript bundle so the answer to the first question seemed pretty simple, we’d just npm install all the things. We’d also need the react preset for babel because we were going to be writing jsx and need it to be transpiled into our bundle.

npm install react react-dom @babel/preset-react

We know that the HTML was being rendered on the server by some Node JS controllers and Jade templates, and we needed to understand how the client-side JavaScript was being executed because in order to use React we’d need to use ReactDOM.render. In our particular situation, we had an entry in our webpack.js defined as

entry: { main: [path.resolve(SCRIPT_DIR, 'client.js')] }

Good or bad, client.js was defining a global object that was being used to choose which JavaScript to associate to each Jade Template which looks like something like this

import Index from './index'
const client = {
Index: Index
}
global.Client = Client

Then in the Jade Template

block footer-scripts
script.
Client.Index.load()

What this told us was that we could put a div in the Jade Template where we wanted our React component and render it similar to how our other JavaScript was being loaded.

First we created a simple React component to test this. We called it test.jsx with the intention of transpiling .jsx files separately from our other .js files to avoid a bunch of refactoring. We also knew that we didn’t want to actually try to render this component until the template was created, so we wrapped ReactDOM.render into a function that we could call similarly to Client.Index.load(). We decided it should be passed the id of the root div in order to avoid coupling of the React Component and the Jade Template

import React from 'react'
import ReactDOM from 'react-dom'
class Test extends React.Component {
render() {
return (<div>Hello React</div>)
}
}
function renderTest(rootDiv) {
ReactDOM.render(<Test/>, document.getElementById(rootDiv))
}
export default renderTest

Once we have the React Component we want to render we need to place a div in the Jade Template, expose our render function, call our render function, and make sure we update our webpack config to support this new file.

Adding the div in the Jade template was simple. We just put it in the body where we knew we’d be able to verify the final result

.div(id='testRoot')

Then updated client.js to expose the renderTest function we wanted

import Index from './index'
import renderTest from './components/test.jsx'
const client = {
Index: Index,
renderTest: renderTest
}
global.Client = Client

and called it the same way we were calling Index.load in our Jade Template

block footer-scripts
script.
Client.Index.load()
script.
Client.renderTest('testRoot')

Finally, we updated our webpack.js to transpile the jsx we had written. We separated this test to handle .jsx separately from our existing JavaScript

module: {
rules: [
{
test: /.jsx$/,
include: [SCRIPT_DIR],
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/react']
}
}
}
]
}

And that was it! Good luck!

--

--