My two apps (a CSR and an SSR version of the same app)
I have created a standard Client Side Rendered (CSR) React application using "create-react-app". This is a client side only application whereby the whole app is deployed and run in a browser.
I have also created a Server Side Rendered (SSR or Universal) version of this application. When this application is built, it creates a server-bundle.js and a client-bundle.js. When the application runs, the browser requests the app from the server. The server returns HTML and a reference to the client-bundle.js. This client-bundle executes in the client browser to either "hydrate" the existing HTML content or render the whole content in the case of client side navigation where the server did not produce the content.
Issue with this design
I have two code bases where most of the code is identical, the styles, the components, the routes, etc. If i need to change one part of the application, I have to make sure I make the same change in both code bases.
When releasing this code to consumers, I would have to release two applications.
What I would like
I would like to have one code base that can produce a CSR application AND a SSR application.
I would like to be able to extend the configurations in my SSR app so that I could create a CSR only application like my "create-react-app". So that the application can either be built/run on one of two ways:
- server side app - where the server is used to get the client bundle for hydrating/rendering when client side navigation occurs OR
- client side app - where the a client bundle is deployed to a client browser and there is NO server running
CSR Application
Application structure
public
index.html
src
components
comp1.jsx
comp2.jsx
home.jsx
page1.jsx
styles
styles.css
index.jsx (contains render method containing the routes)
package.json
etc
the src/index.jsx contains
import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Home from './components/Home';
import Page1 from './components/Page1';
render(
(
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/page1" component={Page1} />
</Switch>
</Router>
),
document.getElementById('root'),
);
To build and run the app, "react-scripts" is used. I believe "webpack-dev-server" is used to serve the application.
"build": "react-scripts build",
"start": "react-scripts start",
The react-scripts updates the index.html to add in the client bundle references and moves it into the "build" directory.
SSR/Universal Application
Application structure
public
index.html
src
client
client.jsx (entry point for the client bundle, contains ReactDOM.hydrate)
components
comp1.jsx
comp2.jsx
pages
home.jsx
page1.jsx
routes.js
server
renderer.jsx (creates the html to return to the client)
server.js (entry point for the server bundle, an Express server)
styles
styles.css
package.json
webpack.base.config.js
webpack.client.config.js (builds the client bundle)
webpack.server.config.js (builds the server bundle)
etc
The webpack.client.config.js contains
const path = require('path');
const merge = require('webpack-merge');
const CopyPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
const baseConfig = require('./webpack.base.config.js');
const config = {
// do not need a 'target' as this is for the browser
entry: './src/client/client.jsx',
output: {
filename: 'client-bundle.js',
path: path.resolve(__dirname, 'public'),
},
plugins: [
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, 'src', 'styles'),
to: path.resolve(__dirname, 'public'),
},
],
}),
new webpack.DefinePlugin({
'process.env.IS_BROWSER': true,
}),
],
};
module.exports = merge(baseConfig, config);
The src/client/client.jsx contains
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';
import Routes from '../pages/Routes';
ReactDOM.hydrate(
<BrowserRouter>
<div>{renderRoutes(Routes)}</div>
</BrowserRouter>,
document.querySelector('#root'),
);
To build the app bundles
"build-server-bundle": "webpack --config webpack.server.config.js",
"build-client-bundle": "webpack --config webpack.client.config.js",
To run the app
node dist/server-bundle.js
The SSR application does not have "react-scripts" as it was not built with "create-react-app".
I was thinking it should be possible to create a client side bundle like "create-react-app" creates which can be then run on a browser only.
Does anyone know if this is possible and if so, how to do it.