Issue
Issue:
Hello! I've been spending a fair bit of time now trying to get a Spring Boot server to host a React Application. I've been able to work out nearly everything except for this last issue and that's getting the React Page to render.
The page (called "Library") is opened by the server. I know this part of the server is working correctly for two reasons.
- The browser tab's name is set to "Library" like it should.
- When I set the page to only open an HTML page with a simple h1 header that says "Hello", it works as well.
Thoughts:
The issue seems to be coming from Thymeleaf but I can't find the cause of the problem. The issue doesn't seem to be Thymeleaf after-all but another issue in the configuration. Though I may be incorrect about this. I've tried a variety of settings and configurations and the one I have now seems to be working the best overall.
It doesn't seem that the React application is the cause. I am building the react project with no obvious issues occurring during the build process. All files are being generated where they're expected. When I open the resulting Webpack bundle as a standalone file in a browser, it opens and functions like it should.
The server itself is, this issue notwithstanding, running exactly as expected.
There is no REST functionality currently being used. The only goal of this server, as of right now, is to simply open the React application.
Files/Configuration:
These are all of the files that I think are relevant. I'll add more if they're requested.
File Structure
+-- src
+-- \main
+-- \java
+-- \react
+-- \resources
+-- \templates
+-- bundle.js
+-- index.html
+-- bundle.js.License.txt
+-- application.properties
+-- \test
+-- package.json
+-- pom.xml
+-- webpack.config.js
Webpack.Config.js
var path = require('path');
const HtmlWebPackPlugin = require("html-webpack-plugin");
module.exports = {
entry: './src/main/react/index.js',
mode: 'development',
output: {
path: path.resolve(__dirname, "src/main/resources/templates/"),
filename: "bundle.js",
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules)/,
use: [{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env", "@babel/preset-react"]
}
}]
},
{
test: /\.css$/,
exclude: /(node_modules)/,
use: ["style-loader", "css-loader"],
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./src/main/react/index.html",
}),
],
};
package.json
{
"name": "website",
"version": "0.1.0",
"main": "index.js",
"dependencies": {
"acorn": "^8.7.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-table": "^7.7.0",
"react-tabulator": "^0.16.1",
"rest": "^1.3.1",
"web-vitals": "^1.1.2"
},
"scripts": {
"start": "npm start",
"build": "webpack",
"watch": "webpack --watch -d --output ./target/classes/static/built/bundle.js"
},
"proxy": "http://localhost:8080",
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"devDependencies": {
"@babel/core": "^7.13.16",
"@babel/preset-env": "^7.13.15",
"@babel/preset-react": "^7.13.13",
"babel-loader": "^8.2.2",
"css-loader": "^5.2.7",
"html-webpack-plugin": "^5.3.1",
"style-loader": "^2.0.0",
"webpack": "^5.35.1",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2"
}
}
pom.xml
<?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.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>jweber.code</groupId>
<artifactId>Library</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Library</name>
<description>Library Website</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</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>
</plugin>
</plugins>
</build>
</project>
aplication.properties
spring.thymeleaf.cache=false
spring.thymeleaf.suffix: .html
Closing:
I'll keep looking for a solution in the meantime and I'll be sure to update the post with any findings that I come across. Thanks for any help!
Followup:
4/24 8:22 am EST
I've added a "mode" field to webpack.config.js and that removed a warning from the react build process but not much more. I've also reworked the dependencies in the pom.xml file to see if there was an issue arising from the order or from an unneeded one. I did run a clean install but this did nothing either. I've updated the files shown above with the new information.
Solution
Okay, so after a week of thinking this problem over, (I stopped for a time because of intense frustration) I've worked out a way that I feel to be much better.
Part of the solution was to drop both Thymeleaf and Webpack. The other was an extensive restructure of the project.
I've broken the larger project up into two separate domains. One for the Frontend and the other for the Backend.
File Structure:
+-- Website
+-- \Backend
+-- \src
+-- \main
+-- \java
+-- \resources
+-- \test
+-- \target
+-- \classes
+-- \static
+-- pom.xml
+-- \Frontend
+-- \build
+-- \public
+-- \src
+-- package.json
+-- build.bat
+-- build_script.ps1
A standard npm run build command will create all the files needed to host a react application on a Spring Boot server. Webpack is not a requirement and by extension, neither is Thymeleaf. All that's required is to then copy/paste the resulting files from the \Frontend\build folder into the \Backend\target\classes\static folder. I created a Powershell file called build_script.ps1 that runs the build script for the react application and then copys the newly built files into the intended folder on the backend. The build.bat file simply runs the Powershell file without me needing to type a lot into command prompt.
build_script.ps1:
#Variables
$Directory = "{dir}\LibraryWebsite\Frontend"
$sourceDirectory = "{dir}\LibraryWebsite\Frontend\build\*"
$targetDirectory = "{dir}\LibraryWebsite\Backend\target\classes\static"
#test
#Build the application and then copy it to the server's
cd $Directory
npm run-script build
Copy-Item -Force -Recurse $sourceDirectory -Destination $targetDirectory
All of this results in a much cleaner program structure and no meaningful increase in my workload. Everything is done with ./build.
I may return to Thymeleaf at some point in the future (If I hate myself) but this meets my needs for the time being.
Answered By - TeaDrinker
Answer Checked By - David Goodson (JavaFixing Volunteer)