Quarkus Quinoa - Advanced Guides

How to use the extension.

Configure the build

Add a build script in the package.json to generate your web application index.html, scripts and assets (styles, images, …​) in some build directory (configurable Build Dir).

 "scripts": {
    "start": "[start the Web UI live coding server]",
    "build": "[build the Web UI]",
    "test": "[test the Web UI]"
The build directory will automatically be moved by Quinoa to target/quinoa-build when using Maven (build/quinoa-build with Gradle) in order to be served.

You can differentiate development from production builds using the environment variable NODE_ENV (production/development). by-node-env can help you if you have different build commands:

"scripts": {
  "build": "by-node-env",
  "build:development": "...",
  "build:production": "...",
"devDependencies": {
  "by-node-env": "~2.0.1"

Package manager

Quinoa can be configured to install NodeJS and NPM in the project directory:

quarkus.quinoa.package-manager-install=true (1)
quarkus.quinoa.package-manager-install.node-version=20.10.0 (2)
quarkus.quinoa.package-manager-install.npm-version=10.2.3 (3)
1 Enable package manager install
2 Define the version of NodeJS to install
3 Define the version of NPM to install
By default, NodeJS and NPM will be installed in {project-dir}/.quinoa/ (can be configured). If not specified, it will use the NPM version provided by NodeJS.

If NodeJS and NPM are not installed by Quinoa, it is possible to override the package manager (NPM, Yarn or PNPM), otherwise, it will be auto-detected depending on the project lockfile (NPM is the fallback):

  • Use quarkus.quinoa.package-manager if present

  • Else if yarn.lock then Yarn

  • Else if pnpm-lock.yaml then PNPM

  • Else NPM

By default, Quinoa is configured with the commands to call depending on the chosen package manager (to always keep the same behavior and make it easy to switch).

Node packages installation (node_modules)

By default, Quinoa will call the appropriate package manager install command (before building or starting) only if the node_modules directory doesn’t exist.

You may force a new installation using -Dquarkus.quinoa.force-install=true.

Quinoa will use the appropriate package manager frozen-lockfile command when installing, if the environment CI=true, or if quarkus.quinoa.frozen-lockfile=true. In this mode, the lockfile have to be present in the project.

Package manager commands

By default, the following commands and environment variables are used in the different faces for each of the supported package managers.


  • npm install (npm ci if quarkus.quinoa.frozen-lockfile=true).

  • pnpm install (pnpm install --frozen-lockfile if quarkus.quinoa.frozen-lockfile=true).

  • yarn install (yarn install --frozen-lockfile if quarkus.quinoa.frozen-lockfile=true).


(npm|pnpm|yarn) run build, with environment MODE=${mode} (dev, test or prod)


(npm|pnpm|yarn) test, with environment CI=true


(npm|pnpm|yarn) start, with environment BROWSER=none

Override package manager commands

By default, Quinoa uses sensible default commands when executing the different phases, install, build, test, dev. It is possible to override one or more of them from the package manager command configuration:

quarkus.quinoa.package-manager-command.install=npm ci --cache $CACHE_DIR/.npm --prefer-offline (1)
quarkus.quinoa.package-manager-command.build-env.BUILD=value (2)
1 This makes npm ci --cache $CACHE_DIR/.npm --prefer-offline the command executed in the install phase. (overriding quarkus.quinoa.package-manager and quarkus.quinoa.frozen-lockfile=true).
2 set environment variable BUILD with value value. Environment variables set in config can be added to the listed commands.
Using custom commands will override quarkus.quinoa.package-manager and quarkus.quinoa.frozen-lockfile.
if NodeJS is installed by Quinoa, you need to enable: quarkus.quinoa.package-manager-command.prepend-binary and adapt the command to only specify the arguments (the binary to call will be prepended by Quinoa).

UI live-coding dev server (proxy mode)

Quinoa provides two options for live-coding:

  • Delegate to the UI live-coding dev server (proxy mode). To enable it, configure the port of the UI server. By convention Quinoa will call the start script from the package.json to start the UI server process. Then it will transparently proxy relevant requests to the given port.

  • Quarkus watches the files and Quinoa triggers a new Web UI build on changes (you can configure different builds for dev and prod).

To enable the UI live-coding dev server, set a start script and set the port in the app config. Quinoa will transparently proxy relevant requests to the given port:

Quinoa relies on the dev server returning a 404 when the file is not found (See How it works). This is not the case on some dev servers configured with SPA routing. Make sure it is disabled in the dev server configuration (for React Create App, see #91). Another option, when possible, is to use Ignored Path Prefixes.

Single Page application routing

Client-side/Browser/SPA routing is the internal handling of a route from the javascript in the browser. It uses the HTML5 History API

When enabled, to allow SPA routing, all relevant requests will be internally re-routed to index.html, this way the javascript can take care of the route inside the web-application.

To enable Single Page application routing:

By default, Quinoa will ignore quarkus.resteasy-reactive.path, quarkus.resteasy.path and quarkus.http.non-application-root-path path prefixes. You can specify different path prefixes to ignore using quarkus.quinoa.ignored-path-prefixes.
Currently, for technical reasons, the Quinoa SPA routing configuration won’t work with RESTEasy Classic. Instead, you may use a workaround (if your app has all the rest resources under the same path prefix):
public class SPARouting {
    // Vite in dev mode requests /@vite/client and /@reactrefresh so add "/@" if you use Vite
    private static final String[] PATH_PREFIXES = {"/q/", "/api/", "/@"};
    private static final Predicate<String> FILE_NAME_PREDICATE = Pattern.compile(".+\\.[a-zA-Z0-9]+$").asMatchPredicate();

    public void init(@Observes Router router) {
        router.get("/*").handler(rc -> {
            final String path = rc.normalizedPath();
            if (!path.equals("/")
                    && Stream.of(PATH_PREFIXES).noneMatch(path::startsWith)
                    && !FILE_NAME_PREDICATE.test(path)) {
            } else {

Http Headers

It’s very common to set up headers for caching on static resources, for example React proposes this configuration:

To configure Quarkus with those headers :


Http Compression

To enable server Http compression:


OpenAPI Code Generation

Many Quinoa projects commonly utilize REST services, specifying their requirements with OpenAPI support in Quarkus. If you wish to streamline the process of generating TypeScript code for your React/Vue/Angular web application using the OpenAPI spec, you can now achieve it effortlessly in a single step.

Start by configuring OpenAPI to consistently generate code in the designated directory, such as /src/main/webui, where your Quinoa UI is intended to reside.


Whenever you execute mvn quarkus:dev, it automatically produces the openapi.json in the specified directory. Following this, it is essential to notify your JavaScript application about the changes and facilitate the automatic generation of new TypeScript code from your OpenAPI. A solution like Orval can be incredibly helpful in this scenario!

"scripts": {
  "dev": "orval --config ./orval.config.ts && vite",
  "build": "orval --config ./orval.config.ts && vite build",
"devDependencies": {
  "orval": "6.24.0",

Continuous Integration (CI)

Most CI images already include NodeJS. if they don’t, just make sure to install it alongside Maven/Gradle (and Yarn/PNPM if needed). Then you can use it like any Maven/Gradle project.

Quinoa can be configured to install packages with a frozen lockfile.

On compatible CIs, don’t forget to enable the Maven/Gradle and NPM/Yarn repository caching.