Author

Silvia Canelón

Published

September 29, 2023

Modified

October 12, 2023

Quarto Docs

Goodbye, Hugo Apéro

In 2019 I created my first website using blogdown and the Hugo Academic theme, and in 2021 I migrated that site to the Hugo Apéro theme created by Alison Hill. I even documented that trek in great detail 😅 to provide something resembling a paved path for others to use on their journey adopting this beautiful and functional theme. Over the past two years, my Apéro site became a wonderful place to cultivate a digital garden1. Now the time has come to say thank you, say goodbye, and travel down a new path. Enter Quarto.

Why port to Quarto?

Quarto is a new and powerful technical publishing system in active development, natively friendly to a variety of programming languages and IDEs. There are countless features available in Quarto that I was interested in having access to in my website, so I’ll list just my top five:

  1. Flexibility in design. Quarto offers options for website navigation (top navigation vs. side navigation), “about” pages like the postcard landing pages that a lot of folks use in their distill sites, listing pages that are generated from a list of documents, among others. These components would act like modules that I could combine to design a site that truly fit my needs.

  2. Flexibility in content layout. With Quarto I would gain the ability to organize content in ways I hadn’t even dreamt of. Tables and figures could be laid out across multiple columns and rows as sub-tables and sub-figures of a panel. How much of the page the content took up would be up to me. Content could even go in the margin! 🤩

  3. Freezing computations. Quarto would give me the option to freeze posts containing executable code. This was huge for me, because updating my blogdown site always produced a little bit of anxiety. I used to worry that blogdown might try to re-render old R Markdown blog posts and break things! 😱

  4. Site search. With content spread out over blog posts, talks, publications, and projects, I wanted site visitors (including myself!) to be able to keyword search for something in particular. Tags and categories in blogdown would get me part of the way there, but Quarto offers built-in search capability.

  5. Embedding computations. Ok this is a feature that I learned about a week ago at posit::conf(2023) from Mine Çetinkaya-Rundel’s talk “Reproducible Manuscripts with Quarto.” Mine demo’d pulling a table from a .qmd file and a figure from an .ipynb file into the same manuscript .qmd file, without having to execute the source code again! 👀 This is a game changer for anyone compiling results from computations performed in different files, and in my personal site I could see it coming in handy if I have a series of blog posts with outputs that I want to cross-reference. Currently we can only embed computations from Jupyter notebook files, but hopefully we’ll be able to embed .qmd computations in Quarto v1.4! 🤞🏽

I mean, it’s pretty cool that content can go in the margin, right??

It could even be a picture:

There is a book open in the green grass with some yellow leaves around it
The book page reads verdere expedities which is Dutch for “further expeditions”

The cost of porting

Gaining the aforementioned Quarto functionality did not come without a cost. To me, the biggest cost was the aesthetics – the Hugo Apéro theme is quite lovely! – but I customized my Quarto site enough to reflect the design aspects I loved the most. More on styling later. Some other costs included:

  • Loss of the sidebar layout. The Apéro theme has options for adding a nicely styled sidebar that I had been using for my blog. It included a featured image and details about the individual post like tags, categories, length, etc. All of this information is available in Quarto posts, it’s just organized a little differently.

    Example of the sidebar

    Post title, author, date, and some content on the left hand side, and a sidebar with metadata on the right hand side. The image featured in the sidebar is a cactus plant in a terracota planter, sitting on top of a pile of books

  • Loss of Utterances comments. Any interactions with folks through Utteranc.es comments did not transfer over, and I’ll miss them. The few comments on my site have been very kind (see example below) and I’ve really enjoy reading them!

    Example of Utterances comments

    User @capolimia says 'Thank you so much!!!!!! This project last year helped me have a great summer, I appreciate your work!!!' and I respond 'Hi @capolimia, thank you for this comment! I don't how how I missed it. I'm happy to hear that the 2022 post was helpful to you! 🚀'

    Update: Oct. 12, 2023

    Thanks to Emily Riederer, I’m happy to report this is a non-issue! See her tips in the comments at the end of the post, or on GitHub.

  • Hugo Apéro collections. The Apéro theme had a way of bundling together posts that were related into a collection of posts. Garrick and I used this feature for our user!2021 xaringan workshop and it worked well to organize learning materials for different sections of our workshop. If I wanted something like this in the future, I would probably use Quarto’s sidebar navigation feature like Andrew Bray did in his Rmd to Quarto workshop for rstudio::conf(2022).

  • Name pronunciation on the About page. Apéro had a built-in option for adding an audio clip under your name that you could use to help people learn how your name is pronounced. I never used this feature myself, but if I wanted to, I could look to Emil Hvitfeldt’s site as an example.

  • Redirects. The Apéro theme gave me the option to define the combination of date information and URL slugs for each of my posts, and I had them set up as year-slug (e.g., blog/2023-hello-quarto). Quarto does not offer that functionality and strictly creates URLs for posts based on the filepath (e.g., blog/2023-09-29-hello-quarto). In order to continue using my year-month-day-slug naming convention for my post folders, I had to set up redirects from the old URLs to the new ones so that there wouldn’t be broken links sprinkled throughout the internet. More on redirects later.

