Made a website using Quarto
Well, I made another website. You’re looking at it right now. While I’ve fumbled around with the good old HTML/CSS/JavaScript combo before, I’ll openly admit that I’m no expert in webdesign. This time around, I’m using Quarto. As you an see, I’ve also added a blog and I’ve registered my own domain name. If you’re interested, let me walk you through how this thing came to be.
Previous Attempts at Web Design
As I might have mentioned, I used to be a full time mathematician at various universities and research intistutes. Whenever I moved from one job to another, I set up a personal homepage (most recently this one). But as a working mathematician, a flashy homepage is not your main priority. In fact, some esteemed professors seem to take pride in their super-simple-no-frills-plain-html pages that scream: “I don’t care! Because I don’t need to care!” Well, I always cared a little bit and tried to make mine look halfway decent. But I never pushed things very far. I wasn’t very good at maintining my websites, either. Making edits was always painful - I just don’t enjoy writing HTML - and this resulted in me only updating the content every once in a blue moon.
Enter Quarto
Now that I want to be taken seriously as an IT person, I figured that I should try a little harder. So I set out to create another homepage. I wanted to have a portfolio homepage which not only looks halfway decent, but is also easy to maintain. In addition, I wanted to have a blogging feature that could easily integrate Jupyter Notebooks. After a couple of conversations with ChatGPT, I settled on Quarto as a framework.
Making it Happen
Creating a Website with a Blog
Setting up a website in Quarto is easy. If you’re also using VS Code, just install the Quarto software and the VS Code extension on your machine, and follow these instructions from the Quarto documentation.
In a nutshel, a Quarto homepage appears to you as its author as a folder structure mostly consisting of plain text files with the extention .qmd
which is short for “Quarto markdown”. More on that later. In addition, there is an all-important file calle _quarto.yml
which instructs the Quarto software how to render the .qmd
files into HTML. Here’s what this looks like for this website (at the time of writing):
_quarto.yml
project:
type: website
preview:
port: 2025
website:
title: "Stefan Behrens | Mathematician | Deep Learner"
navbar:
right:
- href: index.qmd
text: Home
- href: cv.qmd
text: CV
- href: math.qmd
text: Math/Research
- href: projects.qmd
text: Code/Projects
- href: blog.qmd
text: Blog
draft-mode: gone
format:
html:
theme:
- flatly
css: styles.css
toc: true
max-width: 800px
highlight-style: tango
embed-resources: false
include-in-header: _tools/sb4dlatex.html
I’m not going to go into any detail, all I want to say is that this doesn’t look too complicated.
Adding a blog
Adding a blog is just as easy and is explained here. You just have to create a subdirectory for blog posts, say posts/
, and .qmd
file containing something like this:
blog.qmd
---
listing:
contents: posts
sort: "date desc"
type: default
categories: true
sort-ui: false
filter-ui: false
page-layout: full
title-block-banner: true
---
That’s it. These instructiosn (written in YAML again) tell Quarto everything it needs to know to render a blog page with posts correspondin to files in the posts/
directory. These can be either .qmd
files or Jupyter Notebooks. More on adding content later.
Styling with CSS
As I mentioned, while Quarto pages are not written in HTML, they are ultimately rendered as HTML. As you can see, my _quarto.yml
references a file styles.css
. This used to style the rendered HTML pages, so that you can work your CSS magic on your homepage. In my case, this isn’t much:
stylels.css
/* css styles */
/* Custom page background */
body {background-image: url("images/bg_main.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
/* CV stuff */
.cv-entry {
display: block;
}.cv-entry summary {
color: black;
cursor: pointer;
}.cv-entry .title{
font-size: large;
font-weight: bold;
}.cv-entry .titleinfo{
font-size: large;
}.cv-entry .subtitle{
font-style: italic;
margin-top: 0.1em;
}.cv-entry .date{
float: right;
}.cv-entry .details {
background-color: #f3f7ff;
border-radius: 10px;
margin: 0.5em 0 0 1em;
}.cv-entry details p {
margin: 0 0 0 1em;
padding: 0;
}
Essentially, I’m only adding a background image and a few custom styles for the CV page. But I’ll get to that.
Adding Content
Alright, enough about the structure. Let’s talk content.
Quarto Markdown
One of the selling points for me was that I don’t have to write pages in HTML. I much prefer writing *this*
over writing <b>that</b>
. Maybe it’s related to the German keyboard layout, but I’ve alwasy found HTML extra awkward to type. As for the Quarto markdown syntax, it’s bascially the same as the familiar markdown from GitHub and Jupyter Notebooks. It’s cerainly less flexible that HTML, but it gets the job done for basic texts. It’s also possible to use LaTeX, just as you would in Jupyter Notebooks.
Using HMTL in Quarto pages
If Quarto markdown is not flexible enough for you, the good news is that Quarto allows you to use HTML directly in the .qmd
files. For the most part, you can just write it straight into the file. However, for more complicated constructs, you might have to wrap it into a raw HTML code block like this:
```{=html}<div class="cv-entry">
<details>
<summary>
<span class="title">Great Job</span>
<span class="date">2024 — Present</span>
<p class="subtitle">Prestigious Company — Somewhere Fancy</p>
</summary>
<div class="details">
<ul>
<li>Awesome thing I did.</li>
<li>More greatness.</li>
</ul>
</div>
</details>
</div>
```
Naively, I expected this to work without the triple backticks. But for some reason, Quarto didn’t want to render this properly. The content wrapped in <div class="cv-details">...</div>
was displayed verbatim.
After a frustrating conversation with ChatGPT, I went the classical RTFM way and found this. Turns out you have to explicitly announce to quarto that raw HTML is coming.
Jupyter Notebooks and Blog Posts
While choosing a blogging platform, my main requirement was that I can recycle my already existing Jupyter Notebooks as blog posts with as little hassle as possible. This is where Quarto really shines. As explained here, you just have to make sure to that the first cell in your .ipynb
file is “raw” and contains a suitable YAML front matter such as:
---
title: "Made a new website using Quarto"
author: "Stefan Behrens"
date: "8/3/2025"
categories:
- Webdesign
- Quarto
- CSS
---
With this in place, just copy the .ipynb
into your blog’s posts folder and you’re good to go. Occassionally, you might have to run the entire notebook and save it.
Alternatively, you can write blog posts as Quarto markdown .qmd
files. They also need a YAML front matter as above.
Using LaTeX in Quarto
As mentioned, Quarto markdown has native LaTeX support. By default, it uses MathJax but it’s possible to use other rendering engines such as KaTeX. Since I’ve been using LaTeX for many years to write mathematical texts, I’ve gotten used to a couple of custom commands. Luckily, there are ways to use custom commands in Quarto pages. I’ve written more about this in another post.
Getting it Online
Right, now that the website has everything it needs, it’s time to get it online. There are several ways to do so, both free and paid. After doing some research, I opted for the free option offered by GitHub.
Hosting on GitHub Pages
GitHub offers a service called Pages which can be used to host websites within a repository. At the time of writing, it is even available with the free plan, albeit with a caveat. It works roughly like this. Say you have a repository my-repo
with a directory repo_site/
containing data for a website. You can then instruct GitHub Pages to make that folder available under https://username.github.io/my-repo
. However, there are some limitations:
- As far as I can tell, the websites have to be static. Maybe you can play some tricks to get dynamic content going, but I don’t know.
- Important: If you’re on GitHub’s free plan, the repository must be public from here onward!
Method 1: Render Locally, Upload Everything
Assuming that you have the Quarto engine running locally, the easiest way to the get your website published on GitHub Pages.
- Create a GitHub account if you don’t have one, yet.
- Get everything ready locally:
- Add this to your
_quarto.yml
:
project: # whatever you already had in this section output-dir: docs
- Make sure you have a
.gitignore
file containing the line/.quarto/
- Render the project. The pages is now rendered to the subdirectory
docs/
. Delete the previous output directory (the default is_site/
). - Clean up your project folder. Remove everything you don’t want anyone to see.
- Add this to your
- Publish the project folder as a GitHub repository, e.g.
my-quarto-site
.- Again, if you’re on the free plan, the repository needs to be public!
- Activate GitHub Pages:
- Log into your GitHub Account, navigate to the website repository.
- Go the setting tab and navigate to the “Pages” register
- Make the following settings:
- Build and Deplyment / Source -> Deploy from a branch
- Build and Deplyment / Branch -> Choose branch (most likely
main
) - Build and Deplyment / Branch -> Select folder
docs/
- Build and Deplyment / Branch -> Click “Save”
- Check if your page is online:
- Navigate to
githubusername.github.io/my-quarto-site
. Your page should appear.
- Navigate to
Method 2: Upload Sources, Render Using GitHub Actions
There’s another way to do things that doesn’t require you to render your Quarto site locally. You can have it rendered on GitHub’s servers instead. However, the setup is a little more involved. Since I’m fine rendering locally, I haven’t tried this, yet.
Anyway, here’s what ChatGPT told me about the process. Again, I haven’t tried it! I’m posting it here partly to remind myself that I wanted to try his eventually.
Alright — here’s a ready-to-use GitHub Actions workflow that will build and deploy your Quarto site automatically to > GitHub Pages.
You just need to:
- Put this file in your repo at
.github/workflows/publish.yml
.- Push it to GitHub.
- Enable Pages to serve from the
gh-pages
branch (instead of/docs
).
.github/workflows/publish.yml
name: Publish Quarto site to GitHub Pages on: push: branches: [main] # Build on every push to main branch workflow_dispatch: # Allow manual trigger from Actions tab jobs: build-deploy: runs-on: ubuntu-latest permissions: contents: write # Needed for pushing to gh-pages branch pages: write id-token: write steps: - name: Check out repository uses: actions/checkout@v4 - name: Set up Quarto uses: quarto-dev/quarto-actions/setup@v2 with: version: "latest" - name: Set up Pandoc (optional if Quarto needs it) uses: pandoc/actions/setup@v1 - name: Install LaTeX (optional, only if you render PDFs) run: sudo apt-get install -y texlive-latex-base - name: Render Quarto project run: quarto render - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./_site # This is Quarto's default output dir
How to set it up
Remove
output-dir: docs
from_quarto.yml
(so Quarto builds to_site
).Commit this
publish.yml
workflow to.github/workflows/
.In GitHub → Settings → Pages:
- Source → Select
Deploy from a branch
.- Branch →
gh-pages
(created by the workflow after first run).Push your changes to
main
.Wait for the Actions workflow to finish — your site will appear at:
https://<your-username>.github.io/<your-repo>/
That’s all for now. Once this is online, I’m planning to redirect this page to a domain which I’ve already registered. I’ll update this post.