flowchart LR A[working<br/>directory]-.git<br/>add.->B{{staging<br/>area}}-.git<br/>commit.->C([local<br/>repo])-.git<br/>push.->D(remote<br/>repo)
Quarto
Quarto is an open-source software system for turning plain-text source files into outputs like articles, books, blogs, dashboards, presentations, reports, and websites. Announced by Posit CEO JJ Allaire, Quarto is already taking the worldđby stormâď¸!
I strongly believe that everyone, regardless of their background and current technical skill level, can learn and benefit from Quarto. Getting started with Quarto is easy thanks to its excellent documentation and vibrant community of enthusiastic users and developers.
Rather than repeat the basic information already available elsewhere, I will share some advanced techniques along with the fundamental knowledge needed to understand how they work. The topics I cover are very technical, but my goal is to make the content on my blog as accessible as possible.
The first topic I will delve into is creating and publishing a website with Quarto. To follow the Quarto documentation on creating a website, you will need Visual Studio Code (VSCode), VSCodium and RStudio, or a terminal.
Publishing Quarto sites
Installing tools
If you use macOS, Linux, or the Windows Subsystem for Linux (WSL), you can install all of the aforementioned tools with the Homebrew package manager. To install everything you will need to follow along with this blog post, you can first install Homebrew and then run brew
bundle
in a directory that contains the Brewfile shown in Example 1.
Example 1
Brewfile
"gh"
brew "git"
brew "glab"
brew "github"
cask "quarto"
cask "rstudio"
cask "vscodium"
cask "visual-studio-code"
cask "quarto.quarto"
vscode "REditorSupport.r" vscode
Using a package manager like Homebrew to install all the requirements with a single shell command like brew
bundle
is the fastest and easiest way to get ready to follow along with this blog post. If you are curious about how I set up my computer, you can take a look at my Brewfile and other configuration files in my setup
repository (repo) on GitHub.
Apart from RStudio, VSCode, and VSCodium, the Brewfile in Example 1 will install the Git version control system, the GitHub and GitLab command line interfaces (CLIs), and GitHub Desktop
, a Git Graphical User Interface (GUI). For more information on Git, a tool used by 93% of software developers worldwide according to survey results published by StackOverflow, take a look at the âGitHub for supporting, reusing, contributing, and failing safelyâ post by Allison Horst and Julie Lowndes on the Openscapes blog.
As an alternative to installing tools on your computer, you can use the web interface provided by GitHub Codespaces. To set up a Codespace, you can remove the lines that start with cask
from the Brewfile provided in Example 1 and add the file to a repo called dotfiles
and along with a setup.sh
file like the one shown in Example 2.
Example 2
setup.sh
echo | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
(echo; echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"') >> /home/codespace/.profile
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
brew bundle
Publishing overview
Once you are done setting up your computer or Codespace, you create a Quarto website template and make your site publicly available on the internet using one of the many available publishing services. To explore and assess different publishing workflows and free website hosting options, I set up my personal website on four different web hosts: GitHub Pages.
There are so many different ways to publish a Quarto site that I decided to come up with a naming system for Quarto publishing methods. The naming system derives a code for each publishing method from the numbered lists in the Quarto publishing documentation. For example, I refer to the two methods to publish to Quarto Pub as Q1 and Q2:
Table 1 uses this naming system in its Code
column to identify the publishing methods I discuss in this blog post. Each publishing method targets a particular web Host
, Render
s content locally or on a remote server, and deploys sites using either the quarto
publish
or the git
push
shell Command
.
Code | Host | Render | Command |
---|---|---|---|
G1 | GitHub | Local | push |
G2 | GitHub | Local | publish |
G3 | GitHub | Remote | push |
N1 | Netlify | Local | publish |
N2 | Netlify | Local | push |
N3 | Netlify | Remote | push |
Q1 | Quarto | Local | publish |
Q2 | Quarto | Remote | push |
Git workflow
The first publishing method I tried for my personal site was G1, which requires the use of Git to push all website content to a Git provider like GitHub or GitLab. After I set up GitHub Pages and GitLab Pages, I could update my website on both of these web hosts just by going through the standard Git workflow shown in Figure 1.
To make it easier to make incremental changes to my website and frequently release new content, I combined all of the git
shell commands in Figure 1 into a shell alias. You can add shell aliases to a shell configuration file like .bashrc
or .zshrc
on your computer to shorten commands or combine any sequence of commands into one step.
The aacmp
alias in the .zshrc
file in my setup
repo allows me to enter a free-form commit message directly on the command line without quotes, e.g. qp
edit
first
blog
. If you decide to try my aacmp
alias, please exercise extreme caution as any un-escaped or un-quoted metacharacters may yield surprising effects instead of being included verbatim in the commit message. For example, qp
*
will list the contents of the current directory in the commit message in place of the asterisk!
An alternative to a shell alias that combines git
commands is a keyboard shortcut in Git-enabled GUI. Example 3 shows two files, tasks.json
and update.sh
, that we can use to set up VSCode, VSCodium, and GitHub Codespaces to go through the Git workflow whenever we press Ctrl+Shift+B on Linux/Windows or â+Shift+B on Mac (mnemonic: B is for Build).
Example 3
This mechanism is called Tasks and is used to automate software build tasks, which can include any steps required to build and publish a website. Importantly, the Tasks mechanism requires that the tasks.json
file be added to the .vscode
directory and that we enable execution of the update.sh
script by running chmod
+x
update.sh
in our project root.
Shell aliases and keyboard shortcuts can greatly facilitate the Git workflow which is essential not only for G1, but also G3, N3, and Q2. Unlike these other publishing methods, G1 leads to messy commits that contain changes to both source and output files.
quarto
publish
To have cleaner commits, I switched from G1 to G2 by adding quarto
publish
to my publishing workflow. With G2, I can track changes to my source files on my main
branch and publish my output files to GitHub Pages and GitLab Pages from my gh-pages
branch.
Q1, N1, and G2 all use quarto
publish
to render website content locally and then deploy it in one fell swoop. If you do not plan to use the Git version control system or the advanced features offered by GitHub, GitLab, or Netlify, then I recommend deploying your site to Quarto Pub by running quarto
publish
quarto-pub
(Q1 in Table 1).
Like Q1, N1 is a deployment method that does not require Git and makes it possible to deploy our site with a single shell command: quarto
publish
netlify
. N1 provides access to the advanced web hosting features offered by Netlify and can even render content as long as code execution is frozen.
I deployed my site to GitHub Pages currently. When I run quarto
publish
gh-pages
, Quarto renders my site into my output directory, copies the output directory contents to the gh-pages
branch of my local repo, and then commits and pushes the changes to my remote repos on GitHub, I am planning to trigger Netlify and GitLab as well to build my site from the gh-pages
branch through this process.
To summarize the Quarto publishing methods I discussed so far, Q1 and N1 are easy to configure and use, N2 automatically builds sites from Git repos, G1 is not recommended because it pollutes commits with output file changes, and G2 is more difficult to set up and use but provides clean commits and a nice separation of source and output files.
GitHub Actions
All of the publishing methods I have discussed so far require us to generate output files locally by rendering our source files. In contrast, Q2, G3, and N3 make it possible to skip local rendering in favor of relying on GitHub Actions to handle all of the necessary steps.
G3 is noteworthy, because it offers the same convenience of G1 but without messy commits that mix changes to source and output files. An added bonus of G3 is that rendering with GitHub Actions provides a reproducible computational environment that is not dependent on what you have installed on your computer.
Instead of using GitHub Actions, I could have used GitLab CI/CD to build my site. I decided not to go down this route because the Quarto dev team has many GitHub Actions workflows available but currently no official support for GitLab CI/CD.
Before trying to use GitHub Actions or any other continuous integration systems in your publishing workflow, I suggest getting used to working with quarto
publish
(Q1, G2, or N1). You can always set up other publishing methods later without sacrificing anything, because all of the publishing methods except G1 can be combined together.
In Section 1.3, I will walk through the setup of both G2 and G3 to provide the option of rendering locally by running quarto
publish
or rendering remotely with GitHub Actions by pushing to the main
branch. Along the way, I will share many practical tips and general advice that you can apply to any project.
Publishing setup
Repo setup
Before you can use GitHub Actions to publish your site, you will need a GitHub, an SSH key, and a repo like Blog
that has a default branch called main
and another branch which must be called gh-pages
. If you want to publish on GitLab Pages, you will also need a GitLab account.
You can create the repo and the gh-pages
branch using the web interface of https://github.com or https://gitlab.com in your browser, but the best way to start a new project is using the CLI for GitHub or GitLab in your terminal. First, run gh
auth
login
or glab
auth
login
and follow the prompts to authenticate via your web browser or with an authentication token.
The GitHub CLI allows you to add an SSH key to your account during or after authentication. The GitLab CLI does not handle SSH keys during authentication, but has a similar command for adding an SSH key to your GitLab account.
After authentication and SSH key setup, you can run the code in either of the code chunks in Example 4 to set up your local and remote repos and create a Quarto website project in the local repo. You can create shell alias that combine all of the repo creation steps like I did in my .zshrc
.
Example 4
cd # start in home directory
mkdir -p USERNAME
cd USERNAME
gh repo create USERNAME.github.io --add-readme --clone --public
cd SITENAME
quarto create project website USERNAME.github.io
cd # start in home directory
mkdir -p USERNAME
cd USERNAME
glab repo create USERNAME.gitlab.io --readme --defaultBranch main --public
cd SITENAME
git pull origin main
git branch --set-upstream-to=origin/main main
quarto create project website USERNAME.gitlab.io
To make it easier to maintain my site on both GitHub and GitLab Pages, I set up my local repo cloned to have two origin
remote URLs using the code as shown in Example 5. Now, running quarto
publish
or git
push
in my local repo, updates my content on both GitHub and GitLab.
Example 5
git remote add hub git@github.com:NishantBharali/Blog
git remote set-url --add origin $(git remote get-url lab)
If you want to have your website hosted on GitHub Pages, you will need to set gh-pages
as your source branch in your repo settings. For GitLab Pages, you will need to add a .gitlab-ci.yml
file to your repo and update your _quarto.yml
file as shown in Example 6 to include .gitlab-ci.yml
as a resource
in your output directory.
Example 6
By default, GitLab Pages includes a random hash in site URLs. To shorten the URL of my GitLab Pages site, one has to uncheck Use unique domain under Deploy > Pages
in the GitLab sidebar.
At this point, we have completed G2 setup and you should be able to run quarto
publish
gh-pages
from your main
branch to render your site and deploy it to GitHub and/or GitLab Pages. Deploying with quarto
publish
at least once is a prerequisite for setting up any of the publishing methods that rely on GitHub Actions, because quarto
publish
creates a _publish.yml
file in the root of your project that is required for publishing via GitHub Actions.
GitHub Actions
In addition to the steps described above, G3 setup requires that we create a .github/workflows
directory and add a YAML file to that directory. Example 7 contains the gh-pages.yml
file I use for my own site and the shell code that can used to obtain this file.
Example 7
mkdir -p .github/workflows
cd .github/workflows
curl -O https://raw.githubusercontent.com/NishantBharali/Blog/main/.github/workflows/gh-pages.yml
The gh-pages.yml
file in Example 7 installs Quarto, the R and Python programming languages, and the packages in the renv.lock
and requirements.txt
files. If you do not need R and/or Python, you can remove any unnecessary portions of the file.
To modify the Python files that are installed by GitHub Actions, you can edit the requirements.txt
file in your repo. To update your renv.lock
file so that it includes all of the R packages your site requires, run renv::snapshot()
in an R session or Rscript
-e
renv::snapshot()
in a shell.
After pushing the gh-pages.yml
file, you can visit the Actions tab in your remote repo on GitHub to check the progress of the deployment of your site. If your site did not build successfully, you can go through the logs to try to diagnose the problem.
I added a âPush to GitLabâ step to my gh-pages.yml
to make GitHub Actions push my remote gh-pages
to GitLab so that my site is kept in sync on both GitHub and GitLab Pages. This required manually creating a token on GitLab and adding it to GitHub, which I accomplished using the GitHub CLI as shown in Example 8.
Example 8
gh secret set GITLAB_AUTH_TOKEN
After the setup described above, I now have two options for publishing my Quarto site: quarto
publish
and 2) git push
. In addition to GitHub and GitLab Pages, both of these options automatically update my site on Netlify via N2.
To also automatically update my site on Quarto Pub, I created a separate GitHub Actions workflow by adding another YAML file to the .github/workflows
directory in my repo. Example 9 shows my quarto-pub.yml
file and the shell code that can be used to obtain it.
Example 9
mkdir -p .github/workflows
cd .github/workflows
curl -O https://raw.githubusercontent.com/NishantBharali/Blog/main/.github/workflows/gh-pages.yml
My quarto-pub.yml
file is based on Q2, but it runs upon completion of the pages-build-deployment
workflow instead of a push to main
. I changed the workflow trigger so that it runs after my GitHub Pages site is built, regardless of whether I triggered the build by running quarto
publish
gh-pages
or pushing to main
.
Figure 2 summarizes all of the steps that occur during my Quarto publishing workflow. This workflow allows me to publish my site on four web hosts every time I run quarto
publish
gh-pages
(G2) or git
push
(G3)!
flowchart LR A[local<br/>main]-.G2.->B[local<br/>gh-pages]-.G2.->C[GitHub<br/>gh-pages] B[local<br/>gh-pages]-.G2.->D[GitLab<br/>gh-pages] A[local<br/>main]-.G3.->F[GitHub<br/>main]-.G3.->C[GitHub<br/>gh-pages]-.G3.->D[GitLab<br/>gh-pages] A[local<br/>main]-.G3.->G[GitLab<br/>main] C[GitHub<br/>gh-pages]-.N2.->E[Netlify] C[GitHub<br/>gh-pages]-.Q2.->H[Quarto<br/>Pub]
So far I have only noticed one difference between the four web hosts I use for my site: GitHub Pages is the only web host that properly differentiates between internal and external links. All of the other web hosts include the external link icon on all links regardless of whether they target my site or an external site.
I tried unsuccessfully to solve this issue by setting the link-external-filter
property to a regular expression. If you notice a problem with the link-external-icon
feature on other web hosts, I suggest switching to GitHub Pages.
Customizing Quarto sites
HTML blocks
My navbar also provides the current date and time in Dec
ordinal (deco
) format, which counts the years since 1 BC and the days since March 1. Dec
is a calendar and time system that I created and use throughout my blog.
Mine Ăetinkaya-Rundel wrote about HTML blocks as part of her A Quarto tip a day project.
HTML blocks are useful for running JavaScript code in the context of a single page on your site without affecting the others. I also use HTML blocks to add a <style>
element to my .qmd
files as a last ditch effort to fine tune the style of my site.
Before I resort to the <style>
element approach, I try writing CSS in my styles.css
, light.css
, and dark.css
files. So far, this approach has been sufficient to make any styling changes I want, but if it every fails, I can use JavaScript to override the default styling provided by Quarto by modifying style
attributes, which have the highest specificity in CSS.
Pandoc filter
In addition to customizing dates on the listing page of my blog, I wanted to customize the date format in every blog post. To complete this task, I used a Lua script as a Pandoc filter.
Pandoc is a program that converts documents into practically any format. The âpanâ in Pandoc comes from the Ancient Greek word for all. Pandoc strives to convert all document formats, just like Pangea contained all the land and a panacea solves all problems.
Quarto uses Pandoc to convert markdown files into target format(s) like html
or pdf
. If the source files contain executable code, Quarto executes the code via one of two computational engines: Jupyter or Knitr. Figure 3 shows the Quarto workflow.
flowchart LR A[qmd<br/>ipynb]-.Knitr<br/>Jupyter.->B((md))-.Pandoc with<br/>Lua filters.->C(html<br/>pdf<br/>docx<br/>etc.)
Mine Ăetinkaya-Rundelâs Quarto tip series includes a similar Quarto workflow mermaid diagram and her âHello, Quarto!â rstudio::conf(2022) keynote with Julia Stewart Lowndes contains truly beautiful Quarto workflow images by Allison Horst: 1, 2, 3, and 4.
Quarto controls Pandoc, Jupyter, and Knitr in two ways: 1) with arguments passed to the Quarto CLI commands and 2) with YAML key-value pairs in .qmd
, .ipynb
, or .yml
files.
HTML blocks can run JavaScript which excels at making content dynamic and interactive, pre- and post-render scripts can be in any programming language, while Pandoc filters are written in Lua and modify output during rendering.