Designing the site

Apéro had posts in the content folder but with Quarto I could pull blog, talk, publication, and project subfolders into the root folder.

Structure

.
├── _quarto.yml
├── about
├── blog
├── talk
├── publication
├── project
├── index.qmd
└── silvia.Rproj
.
├── content
   ├── _index.md
   ├── about    
   ├── blog      
   ├── project
   ├── publication
   └── talk
├── config.toml
├── netlify.toml
├── index.Rmd
└── silvia.Rproj

About & listing pages

I decided to use a combination of about pages and listing pages to replicate the site structure I enjoyed in my Apéro site. I used an about page layout for both my home and about pages, and listing pages to generate a list of blog posts, talks, publications, and projects. There was also some custom CSS involved, but we haven’t gotten to that part of the blog post yet wink 😉

Dear reader, it’s at this point in my blog post drafting that I start realizing that what was meant to be a brief post is turning into a novel (sigh)

One of the aspects I liked about my Apéro site was that the About page highlighted the most recent posts from the blog, talk, publication, and project groups. In my Quarto site, I achieved this by including a one-entry listing grid for each group on my About page.

These listing cards looked great on a wide screen but as the screen got narrower (think mobile device) the listing cards just became narrower and narrower 😂. I took care of this by tweaking the CSS so that the cards would wrap.

assets/about.css
  /* wrap lately section */
  #lately .grid {
    display: flex;
    flex-wrap: wrap;
  }

  /* listings */
  #blog, #talks, #publications, #projects {
    flex-basis: 100% !important;
  }

source code

Contact form

The last structural piece I wanted to recreate from Apéro was a contact form. I saw a great example on Michael McCarthy’s Tidy Tales blog, site and did a little repo-diving to get ideas on how to organize the content. I ended up using the Bootstrap CSS Grid to layout the content exactly how I wanted to. See what I mean about Quarto offering a lot of flexibility in content layout?

contact.qmd

<!-- start grid -->
::: {.grid} 

<!-- column for the body text -->
::: {.g-col-5}

# Send me a note

< Body text>

< HTML code for social media icons >

:::

<!-- column for spacing -->
::: {.g-col-1}
:::

<!-- column for the form -->
::: {.g-col-6}

< HTML embed code provided by Formspree >

:::

:::
<!-- end grid -->

source code

Adapting old posts

YAML fields

Ok so once I figured out how to incorporate all of the structural elements I wanted, I had to deal with the (relatively small) challenge of porting my old posts to Quarto. Quarto was designed to be compatible with existing R Markdown documents, but I didn’t actually want it trying to re-render old R Markdown files so I had Quarto ignore those completely2 and render the Markdown version of each post instead3. There were also a handful of YAML fields that I had to remove or modify to play nicely with Quarto:

  • Remove layout: (e.g., layout: single-sidebar)
  • Remove publishDate
  • Remove lastUpdated
  • Remove featured (e.g., featured: yes)
  • Merge categories with tags and keep tags
  • Add image (e.g. image: featured.png)

Partials

