Quarkus Roq

Roq allows to easily create a static website or blog using Quarkus super-powers.

How it works

Roq - How it works

Roq depends on a set of extensions which can also be used standalone:

  • Roq Data: Use json/yaml files content from your templates and articles with type safety.

  • Roq FrontMatter: Create a website from your Markdown/Asciidoc/Html pages using FrontMatter headers (url, layout, seo, data).

  • Roq Generator: Command to run any Quarkus web application and extract it in a directory as purely static files (html and assets).

Advantages of Roq

Knowing that you don’t need to learn Quarkus or Java to use Roq, consider the following advantages:

  1. Quarkus-based: Since Roq is just a very thin layer on top of Quarkus, it ensures that the tool stays up to date and benefits from the robust support provided by the Quarkus ecosystem.

  2. For everyone: It is very easy to use with any level of knowledge. You can start with an existing theme (as a dependency) and only write Markdown or Asciidoc content, or customize everything.

  3. Reproducibility Across Platforms: Unlike some other SSG, Roq offers better reproducibility across multiple platforms, ensuring more consistent and predictable results when building or deploying.

  4. Easy to maintain: Managing dependencies and updates is easier with Roq thanks to Maven and Gradle. Other systems such as Go or Ruby can be difficult to maintain and make reproducible across environments.

  5. Speed: Roq is using Quarkus for dev and build. Quarkus is famous for being supersonic and subatomic, with one example showing a Roq site build in just 20 milliseconds, greatly reducing build times compared to traditional generators.

  6. Type Safety (via Qute templates): Roq leverages Quarkus Qute’s type safety in templates and posts, making it easier to handle links, data injection, and other dynamic content with confidence.

  7. Local Development Experience: Roq provides a smoother local development experience without the need to "test in production," as is often necessary with other SSGs due to its difficulties in running locally.

  8. Code Completion (coming soon): Thanks to Qute, Roq offers code completion, which is beneficial when working in templates, helping to reduce errors and improve developer productivity.

Get up and running with Roq

Click here to generate your Roq Starter App.

or use the Quarkus CLI:

quarkus create app roq-with-blog -x=io.quarkiverse.roq:quarkus-roq

Then

cd roq-with-blog
quarkus dev

Getting started

By default, your site should be located in the project root directory.

you can also put it in the resources (i.e. src/main/resources/).

The default directory structure is:

Roq directory structure
The Roq blog is a good example of site made with Roq (sources).

Qute and FrontMatter

All templates can use the awesome type-safe Qute template engine.

Templates for layout, documents and pages can declare a FrontMatter (referred as FM data) header delimited by two ---. This header contains yaml data used to configure many things such as:

  • Routing

  • Data for the templates

  • Content generation

  • Pagination

Layouts and Partials

For your site, you will have one or more kind of pages, this is what we call "layouts", located by default in templates/layouts/. For example:

  • main: the base layout for all kind of pages

  • page: the layout of normal pages

  • post: the layout for blog posts

  • recipe: the layout for recipes or whatever

A layout can be included in pages and documents through the layout FrontMatter key (e.g. layout: page).

Roq layouts are using the Qute include section under the hood to achieve template inheritance. For more details, see the Qute documentation on includes: Qute includes. Unlike partials, layouts can also define Frontmatter data, which is inherited along with the template structure.

You can also prepare partial templates. They are located by default in templates/partials/. For example:

  • pagination.html

  • head.html

if you don’t already have layouts and partials in your project, Install a theme or create your own templates (example templates).

Install a theme

To install a theme, simply add the dependency to your pom.xml. Example with Roq’s default theme:

<dependency>
    <groupId>io.quarkiverse.roq</groupId>
    <artifactId>quarkus-roq-theme-default</artifactId>
    <version>1.0.4</version>
</dependency>

It will provide templates, scripts and styles for your site. To use a theme layout, refer to it with :theme/ prefix (there is an example in the next part). For advanced usage, refer to the Theme section.

Site index template

Your site index template is required and should be located in content/index.html.

content/index.html
---
title: Hello Roqers (1)
description: It is time to start Roqing 🎸!
layout: :theme/main (2)
---

<h1>Hello fellow Roqers 🀘</h1>

<p>
  With Roq, it is very easy to a link to another.
  <a href="{site.url('/roq-bottom')}">page</a>. (3)
</p>
1 The index.html also describe your site information through a FrontMatter header.
2 The layout to use (in this case :theme/main which refers to the main layout from the theme).
3 We use the {site.url(path)} using Qute to manual resolve other pages urls.
There are different ways to link your pages as explained in the Links & Urls section.

Variables

