Migrating to Roq
| If you find any issue or missing info, be awesome and edit this document to help others Roqers. |
Roq uses Qute templates and a different content model than Jekyll, Hugo, or other static site generators. This guide covers the workflow, syntax mappings, and Roq-specific pitfalls for migrating an existing static site to Roq.
Prerequisites
-
Familiarity with Roq concepts (see the Getting Started guide)
-
An existing static site you want to migrate (Jekyll, Hugo, or similar)
-
The Roq CLI installed (see Getting Started)
-
Java 21+ installed
Using an LLM to accelerate migration
An LLM (Large Language Model) such as Claude, ChatGPT, or a locally hosted model can accelerate the migration of your templates, layouts, and content files. Template conversion is mostly mechanical syntax mapping, which LLMs handle effectively.
To give your LLM full context about Roq, point it to https://iamroq.dev/llms-full.txt.
| LLM-assisted migration is not fully automatic. Expect to review and adjust the output. The prompts in this guide are a starting point, refine them as you learn what works for your site. |
Overview of the migration process
Migrating a static site to Roq involves converting four categories of files:
-
Project scaffold —
roq create,application.properties, and directory structure -
Layouts and templates — Liquid/Jinja/Go templates to Qute templates
-
Content files — Markdown or AsciiDoc with front matter adjustments
-
Static assets — JavaScript, SCSS/CSS, images, and data files
Content files typically need minimal changes. Configuration and asset migration require more manual work.
Roq expects content in a content/ directory by default (configurable via site.content-dir).
Templates go in templates/layouts/, static files in public/, and data files in a configurable data directory.
Recommended phased approach
Do not attempt to migrate everything at once. Use a phased approach with validation gates:
| Phase | Scope | Gate |
|---|---|---|
A: Foundation |
Roq project + one page rendering correctly |
Stop if content does not render. Verify |
B: Styling + scale |
JavaScript, CSS/SCSS, all content pages |
Stop if build time exceeds 10 minutes or memory exceeds 4 GB |
C: Full site |
All templates, blog, homepage, static pages, redirects, CI/CD |
Production-ready |
Commit after each step.
Prepare reference material
Having concrete examples of working Roq templates makes migration smoother (whether you are converting manually or with an LLM).
Collect these files from a working Roq project (the Roq blog is a good source):
-
pom.xml— for Maven dependency structure -
config/application.properties— for Roq configuration patterns -
A sample Qute layout (for example,
templates/layouts/default.html) -
A sample content page with front matter (for example, a
.mdor.adocfile fromcontent/)
| If you are using an LLM, attach these files directly or paste them as code blocks. You can also point the LLM to https://iamroq.dev/llms-full.txt for complete Roq documentation. |
Create the project scaffold
Before converting any templates, set up the project and validate that a single page renders.
Create a new Roq project with the Roq CLI:
roq create my-site
This creates a project with the default theme (full blog layout, dark mode, sidebar, SEO support). If you want full control over the design and prefer to build your own layouts from scratch, use the base theme instead:
roq create my-site -x theme:base
| The base theme provides a minimal HTML structure with just SEO, favicon, and Web Bundler. It is a better starting point if you plan to port your existing site’s design rather than adopt Roq’s default look. |
Add any plugins you need:
roq add plugin:asciidoc # if using AsciiDoc content
roq add plugin:sitemap
roq add plugin:aliases # for URL redirects
roq add plugin:tagging # if using tags
LLM prompt for project setup
I am migrating a static website from Jekyll to Roq (a Quarkus-based static
site generator). I have already created the project with `roq create`
and the base theme.
Help me configure application.properties with:
- site.url=https://mysite.example.com
- site.collections.posts.layout=post
- quarkus.qute.alt-expr-syntax=true
- site.slugify-files=false
- quarkus.default-locale=en
Here is my Jekyll _config.yml for reference:
[paste or attach _config.yml]
Roq-specific configuration details
- Alternative expression syntax (recommended)
-
Roq supports an alternative expression syntax where output expressions use
{=expr}instead of{expr}. With alt syntax enabled, only{=...}and{#...}are interpreted as Qute expressions. Regular{...}is treated as plain text, so curly braces in code samples and JSON are safe without escaping.Add to your
application.properties:quarkus.qute.alt-expr-syntax=trueThis will become the default syntax for Roq in a future version. All Qute examples in this guide use the alt syntax (
{=expr}for output,{#...}for sections).
- Qute escaping (without alt syntax)
-
If you choose not to enable the alternative expression syntax, Qute treats
{...}as template expressions. Content with curly braces (Java code, JSON examples) must be escaped from Qute parsing.AsciiDoc files: Qute parsing is disabled by default (
quarkus.asciidoc.qute=false). Curly braces in AsciiDoc content are safe without any extra configuration. To enable Qute parsing for a specific AsciiDoc file, add the:qute:attribute to the document header.Markdown and HTML files: Qute parsing is enabled by default. If your Markdown files contain curly braces in code samples, set
site.escaped-pagesinapplication.properties:site.escaped-pages=posts/**This wraps matched page content with Qute escape markers so curly braces are not parsed as template expressions.
site.collections-
Defining any custom collection replaces Roq’s defaults. If you define
site.collections.guides.layout=guide, you must also explicitly addsite.collections.posts.layout=post— otherwise Roq’s defaultpostscollection is silently dropped. quarkus.roq.data.dir-
Set this to
_datato reuse Jekyll’s data directory in place, avoiding the need to move data files. site.slugify-files-
Set to
falseto preserve original filenames in URLs instead of slugifying them.
Validate the scaffold (Phase A gate)
Copy a single content page into content/ and start dev mode:
roq start
Verify:
-
The page renders with your content (AsciiDoc or Markdown)
-
Curly braces in code samples are treated as plain text (verify
quarkus.qute.alt-expr-syntax=trueis set) -
Content files without YAML front matter are recognized as collection members (Roq discovers content by directory, not by front matter presence)
-
Files and directories starting with
_insidecontent/(such as_includes/or_attributes.adoc) are NOT rendered as standalone pages -
If you use AsciiDoc
include::directives, they resolve correctly
AsciidoctorJ may enforce a security boundary (ROOTDIR) that prevents include:: directives from resolving paths outside the content directory. If includes fail with a security error, move the included files inside content/ or configure AsciidoctorJ’s safe mode.
|
Do not proceed to templates until this gate passes.
Convert layouts and templates
Layouts are the highest-value conversion target. Jekyll uses Liquid templates; Hugo uses Go templates. Roq uses Qute, which has a different syntax but similar concepts.
If you created your project with the default theme, it already provides main, page, and post layouts with a full blog design.
You can override any theme layout by creating a file with the same name in templates/layouts/.
If you used the base theme, you have a minimal HTML structure and will need to create your own layouts to match your existing site’s design.
Key syntax differences (Jekyll Liquid to Qute)
| Concept | Jekyll (Liquid) | Roq (Qute) |
|---|---|---|
Variable output |
|
|
Conditional |
|
|
Loop |
|
|
Include / partial |
|
|
Layout inheritance |
|
|
Content insertion |
|
|
Date formatting |
|
|
Null / empty check |
|
|
If you are migrating from Hugo rather than Jekyll, replace the Liquid syntax column with Go template equivalents ({{ .Title }}, {{ if .Params.image }}, {{ range .Pages }}, etc.) and include the Hugo equivalents in your prompt. The Qute (alt syntax) column stays the same.
|
LLM prompt for layout conversion
I am migrating a static website from Jekyll to Roq (a Quarkus-based static
site generator that uses Qute templates).
Convert the attached Jekyll Liquid layout to a Roq Qute template.
Roq uses the alternative expression syntax: output expressions use {=expr}
instead of {expr}. Section tags ({#if}, {#for}, {#include}) are unchanged.
Key rules:
- Replace Liquid {{ variable }} with Qute {=variable} syntax
- Replace {% if %} with {#if } ... {/if}
- Replace {% for item in collection %} with {#for item in collection} ... {/for}
- Replace {{ content }} with {#insert /}
- Replace {% include file.html %} with {#include file /}
- Replace Liquid filters (| date, | upcase, etc.) with Qute method calls
(e.g., {=post.date.format('yyyy, MMM dd')})
- Site-level variables use the `site` object (e.g., {=site.title})
- Page-level variables use the `page` object (e.g., {=page.title})
- Collections are accessed via site.collections.get('name')
- Preserve all HTML structure and CSS classes unchanged
- Qute null handling differs from Liquid: test conditionals carefully
Here is my Jekyll layout:
[paste or attach your layout file]
Here is an example of a working Roq Qute layout for reference:
[paste or attach a working Roq layout]
Iterate on complex layouts
Template porting is skilled translation work. Liquid and Qute differ in conditionals, loops, filters, null handling, and partial inclusion syntax. Budget extra time for complex templates like headers, footers, and sidebars.
For layouts with many includes, navigation logic, or pagination:
-
Convert the main layout first (usually
default.html) -
Convert each include/partial file separately
-
Convert pagination logic last (Roq pagination works differently from Jekyll)
After each conversion, test in dev mode (roq start).
Qute provides clear error messages with line numbers.
Fix errors before moving to the next file.
Convert content files
Content files (blog posts, pages) usually need fewer changes than layouts.
Content that works without changes
-
Markdown files (
.md) with YAML front matter work as-is in most cases -
AsciiDoc files (
.adoc,.asciidoc) are supported natively by thequarkus-roq-plugin-asciidoc-jrubyplugin -
AsciiDoc files without YAML front matter are recognized as collection members based on their directory — Roq discovers content by directory, not by front matter presence
-
The
layoutfield in front matter resolves automatically — writinglayout: postis sufficient because Roq maps it totemplates/layouts/post.html
Front matter differences
| Field | Jekyll | Roq |
|---|---|---|
Layout |
|
|
Date |
|
|
Permalink |
|
Use file path or |
Categories |
|
Use |
Excerpt |
|
|
LLM prompt for content migration
I am migrating content files from Jekyll to Roq.
Convert the YAML front matter in these files to Roq format:
- Keep `layout`, `title`, `date`, and `author` fields unchanged
- Rename `excerpt` to `description`
- Convert `permalink: /path/` to `redirect_from: [/path/]` (Roq uses the
file path as the URL by default; redirect_from handles old URLs)
- Remove `categories` (use directory structure or `tags` instead)
- Keep the body content unchanged (Markdown and AsciiDoc work as-is)
Here are my content files:
[paste or attach files]
| For bulk content migration, write a script (or ask an LLM to generate one) that processes all files in a directory rather than converting files one at a time. |
Moving content directories
When moving content from Jekyll directories to Roq’s content/ directory, use git mv instead of cp to preserve file history:
git mv _posts content/posts
If the target directory already exists, git mv moves the source directory into it (for example, content/posts/_posts/). Remove the target first if it was created during earlier testing.
|
Migrate static assets
JavaScript
Copy JavaScript files to the public/ directory:
mkdir -p public/js
cp path/to/your/javascript/*.js public/js/
Add <script> tags to your root layout, or place JS/CSS sources in web/ to use the built-in Web Bundler (see Styles and Javascript).
SCSS/CSS
Jekyll processes Sass files automatically and supports Jekyll-specific features in SCSS entry points.
With Roq, the quarkus-web-bundler extension (a transitive dependency of quarkus-roq) handles CSS/SCSS bundling.
Watch for these Jekyll-specific patterns in your SCSS entry point:
-
Jekyll front matter (a pair of
---lines at the top of.scssfiles) — remove it -
Liquid variables (for example,
$baseurl: "{{ site.baseurl }}") — replace with hardcoded values -
@importpaths may need updating for the new directory structure
Place SCSS files in the web/ directory.
The import hierarchy (@import partials referencing other partials) must be ported as a complete tree — do not split individual files.
Images and other static files
Copy images and other static files (fonts, favicons, etc.) to the public/ directory.
Jekyll typically serves these from assets/ or directory-relative paths.
In Roq, files in public/ are served at the site root.
cp -r assets/images public/images
Update any hardcoded image paths in templates and content to match the new location.
Data files
Set quarkus.roq.data.dir=_data in application.properties to reuse Jekyll’s data directory in place.
Each YAML or JSON file in _data/ automatically registers as a named CDI bean accessible in Qute templates.
For example, _data/books.yaml becomes accessible as {=cdi:books}, and nested fields via dot notation such as {=cdi:books.items}.
The file format (YAML/JSON) stays the same.
Only the access syntax in templates changes: replace Jekyll’s {{ site.data.books.items }} with Roq’s {=cdi:books.items}.
Convert site configuration
Site configuration does not map cleanly between generators.
Key configuration mappings
Jekyll (_config.yml) |
Roq (application.properties) |
|---|---|
|
Set in a data file or template partial |
|
|
|
Roq uses file-path-based URLs by default |
|
|
|
|
|
|
LLM prompt for configuration
I am migrating from Jekyll to Roq. Convert my Jekyll _config.yml to Roq
application.properties format.
Key mappings:
- url / baseurl → site.url in application.properties
- permalink → Roq uses file-path-based URLs by default
- plugins → Roq uses plugins (install with `roq add plugin:<name>`)
- collections → site.collections.<name>.layout in application.properties
- exclude → site.ignored-files
Important Roq behaviors:
- Defining ANY custom collection replaces ALL defaults (including posts)
so always explicitly define site.collections.posts if you have blog posts
- Set quarkus.roq.data.dir=_data to reuse Jekyll's data directory
- Set quarkus.qute.alt-expr-syntax=true (uses {=expr} for output, plain {..} is not parsed)
Here is my Jekyll _config.yml:
[paste or attach _config.yml]
Scale to all content (Phase B gate)
After the foundation works with a single page, move all remaining content directories using git mv (as described in Convert content files) and measure performance:
roq start -Djvm.args="-Xmx4g"
Check:
-
Build time: under 5 minutes is good, under 10 is acceptable, over 10 minutes is a signal to investigate
-
Memory: should stay under 4 GB for most sites
-
Spot-check 5-10 pages across different content types: verify includes, code samples, images, and cross-references
If the build is too slow, try reducing the content to a subset during development (move extra files to a temporary directory outside content/) and build the full site only for production validation.
Handle redirects
Jekyll sites often have permalink-based URLs that differ from Roq’s file-path-based URLs.
Use the quarkus-roq-plugin-aliases extension to set up redirects from old URLs to new ones.
The plugin recognizes three equivalent front matter keys: redirect_from, redirect-from, and aliases.
---
title: My Page
redirect_from:
- /old/permalink/path/
- /another/old/path/
---
For bulk redirect migration, write a script that reads your Jekyll _config.yml permalink patterns and generates redirect_from front matter for each content file.
Tips
General
- Commit after each step
-
Frequent commits let you roll back if a conversion introduces issues.
- Test in dev mode after each change
-
Run
roq startafter each conversion. Qute provides clear error messages with line numbers.
When using an LLM
- Ask for explanations
-
When the LLM converts a template, ask it to explain each change. This helps you learn Qute syntax and catch incorrect conversions.
- Provide error messages
-
Paste the full error message from
roq startback to the LLM. Qute error messages are specific enough for the LLM to diagnose. - Batch similar files
-
Group similar templates or content files and convert them together. The LLM produces more consistent output when it can see patterns across files.
- Start new conversations for each phase
-
After 2-3 steps of implementation, build output and file contents fill the LLM context window. A fresh conversation with "Continue from Phase B" works better than pushing through a long session.
Known limitations
-
Liquid filters: Jekyll’s Liquid filters (for example,
| date: "%B %d, %Y") do not have direct Qute equivalents. Qute uses method calls instead (for example,{=post.date.format('MMMM dd, yyyy')}). The LLM usually handles common filters, but verify date formats and edge cases. -
Jekyll plugins: Jekyll plugins cannot be reused in Roq, but Roq has its own plugin ecosystem covering common needs (sitemap, tagging, aliases, RSS, etc.). Check the Plugins & Themes directory and install with
roq add plugin:<name>. -
SCSS processing: Jekyll processes Sass files automatically and supports Jekyll-specific front matter and Liquid variables in SCSS. With Roq, the web-bundler handles SCSS, but you must remove Jekyll-specific syntax from entry points and update import paths. The LLM can help with the syntax changes but not with debugging build tool differences.
-
Data file access: Jekyll’s
_data/files are accessed as{{ site.data.filename.key }}in Liquid. In Roq, data files register as CDI beans and are accessed as{=cdi:filename.key}in Qute templates. The file format (YAML/JSON) stays the same. -
Build error strictness: Jekyll silently ignores missing includes. Roq and AsciidoctorJ may fail the entire build on a single broken
include::directive. Validate includes early when scaling to all content. -
AsciidoctorJ security boundaries: AsciidoctorJ may block
include::directives that resolve to paths outside the content directory. If you see security errors, move included files insidecontent/or adjust the safe mode configuration. -
RSS feeds: Jekyll’s
feed.xmluses Liquid syntax. Roq has built-in RSS support (see RSS). Create acontent/rss.xmlwith{#include fm/rss.html}and add{#rss site /}to your layout’s<head>.
Clean up Jekyll artifacts
After migration is complete and validated, remove Jekyll-specific files:
-
_config.yml(and any variant config files) -
_layouts/,_includes/(top-level Jekyll directories) -
_sass/(if ported toweb/) -
_plugins/ -
Gemfile,Gemfile.lock,.bundle/,.ruby-version -
Any Jekyll serve scripts
Keep files that Roq still uses:
-
_data/(ifquarkus.roq.data.dir=_datais configured)
What’s next
-
Read the Roq the basics for the full feature set
-
Browse the Plugins & Themes directory
-
Check the Roq blog source for a complete working example
-
If you develop migration scripts or improved prompts, consider contributing them back to the project (see issue #780)