If you’ve used Apéro you might have come to love the cute button links created from the links field in the YAML, like the one at the top of this post, pointing to the Quarto docs.

links:
- icon: journal-text
  name: Quarto Docs
  url: https://quarto.org/docs/websites/

Well, these button links are not ready-made by Quarto, but lucky for me (us!), Garrick figured out a way to bring them in right where we I need them. I repo-dived and found that he modified an HTML partial for title blocks, which controls the layout and styling of the post title, description, and tags, shown at the top of the page.

The original title block partial has sections controlling the placement and styling of text provided in the title, subtitle, authors, date, and abstract fields of the document YAML.

As an example, line 2 might read:
If there’s something in the title field of the YAML, give it a level 1 heading and style it with CSS class .title
github.com/quarto-dev/quarto-cli/src/resources/formats/html/pandoc/title-block.html
<header id="title-block-header">
$if(title)$<h1 class="title">$title$</h1>$endif$
$if(subtitle)$
<p class="subtitle">$subtitle$</p>
$endif$
$for(author)$
<p class="author">$author$</p>
$endfor$

$if(date)$
<p class="date">$date$</p>
$endif$
$if(abstract)$
<div class="abstract">
<div class="abstract-title">$abstract-title$</div>
$abstract$
</div>
$endif$
</header>

The modified partial includes some additional sections but I’ll break down the one for links.

There’s more styling going on in the modified partial, but starting on line 6, the structure for the link buttons is effectively:

If the links field is populated in the YAML, create an HTML container styled by some CSS, and give it some specific HTML properies.

Then, for each sub-item of the links field: create a hyperlink from the url sub-item, apply some CSS classes to create the button, add a Bootstrap icon defined by the icon sub-item, and finally add hyperlink text defined by the name sub-item.
_partials/title-block-link-buttons/title-block.html
<header id="title-block-header" class="quarto-title-block default page-columns">
...
$if(links)$
<div class="mt-4 d-flex flex-row flex-wrap gap-1 justify-content-left justify-content-sm-start" role="group" aria-label="Links">
$for(links)$
<a href="$links.url$" class="btn btn-secondary text-capitalize"><i class="bi bi-$links.icon$ me-1"></i>$links.name$</a>
$endfor$
</div>
$endif$
...
</header>

Styling the site

SCSS variables

I may have published my brand new Quarto site in the fall of 2023, but I had actually taken my first go at porting it from Apéro in the fall of 2022. In an attempt to replicate the styling of my old site I tried to strong-arm my Quarto site into using tachyons4, and I wrote way too much custom CSS in the process 😩.

Something didn’t feel right about it, so I took a break and came back to my in-progress Quarto site several months later – outside of the Quarto release craze, and with the perspective that I wanted to work with the built-in theming capabilities, rather than against them 😌. This time around I leaned into the Bootstrap SCSS variables, and added custom styling as needed.

To be more specific, I created a custom theme 5, and populated it with definitions for some of the built-in Bootstrap Sass variables. If you poke around my custom theme, you will notice that some colors are defined by color variables that start with $spc-. These are variables pointing to colors that I’ve defined elsewhere, and they can be replaced with a hex code of your choosing! 🎨

assets/silvia-theme.scss
/*-- scss:defaults --*/

// Colors
$primary:                    $spc-primary-light;
$secondary:                  $spc-secondary;

The Sass variables listed in the documentation got me pretty far, but there were still elements of my site using default colors and styling that I wanted to change. So I repo-dived to learn about additional Quarto Bootstrap variables and rules:

To give you an example, someone in the Quarto Discussions forum asked how to set the color of block quotes, figure captions, and outlines, something not defined in the documentation. One of the replies suggested using the browser’s inspector to find the CSS rule applied to the element in question, we’ll say the figure caption, and then looking at how Quarto implemented that rule. I’ll make that process a little more explicit here and use the figure caption as an example:

  1. Use the browser inspector to highlight a figure caption

  2. The inspector will reveal that the element is styled by CSS class .figure-caption

  3. Run a keyword search for “figure-caption” in the Bootstrap variables file. This will yield no results 🤔

  4. Run the same keyword search in the Bootstrap rules file – success! We have a clue, which is that .figure-caption is styled by a variable named body-secondary 🔍

    .panel-caption,
    .figure-caption,
    .subfigure-caption,
    .table-caption,
    figcaption.quarto-float-caption,
    caption {
      font-size: 0.9rem;
      @include body-secondary;
    }
  5. Define your own body-secondary color variable in your custom theme file

    assets/custom-theme.scss
    /*-- scss:defaults --*/
    
    $body-secondary: #6C757D;
  6. Enjoy the fruits of your labor as a CSS detective 🕵🏽‍♀️