You can use Qute to access site and pages data. For this use the site and page variables:

  • The site variable allow to access site global info from any page, document, layout or partial.

    Show attributes
    Variable Type Description Example

    site.url

    RoqUrl

    The Roq site URL

    http://example.com/my-roq-site/

    site.imagesUrl

    RoqUrl

    Directory to resolve images URL (e.g. /static/images)

    http://example.com/static/images

    site.data

    JsonObject

    The site FM data (declared in the index.html)

    {"title": "My Site", "description": "A description"}

    site.pages

    java.util.List<NormalPage>

    All the pages in this site (without the documents)

    [Page1, Page2, Page3]

    site.collections

    RoqCollections

    All the collections in this site (containing documents)

    {"collection1": Collection1, "collection2": Collection2}

    site.title

    String

    The site title

    My Site

    site.description

    String

    The site description

    A description

    site.image

    RoqUrl

    The site image URL if present

    http://example.com/static/images/site.png

    site.url(Object path, Object…​ others)

    RoqUrl

    Shortcut for site.url.resolve(path)

    site.url.resolve("/about") ⇒ http://example.com/my-roq-site/about

  • The page variable is available in pages, documents, layouts, and partials. It contains the info for the page it is used from.

    Show attributes
    Variable Type Description Example

    page.url

    RoqUrl

    The URL to this page

    http://example.com/about

    page.info

    PageInfo

    The page info (file name, …​)

    page.data

    JsonObject

    The FM data of this page

    {"title": "About Us", "description": "This is the about us page."}

    page.paginator

    Paginator

    The paginator if any

    Paginator{currentPage=1, totalPages=5}

    page.collection

    String

    The collection id if this a document

    posts

    page.title()

    String

    The title of the page (shortcut from FM)

    About Us

    page.description()

    String

    The description of the page (shortcut from FM)

    This is the about us page.

    page.image()

    RoqUrl

    The image URL of the page if present

    http://example.com/static/images/about.png

    page.date()

    ZonedDateTime

    The publication date of the page

    2023-10-01T12:00:00Z

Pages

Any directory without the _ prefix in the site directory will be scanned for pages content.

Let’s create your first page and spice things up a bit by using Markdown.

roq-bottom.md
---
title: Roq Bottom
description: When you hit Roq bottom, try Roq to climb back up!
layout: :theme/page
link: /climb-back-up (1)
the-rope: You Roq! (2)
---

# Roq Bottom

If you thought you hit Roq Bottom, take this πŸͺ’ because :

__{page.data.the-rope}!__ (3)
1 you can use link to give this page a custom link (by default it will use the file-name).
2 you can add other FM data.
3 FM data is available through page.data.

Asciidoc support

Asciidoc is supported by Roq, though partially for the moment.

All the formatting features are available. But others such as diagrams are not yet supported.

Custom attributes can also be used, but be sure to escape their use -{custom-attribute}-. Other wise the Qute rendering engine will try to interpolate them.

Includes

