Building a full package website with Quarto

Overview

pkgsite is primarily designed to convert your package’s reference into .qmd files. However, with a few manual steps it is possible to use it as the foundation for a complete package website. For the vast majority of R packages, pkgdown remains the recommended choice, but if you have a specific reason to use Quarto, this article walks through how to set things up.

What you need

A minimal pkgsite-powered Quarto website consists of:

  1. A homepage (index.qmd) that wraps your README.md.
  2. A news page (changelog.qmd) that wraps your NEWS.md.
  3. Reference pages generated by write_reference().
  4. Auto-linking via downlit (optional).
  5. A _quarto.yml that ties everything together.

The sections below walk through each one.

Homepage and news

Most R packages already maintain a README.md and a NEWS.md. Rather than duplicating that content into new Quarto files, pkgsite takes the approach of wrapping them with thin .qmd files that use Quarto’s include shortcode:

---
toc: true
---

{{< include README.md >}}
---
toc: true
---

{{< include NEWS.md >}}

The include shortcode embeds the Markdown verbatim at render time, so README.md and NEWS.md remain the single source of truth. The .qmd wrappers themselves stay static: you only touch them when making a layout change, such as adding a front-matter option or inserting a custom section, not when updating content.

Because NEWS.md uses standard Markdown headings for each version, Quarto automatically builds a table of contents from them, making it easy for users to jump to a specific release.

Note

The news wrapper is called changelog.qmd rather than news.qmd. If it were named news.qmd, Quarto and R’s documentation tooling would both try to produce a news.html output file. One process deletes it before the other can finish, causing a silent collision. A different name sidesteps the issue entirely.

Reference pages

The reference pages are generated from your package’s roxygen2 documentation by calling write_reference():

pkgsite::write_reference()

This reads the .Rd files in your package’s man/ directory and writes one .qmd file per exported topic into the reference/ directory, along with a reference/index.qmd that lists them all. You only need to re-run this when you add, remove, or rename exported functions. Between those changes, the existing files stay in _freeze/ and Quarto assembles them without re-executing anything.

When to clear the freeze cache

Quarto only re-executes a .qmd file when its source text changes. Two common situations will silently produce stale output even after a normal quarto render:

  • A function’s example output changed. You fixed a bug and the printed result is now different, but the .qmd source is identical, so Quarto considers it up to date.
  • You changed a _quarto.yml setting that affects execution, such as eval or freeze.

In both cases, delete the frozen output and re-render locally:

rm -rf _freeze/reference
quarto render

If reference.dir is set to a custom path in _quarto.yml, replace reference with that path. Commit the updated _freeze/ folder afterwards so GitHub picks up the new output.

Auto-linking functions (optional)

downlit scans rendered HTML files and turns bare function names into hyperlinks that point at their documentation pages. It runs as a post-render script after every quarto render and requires no changes to your .qmd source files. The site works perfectly without it, but the links make it considerably easier to navigate.

The full script, setup instructions, and the corresponding GitHub Actions workflow are covered in the GitHub Pages article.

Quarto project setup

All configuration lives in a single _quarto.yml file at the root of your project. This file does double duty: it tells Quarto how to build the site, and it tells pkgsite where to find your package source and where to write the reference pages. Now that the individual pieces above are familiar, here is how they all come together in the _quarto.yml from this repository:

project:
  type: website
  output-dir: docs
  post-render: _post-render.R

execute:
  freeze: true
  eval: true

website:
  title: mypkg
  site-url: https://myorg.github.io/mypkg
  navbar:
    left:
      - sidebar:articles
      - href: reference/index.qmd
        text: Reference
      - href: changelog.qmd
        text: News
    right:
      - icon: github
        href: https://github.com/myorg/mypkg

pkgsite:
  dir: "."
  reference:
    dir: reference
    index:
      file: index.qmd

A few things worth calling out:

  • output-dir: docs matches the GitHub Pages convention of serving from the docs/ folder on main, which keeps deployment simple.
  • freeze: true under execute is the core of the whole approach. Quarto stores each file’s output in a _freeze/ folder and only re-executes a file when its source changes. You render locally (where you have access to your database, LLM, or other local resources), commit _freeze/, and GitHub can rebuild the site without re-running a single line of R.
  • The pkgsite: block is where you configure pkgsite specifically. dir is the path to the package root (usually ".") and reference.dir is where write_reference() will write the generated .qmd files.
  • post-render: _post-render.R wires up the optional function auto-linking covered in the previous section.

Rendering and publishing

With all of the above in place, a full local render is:

quarto render

Quarto executes any unfrozen .qmd files, writes HTML to docs/, and runs the post-render script if one is configured. Commit the result, including _freeze/ and docs/, and your site is ready. For how to automate this with GitHub Actions, see the GitHub Pages article.

Long-form articles

One additional option that a Quarto site opens up is publishing long-form documentation as regular .qmd files in an articles/ folder, rather than as package vignettes. That is the approach taken in the pkgsite project. Quarto offers more flexibility in both design and publication than the vignette format, and it imposes fewer constraints on how the content is structured or rendered. Articles added to articles/ can be wired into the site navbar or sidebar through _quarto.yml like any other page.