I am finished developing a prototype of a project that is due for user testing in several days, and it works as expected when tested in the IDE. The backend is in Spring Boot, built in STS4 and the frontend is React, built in VS Code.
As a part of the process, I am creating a "war" package out of these two parts that is to be deployed on a testing server. Everything compiles as it should, the file is deployed to Apache (where I am testing it), and the problem manifests there.
UPDATE March 8, 2023
Thanks to @DhavalGhajar, in the comments below, there is some progress on the issue. After the compilation file is completed, rename the file that ends with .original to something else and remove that extension, leaving it only with the war extension. The, open the project with 7zip, navigate to the index.html file of the react project and take out the forward slashes that precede static
and point to the location of the css and js files:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico">
<title>React App</title>
<link href="static/css/main.9a0fe4f1.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="static/js/main.f55352b1.js"></script>
</body>
</html>
I've also changed the contents of the controller to return a joke:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DadJokesController {
@GetMapping("/api/dadjokes")
public String dadJokes() {
return "Justice is a dish best served cold, if it were served warm it would be just water.";
}
}
The program now does display the front page, but cannot find the API endpoint:
The issue is again to do with the routing, as essentially the program is looking here:
Yet, a call to the API does work:
There was a proxy statement in the package.json file of the React project that should not have been there for production build, and I've removed it since.
So far, this is the progress. Thank again to@DhavalGajjar for the insightful help thus far.
UPDATE Mar 7, 2023
This is a link to the example that I took inspiration from for trying to make this work; interestingly, the coded example here also produces the same output: see here for link
UPDATE
I've decided to create a parallel, simpler project to see why this error occurs, but have had no luck.
The project is compiled into a WAR and deployed on Apache:
Running the project produces this output:
UPDATE Another error appears if refreshed several times:
Contents of Controller file:
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
@CrossOrigin
@Controller
public class ClientForwardController {
@GetMapping(value = "/**/{path:[^\\.]*}")
public String forward() {
return "forward:/";
}
}
Project structure:
Main class is standard issue:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Backend6Application {
public static void main(String[] args) {
SpringApplication.run(Backend6Application.class, args);
}
}
Servlet initializer, which came this way with Spring Boot, but can otherwise be bundled with the main class:
package com.example.demo;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Backend6Application.class);
}
}
The application.properties file:
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
For context, this is the POM file in the project, and it uses the Maven frontend plugin to integrate and build the frontend component in the Spring Boot project. I've added that Tomcat is included, in lieu of the project using Apache to run, but it does work fine locally on the IDE on Tomcat.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wazooinc</groupId>
<artifactId>spring-boot-with-react</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>backend-6</name>
<description>Demo project for Spring Boot with React</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.12.1</version>
<executions>
<!-- installing node and npm -->
<execution>
<id>Install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<nodeVersion>v19.6.1</nodeVersion>
<npmVersion>9.4.0</npmVersion>
</configuration>
</execution>
<!-- running npm install -->
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<!-- build our production version -->
<execution>
<id>npm build</id>
<goals>
<goal>npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
<configuration>
<nodeVersion>v19.6.1</nodeVersion>
<workingDirectory>${project.basedir}/src/main/frontend</workingDirectory>
</configuration>
</plugin>
<!-- copy our react build artifacts to spring boot -->
<plugin>
<!-- <groupId>org.apache.maven.plugins</groupId> -->
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>Copy JavaScript app into SpringBoot</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes</outputDirectory>
<resources>
<resource>
<directory>${basedir}/src/main/frontend/build</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
I've looked at a wide range of blogs, Q&A and videos, but I haven't had much luck pinning the issue.
Two notable sources are this one and this one.
I've tried playing around with the output directory; it seems that Spring has a fixed reference point for serving the frontend that has not relevance to what's in the POM file?
The main requirements for packaging are specifying the "war" format, as I am aiming for a web based app, specifying that the the Tomcat instance is provided, since it will be deployed on Apache, as well as putting in place the range of plugins in those links (there are variants), which will build the react file and put it in a dedicated folder that then, as I gather, should be retrieved by Spring.
The default folder from where Spring Boot will get frontend components, as say with Thymeleaf, is under the resources folder, where you either park your HTML files in the static or templates folder.
Something else I tried was attempt to modify the asset-manifest.json file to include the name of the compiled file in the path of these build files, but that made no difference.
I did run npm run build on the React project previously and ported it with its build folder into the static folder. I did notice that the various example use the target folder, in which to store the compiled version of the frontend, so I kept that generally.
Further suggestions that I came across is that the js and css folders that are created in the React build file can be ported into the resources/static folder and then Spring Boot will automatically look in there, but that approach produced a 404 error for the js/css files that were otherwise present in there.
Last idea I came across was to create an index.html file in the static folder, which serves as a proxy to the App.js file that is the entry point of the React-based app, but that approach did not make a lot of sense to me, given the above and I'd appreciate some clarity if anyone has done it before.
I really have no other angles on how to approach this problem.
Thank you in advance!
The solution to this issue was two fold.
As per Dhaval's earlier comment, the first step is to remove the .original extension from the project war
file, then open it with 7zip, or similar and delete the forward slash from the index.html file in the compiled version of the app from where it points to the static
folder and the built version of the react JS and CSS files.
The end result of the index.html file should look like this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico">
<title>React App</title>
<link href="static/css/main.9a0fe4f1.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="static/js/main.f55352b1.js"></script>
</body>
</html>
To resolve the error where the content of the endpoint gets retrieved to the main page of the project, the only requirement is to include the name of the project in the path of the fetch request, like below. Note that the compiled project is called spring-react-example
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
state = {};
componentDidMount() {
this.dadJokes()
}
dadJokes = () => {
fetch('/spring-react-example/api/dadjokes')
.then(response => response.text())
.then(message => {
this.setState({message: message});
});
};
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo"/>
<h3 className="App-title">{this.state.message}</h3>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
Once that's done, you can redeploy it:
And you end up with rendering the info from the API to the frontend:
Hopefully this helps someone!