9. React deployment on Nodejs and Express.js and Tomcat(4). tsconfig.json's 'homepage' and BrowserRouter's 'basename'. Errors and peculiarities
0. Preliminars
0.1 Typescript source project
npx create-react-app my-react-app --template typescript
0.2 Executing the project
0.2.1 In development mode
npm start
0.2.2 Running the production build with 'serve'
#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
#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
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)
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:
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"%>
└─src
7. Summary
SERVER | homepage | basename | web.xml | spa.jsp | Observations:basename in <browserRouter> is necessary |
npm start | Optional | Yes | No | No | The home page can be set or not |
serve -s build | No | Yes | No | No | Setting the homepage, does not work! |
node/express | No | Yes | No | No | Setting the homepage dows not work! But needs the index.js (index_http.js or index_https.js) to activate node/express |
Tomcat | Yes | Yes | Yes | Yes | You 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
Publicar un comentario