Sharing React components between SPA and server rendered views
In this article, we are going to introduce how we use shared react component between SPA(react+mobx+webpack), and a traditional server rendered views(express+pug)
Why using SPA and server rendering together for a web app?
As a Sass app(Cominsoon), we can supply users with the best UX experience by building SPA. On the other hand, we want to optimize our SEO ranking, that is why we are rendering static pages like landing page on the server side.
General steps
The shared react components are header and footer which display different content based on user’s login status, and the general steps are:
- Specify an id(e.g. <div id=”header” />) in pug for rendering React component
- Webpack compiles React component to JS and CSS
- Pug loads the JS and CSS
Detailed steps
using header component for the example
Preparing the React component
For rendering a component, you always need a specific HTML element and let react-dom to render your React component on it. Here is the entry point of header component, let’s call it header.js:
client/header.jsimport React from 'react'
import { render } from 'react-dom'
import HeaderContainer from './container/HeaderContainer'render(
<HeaderContainer />,
document.getElementById('header')
)client/container/HeaderContainer.jsimport React, { Component } from 'react'
import Header from '../pages/Application/Header'
import { userStore } from '../stores'export default class HeaderContainer extends Component {
componentWillMount () {
// Ping server to check and update user's login status
userStore.ping().catch(() => {})
}render () {
return (
<Header />
)
}
}client/pages/Application/Header.jsimport React, { Component } from 'react'
import { observer } from 'mobx-react'
import { userStore } from '../stores'require('./Header.scss')@observer
export default class Header extends Component {
render () {
// the HTML for header, based on userStore
}
}
The reason why we render HeaderContainer.js instead of Header.js is that we need to let header component know how to check user’s login status. In SPA, Header.js is just a pure react component, and it only takes care of rendering header HTML content.
Webpack configuration
We only keep part of the config for a simple demonstration. The outputs are saved to the public folder `/public/shared/` as static assets, and the WebpackPugManifestPlugin is for generating a pug file which contains the scripts we generated.
You may ask why we have to generate a pug file to contain the scripts compiled by webpack? That is because everytime the script files we generated may named with a different hash based on webpack caching strategy, and we do not want to type the files’ name manually in layout pug file every time the output files updated.
const path = require('path')
const webpack = require('webpack')
const WebpackPugManifestPlugin = require('../../bin/webpack-pug')module.exports = {
...
entry: {
vendor: ['react', 'react-dom', 'mobx', 'mobx-react'],
header: path.join(__dirname, '../header.js')
},
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, '../../public/shared')
},
plugins: [
new WebpackPugManifestPlugin(),
...
],
...
}
the content of pug file (/public/shared/sharedScript.pug) generated by WebpackPugManifestPlugin is the following:
script(type="text/javascript",src="/public/shared/manifest.8a0bd365aad45e8144d2.js")
script(type="text/javascript",src="/public/shared/vendor.3d88e7b92df60c725678.js")
script(type="text/javascript",src="/public/shared/header.15f217e43fcd19fc8bf0.js")
Importing shared JS and CSS into layout pug
For the script above we can find that the files(JS, CSS and pug) are generated into the folder( /public/shared/ ). We need to move the pug file to the pug folder (/server/views/ ) first, and we do it in the Makefile
build-shared:
@echo "Building shared files..."
@rm -rf ./public/shared
@mkdir -p ./public/shared
@NODE_ENV=production $(NODE_BIN)/webpack --config client/webpack/shared.js
@mv public/shared/sharedScript.pug server/views/sharedScript.pug
Now we can import them into the layout pug
server/views/_layout.pugdoctype html
html
head
...
link(href='/public/shared/header.css', rel='stylesheet', type='text/css')
body
div(id='header')
include sharedScript
That is all
Basically, the React header component is going to be rendered by the scripts imported in the _layout.pug. In this way, server rendered views can keep the same Header with the SPA.