Individual page styling

In some cases, I wanted to style individual pages differently than I was styling my entire site. Poking around Garrick’s repo (again!), I learned that you can reference CSS stylesheets in the YAML of any particular document. I used this approach to separately style my Home, About, and listing pages.

index.qmd
header-includes: >
  <link rel="stylesheet" href="assets/index.css">
resources:
  - assets/index.css

Background image for title blocks

Ok one of the aesthetic pieces I was excited to add to my Quarto site that I didn’t have in my old site is a featured image at the top of each post. I was inspired by Matt Worthington’s site while I was reading one of his excellent blog posts on a workflow for interactive maps in R – I loved that the title block included an image in the background! 🤩

After some web inspecting and repo-diving in Matt’s website repo I came up with some styling that I could apply to the title block of each post. It took modifying the title block partial described earlier, and adding some CSS to my custom theme.

For styling that I wanted to apply uniformly to all post title blocks, I created a .figured-image CSS class in my custom theme:

Don’t miss the code annotations for this code chunk! 👀
Such a cool feature!
assets/custom-theme.scss
  // style background image on posts
  .featured-image {
1    background-size: cover;
2    background-position: center;
3    color: white;
4    box-shadow: inset 0 0 0 1000px rgba(0,38,66,0.75);
  }
1
Have the image cover the background
2
Center the image
3
Make the overlaying text color white
4
Add shadowing that gives the appearance of a dark overlay

Alright, but why didn’t I use a background-image property to define the image? If I define the background image in my .featured-image class then that is the image that’s going to show up in all of the posts across my site. I want the title block of each post to display the featured image belonging to each individual post. That’s where the partial comes back in.

_partials/title-block-link-buttons/title-block.html
<div 
id="title-block-header-title" 
class="quarto-title page-columns page-full page-layout-full featured-image p-4" 
style="background-image: url(featured.png), url(featured.jpg), url(../featured.jpg);">
...
</div>

Along with some other CSS classes, the div container for the title block is styled with the featured-image class defined earlier, using the class= property. In addition, the div itself is styled using the style= property, and that’s where I defined the background image. The partial will look for a featured image of some kind either in the same folder as the post, or in the parent folder, and display it in the background of the title block.

This is a hacky way of getting what I really want which is for the partial to check for a featured image in the order I specify and then just stop once it finds one. As it stands right now, the styling attempts to layer on all three images, with the first image on the top and the last image on the bottom. This means that I get a warning every time I preview or render my site because the partial can’t find one or two of the other urls, but I’m ok with that!

Example warning
/blog/2023-09-29-hello-quarto/featured.png (404: Not Found)

Setting up redirects

Resources

Last but not least, I can’t finish this blog post without talking about the necessary task of redirecting old Apéro URLs to new Quarto ones 6. Many thanks to Danielle Navarro and Tom Mock for documenting their solutions for automatically generating a _redirects file with each render of the site.

I adapted their code to (1) fit my site, which has multiple folders of posts that all need redirects, and (2) combine these automatically generated redirects with ones I had already defined manually. I placed this code within my home page index.qmd file and I specified freeze: false in the YAML so that the code would run each time I rendered the site. The following sections will take a look at the code piece by piece.

Importing manual redirects

The first step imports the manually-defined redirects that I had already been using in my old site. These redirects primarily have the task of redirecting my rbind.io domain to my custom domain silviacanelon.com.

index.qmd
manual_redirects <-
  readr::read_table(here::here("static", "_manualredirects.txt"),
                    col_names = FALSE) |> 
  dplyr::mutate(redirect = paste0(X1, " ", X2, " ", X3))

