19  Website

At this point, we’ve discussed many ways to document your package:

Wouldn’t it be divine if all of that somehow got bundled up together into a beautiful website for your package? The pkgdown package is meant to provide exactly this magic and that is the topic of this chapter.

19.1 Initiate a site

Assuming your package has a valid structure, pkgdown should be able to make a website for it. Obviously that website will be more substantial if your package has more of the documentation elements listed above. But something reasonable should happen for any valid R package.

Tip

We hear that some folks put off “learning pkgdown”, because they think it’s going to be a lot of work. But then they eventually execute the two commands we show next and have a decent website in less than five minutes!

usethis::use_pkgdown() is a function you run once and it does the initial, minimal setup necessary to start using pkgdown:

usethis::use_pkgdown()
#> ✔ Setting active project to "/tmp/RtmpEUap9F/mypackage".
#> ✔ Adding "^_pkgdown\\.yml$", "^docs$", and "^pkgdown$" to
#>   '.Rbuildignore'.
#> ✔ Adding "docs" to '.gitignore'.
#> ✔ Writing '_pkgdown.yml'.
#> ☐ Edit '_pkgdown.yml'.
#> ✔ Setting active project to "<no active project>".

Here’s what use_pkgdown() does:

  • Creates _pkgdown.yml, which is the main configuration file for pkgdown. In an interactive session, _pkgdown.yml will be opened for inspection and editing. But there’s no immediate need to change or add anything here.

  • Adds various patterns to .Rbuildignore, to keep pkgdown-specific files and directories from being included in your package bundle.

  • Adds docs, the default destination for a rendered site, to .gitignore. This is harmless for those who don’t use Git. For those who do, this opts you in to our recommended lifestyle, where the definitive source for your pkgdown site is built and deployed elsewhere (probably via GitHub Actions and Pages; more on this soon). This means the rendered website at docs/ just serves as a local preview.

pkgdown::build_site() is a function you’ll call repeatedly, to re-render your site locally. In an extremely barebones package, you’ll see something like this:

pkgdown::build_site()
#> ✔ Setting active project to "/tmp/RtmpEUap9F/mypackage".
#> ── Installing package mypackage into temporary library ─────────────
#> ── Initialising site ───────────────────────────────────────────────────────────
#> Copying <pkgdown>/BS5/assets/katex-auto.js to katex-auto.js
#> Copying <pkgdown>/BS5/assets/lightswitch.js to lightswitch.js
#> Copying <pkgdown>/BS5/assets/link.svg to link.svg
#> Copying <pkgdown>/BS5/assets/pkgdown.js to pkgdown.js
#> Updating deps/bootstrap-5.3.1/bootstrap.bundle.min.js
#> Updating deps/bootstrap-5.3.1/bootstrap.bundle.min.js.map
#> Updating deps/bootstrap-5.3.1/bootstrap.min.css
#> Updating deps/bootstrap-toc-1.0.1/bootstrap-toc.min.js
#> Updating deps/clipboard.js-2.0.11/clipboard.min.js
#> Updating deps/font-awesome-6.5.2/css/all.css
#> Updating deps/font-awesome-6.5.2/css/all.min.css
#> Updating deps/font-awesome-6.5.2/css/v4-shims.css
#> Updating deps/font-awesome-6.5.2/css/v4-shims.min.css
#> Updating deps/font-awesome-6.5.2/webfonts/fa-brands-400.ttf
#> Updating deps/font-awesome-6.5.2/webfonts/fa-brands-400.woff2
#> Updating deps/font-awesome-6.5.2/webfonts/fa-regular-400.ttf
#> Updating deps/font-awesome-6.5.2/webfonts/fa-regular-400.woff2
#> Updating deps/font-awesome-6.5.2/webfonts/fa-solid-900.ttf
#> Updating deps/font-awesome-6.5.2/webfonts/fa-solid-900.woff2
#> Updating deps/font-awesome-6.5.2/webfonts/fa-v4compatibility.ttf
#> Updating deps/font-awesome-6.5.2/webfonts/fa-v4compatibility.woff2
#> Updating deps/headroom-0.11.0/headroom.min.js
#> Updating deps/headroom-0.11.0/jQuery.headroom.min.js
#> Updating deps/jquery-3.6.0/jquery-3.6.0.js
#> Updating deps/jquery-3.6.0/jquery-3.6.0.min.js
#> Updating deps/jquery-3.6.0/jquery-3.6.0.min.map
#> Updating deps/search-1.0.0/autocomplete.jquery.min.js
#> Updating deps/search-1.0.0/fuse.min.js
#> Updating deps/search-1.0.0/mark.min.js
#> ── Building pkgdown site for package mypackage ─────────────────────────────────
#> Reading from: /tmp/RtmpEUap9F/mypackage
#> Writing to: /tmp/RtmpEUap9F/mypackage/docs
#> ── Sitrep ──────────────────────────────────────────────────────────────────────
#> ✖ URLs not ok.
#>   In _pkgdown.yml, url is missing.
#>   See details in `vignette(pkgdown::metadata)`.
#> ✔ Favicons ok.
#> ✔ Open graph metadata ok.
#> ✔ Articles metadata ok.
#> ✔ Reference metadata ok.
#> ── Initialising site ───────────────────────────────────────────────────────────
#> ── Building home ───────────────────────────────────────────────────────────────
#> Writing `authors.html`
#> Reading 'DESCRIPTION'
#> Writing `index.html`
#> Writing `404.html`
#> ── Building function reference ─────────────────────────────────────────────────
#> Writing `reference/index.html`
#> ── Building sitemap ────────────────────────────────────────────────────────────
#> Writing `sitemap.xml`
#> ── Building search index ───────────────────────────────────────────────────────
#> ── Checking for problems ───────────────────────────────────────────────────────
#> ── Finished building pkgdown site for package mypackage ────────────────────────
#> ── Finished building pkgdown site for package mypackage ────────────
#> ✔ Setting active project to "<no active project>".

In an interactive session your newly rendered site should appear in your default web browser.

RStudio

Another nice gesture to build your site is via Addins > pkgdown > Build pkgdown.

You can look in the local docs/ directory to see the files that constitute your package’s website. To manually browse the site, open docs/index.html in your preferred browser.

This is almost all you truly need to know about pkgdown. It’s certainly a great start and, as your package and ambitions grow, the best place to learn more is the pkgdown-made website for the pkgdown package itself: https://pkgdown.r-lib.org.

19.2 Deployment

Your next task is to deploy your pkgdown site somewhere on the web, so that your users can visit it. The path of least resistance looks like this:

  • Use Git and host your package on GitHub. The reasons to do this go well beyond offering a package website, but this will be one of the major benefits to adopting Git and GitHub, if you’re on the fence.

  • Use GitHub Actions (GHA) to build your website, i.e. to run pkgdown::build_site(). GHA is a platform where you can configure certain actions to happen automatically when some event happens. We’ll use it to rebuild your website every time you push to GitHub.

  • Use GitHub Pages to serve your website, i.e. the files you see below docs/ locally. GitHub Pages is a static website hosting service that creates a site from files found in a GitHub repo.

The advice to use GitHub Action and Pages are implemented for you in the function usethis::use_pkgdown_github_pages(). It’s not an especially difficult task, but there are several steps and it would be easy to miss or flub one. The output of use_pkgdown_github_pages() should look something like this:

usethis::use_pkgdown_github_pages()
#> ✔ Initializing empty, orphan 'gh-pages' branch in GitHub repo 'jane/mypackage'
#> ✔ GitHub Pages is publishing from:
#> • URL: 'https://jane.github.io/mypackage/'
#> • Branch: 'gh-pages'
#> • Path: '/'
#> ✔ Creating '.github/'
#> ✔ Adding '^\\.github$' to '.Rbuildignore'
#> ✔ Adding '*.html' to '.github/.gitignore'
#> ✔ Creating '.github/workflows/'
#> ✔ Saving 'r-lib/actions/examples/pkgdown.yaml@v2' to '.github/workflows/pkgdown.yaml'
#> • Learn more at <https://github.com/r-lib/actions/blob/v2/examples/README.md>.
#> ✔ Recording 'https://jane.github.io/mypackage/' as site's url in '_pkgdown.yml'
#> ✔ Adding 'https://jane.github.io/mypackage/' to URL field in DESCRIPTION
#> ✔ Setting 'https:/jane.github.io/mypackage/' as homepage of GitHub repo 'jane/mypackage'

Like use_pkgdown(), this is a function you basically call once, when setting up a new site. In fact, the first thing it does is to call use_pkgdown() (it’s OK if you’ve already called use_pkgdown()), so we usually skip straight to use_pkgdown_github_pages() when setting up a new site.

Let’s walk through what use_pkgdown_github_pages() actually does:

  • Initializes an empty, “orphan” branch in your GitHub repo, named gh-pages (for “GitHub Pages”). The gh-pages branch will only live on GitHub (there’s no reason to fetch it to your local computer) and it represents a separate, parallel universe from your actual package source. The only files tracked in gh-pages are those that constitute your package’s website (the files that you see locally below docs/).

  • Turns on GitHub Pages for your repo and tells it to serve a website from the files found in the gh-pages branch.

  • Copies the configuration file for a GHA workflow that does pkgdown “build and deploy”. The file shows up in your package as .github/workflows/pkgdown.yaml. If necessary, some related additions are made to .gitignore and .Rbuildignore.

  • Adds the URL for your site as the homepage for your GitHub repo.

  • Adds the URL for your site to DESCRIPTION and _pkgdown.yml. The autolinking behaviour we’ve touted elsewhere relies on your package listing its URL in these two places, so this is a high-value piece of configuration.

After successful execution of use_pkgdown_github_pages(), you should be able to visit your new site at the URL displayed in the output above.1 By default the URL has this general form: https://USERNAME.github.io/REPONAME/.

19.3 Now what?

For a typical package, you could stop here — after creating a basic pkgdown site and arranging for it to be re-built and deployed regularly — and people using (or considering using) your package would benefit greatly. Everything beyond this point is a “nice to have”.

Overall, we recommend vignette("pkgdown", package = "pkgdown") as a good place to start, if you think you want to go beyond the basic defaults.

In the sections below, we highlight a few areas that are connected to other topics in the book or customizations that are particularly rewarding.

19.5 Reference index

pkgdown creates a function reference in reference/ that includes one page for each .Rd help topic in man/. This is one of the first pages you should admire in your new site. As you look around, there are a few things to contemplate, which we review below.

19.5.1 Rendered examples

pkgdown executes all your examples (Section 16.5) and inserts the rendered results. We find this is a fantastic improvement over just showing the source code. This view of your examples can be eye-opening and often you’ll notice things you want to add, omit, or change. If you’re not satisfied with how your examples appear, this is a good time to review techniques for including code that is expected to error (Section 16.5.3) or that can only be executed under certain conditions (Section 16.5.4).

19.5.2 Linking

These help topics will be linked to from many locations within and, potentially, beyond your pkgdown site. This is what we are talking about in Section 16.1.3 when we recommend putting functions inside square brackets when mentioning them in a roxygen comment:

#' I am a big fan of [thisfunction()] in my package. I
#' also have something to say about [otherpkg::otherfunction()]
#' in somebody else's package.

On pkgdown sites, those square-bracketed functions become hyperlinks to the relevant pages in your pkgdown site. This is automatic within your package. But inbound links from other people’s packages (and websites, etc.) require two things2:

  • The URL field of your DESCRIPTION file must include the URL of your pkgdown site (preferably followed by the URL of your GitHub repo):

    URL: https://dplyr.tidyverse.org, https://github.com/tidyverse/dplyr
  • Your _pkgdown.yml file must include the URL for your site:

    url: https://dplyr.tidyverse.org

