简体   繁体   English

React SSR ReferenceError:文档未定义

[英]React SSR ReferenceError: document is not defined

I am having trouble to render react server side.我无法呈现 React 服务器端。 I always get ^我总是得到^

ReferenceError: document is not defined参考错误:文档未定义

var root = document.getElementById('root'); 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.我在 webpack、服务器或 app.js 中做错了什么吗?

here is my code这是我的代码

server.js服务器.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 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最后这是我的 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") ;根本原因:插件style-loader会在js中输入css,生成js中的样式标签,比如document.createelement ("style") but the server doesn't have the document api.但服务器没有文档 api。 So it crashed.所以它崩溃了。

Try using the mini-css-extract-plugin to pull the CSS code out of the js file.尝试使用mini-css-extract-plugin从 js 文件中mini-css-extract-plugin CSS 代码。 Avoid using document.creatEelement ("style") in js file;避免在js文件中使用document.creatEelement ("style")
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如果您有任何问题,请告诉我

Just move your routes to a different file.只需将您的routes移动到不同的文件。 When you import them, the whole file is executed, causing the error.导入它们时,会执行整个文件,从而导致错误。

When you import a file, the entire contents of the file will be executed, including the const root = document.getElementById('root');导入文件时,将执行文件的全部内容,包括const root = document.getElementById('root'); line.线。

If you move the routes declaration to a separate file and export it from there, you should be able to import that file both in server.js and in app.js you should be fine.如果您将routes声明移动到一个单独的文件并从那里导出,您应该能够在server.jsapp.js导入该文件,您应该没问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM