Skip to content

A Spring Boot back end with a React front end, built by Gradle

This page describes one arrangement: a Spring Boot service that serves a React front end from its own static resources, with Gradle driving the whole build. The attraction is a single build command and a single deployable artefact; the cost is that the front end is coupled to the back end’s release cycle, which is the wrong trade for anything that needs to ship independently or sit behind a CDN.

Node’s package manager is used as the scaffolding tool for the React application.

npm create vite@latest generates a project directory that looks roughly like this:

my-app/
README.md
node_modules/
index.html
package.json
vite.config.ts
public/
favicon.ico
src/
App.css
App.tsx
index.css
main.tsx

Create the project:

mkdir my-react-project
cd my-react-project
gradle init
npm create vite@latest frontend -- --template react-ts

Older versions of this arrangement used npx create-react-app frontend. Create React App was deprecated by the React team on 14 February 2025 and prints a deprecation notice when run; use a build tool such as Vite, or one of the frameworks React now recommends. See Creating a React app.

React components are written with JSX, a syntax extension to JavaScript that allows HTML-like markup inside a JavaScript or TypeScript file. It is optional — the same components can be written as plain React.createElement calls — but essentially every codebase uses it.

File extensions follow the toolchain: .jsx and .tsx mark files containing JSX, .js and .ts those that do not. App.tsx holds the root component; main.tsx is the entry point that renders it into the root element declared in index.html. See Guide to file extensions in React projects for the trade-offs between the four.

Vite keeps its configuration visible in vite.config.ts, so customising the build — changing the output directory, adding a proxy for the API during development, adding a plugin — is an ordinary edit to a file in the repository.

This is the main practical difference from Create React App, which deliberately hid its Webpack and Babel configuration inside the react-scripts package to keep the setup zero-configuration. Customising a CRA build meant one of three things, all of which are still visible in older projects: npm run eject, which copies the configuration into the project irreversibly; CRACO (Create React App Configuration Override); or react-app-rewired.

For this arrangement the setting that matters is the build output directory, which must land where Gradle expects to collect it.

JSX and TypeScript have to be compiled to JavaScript a browser runtime can execute. Rather than invoking Node by hand, Gradle drives it, so gradle build produces the front end as a side effect of building the service. The gradle-node-plugin installs a pinned Node version and exposes the npm scripts as Gradle tasks, which also keeps CI from depending on whatever Node happens to be on the agent.

The compiled bundle is then copied into Spring Boot’s static resources:

/src/main/resources/static/

Anything under that directory is served from the root of the application, so index.html becomes the application’s home page and the hashed asset files are served alongside it.

A common variation terminates authentication in front of the application rather than inside it: a reverse proxy validates the JWT and passes only authenticated requests through, so the service never sees an unauthenticated request.

If NGINX is the proxy, note that its auth_jwt directive comes from ngx_http_auth_jwt_module, which is available only as part of the commercial subscription — open-source NGINX has no such directive. The open-source options are to script the validation with the njs or Lua module, or to put an API gateway in front that does it natively.