The standard Asciidoc include is not supported, but you can use Qute includes instead:

  1. Place your file in a folder under the template directory (for example partials)

  2. Use Qute include directive {# partials/your_included_file.adoc/} to inject it

Images

The standard imagedir attribute is not supported.

You should instead place your images under the static/assets/images folder, and reference them with the image macro :

image::/static/assets/images/your_image.png[]

Collections

Collections are a great way to group related content such as blog posts, recipes, member of a team or talks at a conference. Once created you can easily iterate and link to them.

By default, Roq is configured with a posts collection using the content/posts directory. Let’s create our first post:

content/posts/2024-10-14-roq-solid.md
---
title: Roq bad puns
description: Roq is very good for bad puns 🀭
layout: :theme/post (1)
tags: (2)
  - funny
  - ai
img: 2024/10/roq-solid.jpg
---

# {page.title} (3)

Here is a list of puns suggested by Chat GPT:
1.	Roq and Rule – A play on β€œrock and roll,” implying dominance or success.
2.	Between a Roq and a Hard Place – Classic pun meaning stuck in a difficult situation.
3.	Roq Solid – Something that is extremely reliable or stable.
4.	You Roq! – A compliment, suggesting someone is awesome or does something well.
5.	Roq Bottom – Referring to the lowest possible point, often used metaphorically.
6.	Roq the Boat – To cause trouble or disturb the status quo.
7.	Roq Star – A person who excels or stands out in their field.
8.	Let’s Roq – Slang for getting started or doing something exciting.
9.	Roq On! – An enthusiastic way to say β€œkeep going” or β€œstay awesome.”
10.	Roqy Road – Could be literal (the type of road) or metaphorical for a difficult journey.
11.	Roq of Ages – A historical reference, often implying something long-standing and unchanging.
12.	Roq the Cradle – Can be literal or a pun about nurturing or starting something new.
13.	Roqy Relationship – A tumultuous or unstable relationship.
14.	Heavy as a Roq – Something burdensome or difficult to manage.
15.	Stone Cold Roq – Referring to something very cool or emotionless.
1 This time we use the post layout from the theme.
2 You can define tags (see Plugins to create pages for tags).
3 You have shortcut on the page to access title and description.

Then let’s edit our index page to show the list of posts:

content/index.html
---
title: Hello Roqers
description: It is time to start Roqing 🎸!
layout: main
---

<h1>Hello fellow Roqers 🀘</h1>


{#for post in site.collections.posts} (1)
<article class="post">
  {#if post.image}
  <a class="post-thumbnail" style="background-image: url({post.image})" href="{post.url}"></a> (2) (3)
  {/if}
  <div class="post-content">
    <h2 class="post-title"><a href="{post.url}">{post.title}</a></h2>
    <p>{post.description}</p>
    <span class="post-date">{post.date.format('yyyy, MMM dd')}&nbsp;&nbsp;&nbsp;β€”&nbsp;</span> (4)
    <span class="post-words">
      {post.readTime} minute(s) read (5)
    </span>
  </div>
</article>
{/for}
1 You can use site.collections.[collection id] to access the full list of documents (it is also possible to paginate.
2 post.image is smart and is already resolved to the image url (as a RoqUrl).
3 post.url contains the post url (as a RoqUrl), you could also use post.url.absolute to get the absolute url.
4 post.date returns a ZonedDateTime and can be formatted the way you want.
5 post.readTime is a Qute template extension which compute the read time based on the post content.

Static files

By default, all files in static/ are treated as static..

Styles and Javascript

The Quarkus Web Bundler is included by default with the Roq extension.

You can add css and scripts in your static directory or bundle them. To use bundling scripts (js, ts) and styles (css, scss) should be located in src/main/web/app/. To include the generated bundle in your template, specify the bundle tag in the html>head tag:

layouts/head.html
<head>
  ...
  {#bundle /}
</head>

It will be rendered with the relevant <script> and <style> tags to include your bundle.

You may also consume and bundle npm dependencies among other cool things. For more info, read the Quarkus Web Bundler documentation.

Deploy to GitHub Pages

To create your GitHub Page with Roq:

  1. Open your GitHub repository page

  2. Go to Settings→Page

  3. Pick: Source: GitHub Actions

  4. Click on Static Html "Configure" button

  5. Paste this content (to adapt with Gradle):

.github/workflows/static.yml
## Deploy to GH-Pages for your Quarkus Roq blog.
name: Quarkus Roq Deploy CI

on:
  push:
    branches: [ main ]
  workflow_dispatch:

env:
  QUARKUS_ROQ_GENERATOR_BATCH: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: 21
          distribution: 'temurin'
          cache: 'maven'
      - name: Get GitHub Pages URL
        id: get_url
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          url=$(gh api "repos/$GITHUB_REPOSITORY/pages" --jq '.html_url');echo "ghp-url=$url";
          path=$(echo "$url" | sed -E 's|https?://[^/]+(/.*)?|\1|')
          url=$(echo "$url" | sed -E 's|(https?://[^/]+).*|\1|')
          if [ -z "$path" ]; then
            path="/"
          fi
          echo "SITE_URL=$url"; echo "SITE_URL=$url" >> $GITHUB_ENV;
          echo "SITE_PATH=$path"; echo "SITE_PATH=$path" >> $GITHUB_ENV;
      - name: Build & Generate Blog
        run: mvn -B package quarkus:run -DskipTests -Dquarkus.http.root-path=$SITE_PATH -Dsite.url=$SITE_URL
      - name: Upload site as artifact
        uses: actions/upload-artifact@v4
        with:
          name: site
          path: target/roq
          retention-days: 1
  deploy:
    runs-on: ubuntu-latest
    needs: build
    permissions:
      pages: write      # to deploy to Pages
      id-token: write   # to verify the deployment originates from an appropriate source
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: Download Built site
        uses: actions/download-artifact@v4
        with:
          name: site
          path: _site
      - name: Setup Pages
        uses: actions/configure-pages@v5
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
This is process will be improved in the near future (follow progress).

Configure a custom Roq directory

It can be located in a resource directory such as src/main/resources/my-site:

application.properties
quarkus.roq.resource-dir=my-site

or in your project directory such as my-site:

application.properties
quarkus.roq.dir=my-site

Extension Configuration Reference

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

Path to the Roq directory (relative to the project root).

Environment variable: QUARKUS_ROQ_DIR

string

Path to the Roq directory in the resources.

Environment variable: QUARKUS_ROQ_RESOURCE_DIR

string

If enabled, Jackson will fail when encountering unknown properties.

You can still override it locally with @JsonIgnoreProperties(ignoreUnknown = false).

Environment variable: QUARKUS_ROQ_JACKSON_FAIL_ON_UNKNOWN_PROPERTIES

boolean

true

If enabled, Jackson will fail when no accessors are found for a type. This is enabled by default to match the default Jackson behavior.

Environment variable: QUARKUS_ROQ_JACKSON_FAIL_ON_EMPTY_BEANS

boolean

true

If enabled, Jackson will ignore case during Enum deserialization.

Environment variable: QUARKUS_ROQ_JACKSON_ACCEPT_CASE_INSENSITIVE_ENUMS

boolean

false

Defines how names of JSON properties ("external names") are derived from names of POJO methods and fields ("internal names"). The value can be one of the one of the constants in com.fasterxml.jackson.databind.PropertyNamingStrategies, so for example, LOWER_CAMEL_CASE or UPPER_CAMEL_CASE.

The value can also be a fully qualified class name of a com.fasterxml.jackson.databind.PropertyNamingStrategy subclass.

Environment variable: QUARKUS_ROQ_JACKSON_PROPERTY_NAMING_STRATEGY

string