I am having trouble to render react server side. I always get ^
ReferenceError: document is not defined
var root = document.getElementById('root');
I know what is happing that node dosen't understand browsers document object. But I cant seem to fix it. Am I doing something wrong in webpack or on the server or in app.js.
here is my code
server.js
import express from 'express';
import path from 'path';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import configureStore from './src/store/configureStore';
import routes from './src';
const app = express();
app.use(express.static(path.join(__dirname, 'build')));
/**
* React application route, supports server side rendering.
*/
app.get('/*', function(req, res) {
// Create a context for <StaticRouter>, which will allow us to
// query for the results of the render.
const reactRouterContext = {};
// Compile an initial state
const preloadedState = {};
// Create a new Redux store instance
const store = configureStore(preloadedState);
// Declare our React application.
const app = (
<Provider store={store}>
<StaticRouter location={req.url} context={reactRouterContext}>
{routes}
</StaticRouter>
</Provider>
);
const appString = renderToString(app);
// Load contents of index.html
fs.readFile(
path.join(__dirname, 'build', 'index.html'),
'utf8',
(err, data) => {
if (err) throw err;
// Inserts the rendered React HTML into our main div
const document = data.replace(
/<main id="root"><\/main>/,
`<main id="root">${app}</main>`
);
// Sends the response back to the client
res.status(200).send();
}
);
});
app.listen(9000);
webpack.config.node.js
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
// http://jlongster.com/Backend-Apps-with-Webpack--Part-I
let nodeModules = {};
fs
.readdirSync('node_modules')
.filter(function(f) {
return f.charAt(0) !== '.';
})
.forEach(function(f) {
nodeModules[f] = 'commonjs ' + f;
});
module.exports = {
target: 'node',
entry: './server.js',
output: {
path: path.join(__dirname, 'build'),
filename: 'server.js',
libraryTarget: 'commonjs2',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react', 'stage-0'],
},
},
{
test: /\.css$/,
exclude: /node_modules/,
loader: 'css-loader',
},
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
},
},
],
},
],
},
plugins: [
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
}),
//new UglifyJSPlugin(),
],
externals: nodeModules,
};
And finally here is my app.js
import React from 'react';
import { hydrate, render } from 'react-dom'; // eslint-disable-line
import { Provider } from 'react-redux';
import { BrowserRouter, Route } from 'react-router-dom';
import configureStore from './store/configureStore';
import Home from './routes/home';
import Profile from './routes/profile';
import Contact from './routes/contact';
import RouterWrapper from './routeWrapper';
import './index.css';
const store = configureStore();
window.store = store;
// Does the user's browser support the HTML5 history API?
// If the user's browser doesn't support the HTML5 history API then we
// will force full page refreshes on each page change.
const supportsHistory =
window && window.history && 'pushState' in window.history ? true : false;
const routes = (
<div>
<RouterWrapper>
<Route exact path="/" component={Home} />
<Route path="/profile" component={Profile} />
<Route path="/contact" component={Contact} />
</RouterWrapper>
</div>
);
const app = (
<Provider store={store}>
<BrowserRouter forceRefresh={!supportsHistory}>{routes}</BrowserRouter>
</Provider>
);
const root = document.getElementById('root');
const renderApp = newRoot => {
if (process.env.NODE_ENV === 'development') {
render(app, newRoot);
} else {
hydrate(app, newRoot);
}
};
renderApp(root);
export default routes;
Root cause: the plugin style-loader will type CSS into js to generate the style label in js, such as document.createelement ("style")
; but the server doesn't have the document api. So it crashed.
Try using the mini-css-extract-plugin
to pull the CSS code out of the js file.
Avoid using document.creatEelement ("style")
in js file;
Post a section of my configuration
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: process.env.NODE_ENV === 'development' ? 'development' : 'production',
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
// use: ['style-loader', 'css-loader']
}
],
},
plugins: [new MiniCssExtractPlugin()],
};
Please let me know if you have any questions
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With