Including React in your Spring Boot maven build
I call myself a “fullstack developer”, but really I’m a backend, Java developer that also likes to write frontend code. As such, I often develop applications starting first with my favorite application framework: Spring Boot.
You can build Spring Boot apps with Gradle or Maven, but I remain a fan of the latter due Maven’s declarative approach to build specifications.
On the front-end side, by far the best way to start a new React application is with create-react-app and it in turn prefers the use of yarn to build the React application.
At this point we’re faced with a dilemma where we have two best-of-breed, but disparate build tools. It turns out we can have both: use maven for the “outer” build and have a maven plugin take care of the yarn invocation. I’m sure it can be attacked from yarn invoking maven…but, did I mention I’m a backend developer?
Project Structure
It turns out there’s very little you need to do differently to prepare your project’s structure for this hybrid build.
Since Maven is used as the primary build tool, we should follow it’s convention of src/main/<type>
for situating production code to be built. You could call that <type>
whatever you want, but I usually go with “app”. Something like “ui” or “frontend” are good choices too.
If you haven’t yet run create-react-app
to initialize your React, then run it like normal from within the src/main
directory:
cd src/main
npx create-react-app myNewApp
That will create a directory named for the application name you passed; however, the React build scripts don’t care if that directory is renamed to something else. As such, rename your generated application directory to “app”:
mv myNewApp app
If you had already generated the React code, then just move that directory to be src/main/app
.
Now you can develop and run the front-end just like normal from within the src/main/app
directory:
cd src/main/app
yarn run start
Please note that create-react-app has built in support for development-time proxy’ing the API calls via the typical NodeJS port of 3000 over to your Spring Boot web controllers on port 8080. When running the packaged jar in production, etc you don’t need to use the proxy mechanism since the Spring Boot embedded web server will be serving up the frontend, React content.
Setup the maven build of React code
We need to tell Maven to do two things for us:
- Build the React code via yarn
- Place the result of the React build into the right spot for the packaged Spring Boot app
Declare the tool versions
In your pom.xml
locate or create the project > properties
section and add the following properties:
<frontend-src-dir>${project.basedir}/src/main/app</frontend-src-dir>
<node.version>v12.3.1</node.version>
<yarn.version>v1.16.0</yarn.version>
<frontend-maven-plugin.version>1.7.6</frontend-maven-plugin.version>
NOTE: Be sure to use the latest version of each item listed above since the versions shown here are only for example purposes.
The plugin we’ll add in the next step will take care of downloading NodeJS and Yarn for those versions declared, so you don’t even need to worry about pre-installing those on the final build machine (i.e. CI tool). Very handy, right?
Add the plugin to run yarn
Again, in your pom.xml
locate or create the project > build > plugins
section and add the following plugin definition:
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>${frontend-maven-plugin.version}</version>
<configuration>
<nodeVersion>${node.version}</nodeVersion>
<yarnVersion>${yarn.version}</yarnVersion>
<workingDirectory>${frontend-src-dir}</workingDirectory>
<installDirectory>${project.build.directory}</installDirectory>
</configuration>
<executions>
<execution>
<id>install-frontend-tools</id>
<goals>
<goal>install-node-and-yarn</goal>
</goals>
</execution>
<execution>
<id>yarn-install</id>
<goals>
<goal>yarn</goal>
</goals>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<execution>
<id>build-frontend</id>
<goals>
<goal>yarn</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<arguments>build</arguments>
</configuration>
</execution>
</executions>
</plugin>
That plugin configuration will work generically for most applications. The plugin executions are taking care of three sub-steps for you:
- Installing NodeJS and Yarn local to the project (in
target/node
if you’re curious) - Running
yarn install
to ensure the frontend dependencies are installed - Running
yarn build
to perform the optimized production build of the React code
If you’re wondering how this seamlessly weaves into the overall Spring Boot build process, then the use of theprepare-package
phase for the last execution
of that plugin is important to ensure that the React bits are ready right before the final jar is created.
Stage React build output into final location
The last pom.xml
modification is to add the following plugin configuration after the previous plugin to the project > build > plugins
section:
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>position-react-build</id>
<goals>
<goal>copy-resources</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<outputDirectory>${project.build.outputDirectory}/static</outputDirectory>
<resources>
<resource>
<directory>${frontend-src-dir}/build</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
This adds an execution of the built-in resources plugin to copy over the React build output from src/main/app/build
into the /static
path of the jar’s staging directory, target/classes
. That is where the embedded Spring Boot web container expects index.html
and supporting files to reside. After a mvn package
run you can even look in target/classes/static
to see how the usual web content ended up:
The static/static
path is a bit odd, but that’s just a quirk of both Spring Boot and React build tools using the same naming convention.
Run the build
This step is super easy since it is no different than any other Spring Boot build:
mvn package
You can run the resulting jar just like any other Spring Boot app and then access the front-end code at http://localhost:8080 (or wherever you have it deployed):
java -jar target/your-app-VERSION.jar