1

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>

enter image description here

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:

enter image description here

The issue is again to do with the routing, as essentially the program is looking here:

enter image description here

Yet, a call to the API does work:

enter image description here

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: enter image description here

Running the project produces this output: enter image description here

UPDATE Another error appears if refreshed several times: enter image description here

Details of error: enter image description here

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:

enter image description here

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!

epicUsername
  • 611
  • 2
  • 8
  • 21
  • Please [don't upload text as image](https://meta.stackoverflow.com/a/285557/13447). Edit your question to contain all the information in text form - consider to use the editor's formatting options. Also see [ask], and check tags: I've removed [tag:apache], because there's no indication that you're using _Apache httpd_, it rather looks like Tomcat, but you'll know best (please read tag descriptions when tagging) which one it is, and if it's relevant to this question. – Olaf Kock Mar 03 '23 at 08:17
  • Thanks, Olaf. I included Apache, because it's my test server; on that note, I've added a dependency to identify Tomcat as provided, it was a part of the old version of this question. Does the application.properties or pom file need a specific provision for the server that it will be hosted on? – epicUsername Mar 03 '23 at 15:32
  • Which war file are you deploying in tomcat? You should deploy with `.war.original` to tomcat. Also, check the index.html file generated in react. Does it include a JS file with `/` at the start then it will skip the context path of your application? The same issue you facing. – Dhaval Gajjar Mar 08 '23 at 08:40
  • @DhavalGajjar, the war.original file will not upload, it errors out. For the record, I am using Apache Tomcat 9.0.71 to deploy. As for the index.js file, which comes with the pre-build React project, it just has the connection to App.js. In the index.html file that was created after the React build, the only paths in it are to "/static/css/main.css" "/static/js/main.js". I don't have the slash at the start. One other thing I did notice is that Tomcat server is meant to work with project built exclusively with Java. I'll try to do this with the big boy version of Apache. – epicUsername Mar 08 '23 at 14:35
  • 2
    You need to remove `.original` and use that war file. Also In index.html, you have "/static/css/main.css" and "/static/js/main.js". So change it to `"static/css/main.css"` and`"static/js/main.js"`. Remove first `/`. – Dhaval Gajjar Mar 08 '23 at 14:38
  • Thank you for suggesting that. Though, make sure to open the compiled war file via 7zip or some other compressor, to make those edits in the index.html file inside it. There is progress, but it can't seem to find the actual content from the API endpoint; there was a proxy in package.json file that I took out, which should not be in a production version, but that did not resolve the connection to the endpoint. – epicUsername Mar 08 '23 at 19:45

1 Answers1

1

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 warfile, 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 staticfolder 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:

enter image description here

And you end up with rendering the info from the API to the frontend:

enter image description here

Hopefully this helps someone!

epicUsername
  • 611
  • 2
  • 8
  • 21