devtools takes every chance it gets to do this sort of configuration for you. But if you elect to do things manually, this is something you might overlook. A general resource on auto-linking in pkgdown is vignette("linking", package = "pkgdown").

19.5.3 Index organization

By default, the reference index is just an alphabetically-ordered list of functions. For packages with more than a handful of functions, it’s often worthwhile to curate the index and organize the functions into groups. For example, dplyr uses this technique: https://dplyr.tidyverse.org/reference/index.html.

You achieve this by providing a reference field in _pkgdown.yml. Here’s a redacted excerpt from dplyr’s _pkgdown.yml file that gives you a sense of what’s involved:

reference:
- title: Data frame verbs

- subtitle: Rows
  desc: >
    Verbs that principally operate on rows.
  contents:
  - arrange
  - distinct
  ...

- subtitle: Columns
  desc: >
    Verbs that principally operate on columns.
  contents:
  - glimpse
  - mutate
  ...

- title: Vector functions
  desc: >
    Unlike other dplyr functions, these functions work on individual vectors,
    not data frames.
  contents:
  - between
  - case_match
  ...

- title: Built in datasets
  contents:
  - band_members
  - starwars
  - storms
  ...

- title: Superseded
  desc: >
    Superseded functions have been replaced by new approaches that we believe
    to be superior, but we don't want to force you to change until you're
    ready, so the existing functions will stay around for several years.
  contents:
  - sample_frac
  - top_n
  ...

To learn more, see ?pkgdown::build_reference.

19.6 Vignettes and articles

Chapter 17 deals with vignettes, which are long-form guides for a package. They afford various opportunities beyond what’s possible in function documentation. For example, you have much more control over the integration of prose and code and over the presentation of code itself, e.g. code can be executed but not seen, seen but not executed, and so on. It’s much easier to create the reading experience that best prepares your users for authentic usage of your package.

A package’s vignettes appear, in rendered form, in its website, in the Articles dropdown menu. “Vignette” feels like a technical term that we might not expect all R users to know, which is why pkgdown uses the term “articles” here. To be clear, the Articles menu lists your package’s official vignettes (the ones that are included in your package bundle) and, optionally, other non-vignette articles (Section 17.4.1), which are only available on the website.

19.6.1 Linking

Like function documentation, vignettes can also be the target of automatic inbound links from within your package and, potentially, beyond. We’ve talked about this elsewhere in the book. In Section 16.1.3, we introduced the idea of referring to a vignette with an inline call like vignette("some-topic"). The rationale behind this syntax is because the code can literally be copied, pasted, and executed for local vignette viewing. So it “works” in any context, even without automatic links. But, in contexts where the auto-linking machinery is available, it knows to look for this exact syntax and turn it into a hyperlink to the associated vignette, within a pkgdown site.

The need to specify the host package depends on the context:

  • vignette("some-topic"): Use this form in your own roxygen comments, vignettes, and articles, to refer to a vignette in your package. The host package is implied.

  • vignette("some-topic", package = "somepackage"): Use this form to refer to a vignette in some other package. The host package must be explicit.

Note that this shorthand does not work for linking to non-vignette articles. Since the syntax leans so heavily on the vignette() function, it would be too confusing, i.e. evaluating the code in the console would fail because R won’t be able to find such a vignette. Non-vignette articles must be linked like any other URL.

When you refer to a function in your package, in your vignettes and articles, make sure to put it inside backticks and to include parentheses. Qualify functions from other packages with their namespace. Here’s an example of prose in one of your own vignettes or articles:

I am a big fan of `thisfunction()` in my package. I also have something to
say about `otherpkg::otherfunction()` in somebody else's package.

Remember that automatic inbound links from other people’s packages (and websites, etc.) require that your package advertises the URL of its website in DESCRIPTION and _pkgdown.yaml, as configured by usethis:: use_pkgdown_github_pages() and as described in Section 19.5.2.

19.6.2 Index organization