manual_redirects <- manual_redirects$redirect

head(manual_redirects)
[1] "https://silvia.rbind.io/authors/silvia/avatar.png https://silviacanelon.com/about/sidebar/avatar.png 301!"
[2] "https://silvia.rbind.io/post/* https://silviacanelon.com/blog/:splat 301!"                                
[3] "https://silvia.rbind.io/blog/* https://silviacanelon.com/blog/:splat 301!"                                
[4] "https://silvia.rbind.io/talk/* https://silviacanelon.com/talk/:splat 301!"                                
[5] "https://silvia.rbind.io/project/* https://silviacanelon.com/project/:splat 301!"                          
[6] "http://silvia.rbind.io/* https://silviacanelon.com/:splat 301!"                                           

Listing subdirectories

The next step defines a function that obtains a list of subdirectories, iterates it over the four groups of posts in my site (blog posts, talks, publications, and projects), and compiles the files into a data frame.

index.qmd
# function: obtain list of post paths
list_paths <- function(folder) {
  posts <-
    list.dirs(
    path = c(here::here(folder)),
    full.names = FALSE,
    recursive = FALSE
    ) |> 
    tibble::as_tibble_col(column_name = "path")  |>
    dplyr::mutate(folder = folder)
}

# define post folders
folders <- c("blog", "project", "publication", "talk")

# list post paths by folder
posts <- purrr::map(folders, list_paths) |> purrr::list_rbind()

head(posts)

Defining redirects

This next chunk removes the month and day from year-month-day-slug so that I’m left with shorter paths with the format year-slug, and uses these paths and the folders they are housed in to create redirects.

The resulting redirects will point the short year-slug link to this blog post:

https://silviacanelon.com/blog/2023-hello-quarto

To the longer year-month-day-slug Quarto link to this post:

https://silviacanelon.com/blog/2023-09-29-hello-quarto

# extract short paths and create redirects
posts <- 
  posts |> 
  dplyr::mutate(
    # extract the year-slugs
    short_path = stringr::str_remove(path, "(?!\\d{4}-)\\d{2}-\\d{2}-(?!\\d)"),
    # create short paths
    short_path = paste0(folder, "/", short_path),
    # create lines to insert to a netlify _redirect file
    redirects = paste0("/", short_path, " ", "/", folder, "/", path)
    )

head(posts)

Writing redirects file

The last step takes the redirects from the data frame produced in the previous step, combines them with the manual redirects, and writes them to a new text file _redirects. This file is written into the _site folder where my Quarto site is rendered, and where Netlify will know to find it.

index.qmd
# extract redirects
redirects <- posts$redirects

# combine with manual redirects
redirects_combined <- c(manual_redirects, redirects)

# write the _redirect file
writeLines(redirects_combined, here::here("_site", "_redirects"))

Fin

Now that it’s been over a year since Quarto made its debut, there are so many wonderful blog posts walking folks through how to create a Quarto site. My hope is that the notes I took in this blog post help provide some stepping stones for folks making the transition from blogdown sites, and offer some ideas for what is possible with the flexibility of this new publishing framework. I, for one, am looking forward to growing my digital garden with Quarto tools 🌱.

Back to top

Acknowledgments

Featured photo by Annelies Geneyn on Unsplash.

Footnotes

  1. See also Digital gardens let you cultivate your own little bit of the internet | MIT Technology Review↩︎

  2. By adding an underscore prefix (e.g., _index.Rmarkdown)↩︎

  3. I.e., index.markdown↩︎

  4. A CSS design system used in the Hugo Apéro theme: https://tachyons.io↩︎

  5. For more on custom theming, see Quarto - HTML Theming and Quarto - More About Quarto Themes↩︎

  6. Don’t skip this step! People will almost certainly have shared one or more of your posts somewhere on the internet, and it helps everyone if you can point them to the right spot↩︎

Reuse

Citation

For attribution, please cite this work as:
Canelón, Silvia. 2023. “Hello Quarto: Porting My Website from Hugo Apéro.” September 29, 2023. https://silviacanelon.com/blog/2023-09-29-hello-quarto.