Recently, a former graduation design website article details page to do server-side rendering reconstruction, see SSR implementation document seems to be very simple, but the implementation of a lot of pits.

Cannot be imported using import

  1. Error message:unexpected token import
  2. Scenario: First direct use in Nodeimport Story from '.. /js/containers/story'; I will report this error.
  3. Error: Node itself uses acommonjsThe supported module import and export modes arerequireAs well asmodule.exportHowever, the JS module mode defined by ES6 isimportandexport[default]So, although Node supports most es6 syntax, es6 modules conflict with node’s own CJS modules, so Node does not support ESM modules, thus causing unrecognizableimportIn the case.
  4. Solution: Node and React used to be the same throughout the project.babelrcDocuments, in order to solve this problem, also due toreactandnodeThe differences became bigger and bigger, and finally decided to split.babelrcFile,/node(back-end) directory as well/publicCreate them separately in the (front-end) directory.babelrcFile as front-end and back-end respectively for Babel configuration. One of the node.babelrcIn file configuration:
// .babelrc
"presets": [
    [
        "env",
	{
	    "targets": {
		"node": "current"
	    }
	}
    ],
"react",
"es2015",
"stage-0"
]
Copy the code

You can make Node recognize es6 syntax. The nodemon.json file is then recreated in the root directory to handle the import problem. The babel-Node plugin solves the problem of not recognizing the import.

$npm i babel-cli --save

Then rewrite the nodemon.json file:

// nodemon.json
{
  "verbose": false,
  "env": {
    "NODE_ENV": "development",
    "BABEL_ENV": "node"
  },
  "watch": ["node", "config"],
  "ignore": ["public"],
  "execMap":{
    "js": "babel-node"
  }
}
Copy the code

Then start the Node server again to see that the node correctly recognizes the import.

The Window object cannot be accessed

  1. Error message:window is not defined
  2. Scenario: The js file starts with a lot of window. XXX attributes. This error is reported when imported into the Node environment.
  3. Note: The server lacks BOM and DOM environment, so objects such as Window and Navigator cannot be accessed under the server.
  4. Solutions: There are three solutions to this error:
    1. Create a global Window object for the Node environment by using objects such as fake Window (such as the window library).
    2. The front-end component delays the invocation of these objects until didMount.
    3. Pass all properties of the component that use the window as props, and pass all properties that should be passed to the component as props in Node.
    // storyController.js
const props = {
  userInfo: ctx.session,
  articleInfo: {
    author: author[0].nickname,
    avatar: author[0].avatar,
    author_fans_count: fans_count[0].count, ... info[0]},isSelf: info[0].uid === ctx.session.uid
};
const html = renderToString(<Story {. props} / >);
ctx.render('story', {
  __PROPS__: JSON.stringify(props),
  title: info[0].title,
  html
});
Copy the code
// pages/story.js
render(
  <Story {. window.__PROPS__} / >,
  document.getElementById('root')
);
Copy the code

In this way, the component can get the data as props to solve the problem.

Unable to access the alias path

  1. Error message:cannot find module 'components/xxx'
  2. Scenario: Used in componentswebpackConfiguration of thealiasPath, node will report this error when doing SSR.
  3. Error description: When configured in WebpackaliasWe can abbreviate the path in the component, but we cannot recognize webpack’s path in Nodealias, so the path node takes fromnode_modulesIf the component is not found, an error is reported.
  4. The solution is for Node to also find an alias library:module-resolverLibraries can solve this problem perfectly.$ npm install --save-dev babel-plugin-module-resolverAfter the installation is complete, modify the node below.babelrcFile:
// .babelrc
"plugins": [["module-resolver", {
		"cwd": "babelrc"."root": [".. /public/js"]."alias": {
			"scss": ".. /public/scss"."components": ".. /public/js/components"."containers": ".. /public/js/containers"."constants": ".. /public/js/constants"."lib": ".. /public/js/lib"."router": ".. /public/js/router"."stirngs": ".. /public/js/string.js"."store": ".. /public/js/store"}}]]Copy the code

The aliases are the same as those in webpack.

Unable to import static resources

  1. Error message:/Users/xxx/xxx/node_modules/antd/lib/style/index.css:6
  2. Scenario: Used in the componentantdComponent, or introduce our ownscssFile, this error is reported.
  3. The client usually uses Webpack to compile, and the resources are loaded through variousloaderI’m going to do it, but I’m going to do itloaderCompile-generated code that is specific to the client environment cannot be applied to the server, so Node cannot parse itscss,lessAnd other documents.
  4. Solution: We just have Node not parse these style files. Add the following code to the top of the node entry file app.js:
// app.js
require.extensions['.scss'] = function() {
  return null;
};
require.extensions['.css'] = function() {
  return null;
};
require.extensions['.less'] = function() {
  return null;
};
require.extensions['.png'] = function(module, file) {
  return module._compile('module.exports = ""', file);
};
require.extensions['.svg'] = function() {
  return null;
};
Copy the code

This allowed Node to run normally, but it also exposed the problem of having no style when rendering the first screen, which caused the page style to shake when the client started loading the style.

To this end, we write webpack plug-in, the CSS file generated by ExtractTextPlugin, inline inserted into the puG template of the page, so that the first screen rendering of the server can support the style.

An error occurred when the require mode introduced components

  1. Error message:Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object
  2. Scenario: Node uses require to introduce a component and then passes renderToString to an error.
  3. Error Description: This error involves the interaction between ESM and CJS. What we introduce through require is not the same as what we introduce through import.
  4. Solutions:
    1. We pass in the componentexport default class xxx extends ComponentTo export components in node, you must passconst Component = require('.... ').defaultTo be able to get the components correctly, you can do it yourselfconsole.logOnce, directlyrequireIn came oneobjectThe inside of the,defaultProperties are our components.
    2. The installationbabel-plugin-add-module-exportsThe plug-in.$ npm install babel-plugin-add-module-exports@next --save-devAnd then rewrite react.babelrcFile:
// .babelrc
"plugins": [..."babel-plugin-add-module-exports"
]
Copy the code

This is a hack that forces esM and CJS to behave the same, but it can cause problems, so try not to mix ESM and CJS. It is better to import components directly in Node rather than require.

(to be continued)