As with the reference index, the default listing of the articles (broadly defined) in a package is alphabetical. But if your package has several articles, it can be worthwhile to provide additional organization. For example, you might feature the articles aimed at the typical user and tuck those meant for advanced users or developers behind “More articles …”. You can learn more about this in ?pkgdown::build_articles.

19.6.3 Non-vignette articles

In general, Chapter 17 is our main source of advice on how to approach vignettes and that also includes some coverage of non-vignette articles (Section 17.4.1). Here we review some reasons to use a non-vignette article and give some examples.

An article is morally like a vignette (e.g. it tells a story that involves multiple functions and is written with R markdown), except it does not ship with the package bundle. usethis::use_article() is the easiest way to create an article. The main reason to use an article is when you want to show code that is impossible or very painful to include in a vignette or official example. Possible root causes of this pain:

  • Use of a package you don’t want to formally depend on. In vignettes and examples, it’s forbidden to show your package working with a package that you don’t list in DESCRIPTION, e.g. in Imports or Suggests.

    There is a detailed example of this in Section 11.7.2, featuring a readxl article that uses the tidyverse meta-package. The key idea is to list such a dependency in the Config/Needs/website field of DESCRIPTION. This keeps tidyverse out of readxl’s dependencies, but ensures it’s installed when the website is built.

  • Code that requires authentication or access to specific assets, tools, or secrets that are not available on CRAN.

    The googledrive package has no true vignettes, only non-vignette articles, because it’s essentially impossible to demonstrate usage without authentication. It is possible to access secure environment variables on GitHub Actions, where the pkgdown site is built and deployed, but this is impossible to do on CRAN.

  • Content that involves a lot of figures, which cause your package to bump up against CRAN’s size constraints.

    The ggplot2 package presents several FAQs as articles for this reason.

19.7 Development mode

Every pkgdown site has a so-called development mode, which can be specified via the development field in _pkgdown.yml. If unspecified, the default is mode: release, which results in a single pkgdown site. Despite the name, this single site reflects the state of the current source package, which could be either a released state or a development state. The diagram below shows the evolution of a hypothetical package that is on CRAN and that has a pkgdown site in “release” mode.

...
 |
 V
Tweaks before release     v0.1.9000
 |
 V
Increment version number  v0.2.0     <-- install.packages() gets this
 |
 V
Increment version number  v0.2.9000  
 |
 V
Improve error message     v0.2.9000  <-- site documents this
 |
 V
...

Users who install from CRAN get version 0.2.0. But the pkgdown site is built from the development version of the package.

This creates the possibility that users will read about some new feature on the website that is not present in the package version that they have installed with install.packages(). We find that the simplicity of this setup outweighs the downsides, until a package has a broad user base, i.e. lots of users of varying levels of sophistication. It’s probably safe to stay in “release” mode until you actually hear from a confused user.

Packages with a substantial user base should use “auto” development mode:

development:
  mode: auto

This directs pkgdown to generate a top-level site from the released version and to document the development version in a dev/ subdirectory. We revisit the same hypothetical package as above, but assuming the pkdown site is in “auto” mode.

...
 |
 V
Tweaks before release     v0.1.9000
 |
 V
Increment version number  v0.2.0     <-- install.packages() gets this
 |                                       main site documents this
 V
Increment version number  v0.2.9000  
 |
 V
Improve error message     v0.2.9000  <-- dev/ site documents this
 |
 V
...

All of the core tidyverse packages use “auto” mode. For example, consider the website of the readr package:

Automatic development mode is recommended for packages with a broad user base, because it maximizes the chance that a user will read web-based documentation that reflects the package version that is locally installed.


  1. Sometimes there’s a small delay, so give it up to a couple of minutes to deploy.↩︎

  2. Another pre-requisite is that your package has been released on CRAN, because the auto-linking machinery has to look up the DESCRIPTION somewhere. It is possible to allow locally installed packages to link to each other, which is described in vignette("linking", package = "pkgdown").↩︎