9. React deployment on Nodejs and Express.js and Tomcat(4). tsconfig.json's 'homepage' and BrowserRouter's 'basename'. Errors and peculiarities

0. Preliminars


It is assumed that we are using CLIENT-SIDE ROUTING!

0.1 Typescript source project 

The source project is in the "my-react-app" folder. It was created by typing
npx create-react-app my-react-app --template typescript
and then adding extra libraries as react-router-dom etc.
 

0.2 Executing the project

The source project has been executed by 3 diferent ways

0.2.1 In development mode

By typing 
npm start

0.2.2 Running the production build with 'serve'

First, build the project, then install 'serve' if it is not installed and finally run the build
#Build the project
npm run build

#Install globally the "serve" javascript web server IF NOT INSTALLED!!!
sudo npm install -g serve  

#Run the application from the build folder
serve -s buid

0.2.3 Running the production build with 'express' or nodej

Once you have build the project in the "build" folder. Create a typescript project for the server and then copy the "build" folder to the server project and rename it to my-react-app
#Create react project
npx create-react-app server --template typescript

#Move to the server folder
cd server  

#Copy the build folder to the development project to the server folder
cp -r ../my-react-app/build .

#rename the build folder to my-react-app
rename build my-react-app
But in the 'server' folder you should have one of this files: 
- index_http.js for http
- index_https.js for https

Here is the index_http.js 
const express = require('express');  //Express web server
const fs = require('fs');            // File treatment
const http = require('http');      // http
const path = require('path');
const app = express();    // Use express

// serve up production assets
app.use(express.static('my-app06'));

// let the react app to handle any unknown routes 
// serve up the index.html if express does'nt recognize the route

app.get('*', (req, res) => {
    res.sendFile(path.resolve(__dirname, 'my-react-app', 'index.html'));
}); // if not in production use the port 5000 const PORT = process.env.PORT || 5000; console.log('server started on port:',PORT); //app.listen(PORT); //also runs http.createServer(app).listen(PORT)

And here is the index_https.js, but you need to have a server certificate as explained in a previous post  

const express = require('express');  //Express web server
const fs = require('fs');            // File treatment
const https = require('https');      // https
const path = require('path');
const app = express();    // Use express

// Certificate definitions
const opts = { 
    pfx: fs.readFileSync('keystores/mycert.p12'),  // change extension from p12 to pfx (as .p12 is equivalent to pfx)
    passphrase: 'mypassword' ,
    ca: fs.readFileSync('keystores/.npm.certs.pem'),
} 

// serve up production assets (as static assets)
app.use(express.static('my-react-app/build'));


// let the react app to handle any unknown routes 
// serve up the index.html if express does'nt recognize the route

app.get('*', (req, res) => {
    console.log('1: app.get(*)')
    res.sendFile(path.resolve(__dirname, 'my-react-app', 'build', 'index.html'));
});

// if not in production use the port 5000
const PORT = process.env.PORT || 5000;
console.log('server started on port:',PORT);
//app.listen(PORT);
https.createServer(opts, app).listen(PORT)

The structure of this folder is:

server
    └─index.js
    └─keystores (only if using https)
    └─my-react-app
                └─ index.html
                └─ static

1. Adding homepage in package.json

If add "homepage" to "package.json", when you run npm start in development mode, the server will start at the URL you have indicated. For instance:

{
  "name": "my-react-app",
  "version": "0.1.0",
  "private": true,
  "homepage": "/W00-REST/my-react-app",
  "dependencies": {
    "@emotion/react": "^11.10.4",
     ..... 
  }
}

When you run npm start, you get this message:

You can now view my-app05 in the browser.

  Local:            http://localhost:3000/W00-REST/my-react-app
  On Your Network:  shttp://192.xxx.xxx.xxx:3000/W00-REST/my-react-app

And your browser gets an undesired view and doesn't work!!

2. Adding also basename to <BrowseRouter>

if it is added  basename="/W00-REST/my-react-app" to the <BrowseRouter>

For instance in the App.tsx file:

import './App.css';

import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"
import { Login } from './components/Login'
import Main from './components/Main'

function App() {
  return (
    <BrowserRouter basename="/W00-REST/my-react-app">
      <Routes>
      <Route path="login"    element={<Login />} />
      <Route path="main"     element={<Main />} />
      <Route path=""         element={<Navigate to="/login" />} />

      <Route path="*"        
        element={
          <main style={{ padding: "1rem" }}>
            <p>There is nothing here!</p>
          </main>
        }
      />
    </Routes>
  </BrowserRouter>
  );
}

export default App;

Now it should run OK when you run npm start.

But if you build your project with npm run build and run it with serve -s build it doesn't run !!! Neither runs with express. It doesn't matter whether your URL is "http://localhost.3000" or "http://localhost/W00-REST/my-react-app". But surprise! With some arrangements (as explained nextly) it can run on Tomcat!!1

3. Adding basename (but NOT homepage)

Removing "homepage" from package.json and adding "basename="/W00-REST/my-react-app" from <BrowseRouter> and running npm start in development mode it runs OK in the URL "http://localhost.3000/W00-REST/my-react-app"

Executing npm run build and running serve -s build in production mode it runs OK in the URL "http://localhost.3000/W00-REST/my-react-app"

Also runs with express

4. Removing basename and homepage

Removing "homepage" from package.json and removing "basename" from <BrowseRouter> and running npm start in development mode it runs OK in the URL "http://localhost.3000"

Executing npm run build and running serve -s build in production mode it runs OK in the URL "http://localhost.3000"

