How to serve ReactJS static files with expressJS? - reactjs

The Problem
I have successfully served the index.html file of my React app, but the index.js that replaces <root> in the html file
with my first React component is not triggering on ReactDOM.render.
How do I get the index.js file to start? If my understanding of serving a React app is skewed in certain ways, I would greatly
appreciate clarification.
Folder Structure
/ - contains all server-side files, including server.js
/client/ - contains all React files
/client/build/ - contains all production-ready client files
/client/build/index.html
/client/build/static/js/main.[hash].js - seems to be a minified version of index.js that contains the ReactDOM.render for
my React app
Current Deployment
I am using Facebook's create-react-app for the /client/ directory, including npm run build to automatically populate /client/build/
File Snippets
// server.js
let app = express();
app.use(express.static(path.join(__dirname, '../client/public')));
This successfully loads the default index.html provided by
create-react-app
// index.html
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
The above section of code may/may not be useful, but it is the default
html file that comes with create-react-app. Do I need to replace the
noscript tag with a script tag that references the minified index.js
file? I have attempted that, and nothing changed, but perhaps it is
because of incorrect relative path-making.

After trying many different things through trial/error, the solution is quite simple:
Serve the /client/build folder in the static call, like so:
app.use(express.static(path.join(__dirname, '../client/build')));

I had the same problem for a while and I would say that the solution that works is divided into 2 parts to avoid problems with the routers
Server the static from the CRA build (in your case the client/build)
const buildPath = path.normalize(path.join(__dirname, '../client/build'));
app.use(express.static(buildPath));
Set a final route (after all other routers in your server) to use the following:
const rootRouter = express.Router();
/*
* all your other routes go here
*/
rootRouter.get('(/*)?', async (req, res, next) => {
res.sendFile(path.join(buildPath, 'index.html'));
});
app.use(rootRouter);

//on your react app run
npm run build
//The insert the following code on your server
const path = require("path");
app.use(express.static(path.join(__dirname,"nameOfYourReactApp","build")))
//Replace nameOfYourReactApp with the name of your app

my project structure
project
-->client
back end(express)\
after using npn run build i found out that index.html in build was using wrong directory of css files or static instead of using
const path = require('path');
app.use(express.static(path.join(__dirname, '/client/build/')));
i know i also tried ../client...... but not working
so what i did is cut and paste static folder of build in root directory this image can give you the idea, and its working structure

Related

Dynamically updating index.html file in production build of a create react app

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!
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.
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.

How to deploy Next.js app without Node.js server?

I was hoping to deploy a Next.js app with Laravel API. I had developed React apps with CRA and in those I used the API server to serve the index.html of the CRA as the entry point of the app.
But in Next.js, after development I get to know that it needs a Node.js server to serve (which is my bad, didn't notice that). There is an option next export that builds a static representation of the Next.js app and it has an index.html. I am serving the index.html as the entry of the app by my Laravel API. It is serving the page, but just some of the static contents.
What I was hoping to know is it possible to host the aPI and the Next app from a single PHP shared hosting without any node server? If so, how? If not so, what could be the alternatives?
Actually the acepted answer is completly wrong, when you do yarn build and in your package.json is set like "build": "next build && next export", you will get an out folder which all the items in there are used to build without node.js server
Now since you are using laravel, and you use the out folder you will only load half of the page because the routes are not set properly. for that to work you need to edit your next.config.js edit it to
module.exports = {
distDir: '/resources/views',
assetPrefix: '/resources/views',
}
These will set the root directory to the root one in Laravel. now this will work for SPA (single page application) only for the dynamic routes you need to match with a view file for each one that you have in your out folder
For each route that you have you need to create a new "get" route in laravel
Route::get('/', function () {
return require resource_path('views/index.html');
});
Route::get('/contacts', function () {
return require resource_path('views/contacts.html');
});
Route::get('/post/{slug}', function () {
return require resource_path('views/post/[slug].html');
});
Notice that you can pass a wildcard for dynamic routes and they are all gonna work. once you do that and you deploy route out folder inside /resources/views in Laravel it's going to work
Apparently there is no alternative to nodejs server, which is not an option for me currently, so I unfortunately had to abandon next.js and create a CRA app and used as much from the next.js as I could.

React routes with two parameters not working on react build

I'm using react router and everything works fine in development, but the production build of the react app is not working when I visit some routes with more than one parameter for example http://localhost:3000/me/edit.
I tried deploying in netlify with the _redirects file, also with nginx and also with serve. None of the three options worked.
It seems to be requesting a file inside the static folder. I will attach two images so you can see what I'm refering to.
This is what I can see under Sources tab:
When I visit http://localhost:3000/me it works fine and this is what I can see on chrome:
When I visit http://localhost:3000/me/edit if leads to a blank page, and it seems to be requesting a file inside me > static > edit which obviously does not exist; and this is what I can see on chrome:
So, I could finally solve this, the problem was that routes with more than one parameter either static or dynamic (eg: /me/edit or /user/:id) were looking for a file inside a folder, in this case me or user which do not exist.
Here's how I solved it:
I created a folder called 'production' inside this folder I ran npm init, I pasted the entire build folder generated by react. Then I created a file called server.js (which is the entry point I specified). This file contains the following:
const express = require('express')
const path = require('path');
const cors = require('cors')
// Initializing express
const app = express()
// Initializing middleware to indicate express where to serve the static files from:
app.use(express.static(path.join(__dirname, 'build')));
app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.use(cors())
const PORT = process.env.PORT || 4000
app.listen(PORT, () => console.log(`Server listening on port ${PORT} 🔥`))
In addition, I added this: <base href="/"> to my index.html file inside the build folder.
On the package.json I added the start script "start": "node server.js" and that's it, never was typing npm start so satisfactory 😂
The entire project now looks like this:

How to set environment variable for a deployed Reactjs app on server?

I think my question is different from previously asked questions about environment variables. I know how to set the environment variables while building the ReactJs app. Once the app is built, it gives me the static files and i put it on server. This app works like a normal webapp.
Question:
If i want to give the client provision of changing one of the environment variables on a deployed build on server without opening the code files, how i can do that?
Note:
As this is not a specific coding question, please move this question to relevant SO forum if you think.
Thanks
It is not possible to change REACT_APP_XXXXX env. vars after compilation. They are getting "baked in" into the app permanently.
I solved this problem by having a dynamic JSON manifest file that is being served from the backend. The app loads the JSON file and reads its values. A variation of this approach is to have a Javascript js file served from the backend. The script executes a function which sets some variables in global window object.
Here is a snippet of a node express app that serves the manifest as JSON and as a JS file.
const express = require('express')
const app = express()
const manifest = {
appTheme: process.env.APP_THEME,
foo: "bar"
};
const cacheTimeoutSec = 600
class ManifestController {
static getJS(req, res) {
/// Generate IIFE function that sets window.serverManifest object
let fileChunks = [
'(function(){',
'var serverManifest=',
JSON.stringify(manifest),
'; window.serverManifest = serverManifest',
'})()',
].join('');
res.set('Cache-Control', `public, max-age=${cacheTimeoutSec}`);
res.setHeader('content-type', 'text/javascript');
res.write(fileChunks);
res.end();
}
static getJSON(req, res) {
res.json(manifest);
}
}
// Serve manifest in JS
app.get('/server-manifest.js', ManifestController.getJS);
// Serve manifest as JSON
app.get('/server-manifest.json', ManifestController.getJSON);
Option 1:
The react app manually fetches the JSON file from your backend (e.g. https://api.mybackend.com/server-manifest.json) and act on the data.
Option 2:
Include <script> tag in the html file header like so
<html lang="en">
<head>
<script src="https://api.mybackend.com/server-manifest.js"></script>
<title>Home</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
If you put it in the <head> the browser will load the script and will execute it. The script sets the manifest in the global window.serverManifest object which the React app can access now at any time.

NUXTJS Static Hosting path

I created a small SPA Nuxt.js app and I’m having a hosting issue.
I’m trying to upload the app to a static hosting in bluehost. Under the domain name I created a sub-folder where I would like to run the app. (www.domain.com/myapp/)
I ran the command NUXT Generate to generate the static folder (Dist) - When I upload the content of the Dist folder to the folder myapp in the server and try to access it, it does not work.
I can access index.html but everything is broken. The reason is because it is looking for all the files in the domain’s root folder and not in the myapp folder.
I there a way I can tell the generate command to use the myapp folder path as its root folder?
Any help figuring this out would be greatly appreciated.
Thanks
CES
You have to set the base path for the router in your nuxt config file:
// nuxt.config.js
export default {
router: {
base: '/myapp/'
},
// ...
}
see nuxt documentation

Resources