Hello Quarto
incorporating Quarto into my Hugo site workflow
post hugo python ssg literate-programmingI just made Quarto part of my Hugo site building workflow.
import arrow
arrow.utcnow().to("local").format(arrow.FORMAT_RSS)
'Fri, 28 Feb 2025 18:50:36 -0700'
The Quarto tool provides a workflow for publishing technical and scientific documents in Markdown, complete with code execution in multiple languages. You can use Jupyter notebooks or Markdown files. You can use it for papers, books, dashboards, nerdy blogs, and nerdy Web sites. Stuff even cooler than an Arrow timestamp. Overall, a nice approach to literate programming.
There’s a Quarto static site generator of course, which is the direction most folks should go. But the fun part? It also works with existing sites built by other tools such as Hugo. That’s handy for me.
So I added Quarto here, writing a couple quick notes about things I noticed along the way.
Why?
No grand plans. I just wanted to see if Quarto + Hugo works. Now that I know it does, the option is available next time I have a code-heavy blog post idea.
And if Quarto starts to bother me, there’s always Org Babel.
Setup
I grabbed Quarto from the Quarto download page at some point. I’m at 1.6.40. Current release is 1.6.42. I think we’re good for now.
Configure Hugo
The Quarto Hugo Markdown guide provided some important starting details. Make sure Hugo ignores files handled by Quarto. While we’re there, tell Goldmark we allow raw HTML in Markdown.
ignoreFiles:
- "\\.qmd$"
- "\\.ipynb$"
- "\\.py$"
# ...
markup:
goldmark:
renderer:
unsafe: true
Make it a Quarto project
Adding _quarto.yml
to the site project root folder.
That’s where we make sure Quarto knows this project is a Hugo site, and that it should render
.qmd
files but not anything under the Hugo site’s archetypes/
folder.
Those templates will only confuse it.
project:
type: hugo
render:
- "*.qmd"
- "!archetypes/"
format: hugo-md
Add a .qmd
file
I plan to keep this one real simple, as far as code goes. I’m writing a test, not a treatise.
---
title: "Hello Quarto"
jupyter: python3
---
```{python}
import arrow
arrow.utcnow().to("local").format(arrow.FORMAT_RSS)
```
Check it
Since Quarto knows we are in a Hugo site, it can handle interaction with the
hugo
executable during preview mode.
quarto preview
Quarto evaluates the contents of index.qmd
, executing code when needed, and pushes Hugo-friendly Markdown into index.md
.
Pretty similar to ox-hugo’s process of converting Org trees to Hugo content.
I do get noticable flicker as Quarto does its thing and then Hugo does its thing,
but it’s all pretty quick and gets us roughly where I opened the post.
warning
Quarto’s Hugo extension does not use -D
/ --buildDrafts
when launching the Hugo local server.
For now at least, make sure your .qmd
files have draft: false
in frontmatter.
Automate it a little
I drive my site workflow with just
, so rather than fix my muscle memory I’ll
see to it that my muscle memory invokes quarto
where needed.
serve:
quarto preview
build:
quarto render
hugo --environment production
Caveats
The folks behind Quarto put a lot of work into making this as easy as possible. There are some fiddly bits, of course. Only to be expected when you’re mashing two static site generator workflows together.
If you’re writing about Quarto code blocks, you’ll want extra backticks for the code block and extra curly braces for the language identifier.
````{markdown}
```{python}
print("Yo")
```
````
It will almost certainly confuse Hugo syntax highlighting when you do this. I temporarily worked around the problem in my code block render hook, by disabling support for syntax highlighting of Markown.
That template is getting chunky, but here’s the most relevant bit:
{{- if eq .Type "markdown" }}
{{- $shouldHighlight = false }}
{{- end }}
...
<pre tabindex="0" class="chroma">
<code class="language-{{ .Type }}" data-lang="{{ .Type }}">
{{- if $shouldHighlight }}
{{- with transform.HighlightCodeBlock . -}}
{{- .Inner -}}
{{- end -}}
{{- else }}{{ .Inner }}{{- end -}}
</code>
</pre>
The other fun part is shortcodes. Using {=markdown}
for the language indicator should make Quarto
pass the contents of that code block unchanged for Hugo.
```{=markdown}
{{% warning %}}
Quarto's Hugo extension does not use `-D` when launching the Hugo local server.
Draft posts will not be generated.
{{% /warning %}}
```
Wrapping up
That’s about it. Started writing because it took some work to find out about Hugo and _quarto.yml
.
Kept writing for a bit.
Still plent of things to dig deeper on, like rendering Jupyter notebooks, and handling other languages.
Should be fun!