Also runs in express

5. SPA, Tomcat and static servers

React is SPA (single page application), that means that there is only a file that manages routing. So all the URL should be addressed to this file.

In multiple page application, each URL addresses to a different page

But in a SPA, we need to use different URLs, so when when a URL is requested, we need to filter it and redirect to the file that manages the routing. This file analizes the URL and routes it.

So we need to use the urls that have been defined in the routing component.

For instance this routing component in the previous section as basename equals to "/W00-REST/my-react-app" we can only have URLS that begins with 

http://<host>:<port>/W00-REST/my-reat-app

but int the <Route> sections, are only allowed this suffixes : "" (nothing), "login" and "main" that means that only these URLS will be served:

http://<host>:<port>/W00-REST/my-reat-app

http://<host>:<port>/W00-REST/my-reat-app/

http://<host>:<port>/W00-REST/my-reat-app/login

http://<host>:<port>/W00-REST/my-reat-app/main 

So, you should know perfectly that basename+routes must match perfectly with the supplied URLS, or you will be redirected to the "not found page".

In addition Tomcat needs to set the "homepage" property of the package.json to the same value of the "basename" of the <BrowseRouter>

Tomcat, (and also node/express) need the redirection the requests to to the index.html file.

In node/express this code is used (see previous section the file index.js)

// let the react app to handle any unknown routes 
// serve up the index.html if express does'nt recognize the route
app.get('*', (req, res) => {
    console.log('1: app.get(*)')
    res.sendFile(path.resolve(__dirname, 'my-react-app', 'build', 'index.html')); 

});

And in Tomcat it is used a filter in web.xml file for redirecting the 404 error pages (not found).

<?xml version="1.0" encoding="UTF-8"?>
<web-app 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> 
    https://create-react-app.dev/docs/deployment/
    <error-page>
    	<error-code>404</error-code>
    	<location>/spa.jsp</location>
	</error-page>   
         
</web-app>

 There are alternatives to the web.xml file (for Tomcat), as using Valve (Tomcat 8+) or with java files with annotations, for instance:

public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain)
        throws IOException, ServletException {
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    if (httpResponse.getStatus() == 404) {
        //httpResponse.sendRedirect("pages/PageNotFound.html");
        response.setHeader("Location", request.getContextPath() + "/spa.jsp");
    } else {
        filterChain.doFilter(request, response);
    }
}

6. Deploying to Tomcat

To deploy to Tomcat:

1. In the package.json  add this entry "homepage":"/W00-REST/my-react-app"  (in the typescript development folder "my-react-app")

2.  Look for the react component in charge of the application routing and add basename clause

<BrowserRouter basename="/W00-REST/my-react-app"> 

3. Run "npm run build" in the development folder (that creates the build folder). The development folder is "my-react-app" in this case.

4. Copy the my-react-app/build folder to the src/main/webapp folder of the java project.

5. Rename the"src/main/webapp/build" to "src/main/webapp/my-react-app" in the java project

6. Create this "web.xml" file in the "src/main/webapp/WEB-INF/" folder thar redirects all the not found pages (error 404) to the servlet spa.jsp that in turn redirects to the index.html

<?xml version="1.0" encoding="UTF-8"?>
<web-app 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> 
    https://create-react-app.dev/docs/deployment/
    <error-page>
    	<error-code>404</error-code>
    	<location>/spa.jsp</location>
	</error-page>   
         
</web-app>

7. Add this file "spa.jsp" to the "src/main/webapp/" folder that points to 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%response.setStatus(200);%>
<%@include file="./my-react-app/index.html"%> 

JavaApp (W00-REST)
   └─src
      └─main
          └─webapp
               └─spa.jsp
               ├─WEB-INF
               │    └─web.xml
                                    └─my-react-app (Copy of th build folder)
                    └─index.html
                    └─other css, jpg, stc files
                    └─static
                         └─css
                         └─js
                         └─media

7. Summary

To use the URL http:<host>:3000/<java_app>/<ts_app> in these servers :

SERVER

homepage

basename

web.xml

spa.jsp

Observations:

basename in <browserRouter> is necessary 

npm start

Optional YesNoNoThe home page can be set or not

serve -s build

NoYesNoNoSetting the homepage, does not work!

node/express

NoYesNoNoSetting the homepage dows not work! But needs the index.js (index_http.js or index_https.js) to activate node/express

Tomcat

YesYesYesYesYou need to set the homepage

Where the values are:

<java_app> = "W00-REST" (in the last example)

<ts_app> ="my-react-app" (in the last example)

<port> = (3000 in npm start and serve-s build but in serve it can be set using -l parameter; in node/express the port can be set by the index.js, and in Tomcat it is defined in the server.xml file)

"homepage"='/<java_app>/<ts_app>'  (for instance '/W00-REST/my-react-app')

"basename=='/<java_app>/<ts_app>'  (for instance '/W00-REST/my-react-app')

web.xml: is the file in the "src/main/webapp/WEB-INF" and redirects to spa.jsp that redirects to the deployed index.html

Note. in Tomcat an node/express, the https protocol can be used also. And in that case a "redirect" mechanism is needed for passing from http to https protocol. In the previous post it is explained for node/expres and in this post it is for Tomcat




Comentarios

Entradas populares de este blog

15. Next.js Tutorial (2). Fetching data. Async functions getStaticProps, getServerSideProps, getStaticPaths

14. Next.js Tutorial (1)

10. React deployment on Nginx (5). Passing client certificate DN to react client