Skip to content

Instantly share code, notes, and snippets.

@braska
Last active September 25, 2022 16:55
Show Gist options
  • Save braska/9e04975fd0c2c1ec09f904035b51c6d9 to your computer and use it in GitHub Desktop.
Save braska/9e04975fd0c2c1ec09f904035b51c6d9 to your computer and use it in GitHub Desktop.
Express React SSR
doctype html
<html !{helmet.htmlAttributes.toString()}>
head.
!{helmet.title.toString()}
!{helmet.meta.toString()}
!{helmet.link.toString()}
<body !{helmet.bodyAttributes.toString()}>
#root.
!{content}
script(src=assets['manifest.js'])
script(src=assets['polyfill.js'])
script(src=assets['vendor.js'])
script(src=assets['main.js'])
</body>
</html>
const NODE_ENV = process.env.NODE_ENV || 'development';
const express = require('express');
const React = require('react');
const Helmet = require('react-helmet').Helmet;
const renderToString = require('react-dom/server').renderToString;
const ServerStyleSheet = require('styled-components').ServerStyleSheet;
const path = require('path');
const requireFromString = require('require-from-string');
let ReactApp;
let manifest;
const app = express();
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));
/* eslint-disable import/no-extraneous-dependencies, global-require, import/no-unresolved */
if (NODE_ENV === 'development') {
const webpack = require('webpack');
const webpackConfig = require('./webpack.config');
const compiler = webpack(webpackConfig);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true, publicPath: '/',
}));
app.use((req, res, next) => {
const content = compiler.compilers[1].outputFileSystem.readFileSync(path.join(__dirname, 'dist', 'server', 'app.js'), 'utf8');
ReactApp = requireFromString(content);
manifest = JSON.parse(compiler.compilers[0].outputFileSystem.readFileSync(path.join(__dirname, 'dist', 'client', 'manifest.json'), 'utf8'));
next();
});
app.use(require('webpack-hot-middleware')(compiler));
} else {
ReactApp = require('./dist/server/app');
manifest = require('./dist/client/manifest.json');
app.use(express.static(path.join(__dirname, 'dist', 'client')));
}
/* eslint-enable import/no-extraneous-dependencies, global-require */
app.get('/', (req, res) => {
const sheet = new ServerStyleSheet();
const appString = renderToString(sheet.collectStyles(React.createElement(ReactApp.default)));
const css = sheet.getStyleTags();
const helmet = Helmet.renderStatic();
res.render('index', {
assets: manifest,
styles: css,
helmet,
content: appString,
});
});
app.listen(3000);
const NODE_ENV = process.env.NODE_ENV || 'development';
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const nodeExternals = require('webpack-node-externals');
const path = require('path');
const addHash = (template, hash) => (NODE_ENV === 'production' ? template.replace(/\.[^.]+(\.map)?$/, `.[${hash}]$&`) : template);
const clientConfig = {
name: 'client',
context: path.resolve(__dirname, 'src'),
entry: {
polyfill: ['babel-polyfill'],
main: (files => (NODE_ENV !== 'production' ? [
'react-hot-loader/patch',
// activate HMR for React
'webpack/hot/only-dev-server',
// bundle the client for hot reloading
// only- means to only hot reload for successful updates
] : []).concat(files))(['./js/app']),
vendor: ['./js/vendor'],
},
output: {
path: path.resolve(__dirname, 'dist', 'client'),
publicPath: '/',
filename: addHash('assets/js/[name].js', 'chunkhash'),
sourceMapFilename: addHash('assets/js/[name].js.map', 'chunkhash'),
},
devtool: 'source-map',
resolve: {
extensions: ['.js', '.jsx', '.json'],
modules: ['shared', 'node_modules'],
},
module: {
rules: [
{
enforce: 'pre',
test: /\.jsx?$/,
use: 'eslint-loader',
exclude: /node_modules/,
},
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['css-loader'],
},
{
test: /\.(jpe?g|png|gif)$/,
use: 'file-loader',
},
],
},
plugins: [
new CleanWebpackPlugin(path.join('dist', 'client')),
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'manifest'],
minChunks: Infinity,
}),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.NamedModulesPlugin(),
// prints more readable module names in the browser console on HMR updates
new webpack.DefinePlugin({
__DEV__: JSON.stringify(NODE_ENV !== 'production'),
}),
new CopyWebpackPlugin([
{
from: 'static',
},
]),
new ManifestPlugin({
publicPath: '/',
}),
],
};
if (NODE_ENV !== 'production') {
clientConfig.entry.main.splice(1, 0, 'webpack-hot-middleware/client?path=/__webpack_hmr&name=client');
clientConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
}
const clientSSR = {
name: 'client SSR',
target: 'node',
context: path.resolve(__dirname, 'src'),
entry: ['./js/app/app'],
output: {
path: path.resolve(__dirname, 'dist', 'server'),
publicPath: '/',
filename: 'app.js',
libraryTarget: 'commonjs2',
},
plugins: [
new CleanWebpackPlugin(path.join('dist', 'server')),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.NamedModulesPlugin(),
],
resolve: {
extensions: ['.js', '.jsx', '.json'],
modules: ['shared', 'node_modules'],
},
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.(jpe?g|png|gif)$/,
use: 'file-loader?emitFile=false',
},
],
},
};
module.exports = [clientConfig, clientSSR];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment