React server side rendering is not updating after client side route change

Viewed 1105

For the first time, the server side is refreshed, but the next time, only the client side is changed as usual, and the server does not change.

For example, with each refresh or typing of the address in the browser, the server also changes and works, but if I switch between pages on the client side with react router, the server does not change.

what is the problem?

#server/server.js

import path from 'path';
import fs from 'fs';

import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';

import App from '../src/App';

const PORT = 5000;
const app = express();

const router = express.Router();
app.use('/build', express.static('build'));

app.use((req, res, next) => {
  if (/\.js|\.css|\.png|\.jpg|\.jpeg/.test(req.path)) {
    res.redirect('/build' + req.path);
  } else {
    next();
  }
})
app.get('*', (req, res) => {
  const context = {};
  const app = ReactDOMServer.renderToString(
    <StaticRouter location={req.path} context={context}>
      <App />
    </StaticRouter>
  );

  const indexFile = path.resolve('./build/index.html');
  fs.readFile(indexFile, 'utf-8', (err, data) => {
    if (err) {
      console.log("Something went wrong:", err);
      return res.status(500).send("Oops, better luck next time!");
    }

    return res.send(data.replace('<div id="root"></div>', `<div id="root">${app}</div>`));
  });
});

router.use(express.static(path.resolve(__dirname, '..', 'build'), { maxAge: '10d' }));

app.use(router);

app.listen(PORT, () => {
  console.log(`SSR running on ${PORT}`);
});

#server/index.js

require('ignore-styles');

require('@babel/register')({
    ignore: [/(node_module)/],
    presets: ['@babel/preset-env', '@babel/preset-react'],
    plugins: ['@babel/transform-runtime'],
});

require('./server');

#index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import * as serviceWorker from './serviceWorker';
import App from './App';

ReactDOM.hydrate(
  <BrowserRouter>
      <App />
  </BrowserRouter>,
  document.getElementById('root')
);
1 Answers

There is nothing wrong with your Server Side Rendering setup. This is what's going on under the hood.

When you first type and enter the URL for a route of the application or you refresh the browser tab it hits the server and loads the index.html file rendered on the server-side with the help of renderToString.

ReactDOMServer.renderToString(...)`

Then index.html is viewed and is hydrated (attaching event handlers...etc) to this skeleton HTML file.

Note that you load the index.html from the build folder and replace only the div with root as the id. After building the app it adds js resources need to change the DOM (these resources are actually your frontend logic you wrote for your app) and this is required for client-side rendering. If you check the index.html it has the following script tags to load them.

...
<script src="/static/js/xxxxxxxxxxxxxxx.chunk.js">
...

When you go to another route by clicking a link inside your app. It does not hit the server again and it starts to execute js attached from the client bundle as I said above. Client-side js does the routing of the app properly. That's why it's not hitting your server. This is the expected nature of an isomorphic web application(Behaves the same on the server-side or client-side). And after that, if you refresh the browser that again loads the index.html from the server.

Related