Hello Quarto

incorporating Quarto into my Hugo site workflow

post hugo python ssg literate-programming

I 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.

config/_default/config.yaml
    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.

_quarto.yml
    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.

content/post/2025/hello-quarto/index.md
    ---
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.

Justfile
    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:

layouts/_default/_markup/render-codeblock.html
    {{- 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!