Quarkus Roq Generator
It provides a command to run any Quarkus web application and extract it in a directory as purely static files (html and assets).
Roq Generator is already included as part of the Roq Static Site Generator extension io.quarkiverse.roq:quarkus-roq, Follow Standalone installation section to use it standalone.
|
Generating your static site
You can generate your static site using:
QUARKUS_ROQ_GENERATOR_BATCH=true mvn package quarkus:run -DskipTests
By default, it will generate the static site in the target/roq directory.
When used within the Roq Static Site Generator extension, it is already pre-configured to generate the whole site (which can be disabled with site.generator=false). In standalone mode, by default, only the / (index.html) and static/** will be generated. Follow the next section to configure the selection.
|
You can now try or deploy your static website with any static file server. We provide a small tool to try it:
$ jbang app install --fresh roq@quarkiverse/quarkus-roq
$ roq decks->!+(ia3andy/decks)
Serving: target/roq/
Server started on port http://localhost:8181
| at any time you can open the dev-ui to see the Roq Generator selection. |
Github Pages configuration
You can configure Roq to deploy Github Pages, Netlify, or any static website server.
Check how the Roq blog is deployed on GitHub pages here.
Advanced Usage
Understanding selections and sources
Roq Generator works by making HTTP requests to your running Quarkus application and saving the responses as static files. You control which pages to generate through selections.
For typical Roq sites using file-based content (Markdown/AsciiDoc in content/), no selection configuration is needed - the
Roq extension automatically discovers and generates your pages.
Where content comes from:
-
File-based content - Markdown/AsciiDoc files in your
content/directory (the default Roq pattern) -
Database content - Blog posts, products, or other content stored in PostgreSQL, MySQL, etc.
-
Remote APIs - Content fetched from headless CMS (Contentful, Strapi), external APIs, or microservices
-
Other - If you can code it, you can create it
How you specify what to generate (selections):
-
Static selection - Fixed paths configured in
application.properties(simple, declarative) -
Dynamic selection - Paths computed at build time from databases or APIs (programmatic, flexible)
The choice between static and dynamic selection depends on whether you know all your URLs at configuration time or need to discover them at build time.
Static selection (configuration-based)
Use static selection when you want to include a fixed set of extra paths that don’t change based on external data.
Configure paths in application.properties:
quarkus.roq.generator.paths=/,/static/**,/about/,/blog/
Path conventions:
-
Paths ending with
/generate HTML:/about/→/about/index.html -
Paths without
/are used as-is:/robots.txt→/robots.txt -
Glob patterns (
**) only work for fixed directory structures
Custom output paths:
You can override where files are written:
quarkus.roq.generator.custom-paths."/api/posts"=/posts.json
quarkus.roq.generator.custom-paths."/feed.xml"=/rss.xml
When to use static selection:
-
Your site has a known, fixed structure
-
All content lives in files (no database or API queries needed)
-
You’re migrating from Jekyll/Hugo and have a stable set of pages
Limitations:
-
Cannot use query parameters or path variables dynamically
-
Cannot discover pages from databases or APIs at build time
-
For those cases, use dynamic selection instead
Dynamic selection (code-based)
Use dynamic selection when pages depend on runtime data sources like databases, APIs, or computed values.
Why use dynamic selection?
Dynamic selection solves the problem of bringing external content into your static site. Instead of manually listing every URL, you fetch data at build time and programmatically generate the page list.
Common use cases:
-
Headless CMS - Fetch content from Contentful, Strapi, Sanity, or similar
-
Database-driven sites - Generate pages for products, blog posts, or user profiles stored in a database
-
API integration - Pull documentation from GitHub, issues from Jira, or any REST API
-
Complex routing - Generate arbitrary urls
-
Multi-language sites - Generate pages for each locale based on translation data
Basic example
Produce a RoqSelection bean in your application:
import io.quarkiverse.roq.generator.runtime.RoqSelection;
import io.quarkiverse.roq.generator.runtime.SelectedPath;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Singleton;
import java.util.List;
@ApplicationScoped
public class SiteSelection {
@Produces
@Singleton
RoqSelection produce() {
return new RoqSelection(List.of(
SelectedPath.builder().html("/blog/hello-roq").build(), (1)
SelectedPath.builder().html("/search?tag=java").build(), (2)
SelectedPath.builder().path("/api/posts").outputPath("/posts.json").build() (3)
));
}
}
| 1 | Generates /blog/hello-roq/index.html - the .html() method auto-generates the output path |
| 2 | Generates /search-tag-java/index.html - query params are normalized for the filesystem |
| 3 | Generates /posts.json - manually specify the output path for non-HTML content |
Remote API example
Pull content from an external API at build time:
import io.quarkiverse.roq.generator.runtime.RoqSelection;
import io.quarkiverse.roq.generator.runtime.SelectedPath;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Singleton;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
@ApplicationScoped
public class RemoteContentSelection {
@Inject
@RestClient
ContentApiClient apiClient; (1)
@Produces
@Singleton
RoqSelection produce() throws IOException {
// Another use case: download a JavaScript file from the API
String analyticsScript = apiClient.getAnalyticsScript();
// Download into the public directory (this file should be included in the .gitignore)
// Alternatively, you can download outside the source tree and add a vertx route so the generator can access the served content
java.nio.file.Path publicDir = Paths.get("public/js");
Files.createDirectories(publicDir);
Files.writeString(publicDir.resolve("analytics.js"), analyticsScript); (2)
// Build selection paths
List<SelectedPath> paths = new ArrayList<>();
// Add the downloaded JavaScript file to the selection
// Files downloaded during the selection phase need
// explicit selection paths, because they're created after the normal public/
// directory copy happens
paths.add(SelectedPath.builder()
.path("/js/analytics.js")
.outputPath("/js/analytics.js")
.build()); (3)
return new RoqSelection(paths);
}
}
record Post(String slug, String title, String content) {}
| 1 | Inject a REST client pointing to your headless CMS or API |
| 2 | Download a JavaScript file from the API and write it to the public/ directory |
| 3 | Add a selection path for the downloaded file so it gets included in the static site |
Configuration for the REST client:
quarkus.rest-client.content-api.url=https://api.example.com
Why this is useful:
-
Decouple content from code - Content editors use a CMS, developers control the site structure
-
Static performance with dynamic sources - Your live site serves static HTML, but content comes from anywhere
-
No runtime database - The database/API is only needed at build time, not when serving the site
-
Flexible content sources - Swap CMSs or APIs without changing your templates
Database example
Generate pages from database records:
import io.quarkiverse.roq.generator.runtime.RoqSelection;
import io.quarkiverse.roq.generator.runtime.SelectedPath;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Singleton;
import jakarta.inject.Inject;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Id;
import jakarta.transaction.Transactional;
import java.util.List;
@ApplicationScoped
public class DatabaseSelection {
@Inject
EntityManager em;
@Produces
@Singleton
@Transactional (1)
RoqSelection produce() {
// Query all products from the database
List<Product> products = em.createQuery(
"SELECT p FROM Product p WHERE p.published = true",
Product.class
).getResultList(); (2)
// Generate a page for each product
List<SelectedPath> paths = products.stream()
.map(product -> SelectedPath.builder()
.html("/products/" + product.slug)
.build())
.toList();
return new RoqSelection(paths);
}
}
@Entity
class Product {
@Id Long id;
String slug;
String name;
boolean published;
}
| 1 | Use @Transactional when querying the database - this runs at build time |
| 2 | The database must be accessible during the build (mvn package quarkus:run) |
Combining static and dynamic selection
You can use both approaches together:
quarkus.roq.generator.paths=/,/about/,/contact/ (1)
@Produces
@Singleton
RoqSelection produce() {
return new RoqSelection(dynamicPaths()); (2)
}
| 1 | Static pages that never change |
| 2 | Dynamic pages computed from databases or APIs |
Roq will generate both the static paths and the dynamic paths.
Standalone installation
It is included and pre-configured as part of the Roq Static Site Generator extension io.quarkiverse.roq:quarkus-roq.
|
You can also use it standalone on any Quarkus application. If you want to use this extension standalone, you need to add the io.quarkiverse.roq:quarkus-roq-generator extension first to your build file.
For instance, with Maven, add the following dependency to your POM file:
<dependency>
<groupId>io.quarkiverse.roq</groupId>
<artifactId>quarkus-roq-generator</artifactId>
<version>2.1.6</version>
</dependency>
Extension Configuration Reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
Type |
Default |
|---|---|---|
The selected paths to include in the static website. The output path is generated automatically: paths ending with a slash are completed with index.html, while other paths remain unchanged. Glob syntax is authorized for non-dynamic resources (without query or path params) For dynamic paths selection, produce a
Environment variable: |
list of string |
|
Enable path character replace Environment variable: |
boolean |
|
The regex of allowed characters for file names (other characters will be replaced), for example: By default, all characters are unchanged. Environment variable: |
string |
|
The character to use to replace characters which doesn’t match the 'allowed-regex' Environment variable: |
string |
|
With this config you can configure the path to get content from AND also the output path that will be generated for it. Environment variable: |
Map<String,String> |
|
Output directory for the static website relative to the target directory Environment variable: |
string |
|
Build as a CLI to export the static website Environment variable: |
boolean |
|
Timeout for full generation in seconds Environment variable: |
long |
|
How many times should a request be retried Environment variable: |
int |
|