2

My client app is built with Create React App, which comes which lots of conveniences built-in. However it's not possible to do server side rendering out of the box. As a result, once the build command is run, all of the output is effectively static.

I have a use case where I do NOT want to switch to complete SSR setup, but would like to dynamically add some data to index.html file so that it is immediately available in javascript when client first loads file.

I worked out the following solution:

  • React app runs as a docker container, using the serve lib to serve static build content
  • A separate node service runs in a different docker container and has access to the build content from the react app via a shared volume
  • The node service runs a function every few minutes that reads the contents of index.html file using fs, inserts some additional data into a script tag (e.g. window.myData={someKey: 'someValue'}), and writes the updated string to index.html.

Locally using docker-compose, this works great. However, I'm wondering about possible ramifications of this approach, especially cases where an incoming request for the react app will fail because of some kind of file lock on index.html as it's being read / written by the node service.

I don't think this would be an issue, but I had enough doubt to post this question. The last thing I can afford are failed requests in my production app because of some unforeseen issue.

Any advice, suggestions, anecdotes, etc. are appreciated!

1
  • I know quite some time passed, but still: did you manage to solve this, without resorting to docker etc.? Maybe in a way similar to @Phuoc Do's answer? Commented Dec 28, 2021 at 18:19

2 Answers 2

1

I've successfully added SSR for CRA without ejecting. If that's your only use case, the SSR setup will be super simple as you don't have to deal with webpack or babel configs. If you are interested, follow 3 steps below:

  • Step 1: Add a basic Express server in a new folder at your root/ project folder (same level as src/)
  • Step 2: For main app routes, read file build/index.html and edit it as you want before sending.
  • Step 3: Other than those routes, serve CRA build/ static files as suggested by CRA SSR documentation

TL;DR

// root/server/index.js (your build is at root/build)
const express = require("express");
const fs = require("fs");
const path = require("path");

const app = express();

// step 2
const renderContent = (req, res) => {
  fs.readFile(
    path.resolve(__dirname, "../build/index.html"), "utf8", (err, htmlData) => {
      if (err) {
        return res.sendStatus(500);
      }

      /* Do something with htmlData here */

      return res.send(htmlData);
    });
}

app.use("^/$", renderContent); // step 2
app.use(express.static(path.resolve(__dirname, "../build"))); // step 3
app.use("*", renderContent); // step 2

// step 1
app.listen(process.env.PORT, () => {
    console.log(`Listening on port ${process.env.PORT}`);
});

Then, you just need to run: node server/index.js, and this server will serve your Create React App normally, except for the part that you edit your HTML above.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for response. I also did this a few months back and I believe it mostly worked, but I recall that using typescript presented some challenges where I needed separate tsconfig.json files for server and client. Also I believe using built-in CRA live reload for local development presented some challenges. Do you run this setup locally and if so have you run into any issues?
To use the same file tsconfig.json for the server-side, change the rootDir option to project folder root/ instead of folder root/src. With this tweak, root/server folder will also be included in the tsconfig.json. To use the built-in CRA development server, change step 3 as below: if (NODE_ENV === "development") { const proxy = createProxyMiddleware({ target: "http://localhost:3000", changeOrigin: true }); app.use(Express.static("public")); app.use(/^\/(static|asset-manifest.json).*/, proxy); } else { app.use(Express.static("build")); }
0

I don't know if this will help, but as a suggestion. I faced a similar usecase. I solved it very simply using HtmlWebpackPlugin.

Inside the webpack configuration. I write something like:

 new htmlWebpackPlugin({
      template: path.join(rootpath, '', 'index.html'),
      inject: false //<-- Notice here
    })

I set the "inject" to false and using this flag it does not append the script's/bundle on index.html and at the same time it allowed us to use ejs templating to loop over the file's we want to append on index.html.

Like on index.html I wrote:

 <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
                <link rel="stylesheet" type="text/css" href="<%= htmlWebpackPlugin.files.chunks[chunk].css %>">
                <script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
                <% } %>

My suggestion would be create a separate chunk/file(js) of that data you want to append dynamically on index.html and using EJS templating, you can achieve the desired result.

How to Create Chunks: READ HERE

Thanks, Hope it Help.

1 Comment

You do this with a creat-react-app application? Where is your webpack configuration?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.