Blogdown
Blogdown
List of Figures ix
Preface xi
1 Get Started 1
1.1 Installation . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Update . . . . . . . . . . . . . . . . . . . . . . 2
1.2 A quick example . . . . . . . . . . . . . . . . . . . . . 2
1.3 RStudio IDE . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Global options . . . . . . . . . . . . . . . . . . . . . . 11
1.5 R Markdown vs. Markdown . . . . . . . . . . . . . . . 13
1.6 Other themes . . . . . . . . . . . . . . . . . . . . . . . 19
1.7 A recommended workflow . . . . . . . . . . . . . . . . 21
2 Hugo 25
2.1 Static sites and Hugo . . . . . . . . . . . . . . . . . . . 25
2.2 Configuration . . . . . . . . . . . . . . . . . . . . . . 27
2.2.1 TOML Syntax . . . . . . . . . . . . . . . . . . . 28
2.2.2 Options . . . . . . . . . . . . . . . . . . . . . . 30
2.3 Content . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.3.1 YAML metadata . . . . . . . . . . . . . . . . . 33
2.3.2 Body . . . . . . . . . . . . . . . . . . . . . . . 34
2.3.3 Shortcode . . . . . . . . . . . . . . . . . . . . 34
2.4 Themes . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.4.1 The default theme . . . . . . . . . . . . . . . . 36
2.5 Templates . . . . . . . . . . . . . . . . . . . . . . . . 40
2.5.1 A minimal example . . . . . . . . . . . . . . . 41
iii
iv Contents
3 Deployment 67
3.1 Netlify . . . . . . . . . . . . . . . . . . . . . . . . . . 68
3.2 Updog . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.3 GitHub Pages . . . . . . . . . . . . . . . . . . . . . . . 71
3.4 Travis + GitHub . . . . . . . . . . . . . . . . . . . . . 74
3.5 GitLab Pages . . . . . . . . . . . . . . . . . . . . . . . 78
4 Migration 81
4.1 From Jekyll . . . . . . . . . . . . . . . . . . . . . . . . 82
4.2 From WordPress . . . . . . . . . . . . . . . . . . . . . 86
4.3 From other systems . . . . . . . . . . . . . . . . . . . 87
5 Other Generators 89
5.1 Jekyll . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.2 Hexo . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.3 Default site generator in rmarkdown . . . . . . . . . . 95
5.4 pkgdown . . . . . . . . . . . . . . . . . . . . . . . . . 97
Appendix 99
A R Markdown 99
Bibliography 147
Index 149
List of Tables
vii
List of Figures
2.1 Possible files and folders created when you create a new
site using blogdown. . . . . . . . . . . . . . . . . . . . 27
2.2 A tweet by Jeff Leek. . . . . . . . . . . . . . . . . . . . . 35
2.3 Edit a text file online on GitHub. . . . . . . . . . . . . . 61
ix
Preface
Carlos’s words resonated very well with me, although they were a little
exaggerated. A well-designed and maintained website can be extremely
helpful for other people to know you, and you do not need to wait for suit-
able chances at conferences or other occasions to introduce yourself in
person to other people. On the other hand, a website is also highly useful
for yourself to keep track of what you have done and thought. Sometimes
you may go back to a certain old post of yours to relearn the tricks or meth-
ods you once mastered in the past but have forgotten.
We introduce an R package, blogdown, in this short book, to teach you
how to create websites using R Markdown and Hugo. If you have expe-
rience with creating websites, you may naturally ask what the benefits
of using R Markdown are, and how blogdown is different from existing
popular website platforms, such as WordPress. There are two major high-
lights of blogdown:
xi
xii Preface
You can host the website on any web server (see Chapter 3 for
details). The website does not require server-side scripts such
as PHP or databases like WordPress does. It is just one folder
of static files. We will explain more benefits of static websites
in Chapter 2, when we introduce the static website generator
Hugo.
2. The website is generated from R Markdown documents (R is op-
tional, i.e., you can use plain Markdown documents without R
code chunks). This brings a huge amount of benefits, especially
if your website is related to data analysis or (R) programming.
Being able to use Markdown implies simplicity and more im-
portantly, portability (e.g., you are giving yourself the chance to
convert your blog posts to PDF and publish to journals or even
books in the future). R Markdown gives you the benefits of dy-
namic documents — all your results, such as tables, graphics,
and inline values, can be computed and rendered dynamically
from R code, hence the results you present on your website are
more likely to be reproducible. An additional yet important ben-
efit of using R Markdown is that you will be able to write tech-
nical documents easily, due to the fact that blogdown inherits
the HTML output format from bookdown (Xie, 2016). For exam-
ple, it is possible to write LaTeX math equations, citations, and
even theorems and proofs if you want.
Please do not be misled by the word “blog” in the package name: blog-
down is for general-purpose websites, and not only for blogs. For exam-
ple, all authors of this book have their personal websites, where you can
find information about their projects, blogs, package documentations,
and so on.2 All their pages are built from blogdown and Hugo.
2
Yihui’s homepage is at https://yihui.org. He writes blog posts in both Chinese
(https://yihui.org/cn/) and English (https://yihui.org/en/), and documents his soft-
ware packages such as knitr (https://yihui.org/knitr/) and animation (https://yihui.
org/animation/). Occasionally he also writes articles like https://yihui.org/rlp/ when
he finds interesting topics but does not bother with a formal journal submission. Am-
ber’s homepage is at https://amber.rbind.io, where you can find her blog and project
pages. Alison’s website is at https://alison.rbind.io, which uses an academic theme at
the moment.
Preface xiii
If you do not prefer using Hugo, there are other options, too. Chapter
5 presents possibilities of using other site generators, such as Jekyll and
rmarkdown’s default site generator.
This book has been published by Chapman & Hall/CRC3 . The online ver-
sion of this book is licensed under the Creative Commons Attribution-
NonCommercial-ShareAlike 4.0 International License4 .
sessionInfo()
We do not add prompts (> and +) to R source code in this book, and we
comment out the text output with two hashes ## by default, as you can
see from the R session information above. This is for your convenience
when you want to copy and run the code (the text output will be ignored
since it is commented out). Package names are in bold text (e.g., rmark-
Preface xv
down), and inline code and filenames are formatted in a typewriter font
(e.g., knitr::knit('foo.Rmd')). Function names are followed by parenthe-
ses (e.g., blogdown::serve_site()). The double-colon operator :: means
accessing an object from a package.
A trailing slash often indicates a directory name, e.g., content/ means
a directory named content instead of a file named content. A lead-
ing slash in a path indicates the root directory of the website, e.g.,
/static/css/style.css means the file static/css/style.css under the
root directory of your website project instead of your operating system.
Please note that some directory names are configurable, such as public/,
but we will use their default values throughout the book. For example,
your website will be rendered to the public/ directory by default, and
when you see public/ in this book, you should think of it as the actual
publishing directory you set if you have changed the default value. Rmd
stands for R Markdown in this book, and it is the filename extension of
R Markdown files.
A “post” often does not literally mean a blog post, but refers to any source
documents (Markdown or R Markdown) in the website project, including
blog posts and normal pages. Typically blog posts are stored under the
content/post/ directory, and pages are under other directories (includ-
ing the root content/ directory and its subdirectories), but Hugo does not
require this structure.
The URL http://www.example.com is used only for illustration purposes.
We do not mean you should actually visit this website. In most cases, you
should replace www.example.com with your actual domain name.
An asterisk * in a character string often means an arbitrary string. For
example, *.example.com denotes an arbitrary subdomain of example.com.
It could be foo.example.com or 123.example.com. Actually, foo and bar also
indicate arbitrary characters or objects.
xvi Preface
Acknowledgments
Originally I planned to write only one sentence in this section: “I thank
Tareef.” This book and the blogdown package would not have been fin-
ished without Tareef, the president of RStudio. He has been “gently nudg-
ing” me every week since Day 1 of blogdown. As a person without strong
self-discipline and working remotely, I benefited a lot from weekly meet-
ings with him. He also gave me a lot of good technical suggestions on
improving the package. Actually, he was one of the very earliest users of
blogdown.
Of course, I’d like to thank RStudio for the wonderful opportunity to work
on this new project. I was even more excited about blogdown than book-
down (my previous project). I started blogging 12 years ago, and have
used and quit several tools for building websites. Finally I feel satisfied
with my own dog food.
Many users have provided helpful feedback and bug reports through
GitHub issues (https://github.com/rstudio/blogdown/issues). Two of
my favorites are https://github.com/rstudio/blogdown/issues/40 and
https://github.com/rstudio/blogdown/issues/97. Some users have also
contributed code and improved this book through pull requests (https:
//github.com/rstudio/blogdown/pulls). You can find the list of con-
tributors at https://github.com/rstudio/blogdown/graphs/contributors.
Many users followed my suggestion to ask questions on StackOverflow
(https://stackoverflow.com/tags/blogdown) instead of using GitHub is-
sues or Emails. I appreciate all your help, patience, and understanding.
I also want to make special mention of my little friend Jerry Han, who
was probably the youngest blogdown user.
For this book, I was fortunate enough to work with my co-authors Am-
ber and Alison, who are exceptionally good at explaining things to begin-
ners. That is the ability I desire most. Needless to say, they have made this
book friendlier to beginners. In addition, Sharon Machlis contributed
some advice on search engine optimization in this book (https://github.
com/rstudio/blogdown/issues/193). Raniere Silva contributed Section 3.5
(https://github.com/rstudio/blogdown/pull/225).
Preface xvii
I’d like to thank all Hugo authors and contributors (Bjørn Erik Pedersen
and Steve Francia et al.) for such a powerful static site generator. At least
it made me enjoy building static websites and blogging again.
For some reason, a part of the R community started to adopt the “sticker-
driven development” model when developing packages. I was hoping
blogdown could have a hexbin sticker, too, so I asked for help on Twit-
ter (https://twitter.com/xieyihui/status/907269861574930432) and got
tons of draft logos. In particular, I want to thank Thomas Lin Pedersen
for his hard work on a very clever design. The final version of the logo was
provided by Taras Kaduk and Angelina Kaduk, and I truly appreciate it.
This is the third book I have published with my editor at Chapman &
Hall/CRC, John Kimmel. I always love working with him. Rebecca Con-
dit and Suzanne Lassandro proofread the manuscript, and I learned a lot
from their comments and professional suggestions.
Yihui Xie
Elkhorn, Nebraska
About the Authors
Yihui is the main developer of the blogdown package. He did not start
working on the systematic documentation (i.e., this book) until four
months after he started the blogdown project. One day, he found a very
nice blogdown tutorial on Twitter written by Amber Thomas. Being sur-
prised that she could create a great personal website using blogdown and
write a tutorial when there was no official documentation, Yihui immediately
invited her to join him to write this book, although they had never met
each other before. This definitely would not have happened if Amber did
not have a website. By the way, Amber asked the very first question5 with
the blogdown tag on StackOverflow.
About half a year later, Yihui noticed another very well-written blogdown
tutorial by Alison on her personal website, when this book was still not
complete. The same story happened, and Alison became the third author
of this book. The three authors have never met each other.
Hopefully, you can better see why you should have a website now.
Yihui Xie
Yihui Xie (https://yihui.org) is a software engineer at RStudio (https://
www.rstudio.com). He earned his PhD from the Department of Statistics,
Iowa State University. He is interested in interactive statistical graphics
and statistical computing. As an active R user, he has authored several R
packages, such as knitr, bookdown, blogdown, xaringan, animation, DT,
tufte, formatR, fun, mime, highr, servr, and Rd2roxygen, among which
the animation package won the 2009 John M. Chambers Statistical Soft-
5
https://stackoverflow.com/q/41176194/559676
xix
xx About the Authors
Amber Thomas
Amber Thomas (https://amber.rbind.io) is a data journalist and “maker”
at the online publication of visual essays: The Pudding (https://pudding.
cool). Her educational background, however, was in quite a different
field altogether: marine biology. She has a bachelor’s degree in marine
biology and chemistry from Roger Williams University and a master’s de-
gree in marine sciences from the University of New England. Throughout
her academic and professional career as a marine biologist, she realized
that she had a love of data analysis, visualization, and storytelling and
thus, she switched career paths to something a bit more data focused.
While looking for work, she began conducting personal projects to ex-
pand her knowledge of R’s inner workings. She decided to put all of her
projects in a single place online (so that she could be discovered, natu-
rally) and after lots of searching, she stumbled upon an early release of
the blogdown package. She was hooked right away and spent a few days
setting up her personal website and writing a tutorial on how she did it.
You can find that tutorial and some of her other projects and musings on
her blogdown site.
When she is not crunching numbers and trying to stay on top of her email
inbox, Amber is usually getting some fresh Seattle air or cuddling with
About the Authors xxi
her dog, Sherlock. If you are looking for her in the digital world, try https:
//twitter.com/ProQuesAsker.
In this chapter, we show how to create a simple website from scratch. The
website will contain a home page, an “About” page, one R Markdown post,
and a plain Markdown post. You will learn the basic concepts for creat-
ing websites with blogdown. For beginners, we recommend that you get
started with the RStudio IDE, but it is not really required. The RStudio
IDE can make a few things easier, but you are free to use any editor if
you do not care about the extra benefits in RStudio.
1.1 Installation
We assume you have already installed R (https://www.r-project.org) (R
Core Team, 2019) and the RStudio IDE (https://www.rstudio.com). If you
do not have RStudio IDE installed, please install Pandoc (http://pandoc.
org). Next we need to install the blogdown package in R. It is available on
CRAN and GitHub, and you can install it with:
1
2 1 Get Started
blogdown::install_hugo()
By default, it installs the latest version of Hugo, but you can choose a spe-
cific version through the version argument if you prefer.
For macOS users, install_hugo() uses the package manager Homebrew
(https://brew.sh) if it has already been installed, otherwise it just down-
loads the Hugo binary directly.
1.1.1 Update
To upgrade or reinstall Hugo, you may use blogdown::update_hugo(),
which is equivalent to install_hugo(force = TRUE). You can check the
installed Hugo version via blogdown::hugo_version(), and find the latest
version of Hugo at https://github.com/gohugoio/hugo/releases.
blogdown::new_site()
Then wait for this function to create a new site, download the default
theme, add some sample posts, open them, build the site, and launch it in
the RStudio Viewer, so you can immediately preview it. If you do not use
1
One day I was almost ready to kill myself when I was trying to figure out how
_index.md works by reading the documentation over and over again, and desperately
searching on the Hugo forum.
1.2 A quick example 3
the RStudio IDE, you need to make sure you are currently in an empty di-
rectory,2 in which case new_site() will do the same thing, but the website
will be launched in your web browser instead of the RStudio Viewer.
Now you should see a bunch of directories and files under the RStudio
project or your current working directory. Before we explain these new
directories and files, let’s introduce an important and helpful technology
first: LiveReload. This means your website3 will be automatically rebuilt
and reloaded in your web browser4 when you modify any source file of
your website and save it. Basically, once you launch the website in a web
browser, you do not need to rebuild it explicitly anymore. All you need to
do is edit the source files, such as R Markdown documents, and save them.
There is no need to click any buttons or run any commands. LiveReload is
implemented via blogdown::serve_site(), which is based on the R pack-
age servr (Xie, 2019c) by default.5
The new_site() function has several arguments, and you may check
out its R help page (?blogdown::new_site) for details. A minimal default
theme named “hugo-lithium” is provided as the default theme of the new
site,6 and you can see what it looks like in Figure 1.1.
You have to know three most basic concepts for a Hugo-based website:
baseurl = "/"
languageCode = "en-us"
title = "A Hugo website"
theme = "hugo-lithium"
[[menu.main]]
name = "About"
url = "/about/"
[[menu.main]]
name = "GitHub"
url = "https://github.com/rstudio/blogdown"
[[menu.main]]
name = "Twitter"
url = "https://twitter.com/rstudio"
You can change the website title, e.g., title = "My own cool
website", and update the GitHub and Twitter URLs.
If you are satisfied with this default theme, you are basically ready to start
writing and publishing your new website! We will show how to use other
themes in Section 1.6. However, please keep in mind that a more com-
plicated and fancier theme may require you to learn more about all the
underlying technologies like the Hugo templating language, HTML, CSS,
and JavaScript.
• “New Post”: This addin provides a dialog box for you to enter the meta-
data of your blog post, including the title, author, date, and so on. See
Figure 1.2 for an example. This addin actually calls the function blog-
down::new_post() under the hood, but does a few things automatically:
– As you type the title of the post, it will generate a filename for you,
and you can edit it if you do not like the automatically generated
one. In fact, you can also use this addin to create normal pages
under any directories under content/. For example, if you want
to add a resume page, you can change the filename to resume.md
from the default post/YYYY-mm-dd-resume.md.
– You can select the date from a calendar widget provided by Shiny.8
– It will scan the categories and tags of existing posts, so when you
want to input categories or tags, you can select them from the
dropdown menus, or create new ones.
– After a new post is created, it will be automatically opened, so you
can start writing the content immediately.
• “Update Metadata”: This addin allows you to update the YAML metadata
of the currently opened post. See Figure 1.3 for an example. The main
advantage of this addin is that you can select categories and tags from
dropdown menus instead of having to remember them.
• “Insert Image”: This addin allows you to insert any image in your com-
puter to your currently opened post.9 Figure 1.4 shows how the addin
looks like. This addin copies the image to the final location for your post
files, and adds the Markdown/HTML code to embed the image. You can
specify the width and height of the image, as well as the alternative text.
The addin will show the final image file path after the image is uploaded.
You can edit the path if necessary. If the image already exists, the addin
will ask you whether you want to overwrite it as in Figure 1.5: when in
8
Shiny is an R package for building interactive web apps using R. Using this addin,
the calendar widget allows you to view an interactive calendar by month to select dates.
This is a simple use of Shiny, but you can read more about Shiny apps here: https://
shiny.rstudio.com.
9
Check https://lcolladotor.github.io/2018/03/07/blogdown-insert-image-addin
for a more in-depth explanation and for how to insert an image without using this
addin.
1.3 RStudio IDE 7
doubt, change the name in the “Target file path” text input to avoid a
conflict with previous images you have inserted.
With these addins, you should rarely need to run any R commands man-
ually after you have set up your website, since all your posts will be auto-
matically compiled whenever you create a new post or modify an existing
post due to the LiveReload feature.
If your RStudio version is at least v1.1.383,10 you can actually create a web-
site project directly from the menu File -> New Project -> New Directory
(see Figure 1.6 and 1.7).
If your website was created using the function blogdown::new_site() in-
stead of the RStudio menu for the first time, you can quit RStudio and
open the project again. If you go to the menu Tools -> Project Options,
your project type should be “Website” like what you can see in Figure 1.8.
Then you will see a pane in RStudio named “Build,” and there is a but-
10
You may download all RStudio official releases including v1.1.383 from https://www.
rstudio.com/products/rstudio/download/.
8 1 Get Started
FIGURE 1.3: Update the metadata of an existing post using the RStudio
addin.
FIGURE 1.4: Insert an image to an existing post using the RStudio addin.
1.3 RStudio IDE 9
FIGURE 1.5: Overwrite the image you are inserting in your blog post.
ton “Build Website.” When you click this button, RStudio will call blog-
down::build_site() to build the website. This will automatically generate
files in the public/ directory.11 If you want to build the website and pub-
lish the output files under the public/ manually, you are recommended to
restart your R session and click this “Build Website” button every time be-
fore you publish the website, instead of publishing the public/ folder gen-
erated continuously and automatically by blogdown::serve_site(), be-
cause the latter calls blogdown::build_site(local = TRUE), which has
some subtle differences with blogdown::build_site(local = FALSE) (see
Section D.3 for details).
We strongly recommend that you uncheck the option “Preview site af-
ter building” in your RStudio project options (Figure 1.8).12 You can
also uncheck the option “Re-knit current preview when supporting files
change,” since this option is not really useful after you call serve_site().
11
Or wherever your publishing directory is located. It is public/ by default, but it can
be changed by specifying the publishDir = "myNewDirectory" in the config.toml file.
12
In case you wonder why: unless you have set the option relativeurls to true in con-
fig.toml, it requires a web server to preview the website locally, otherwise even if you
can see the homepage of your website in the RStudio Viewer, most links like those links
to CSS and JavaScript files are unlikely to work. When the RStudio Viewer shows you
the preview, it does not actually launch a web server.
1.4 Global options 11
We recommend that you set these options in your R startup profile file.
You can check out the help page ?Rprofile for more details, and here is a
simplified introduction. A startup profile file is basically an R script that
is executed when your R session is started. This is a perfect place to set
global options, so you do not need to type these options again every time
you start a new R session. You can use a global profile file ~/.Rprofile,13
or a per-project file .Rprofile under the root directory of your RStudio
project. The former will be applied to all R sessions that you start, unless
you have provided the latter to override it. The easiest way to create such
a file is to use file.edit() in RStudio, e.g.,
file.edit("~/.Rprofile")
# or file.edit('.Rprofile')
Suppose you always prefer writing Rmd posts (instead of the default
*.md), and want the author of new posts to be “John Doe” by default. You
can set these options in the profile file:
A nice consequence of setting these options is that when you use the RStu-
dio addin “New Post,” the fields “Author,” “Subdirectory,” and “Format”
will be automatically populated, so you do not need to manipulate them
every time unless you want to change the defaults (occasionally).
R only reads one startup profile file. For example, if you have a .Rpro-
file under the current directory and a global ~/.Rprofile, only the for-
mer one will be executed when R starts up from the current directory.
This may make it inconvenient for multiple authors collaborating on the
same website project, since you cannot set author-specific options. In
particular, it is not possible to set the blogdown.author option in a single
.Rprofile, because this option should be different for different authors.
One workaround is to set common options in .Rprofile under the root
directory of the website project, and also execute the global ~/.Rprofile
if it exists. Author-specific options can be set in the global ~/.Rprofile on
each author’s computer.
13
The tilde ~ denotes your home directory in your system.
1.5 R Markdown vs. Markdown 13
Note that R will silently ignore the last line of your .Rprofile14 if it does
not have a trailing newline, so please make sure you add at least one new-
line to the end of your .Rprofile.
```r
1 + 1 # not executed
```
Similarly, Blackfriday does not support LaTeX math and Pandoc does.
We have added the MathJax20 support to the default theme (hugo-
lithium21 ) in blogdown to render LaTeX math on HTML pages, but there
is a caveat for plain Markdown posts: you have to include inline math
expressions in a pair of backticks `$math$`, e.g., `$S_n = \sum_{i=1}^n
X_i$`. Similarly, math expressions of the display style have to be written
18
Support for task lists was added to Pandoc with version 2.6, released on January
201919 .
20
https://www.mathjax.org/#docs
21
https://github.com/yihui/hugo-lithium
16 1 Get Started
in `$$math$$`. For R Markdown posts, you can use $math$ for inline math
expressions, and $$math$$ for display-style expressions.22
If you find it is a pain to have to remember the differences between R
Markdown and Markdown, a conservative choice is to always use R Mark-
down, even if your document does not contain any R code chunks. Pan-
doc’s Markdown is much richer than Blackfriday, and there are only a
small number of features unavailable in Pandoc but present in Blackfri-
day. The main disadvantages of using R Markdown are:
1. You may sacrifice some speed in rendering the website, but this
may not be noticeable due to a caching mechanism in blogdown
(more on this in Section D.3). Hugo is very fast when processing
plain Markdown files, and typically it should take less than one
second to render a few hundred Markdown files.
2. You will have some intermediate HTML files in the source direc-
tory of your website, because blogdown has to call rmarkdown
to pre-render *.Rmd files into *.html. You will also have inter-
mediate folders for figures (*_files/) and cache (*_cache/) if
you have plot output in R code chunks or have enabled knitr’s
caching. Unless you care a lot about the “cleanness” of the
source repository of your website (especially when you use a ver-
sion control tool like GIT), these intermediate files should not
matter.
In this book, we usually mean .Rmd files when we say “R Markdown docu-
ments,” which are compiled to .html by default. However, there is another
type of R Markdown document with the filename extension .Rmarkdown.
Such R Markdown documents are compiled to Markdown documents
with the extension .markdown, which will be processed by Hugo instead of
22
The reason that we need the backticks for plain Markdown documents is that we
have to prevent the LaTeX code from being interpreted as Markdown by Blackfriday.
Backticks will make sure the inner content is not translated as Markdown to HTML, e.g.,
`$$x *y* z$$` will be converted to <code>$$x *y* z$$</code>. Without the backticks, it
will be converted to $$x <em>y</em> z$$, which is not a valid LaTeX math expression
for MathJax. Similar issues can arise when you have other special characters like under-
scores in your math expressions.
1.5 R Markdown vs. Markdown 17
For example, we can add a table of contents to a page, set the figure width
to be 6 inches, and use the svg device for plots by setting these options in
YAML:
---
title: "My Awesome Post"
author: "John Doe"
date: "2017-02-14"
output:
blogdown::html_page:
toc: true
fig_width: 6
dev: "svg"
---
blogdown::html_page:
toc: true
fig_width: 6
dev: "svg"
24
For most themes, you can find this by navigating to the theme of your choice from
http://themes.gohugo.io and then clicking on Homepage.
25
In a workaround, if you used install_theme() and set the theme_example argument
to TRUE, then you can access an example config.toml file. In the themes/ directory, nav-
igate to the file for your newly downloaded theme and find exampleSite/config.toml.
This file can be copied to your root directory (to replace the config.toml file from your
original theme) or used as a template to correctly write a new config.toml file for your
new theme.
20 1 Get Started
To save you some time, we list a few themes below that match our taste:
• Simple/minimal themes: XMin,26 Tanka,27 simple-a,28 and ghost-
writer.29
• Sophisticated themes: hugo-academic30 (strongly recommended
for users in academia), hugo-tranquilpeak-theme,31 hugo-creative-
portfolio-theme,32 and hugo-universal-theme.33
• Multimedia content themes: If you are interested in adding multime-
dia content to your site (such as audio files of a podcast), the castanet34
theme provides an excellent framework tailored for this application.
An example of a site using blogdown with the castanet theme is the R-
Podcast.35
If you do not understand HTML, CSS, or JavaScript, and have no experi-
ence with Hugo themes or templates, it may take you about 10 minutes
to get started with your new website, since you have to accept everything
you are given (such as the default theme); if you do have the knowledge
and experience (and desire to highly customize your site), it may take
you several days to get started. Hugo is really powerful. Be cautious with
power.
Another thing to keep in mind is that the more effort you make in a com-
plicated theme, the more difficult it is to switch to other themes in the
future, because you may have customized a lot of things that are not
straightforward to port to another theme. So please ask yourself seri-
26
https://github.com/yihui/hugo-xmin
27
https://github.com/road2stat/hugo-tanka
28
https://github.com/AlexFinn/simple-a
29
https://github.com/jbub/ghostwriter
30
https://github.com/gcushen/hugo-academic
31
https://github.com/kakawait/hugo-tranquilpeak-theme
32
https://github.com/kishaningithub/hugo-creative-portfolio-theme
33
https://github.com/devcows/hugo-universal-theme
34
https://github.com/mattstratton/castanet
35
https://www.r-podcast.org
1.7 A recommended workflow 21
ously, “Do I like this fancy theme so much that I will definitely not change
it in the next couple of years?”
If you choose to dig a rather deep hole, someday you will have no choice
but keep on digging, even with tears.
— Liyun Chen36
3. Play with the new site for a while and if you do not like it, you
can repeat the above steps, otherwise edit the options in con-
fig.toml. If you do not understand certain options, go to the
36
Translated from her Chinese Weibo: http://weibo.com/1406511850/Dhrb4toHc (you
cannot view this page unless you have logged in).
22 1 Get Started
To edit a website:
1. Click the RStudio addin “Serve Site” to preview the site in RStu-
dio Viewer. This only needs to be done once every time you open
the RStudio project or restart your R session. Do not click the
Knit button on the RStudio toolbar.
2. Use the “New Post” addin to create a new post or page, then
start writing the content.
3. Use the “Update Metadata” addin to modify the YAML metadata
if necessary.
It can be much easier to publish a website if you are familiar with GIT and
GitHub. We recommend that you create a new site on Netlify from your
GitHub repository that contains the source files of your website, so that
you can enjoy the benefits of continuous deployment instead of manually
uploading the public/ folder every time. With this approach, you do not
1.7 A recommended workflow 23
25
26 2 Hugo
ages to serve a dynamic site. For more advantages of static sites, please
read the page “Benefits of Static Site Generators1 ” on Hugo’s website.
There are many existing static site generators, including Hugo, Jekyll,2
and Hexo,3 etc. Most of them can build general-purpose websites but are
often used to build blogs.
We love Hugo for many reasons, but there are a few that stand out. Un-
like other static site generators, the installation of Hugo is very simple
because it provides a single executable without dependencies for most
operating systems (see Section 1.1). It was also designed to render hun-
dreds of pages of content faster than comparable static site generators
and can reportedly render a single page in approximately 1 millisecond.
Lastly, the community of Hugo users is very active both on the Hugo dis-
cussion forum4 and on GitHub issues.5
Although we think Hugo is a fantastic static site generator, there is really
one and only one major missing feature: the support for R Markdown.
That is basically the whole point of the blogdown package.6 This miss-
ing feature means that you cannot easily generate results using R code
on your web pages, since you can only use static Markdown documents.
Besides, Hugo’s default Markdown engine is “Blackfriday”, which is less
powerful than Pandoc.7
1
https://gohugo.io/about/benefits/
2
http://jekyllrb.com
3
https://hexo.io
4
https://discuss.gohugo.io
5
https://github.com/gohugoio/hugo/issues
6
Another motivation was an easier way to create new pages or posts. Static site gen-
erators often provide commands to create new posts, but you often have to open and
modify the new file created by hand after using these commands. I was very frustrated
by this, because I was looking for a graphical user interface where I can just fill out the
title, author, date, and other information about a page, then I can start writing the con-
tent right away. That is why I provided the RStudio addin “New Post” and the function
blogdown::new_post(). In the past few years, I hated it every time I was about to create
a new post either by hand or via the Jekyll command line. Finally, I felt addicted to blog-
ging again after I finished the RStudio addin.
7
The Pandoc support has been added in a Hugo pull request: https://github.com/
gohugoio/hugo/pull/4060. However, I think the support is quite limited, and I’d recom-
mend that you use the R Markdown format instead, because with the official Pandoc
support in Hugo, you cannot customize the Pandoc command-line options, rendering
2.2 Configuration 27
Hugo uses a special file and folder structure to create your website (Fig-
ure 2.1). The rest of this chapter will give more details on the following
files and folders:
• config.toml
• content/
• static/
• themes/
• layouts/
FIGURE 2.1: Possible files and folders created when you create a new site
using blogdown.
2.2 Configuration
The first file that you may want to look at is the configuration or config
file in your root directory, in which you can set global configurations of
your site. It may contain options like the title and description of your site,
as well as other global options like links to your social networks, the nav-
igation menu, and the base URL for your website.
When generating your site, Hugo will search for a file called config.toml
is not cached (it could be slow), and you will not be able to use any Markdown extensions
from the bookdown package (such as numbering figure captions).
28 2 Hugo
We recommend that you use the TOML syntax only for the config file (you
can also use YAML if you prefer), and use YAML as the data format for the
metadata of (R) Markdown pages and posts, because R Markdown and
blogdown fully support only YAML.9 If you have a website that has already
used TOML, you may use blogdown::hugo_convert(unsafe = TRUE) to con-
vert TOML data to YAML, but please first make sure you have backed up
the website because it will overwrite your Markdown files.
The Hugo documentation does not use TOML or YAML consistently in
its examples, which can be confusing. Please pay close attention to the
configuration format when copying examples to your own website.
key = value
When you want to edit a configuration in the TOML file, simply change
the value. Values that are character strings should be in quotes, whereas
Boolean values should be lowercase and bare.
For example, if you want to give your website the title “My Awesome Site,”
8
Hugo also supports config.json, but blogdown does not support it, so we do not
recommend that you use it.
9
TOML has its advantages, but I feel they are not significant in the context of Hugo
websites. It is a pain to have to know yet another language, TOML, when YAML stands
for “Yet Another Markup Language.” I’m not sure if the XKCD comic applies in this case:
https://xkcd.com/927/.
10
https://github.com/toml-lang/toml
2.2 Configuration 29
and use relative URLs instead of the default absolute URLs, you may have
the following entries in your config.toml file.
relativeURLs = true
Most of your website’s global variables are entered in the config.toml file
in exactly this manner.
Further into your config file, you may notice some values in brackets like
this:
[social]
github = "https://github.com/rstudio/blogdown"
twitter = "https://twitter.com/rstudio"
This is a table in the TOML language and Hugo uses them to fill in infor-
mation on other pages within your site. For instance, the above table will
populate the .Site.Social variable in your site’s templates (more infor-
mation on this in Section 2.5).
Lastly, you may find some values in double brackets like this:
[[menu.main]]
name = "Blog"
url = "/blog/"
[[menu.main]]
name = "Categories"
url = "/categories/"
[[menu.main]]
name = "About"
url = "/about/"
In TOML, double brackets are used to indicate an array of tables. Hugo in-
terprets this information as a menu. If the code above was found in a con-
30 2 Hugo
fig.toml file, the resulting website would have links to Blog, Categories,
and About pages in the site’s main menu. The location and styling of that
menu are specified elsewhere, but the names of each menu’s choices and
the links to each section are defined here.
The config.toml file is different for each theme. Make sure that when you
choose a theme, you read its documentation thoroughly to get an under-
standing of what each of the configuration options does (more on themes
in Section 2.4).
2.2.2 Options
All built-in options that you may set for Hugo are listed at https://
gohugo.io/overview/configuration/. You can change any of these options
except contentDir, which is hard-coded to content in blogdown. Our gen-
eral recommendation is that you’d better not modify the defaults unless
you understand the consequences. We list a few options that may be of
interest to you:
• baseURL: Normally you have to change the value of this op-
tion to the base URL of your website. Some Hugo themes may
have it set to http://replace-this-with-your-hugo-site.com/ or
http://www.example.com/ in their example sites, but please make sure
to replace them with your own URL (see Chapter 3 and Appendix C
for more information on publishing websites and obtaining domain
names). Note that this option can be a URL with a subpath, if your
website is to be published under a subpath of a domain name, e.g.,
http://www.example.com/docs/.
11
• enableEmoji: You may set it to true so that you can use Emoji emoticons
like :smile: in Markdown.
• permalinks: Rules to generate permanent links of your pages. By de-
fault, Hugo uses full filenames under content/ to generate links, e.g.,
content/about.md will be rendered to public/about/index.html, and
content/post/2015-07-23-foo.md will be rendered to public/post/2015-
07-23-foo/index.html, so the actual links are /about/ and /post/2015-
07-23-foo/ on the website. Although it is not required to set custom
11
http://www.emoji-cheat-sheet.com
2.2 Configuration 31
[permalinks]
post = "/:year/:month/:day/:title/"
[permalinks]
post = "/:year/:month/:day/:slug/"
This is because your post title may change, and you probably do not
want the link to the post to change, otherwise you have to redirect the
old link to the new link, and there will other types of trouble like Disqus
comments. The :slug variable falls back to :title if a field named slug
is not set in the YAML metadata of the post. You can set a fixed slug so
that the link to the post is always fixed and you will have the freedom to
update the title of your post.
You may find a list of all possible variables that you can use in the perma-
links option at https://gohugo.io/extras/permalinks/.
• publishDir: The directory under which you want to generate the web-
site.
• theme: The directory name of the Hugo theme under themes/.
to ignore certain files when building the site. I recommend that you
specify at least these patterns ["\\.Rmd$", "\\.Rmarkdown$", "_files$",
"_cache$"]. You should ignore .Rmd files because blogdown will compile
them to .html, and it suffices for Hugo to use the .html files. There is no
need for Hugo to build .Rmd files, and actually Hugo does not know how.
Directories with suffixes _files and _cache should be ignored because
they contain auxiliary files after an Rmd file is compiled, and blogdown
will store them. Hugo should not copy them again to the public/ direc-
tory.
• uglyURLs: By default, Hugo generates “clean” URLs. This may be a
little surprising and requires that you understand how URLs work
when your browser fetches a page from a server. Basically, Hugo
generates foo/index.html for foo.md by default instead of foo.html,
because the former allows you to visit the page via the clean URL
foo/ without index.html. Most web servers understand requests like
http://www.example.com/foo/ and will present index.html under foo/ to
you. If you prefer the strict mapping from *.md to *.html, you may en-
able “ugly” URLs by setting uglyURLs to true.
• hasCJKLanguage: If your website is primarily in CJK (Chinese, Korean,
and Japanese), I recommend that you set this option to true, so that
Hugo’s automatic summary and word count work better.
Besides the built-in Hugo options, you can set other arbitrary options
in config.toml. For example, it is very common to see an option named
params, which is widely used in many Hugo themes. When you see a vari-
able .Site.Params.FOO in a Hugo theme, it means an option FOO that you
set under [params] in config.toml, e.g., .Site.Params.author is Frida Go-
mam with the following config file:
[params]
author = "Frida Gomam"
dateFormat = "2006/01/02"
to their websites, instead of going through many HTML files and making
changes one by one.
2.3 Content
The structure of the content/ directory can be arbitrary. A common struc-
ture is that there are a few static pages under the root of content/, and a
subdirectory post/ containing blog posts:
├── _index.md
├── about.md
├── vitae.md
├── post/
│ ├── 2017-01-01-foo.md
│ ├── 2017-01-02-bar.md
│ └── ...
└── ...
der a directory, and two posts have the same date, you may assign dif-
ferent weights to them to get your desired order on the list.
• slug:
A character string as the tail of the URL. It is particularly useful
when you define custom rules for permanent URLs (see Section 2.2.2).
2.3.2 Body
As we mentioned in Section 1.5, your post can be written in either R Mark-
down or plain Markdown. Please be cautious about the syntax differences
between the two formats when you write the body of a post.
2.3.3 Shortcode
Besides all Markdown features, Hugo provides a useful feature named
“shortcodes.” You can use a shortcode in the body of your post. When
Hugo renders the post, it can automatically generate an HTML snippet
based on the parameters you pass to the shortcode. This is convenient
because you do not have to type or embed a large amount of HTML code
in your post. For example, Hugo has a built-in shortcode for embedding
Twitter cards. Normally, this is how you embed a Twitter card (Figure 2.2)
on a page:
<blockquote class="twitter-tweet">
<p lang="en" dir="ltr">Anyone know of an R package for
interfacing with Alexa Skills?
<a href="https://twitter.com/thosjleeper">@thosjleeper</a>
<a href="https://twitter.com/xieyihui">@xieyihui</a>
<a href="https://twitter.com/drob">@drob</a>
<a href="https://twitter.com/JennyBryan">@JennyBryan</a>
<a href="https://twitter.com/HoloMarkeD">@HoloMarkeD</a> ?
</p>
— Jeff Leek (@jtleek)
<a href="https://twitter.com/jtleek/status/852205086956818432">
April 12, 2017
</a>
</blockquote>
2.4 Content 35
If you use the shortcode, all you need in the Markdown source document
is:
Basically, you only need to pass the ID of the tweet to a shortcode named
tweet. Hugo will fetch the tweet automatically and render the HTML snip-
pet for you. For more about shortcodes, see https://gohugo.io/extras/
shortcodes/.
```{r echo=FALSE}
blogdown::shortcode('tweet', '852205086956818432')
```
36 2 Hugo
2.4 Themes
A Hugo theme is a collection of template files and optional website as-
sets such as CSS and JavaScript files. In a nutshell, a theme defines what
your website looks like after your source content is rendered through the
templates.
Hugo has provided a large number of user-contributed themes at https:
//themes.gohugo.io. Unless you are an experienced web designer, you’d
better start from an existing theme here. The quality and complexity of
these themes vary a lot, and you should choose one with caution. For ex-
ample, you may take a look at the number of stars of a theme repository
on GitHub, as well as whether the repository is still relatively active. We
do not recommend that you use a theme that has not been updated for
more than a year.
In this section, we will explain how the default theme in blogdown works,
which may also give you some ideas about how to get started with other
themes.
baseurl = "/"
relativeurls = false
languageCode = "en-us"
title = "A Hugo website"
theme = "hugo-lithium"
googleAnalytics = ""
disqusShortname = ""
ignoreFiles = ["\\.Rmd$", "\\.Rmarkdown", "_files$", "_cache$"]
[permalinks]
post = "/:year/:month/:day/:slug/"
[[menu.main]]
name = "About"
url = "/about/"
[[menu.main]]
name = "GitHub"
url = "https://github.com/rstudio/blogdown"
[[menu.main]]
name = "Twitter"
url = "https://twitter.com/rstudio"
[params]
description = "A website built through Hugo and blogdown."
highlightjsVersion = "9.12.0"
highlightjsCDN = "//cdnjs.cloudflare.com/ajax/libs"
highlightjsLang = ["r", "yaml"]
highlightjsTheme = "github"
MathJaxCDN = "//cdnjs.cloudflare.com/ajax/libs"
MathJaxVersion = "2.7.5"
[params.logo]
url = "logo.png"
width = 50
38 2 Hugo
height = 50
alt = "Logo"
Some of these options may be obvious to understand, and some may need
explanations:
• baseurl:You can configure this option later, after you have a domain
name for your website. Do not forget the trailing slash.
• relativeurls: This is optional. You may want to set it to true only if you
intend to view your website locally through your file viewer, e.g., double-
click on an HTML file and view it in your browser. This option defaults
to false in Hugo, and it means your website must be viewed through a
web server, e.g., blogdown::serve_site() has provided a local web server,
so you can preview your website locally when relativeurls = false.
• title:The title of your website. Typically this is displayed in a web
browser’s title bar or on a page tab.
• theme: The directory name of the theme. You need to be very careful
when changing themes, because one theme can be drastically different
from another theme in terms of the configurations. It is quite possi-
ble that a different theme will not work with your current config.toml.
Again, you have to read the documentation of a theme to know what
options are supported or required.
• googleAnalytics: The Google Analytics tracking ID (like UA-000000-2).
You can sign up at https://analytics.google.com to obtain a tracking
ID.
• disqusShortname: The Disqus ID that you created during the account
setup process at https://disqus.com. This is required to enable com-
menting on your site.13 Please note that you have to set up a functional
baseurl and publish your website before Disqus comments can work.
• menu: This list of options specifies the text and URL of menu items at the
top. See Figure 1.1 for a sample page. You can change or add more menu
items. If you want to order the items, you may assign a weight to each
item, e.g.,
[[menu.main]]
name = "Home"
url = "/"
weight = 1
[[menu.main]]
name = "About"
url = "/about/"
weight = 2
[[menu.main]]
name = "GitHub"
url = "https://github.com/rstudio/blogdown"
weight = 3
[[menu.main]]
name = "CV"
url = "/vitae/"
weight = 4
[[menu.main]]
name = "Twitter"
url = "https://twitter.com/rstudio"
weight = 5
In the above example, I added a menu item CV with the URL /vitae/, and
there is supposed to be a corresponding source file vitae.md under the
content/ directory to generate the page /vitae/index.html, so the link
will actually function.
• params: Miscellaneous parameters for the theme.
2.5 Templates
A Hugo theme consists of two major components: templates, and web
assets. The former is essential, and tells Hugo how to render a page.16
The latter is optional but also important. It typically consists of CSS and
JavaScript files, as well as other assets like images and videos. These as-
sets determine the appearance and functionality of your website, and
some may be embedded in the content of your web pages.
You can learn more about Hugo templates from the official documen-
tation (https://gohugo.io/templates/overview/). There are a great many
different types of templates. To make it easier for you to master the key
14
https://highlightjs.org
15
https://cdnjs.com
16
The most common functionality of templates is to render HTML pages, but there
can also be special templates, for example, for RSS feeds and sitemaps, which are XML
files.
2.5 Templates 41
ideas, I created a very minimal Hugo theme, which covers most function-
alities that an average user may need, but the total number of lines is only
about 150, so we can talk about all the source code of this theme in the fol-
lowing subsection.
hugo-xmin/
├── LICENSE.md
├── README.md
├── archetypes
│ └── default.md
├── layouts
│ ├── 404.html
│ ├── _default
│ │ ├── list.html
│ │ ├── single.html
│ │ └── terms.html
│ └── partials
│ ├── foot_custom.html
│ ├── footer.html
17
https://github.com/yihui/hugo-xmin
42 2 Hugo
│ ├── head_custom.html
│ └── header.html
├── static
│ └── css
│ ├── fonts.css
│ └── style.css
└── exampleSite
├── config.toml
├── content
│ ├── _index.md
│ ├── about.md
│ ├── note
│ │ ├── 2017-06-13-a-quick-note.md
│ │ └── 2017-06-14-another-note.md
│ └── post
│ ├── 2015-07-23-lorem-ipsum.md
│ └── 2016-02-14-hello-markdown.md
├── layouts
│ └── partials
│ └── foot_custom.html
└── public
└── ...
---
---
HTML templates are stored under layouts/, and assets are stored under
static/.
To understand layouts/, you must know some basics about HTML (see
Section B.1) because the templates under this directory are mostly HTML
documents or fragments. There are many possible types of subdirectories
under layouts/, but we are only going to introduce two here: _default/
and partials/.
• The _default/ directory is where you put the default templates for your
web pages. In the XMin theme, we have three templates: single.html,
list.html, and terms.html.
{{ partial "header.html" . }}
<div class="article-meta">
<h1><span class="title">{{ .Title }}</span></h1>
{{ with .Params.author }}
<h2 class="author">{{ . }}</h2>
{{ end }}
{{ if .Params.date }}
<h2 class="date">{{ .Date.Format "2006/01/02" }}</h2>
{{ end }}
</div>
<main>
{{ .Content }}
</main>
{{ partial "footer.html" . }}
You see a lot of pairs of double curly braces {{}}, and that is how
you program the templates using Hugo’s variables and functions.
44 2 Hugo
---
title: Hello World
author: Frida Gomam
date: 2017-06-19
---
A single paragraph.
2.5 Templates 45
<div class="article-meta">
<h1><span class="title">Hello World</span></h1>
<h2 class="author">Frida Gomam</h2>
<h2 class="date">2017/06/19</h2>
</div>
<main>
<p>A single paragraph.</p>
</main>
{{ partial "header.html" . }}
{{ .Content }}
<ul>
{{ range (where .Data.Pages "Section" "!=" "") }}
<li>
<span class="date">{{ .Date.Format "2006/01/02" }}</span>
<a href="{{ .URL }}">{{ .Title }}</a>
</li>
{{ end }}
</ul>
46 2 Hugo
{{ partial "footer.html" . }}
Please note that the variable .Data is dynamic, and its value
changes according to the specific list you want to generate. For
example, the list page https://xmin.yihui.org/post/ only con-
tains pages under content/post/, and https://xmin.yihui.org/
note/ only contains pages under content/note/. These list pages
are automatically generated by Hugo, and you do not need to ex-
plicitly loop through the sections post and note. That is, a single
template list.html will generate multiple lists of pages according
to the sections and taxonomy terms (e.g., categories and tags) you
have on your website.
2.5 Templates 47
The list items are represented by the HTML tags <li> in <ul>. Each
item consists of the date, link, and title of a page. You may see
https://xmin.yihui.org/post/ for a full example of a list page.
{{ partial "header.html" . }}
<ul class="terms">
{{ range $key, $value := .Data.Terms }}
<li>
<a href='{{ (print "/" $.Data.Plural "/" $key) | relURL }}'>
{{ $key }}
</a>
({{ len $value }})
</li>
{{ end }}
</ul>
{{ partial "footer.html" . }}
are inside a loop, and need to access variables from the outside
scope. The link of the term is passed to the Hugo function relURL
via a pipe | to make it relative, which is good practice because rel-
ative links are more portable (independent of the domain name).
• The partials/ directory is the place to put the HTML fragments to be
reused by other templates via the partial function. We have four partial
templates under this directory:
– header.html main defines the <head> tag and the navigation menu
in the <nav> tag.
<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode }}">
<head>
<meta charset="utf-8">
<title>{{ .Title }} | {{ .Site.Title }}</title>
<link href='{{ "/css/style.css" | relURL }}'
rel="stylesheet" />
<link href='{{ "/css/fonts.css" | relURL }}'
rel="stylesheet" />
{{ partial "head_custom.html" . }}
</head>
<body>
<nav>
<ul class="menu">
{{ range .Site.Menus.main }}
<li><a href="{{ .URL | relURL }}">{{ .Name }}</a></li>
{{ end }}
</ul>
<hr/>
</nav>
[[menu.main]]
name = "Home"
url = "/"
[[menu.main]]
name = "About"
url = "/about/"
<ul class="menu">
<li><a href="/">Home</a></li>
<li><a href="/about/">About</a></li>
</ul>
Hugo has a powerful menu system, and we only used the simplest
type of menu in this theme. If you are interested in more features
like nested menus, please see the full documentation at http://
gohugo.io/extras/menus/.
– footer.html defines the footer area of a page and closes the HTML
document:
<footer>
{{ partial "foot_custom.html" . }}
{{ with .Site.Params.footer }}
<hr/>
{{ . | markdownify }}
{{ end }}
</footer>
50 2 Hugo
</body>
</html>
[params]
footer = "© [Yihui Xie](https://yihui.org) 2017"
There is a special template 404.html, which Hugo uses to create the 404
page (when a page is not found, this page is displayed):
{{ partial "header.html" . }}
{{ partial "footer.html" . }}
With all templates above, we will be able to generate a website from Mark-
down source files. You are unlikely to be satisfied with the website, how-
ever, because the HTML elements are not styled at all, and the default ap-
pearance may not look appealing to most people. You may have noticed
2.5 Templates 51
You can find many existing open-source CSS frameworks online that
may be applied to a Hugo theme. For example, the most popular CSS
framework may be Bootstrap: http://getbootstrap.com. When I was de-
signing XMin, I wondered how far I could go without using any of these
existing frameworks, because they are usually very big. For example,
bootstrap.css has nearly 10000 lines of code when not minimized. It
turned out that I was able to get a satisfactory appearance with about 50
lines of CSS, which I will explain in detail below:
• style.css defines all styles except the typefaces:
body {
max-width: 800px;
margin: auto;
padding: 1em;
line-height: 1.5em;
}
The maximum width of the page body is set to 800 pixels because an ex-
cessively wide page is difficult to read (800 is an arbitrary threshold that
I picked). The body is centered using the CSS trick margin: auto, which
means the top, right, bottom, and left margins are automatic. When a
block element’s left and right margins are auto, it will be centered.
/* code */
pre {
border: 1px solid #ddd;
box-shadow: 5px 5px 5px #eee;
padding: 1em;
overflow-x: auto;
}
code { background: #f9f9f9; }
pre code { background: none; }
For code blocks (<pre>), I apply light gray borders with drop-shadow ef-
fects. Every inline code element has a very light gray background. These
decorations are merely out of my own peculiar interest and emphasis in
code.
2.5 Templates 53
/* misc elements */
img, iframe, video { max-width: 100%; }
main { hyphens: auto; }
blockquote {
background: #f9f9f9;
border-left: 5px solid #ccc;
padding: 3px 1em 3px;
}
table {
margin: auto;
border-top: 1px solid #666;
border-bottom: 1px solid #666;
}
table thead th { border-bottom: 1px solid #ddd; }
th, td { padding: 5px; }
tr:nth-child(even) { background: #eee }
Embedded elements like images and videos that exceed the page mar-
gin are often ugly, so I restrict their maximum width to 100%. Hyphen-
ation is turned on for words in <main>. Blockquotes have a gray left side-
bar and a light gray background. Tables are centered by default, with
only three horizontal rules: the top and bottom borders of the table, and
the bottom border of the table head. Table rows are striped to make it
easier to read the table especially when the table is wide.
• fonts.css is a separate style sheet because it plays a critical role in the
appearance of a website, and it is very likely that you will want to cus-
tomize this file. In most cases, your readers will spend the most time
on reading the text on your pages, so it is important to make the text
comfortable to read. I’m not an expert in web design, and I just picked
Palatino for the body and Lucida Console or Monaco (whichever is avail-
able in your system) for the code. It is common to use Google web fonts
nowadays. You may try some web fonts and see if you like any of them.
54 2 Hugo
body {
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
}
code {
font-family: "Lucida Console", Monaco, monospace;
font-size: 85%;
}
The two CSS files are placed under the static/css/ directory of the theme.
In the HTML template header.html, the path /css/style.css refers to the
file static/css/style.css.
Lastly, this theme provided an example site under exampleSite/. The di-
rectory structure may be a little confusing because this is a theme instead
of a website. In practice, everything under exampleSite/ should be un-
der the root directory of a website, and the top-level hugo-xmin/ directory
should be under the themes/ directory of this website, i.e.,
├── config.toml
├── content/
├── ...
├── themes/
│ └── hugo-xmin/
│
└── ...
{{ template "_internal/google_analytics.html" . }}
{{ template "_internal/disqus.html" . }}
<script src="//YOUR-CDN-LINK/highlight.min.js"></script>
<script src="//YOUR-CDN-LINK/languages/r.min.js"></script>
<script>
hljs.configure({languages: []});
hljs.initHighlightingOnLoad();
</script>
<script src="//yihui.org/js/math-code.js"></script>
<script async
src="//YOUR-CDN-LINK/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
• Show the table of contents (TOC). To show a TOC for R Markdown posts,
you only need to add the output format blogdown::html_page with the
option toc: true to YAML:
output:
blogdown::html_page:
toc: true
For plain Markdown posts, you have to modify the template single.html.
The TOC of a post is stored in the Hugo template variable .TableOfCon-
tents. You may want an option to control whether to show the TOC, e.g.,
2.5 Templates 57
you may add an option toc: true to the YAML metadata of a Markdown
post to show the TOC. The code below can be added before the content
of a post in single.html:
{{ if .Params.toc }}
{{ .TableOfContents }}
{{ end }}
<p class="terms">
{{ range $i := (slice "categories" "tags") }}
{{ with ($.Param $i) }}
{{ $i | title }}:
{{ range $k := . }}
<a href='{{ relURL (print "/" $i "/" $k | urlize) }}'>{{$k}}</a>
{{ end }}
{{ end }}
{{ end }}
</p>
Basically the code loops through the YAML metadata fields categories
and tags, and for each field, its value is obtained from .Param, then
we use an inside loop to write out the terms with links of the form <a
href="/tags/foo/">foo</a>.
<ul>
{{ $paginator := .Paginate .Data.Pages }}
{{ range $paginator.Pages }}
<li>
<span class="date">{{ .Date.Format "2006/01/02" }}</span>
<a href="{{ .URL }}">{{ .Title }}</a>
</li>
{{ end }}
</ul>
{{ template "_internal/pagination.html" . }}
{{ with .File.Path }}
<a href="https://github.com/.../{{ . }}">Edit this page</a>
{{ end }}
However, the case is a little more complicated for blogdown users, when
R Markdown posts are involved. You cannot just use .File.Path because
it actually points to the .html output file from an .Rmd file, whereas the
.Rmd file is the actual source file. The Edit button or link should not point
to the .html file. Below is the complete implementation that you may
add to a template file depending on where you want to show the Edit
link (e.g., footer.html):
{{ if .File.Path }}
18
In my opinion, it really should be called “merge request” instead.
60 2 Hugo
{{ with .Site.Params.GithubEdit}}
<a href='{{ . }}{{ $.Scratch.Get "FilePath" }}'>Edit this page</a>
{{ end }}
{{ end }}
The basic logic is that for a file, if the same filename with the exten-
sion .Rmd exists, we will point the Edit link to the Rmd file. First, we
define a variable $Rmd to be the filename with the .Rmd extension. Then
we check if it exists. Unfortunately, there is no function in Hugo like
file.exists() in R, so we have to use a hack: list all files under the di-
rectory and see if the Rmd file is in the list. $.Scratch19 is the way to dy-
namically store and obtain variables in Hugo templates. Most variables
in Hugo are read-only, and you have to use $.Scratch when you want to
modify a variable. We set a variable FilePath in $.Scratch, whose value
is the full path to the Rmd file when the Rmd file exists, and the path to
the Markdown source file otherwise. Finally, we concatenate a custom
option GithubEdit in config.toml with the file path to complete the Edit
link <a>. Here is an example of the option in config.toml:
[params]
GithubEdit = "https://github.com/rbind/yihui/edit/master/content/"
Please note that if you use Hugo on Windows to build and deploy your
site, you may have to change the file path separators from backslashes to
forward slashes, e.g., you may need {{ $.Scratch.Set "FilePath" (re-
place ($.Scratch.Get "FilePath") "\\" "/") }} in the template. To
avoid this complication, we do not recommend that you deploy your site
through Windows (see Chapter 3 for deployment methods).
You may see https://github.com/yihui/hugo-xmin/pull/6 for an actual
implementation with R Markdown examples, and see the footer of this
19
http://gohugo.io/extras/scratch/
2.6 Custom layouts 61
After you digest the XMin theme and the implementations of additional
features, it should be much easier to understand other people’s templates.
There are a large number of Hugo themes but the primary differences
among them are often in styles. The basic components of templates are
often similar.
your-website/
├── config.toml
├── ...
├── themes/
│ └── hugo-xmin/
│ ├── ...
│ └── layouts/
│ ├── ...
21
https://en.wikipedia.org/wiki/Pottery_Barn_rule
2.7 Static files 63
│ └── partials
│ ├── foot_custom.html
│ ├── footer.html
│ ├── head_custom.html
│ └── header.html
└── layouts
└── partials
├── foot_custom.html
└── head_custom.html
All files under layouts/ under the root directory will override files
with the same relative paths under themes/hugo-xmin/layouts/, e.g., the
file layouts/partials/foot_custom.html, when provided, will override
themes/hugo-xmin/layouts/partials/foot_custom.html. That means you
only need to create and maintain at most two files under layouts/ instead
of maintaining all files under themes/. Note that this overriding mecha-
nism applies to all files under layouts/, and is not limited to the partials/
directory. It also applies to any Hugo theme that you actually use for your
website, and is not limited to hugo-xmin.
22
The link of the image depends on your baseurl setting in config.toml. If it does not
contain a subpath, /foo/bar.png will be the link of the image, otherwise you may have
to adjust it, e.g., for baseurl = "http://example.com/subpath/", the link to the image
should be /subpath/foo/bar.png.
64 2 Hugo
theme, I have two CSS files style.css and fonts.css. The former is the
main style sheet, and the latter is a quite small file to define typefaces
only. You may want to define your own typefaces, and you can only pro-
vide a static/css/fonts.css to override the one in the theme, e.g.,
body {
font-family: "Comic Sans MS", cursive, sans-serif;
}
code {
font-family: "Courier New", Courier, monospace;
}
blogdown::build_dir("static")
The function build_dir() finds all Rmd files under a directory, and calls
rmarkdown::render() to build them to the output formats specified in the
YAML metadata of the Rmd files. If your Rmd files should not be rendered
by a simple rmarkdown::render() call, you are free to provide your own
code to render them in R/build.R. There is a built-in caching mechanism
in the function build_dir(): an Rmd file will not be compiled if it is older
than its output file(s). If you do not want this behavior, you can force all
Rmd files to be recompiled every time: build_dir(force = TRUE).
I have provided a minimal example in the GitHub repository
yihui/blogdown-static,23 where you can find two Rmd examples
under the static/ directory. One is an HTML5 presentation based on the
xaringan package, and the other is a PDF document based on bookdown.
23
https://github.com/yihui/blogdown-static
2.7 Static files 65
You need to be cautious about arbitrary files under static/, due to Hugo’s
overriding mechanism. That is, everything under static/ will be copied
to public/. You need to make sure that the files you render under static/
will not conflict with those files automatically generated by Hugo from
content/. For example, if you have a source file content/about.md and
an Rmd file static/about/index.Rmd at the same time, the HTML output
from the latter will overwrite the former (both Hugo and you will gener-
ate an output file with the same name public/about/index.html).
3
Deployment
Since the website is basically a folder containing static files, it is much eas-
ier to deploy than websites that require dynamic server-side languages
such as PHP or databases. All you need is to upload the files to a server,
and usually your website will be up and running shortly. The key question
is which web server you want to use. If you do not have your own server,
you may try the ones listed in this chapter. Most of them are free (except
Amazon S3), or at least provide free plans. Disclaimer: the authors of this
book are not affiliated with any of these services or companies, and there
is no guarantee that these services will be provided forever.1
Considering the cost and friendliness to beginners, we currently recom-
mend Netlify (https://www.netlify.com). It provides a free plan that ac-
tually has quite a lot of useful features. If you have no experience in pub-
lishing websites before, just log in using your GitHub account or other
accounts, drag the public/ folder built by blogdown for your website to
the Netlify page, and your website will be online in a few seconds with
a random subdomain name of the form random-word-12345.netlify.com
provided by Netlify (you can customize the name). You can easily auto-
mate this process (see Section 3.1 for more details). You do not need to
wrestle with ssh or rsync -zrvce anymore, if you know what these com-
mands mean.
The second easiest solution may be Updog (https://updog.co), which fea-
tures Dropbox integration. Publishing a website can be as easy as copying
the files under the public/ folder of your blogdown website to a Dropbox
folder. The free plan of Updog only provides limited features, and its paid
plan will give you access to much richer features.
If you do not mind using command-line tools or are familiar with
GIT/GitHub, you can certainly consider services like GitHub Pages,
1
You can easily find other similar services if you use your search engine.
67
68 3 Deployment
3.1 Netlify
As we just mentioned, Netlify allows you to quickly publish a website by
uploading the public/ folder through its web interface, and you will be
assigned a random subdomain *.netlify.com.2 This approach is good for
those websites that are not updated frequently (or at all). However, it is
unlikely that you will not need to update your website, so we introduce
a better approach in this section,3 which will take you a few more min-
utes to complete the configurations. Once it is properly configured, all
you need to do in the future is to update the source repository, and Netlify
will call Hugo to render your website automatically.
Basically, you have to host all source files of your website in a GIT reposi-
tory.4 You do not need to put the public/ directory under version control5
because it will be automatically generated. Currently, Netlify supports
GIT repositories hosted on GitHub, GitLab, and BitBucket. With any of
these accounts, you can log into Netlify from its homepage and follow the
guide to create a new site from your GIT repository.
Netlify supports several static website generators, including Jekyll and
Hugo. For a new site, you have to specify a command to build your web-
site, as well as the path of the publish directory. Netlify also supports
2
You don’t have to keep the *.netlify.com domain. See Appendix C for more infor-
mation.
3
Please bear in mind that the purpose of this section is to outline the basic steps
of publishing a website with Netlify, and the technical details may change from time
to time, so the official Netlify documentation should be the most reliable source if you
have any questions or anything we introduced here does not work.
4
If the contents of your blogdown site are not in the root directory of your GIT repos-
itory, Netlify will not build.
5
You can add public to .gitignore to ignore it in GIT.
3.1 Netlify 69
multiple versions of Hugo, so the build command can be the default hugo.
The default version is 0.17, which is too old, and we recommend that you
use at least version 0.20. To specify a Hugo version greater or equal to
0.20, you need to create an environment variable HUGO_VERSION on Netlify.
See the Netlify documentation6 for more information. The publish direc-
tory should be public unless you have changed it in your config.toml. Fig-
ure 3.1 shows the settings of the website https://t.yihui.org. You do not
have to follow the exact settings for your own website; in particular, you
may need to change the value of the environment variable HUGO_VERSION
to a recent version of Hugo.7
It may take a minute or two to deploy your website on Netlify for the first
time, but it can be much faster later (a few seconds) when you update
your website source, because Netlify deploys incremental changes in the
6
https://www.netlify.com/docs/continuous-deployment/
7
By the time when this book is published, the version 0.24.1 may be too old.
70 3 Deployment
public/ directory, i.e., only the newer files compared to the last time are
deployed.
After your GIT repository is connected with Netlify, the last issue you
may want to solve is the domain name, unless you are satisfied with
the free Netlify subdomain. If you want to use a different domain, you
need to configure some DNS records of the domain to point it to the
Netlify server. See Appendix C for some background knowledge on do-
main names.
If you are not familiar with domain names or do not want to learn more
about them, something to consider is a free subdomain *.rbind.io of-
fered by RStudio, Inc. Please visit the Rbind support website https://
support.rbind.io to learn how to apply for a subdomain. In fact, the
Rbind organization also offers free help on how to set up a website based
on blogdown, thanks to a lot of volunteers from the R and statistics com-
munity.
Netlify is the only solution in this chapter that does not require you to pre-
build your website. You only need to update the source files, push them
to GitHub, and Netlify will build the website for you.8 The rest of the solu-
tions in this chapter will require you to build your website locally and up-
load the public/ folder explicitly or implicitly. That said, you can certainly
prebuild your website using any tools, push it to GitHub, and Netlify is
still able to deploy it for you. What you need to do is leave the build com-
mand empty, and tell Netlify your publish directory (e.g., Hugo’s default
public/, but if your prebuilt website is under the root directory, specify
. as the publish directory instead). Then Netlify simply uploads all files
under this directory to its servers without rebuilding your website.
3.2 Updog
Updog (https://updog.co) provides a simple service: it turns a specified
Dropbox (or Google Drive) folder into a website. The idea is that you
grant Updog the permission to read the folder, and it will act as a mid-
8
This is called “continuous deployment.”
3.3 GitHub Pages 71
dleman to serve your files under this folder to your visitors. This folder
has to be accessed via a domain name, and Updog offers a free sub-
domain *.updog.co. For example, if you have assigned the domain ex-
ample.updog.co to your Dropbox folder, and a visitor wants to see the
page https://example.updog.co/foo/index.html, Updog will read the file
foo/index.html in your Dropbox folder and display it to the visitor.
At the moment, Updog’s free plan only allows one website per account
and will insert a footer “Hosted on Updog” on your web pages. You may
not like these limitations. The major benefit of using Updog is that pub-
lishing a website becomes implicit since Dropbox will continuously sync
files. All you need to do is to make sure your website is generated to
the correct Dropbox folder. This can be easily achieved by setting the op-
tion publishDir in config.toml. For example, suppose the folder that you
assign to Updog is ~/Dropbox/Apps/updog/my-website/, and your source
folder is at ~/Dropbox/Apps/updog/my-source/, then you can set publishDir
= "../my-website" in ~/Dropbox/Apps/updog/my-source/config.toml.
You can also use your custom domain name if you do not want the default
Updog subdomain, and you only need to point the CNAME record of your
domain name to the Updog subdomain.9
• One of the best features of Netlify that is not available with GitHub
Pages is that Netlify can generate a unique website for preview when a
GitHub pull request is submitted to your GitHub repository. This is ex-
tremely useful when someone else (or even yourself) proposes changes
to your website, since you have a chance to see what the website would
look like before you merge the pull request.
Basically, Netlify can do everything that GitHub Pages can, but there
is still one little missing feature, which is closely tied to GitHub
itself, which is GitHub Project Pages.11 This feature allows you to
have project websites in separate repositories, e.g., you may have
two independent websites https://username.github.io/proj-a/ and
https://username.github.io/proj-b/, corresponding to GitHub repos-
itories username/proj-a and username/proj-b, respectively. However,
since you can connect any GitHub repositories with Netlify, and each
repository can be associated with a domain or subdomain name, you
may replace GitHub Project Pages with different subdomains like
proj-a.netlify.com and proj-b.netlify.com. The actual limitation is that
you cannot use subpaths in the URL but you can use any (sub)domain
names.
Although GitHub does not officially support Hugo (only Jekyll is sup-
ported), you can actually publish any static HTML files on GitHub Pages,
even if they are not built with Jekyll. The first requirement for using
GitHub Pages is that you have to create a GitHub repository named
username.github.io under your account (replace username with your ac-
tual GitHub username), and what’s left is to push your website files
to this repository. The comprehensive documentation of GitHub Pages
is at https://pages.github.com, and please ignore anything related to
Jekyll there unless you actually use Jekyll instead of Hugo. To make sure
GitHub does not rebuild your website using Jekyll and just publish what-
ever files you push to the repository, you need to create a (hidden) file
named .nojekyll in the repository.12 GitHub offers a free subdomain
username.github.io, and you can use your own domain name by config-
11
https://help.github.com/articles/user-organization-and-project-pages/
12
You may use the R function file.create('.nojekyll') to create this file if you do
not know how to do this.
3.3 GitHub Pages 73
source/
│
├── config.toml
├── content/
├── themes/
├── ...
└── public/
|
├── .git/
├── .nojekyll
├── index.html
├── about/
└── ...
If you know how to use the command line, change the working directory
to public/, and initialize the GIT repository there:
cd public
git init
git remote add origin https://github.com/username/username.github.io
The other choice is to clone the GitHub repository you created to the same
directory as your website source:
source/
│
├── config.toml
├── content/
├── themes/
└── ...
username.github.io/
│
├── .git/
├── .nojekyll
├── index.html
├── about/
└── ...
The source directory and the username.github.io directory are under the
same parent directory. In this case, you need to set the option publishDir
= "../username.github.io" in source/config.toml.
websites. Before I show you how, I’d like to mention two issues that you
should be aware of:
• Personally, I prefer taking a look at the output in GIT to see the changes
when I have any output that is dynamically computed from R, so that I
know for sure what I’m going to publish exactly. With Travis, it is some-
what unpredictable because it is fully automatic and you do not have a
chance to see the new content or results to be published. There are many
factors that could affect building the site: the R version, availability of
certain R packages, system dependencies, and network connection, etc.
• The time required to compile all Rmd files may be very long and cause
timeouts on Travis, depending on how time-consuming your R code is.
There is a caching mechanism in blogdown to speed up the building
of your site (see Section D.9), and if you use Travis to build your web-
site, you will not benefit from this caching mechanism unless you take
advantage of Travis’s caching. You have to cache the directories con-
tent/, static/, and blogdown/, but Travis’s cache is a little fragile in my
experience. Sometimes the cache may be purged for unknown reasons.
What is more, you cannot directly cache content/ and static/, because
Travis clones your repository before restoring the cache, which means
old files from the cached content/ and static/ may overwrite new files
you pushed to GitHub.
The second problem can be solved, but I do not want to explain how in this
book since the solution is too involved. If you really want to use Travis
to build your website and run into this problem, you may file an issue
to the GitHub repository https://github.com/yihui/travis-blogdown. In
fact, this repository is a minimal example I created to show how to build
a website on Travis and publish to GitHub Pages.
The Travis documentation shows how to deploy a site to GitHub Pages:
https://docs.travis-ci.com/user/deployment/pages/, but does not show
how to build a site. Here is the Travis configuration file, .travis.yml, for
the travis-blogdown repository:
language: r
dist: xenial
latex: false
76 3 Deployment
branches:
only:
- master
cache:
packages: yes
directories:
- $HOME/bin
script:
- Rscript -e 'blogdown::install_hugo()'
- Rscript -e 'blogdown::build_site()'
deploy:
provider: pages
skip_cleanup: true
github_token: $GITHUB_TOKEN
on:
branch: master
local_dir: public
fqdn: travis-blogdown.yihui.name
Package: placeholder
Type: Website
Title: Does not matter.
Version: 0.0.1
Imports: blogdown
Remotes: rstudio/blogdown
• The branches option specifies that only changes in the master branch will
trigger building on Travis.
• The cache option specifies all R packages to be cached, so the next time it
will be faster to build the site (R packages do not need to be reinstalled
from source). The bin/ directory in the home directory is also cached
because Hugo is installed there, and the next time Hugo does not need
to be reinstalled.
• For the deploy option, there is an environment variable named
GITHUB_TOKEN, and I have specified its value to be a GitHub personal ac-
cess token via the Travis settings of this repository, so that Travis will
be able to write to my repository after the website is built. The option
on specifies that the deployment will only occur when the master branch
is built. The local_dir option is the publish directory, which should de-
fault to public in Hugo. By default, the website is pushed to the gh-pages
branch of this repository. The fqdn option specifies the custom domain
of the website. I have set a CNAME record (see Appendix C) to point
travis-blogdown.yihui.org to yihui.github.io, so that GitHub is able to
serve this website through this domain (in fact, Travis will write a CNAME
file containing the domain to the gh-pages branch).
If you use the username.github.io repository on GitHub, the website must
be pushed to its master branch instead of gh-pages (this is the only excep-
tion). I recommend that you separate the source repository and the out-
put repository. For example, you may have a website-source repository
with the same settings as the above .travis.yml except for two new op-
tions under deploy:
deploy:
...
repo: username/username.github.io
target_branch: master
This means the website will be pushed to the master branch of the repos-
itory username/username.github.io (remember to replace username with
your actual username).
You can also deploy your website to Amazon S3, and the setup on the R
78 3 Deployment
side is very similar to what we have introduced for GitHub Pages. The
only difference is in the last step, where you change the target from
GitHub Pages to Amazon S3. For more information, please see the docu-
mentation on Travis: https://docs.travis-ci.com/user/deployment/s3/.
image: debian:buster-slim
before_script:
- apt-get update && apt-get -y install pandoc r-base
- R -e "install.packages('blogdown',repos='http://cran.rstudio.com')"
- R -e "blogdown::install_hugo()"
pages:
script:
- R -e "blogdown::build_site()"
artifacts:
13
https://about.gitlab.com/features/gitlab-ci-cd/
14
https://about.gitlab.com/features/pages/
3.5 GitLab Pages 79
paths:
- public
only:
- master
The image option specifies what Docker15 image will be use as a start point.
We are using a Debian image but any image from Docker Hub16 can be
used. Other settings and options are similar to .travis.yml in Section 3.4.
The above example generates the website at https://rgaiacs.gitlab.io/
blogdown-gitlab.
15
https://www.docker.com
16
https://hub.docker.com/
4
Migration
81
82 4 Migration
post https://support.rbind.io/2017/05/15/converting-robjhyndman-to-
blogdown/ for the stories about how he migrated his WordPress website
to blogdown. The key is that you probably need a long international flight
when you want to migrate a complicated website.
A simpler example
is the Simply Statistics blog (https:
1
//simplystatistics.org). Originally it was built on Jekyll and the source
was hosted in the GitHub repository https://github.com/simplystats/
simplystats.github.io. I volunteered to help them move to blogdown,
and it took me about four hours. My time was mostly spent on cleaning
up the YAML metadata of posts and tweaking the Hugo theme. They had
about 1000 posts, which sounds like a lot, but the number does not really
matter, because I wrote an R script to process all posts automatically.
The new repository is at https://github.com/rbind/simplystats.
If you do not really have too many pages (e.g., under 20), I recommend
that you cut and paste them to Markdown files, because it may actually
take longer to write a script to process these pages.
It is likely that some links will be broken after the migration because
Hugo renders different links for your pages and posts. In that case, you
may either fix the permanent links (e.g., by tweaking the slug of a post),
or use 301 redirects (e.g., on Netlify).
Usually, posts in Jekyll are under the _posts/ directory, and you can move
them to content/post/ (you are free to use other directories). Then you
need to define a custom rule for permanent URLs in config.toml like (see
Section 2.2.2):
[permalinks]
post = "/:year/:month/:day/:slug/"
This depends on the format of the URLs you used in Jekyll (see the perma-
link option in your _config.yml).
If there are static assets like images, they can be moved to the static/
directory in Hugo.
Then you need to use your favorite tool with some string manipulation
techniques to process all Markdown files. If you use R, you can list all
Markdown files and process them one by one in a loop. Below is a sketch
of the code:
files = list.files(
'content/', '[.](md|markdown)$', full.names = TRUE,
recursive = TRUE
)
for (f in files) {
blogdown:::process_file(f, function(x) {
# process x here and return the modified x
x
})
}
blogdown:::remove_extra_empty_lines
function (...)
process_file(..., FUN = function(x) {
x = paste(gsub("\\s+$", "", x), collapse = "\n")
trim_ws(gsub("\n{3,}", "\n\n", x))
})
<bytecode: 0x7ff1d2d3de50>
<environment: namespace:blogdown>
blogdown:::process_bare_urls
function (...)
process_file(..., FUN = function(x) {
gsub("\\[([^]]+)]\\(\\1/?\\)", "<\\1>", x)
})
<bytecode: 0x7ff1d5d29490>
<environment: namespace:blogdown>
The first function substitutes two or more empty lines with a single
empty line. The second function replaces links of the form [url](url)
with <url>. There is nothing wrong with excessive empty lines or the
syntax [url](url), though. These helper functions may make your Mark-
down text a little cleaner. You can find all such helper functions at
https://github.com/rstudio/blogdown/blob/master/R/clean.R. Note they
are not exported from blogdown, so you need triple colons to access them.
The YAML metadata of your posts may not be completely clean, especially
when your Jekyll website was actually converted from an earlier Word-
Press website. The internal helper function blogdown:::modify_yaml()
may help you clean up the metadata. For example, below is the YAML
metadata of a blog post of Simply Statistics when it was built on Jekyll:
---
id: 4155
title: Announcing the JHU Data Science Hackathon 2015
date: 2015-07-28T13:31:04+00:00
4.1 From Jekyll 85
You can discard the YAML fields that are not useful in Hugo. For exam-
ple, you may only keep the fields title, author, date, categories, and tags,
and discard other fields. Actually, you may also want to add a slug field
that takes the base filename of the post (with the leading date removed).
For example, when the post filename is 2015-07-28-announcing-the-jhu-
data-science-hackathon-2015.md, you may want to add slug: announcing-
the-jhu-data-science-hackathon-2015 to make sure the URL of the post
on the new site remains the same.
Here is the code to process the YAML metadata of all posts:
for (f in files) {
blogdown:::modify_yaml(f, slug = function(old, yaml) {
# YYYY-mm-dd-name.md -> name
gsub('^\\d{4}-\\d{2}-\\d{2}-|[.](md|markdown)', '', f)
}, categories = function(old, yaml) {
# remove the Uncategorized category
setdiff(old, 'Uncategorized')
86 4 Migration
}, .keep_fields = c(
'title', 'author', 'date', 'categories', 'tags', 'slug'
), .keep_empty = FALSE)
}
You can pass a file path to modify_yaml(), define new YAML values (or
functions to return new values based on the old values), and decide
which fields to preserve (.keep_fields). You may discard empty fields via
.keep_empty = FALSE. The processed YAML metadata is below, which looks
much cleaner:
---
title: Announcing the JHU Data Science Hackathon 2015
author: Roger Peng
date: '2015-07-28T13:31:04+00:00'
slug: announcing-the-jhu-data-science-hackathon-2015
---
Press XML file there, and get a download link to a ZIP archive that con-
tains your posts in Markdown.
The biggest challenge in converting WordPress posts to Hugo is to clean
up the post content in Markdown. Fortunately, I have done this for three
different WordPress blogs,4 and I think I have managed to automate this
process as much as possible. You may refer to the pull request I submitted
to Karl Broman to convert his WordPress posts to Markdown (https://
github.com/kbroman/oldblog_xml/pull/1), in which I provided both the R
script and the Markdown files. I recommend that you go to the “Commits”
tab and view all my GIT commits one by one to see the full process.
The key is the R script https://github.com/yihui/oldblog_xml/blob/
master/convert.R, which converts the WordPress XML file to Markdown
posts and cleans them. Before you run this script on your XML file, you
have to adjust a few parameters, such as the XML filename, your old
WordPress site’s URL, and your new blog’s URL.
Note that this script depends on the Exitwp tool. If you do not know
how to run Exitwp, please use the online tool I mentioned before (travis-
exitwp), and skip the R code that calls Exitwp.
The Markdown posts should be fairly clean after the conversion, but there
may be remaining HTML tags in your posts, such as <table> and <block-
quote>. You will need to manually clean them, if any exist.
If you are very familiar with web scraping techniques, you can also scrape
the HTML pages of your website, and convert them to Markdown via Pan-
doc, e.g.,
rmarkdown::pandoc_convert(
'foo.html', to = 'markdown', output = 'foo.md'
)
I have actually tried this way on a website, but was not satisfied, since I
still had to heavily clean up the Markdown files. If your website is simpler,
this approach may work better for you.
5
Other Generators
We mentioned the possibility to bypass Hugo and use your own building
method in Section D.9. Basically you have to build the site using blog-
down::build_site(method = "custom"), and provide your own building
script /R/build.R. In this chapter, we show you how to work with other
popular static site generators like Jekyll and Hexo. Besides these static
site generators written in other languages, there is actually a simple site
generator written in R provided in the rmarkdown package (Allaire et al.,
2019), and we will introduce it in Section 5.3.
5.1 Jekyll
For Jekyll (https://jekyllrb.com) users, I have prepared a minimal exam-
ple in the GitHub repository yihui/blogdown-jekyll.1 If you clone or down-
load this repository and open blogdown-jekyll.Rproj in RStudio, you can
still use all addins mentioned in Section 1.3, such as “New Post,” “Serve
Site,” and “Update Metadata,” but it is Jekyll instead of Hugo that builds
the website behind the scenes now.
I assume you are familiar with Jekyll, and I’m not going to introduce the
basics of Jekyll in this section. For example, you should know what the
_posts/ and _site/ directories mean.
The key pieces of this blogdown-jekyll project are the files .Rprofile,
R/build.R, and R/build_one.R. I have set some global R options for this
project in .Rprofile:2
1
https://github.com/yihui/blogdown-jekyll
2
If you are not familiar with this file, please read Section 1.4.
89
90 5 Other Generators
options(
blogdown.generator = "jekyll",
blogdown.method = "custom",
blogdown.subdir = "_posts"
)
First, the website generator was set to jekyll using the option blog-
down.generator, so that blogdown knows that it should use Jekyll to build
the site. Second, the build method blogdown.method was set to custom, so
that we can define our custom R script R/build.R to build the Rmd files
(I will explain the reason later). Third, the default subdirectory for new
posts was set to _posts, which is Jekyll’s convention. After you set this
option, the “New Post” addin will create new posts under the _posts/ di-
rectory.
When the option blogdown.method is custom, blogdown will call the R script
R/build.R to build the site. You have full freedom to do whatever you want
in this script. Below is an example script:
build_one = function(io) {
# if output is not older than input, skip the
# compilation
if (!blogdown:::require_rebuild(io[2], io[1]))
return()
system2("jekyll", "build")
The script R/build_one.R looks like this (I have omitted some non-
essential settings for simplicity):
local({
# fall back on "/" if baseurl is not specified
baseurl = blogdown:::get_config2("baseurl", default = "/")
knitr::opts_knit$set(base.url = baseurl)
knitr::render_jekyll() # set output hooks
knitr::knit(
a[1], a[2], quiet = TRUE, encoding = "UTF-8",
envir = globalenv()
)
})
For example, for a post _posts/foo.Rmd, its figures will be written to fig-
ure/foo/ and its cache databases (if there are any) will be stored under
cache/foo/. Both directories are under the root directory of the project.
exclude: ['*.Rmd']
5.2 Hexo
The ideas of using Hexo (https://hexo.io) are very similar to what we
have applied to Jekyll in the previous section. I have also prepared a min-
imal example in the GitHub repository yihui/blogdown-hexo.3
The key components of this repository are still .Rprofile, R/build.R,
and R/build_one.R. We set the option blogdown.generator to hexo, the
build.method to custom, and the default subdirectory for new posts to
source/_posts.
options(
blogdown.generator = 'hexo',
blogdown.method = 'custom',
blogdown.subdir = 'source/_posts'
)
1. We find all Rmd files under the source/ directory instead of the
root directory, because Hexo’s convention is to put all source
files under source/.
2. We call system2('hexo', 'generate') to build the website.
For the script R/build_one.R, the major difference with the script in the
blogdown-jekyll repository is that we set the base.dir option for knitr, so
that all R figures are generated to the source/ directory. This is because
Hexo copies everything under source/ to public/, whereas Jekyll copies
everything under the root directory to _site/.
local({
# fall back on '/' if baseurl is not specified
3
https://github.com/yihui/blogdown-hexo
5.3 Default site generator in rmarkdown 95
and we are not going to repeat the documentation here, but just want
to highlight the major differences between the default site generator in
rmarkdown and other specialized site generators like Hugo:
• The rmarkdown site generator requires all Rmd files to be under the
root directory. Hugo has no constraints on the site structure, and you
can create arbitrary directories and files under /content/.
• Hugo is a general-purpose site generator that is highly customizable,
and there are a lot of things that rmarkdown’s default site generator
does not support, e.g., RSS feeds, metadata especially common in blogs
such as categories and tags, and customizing permanent links for cer-
tain pages.
There are still legitimate reasons to choose the rmarkdown default site
generator, even though it does not appear to be as powerful as Hugo, in-
cluding:
• You are familiar with generating single-page HTML output from R
Markdown, and all you want is to extend this to generating multiple
pages from multiple Rmd files.
• It suffices to use a flat directory of Rmd files. You do not write a blog or
need RSS feeds.
• You prefer the Bootstrap styles. In theory, you can also apply Bootstrap
styles to Hugo websites, but it will require you to learn more about
Hugo. Bootstrap is well supported in rmarkdown, and you can spend
more time on the configurations instead of learning the technical de-
tails about how it works.
• There are certain features in rmarkdown HTML output that are miss-
ing in blogdown. For example, currently you cannot easily print data
frames as paged tables, add a floating table of contents, or fold/unfold
code blocks dynamically in the output of blogdown. All these could be
implemented via JavaScript and CSS, but it is certainly not as simple as
specifying a few options in rmarkdown like toc_float: true.
Please note that the rmarkdown site generator is extensible, too. For ex-
ample, the bookdown package (Xie, 2019a) is essentially a custom site gen-
erator to generate books as websites.
5.4 pkgdown 97
5.4 pkgdown
The pkgdown package (Wickham and Hesselberth (2019), https://
github.com/hadley/pkgdown) can help you quickly turn the R documenta-
tion of an R package (including help pages and vignettes) into a website.
It is independent of blogdown and solves a specific problem. It is not a
general-purpose website generator. We want to mention it in this book
because it is very easy to use, and also highly useful. You can find the in-
structions on its website or in its GitHub repository.
A
R Markdown
---
title: A Simple Linear Regression
author: Yihui Xie
---
```{r}
fit = lm(dist ~ speed, data = cars)
b = coef(summary(fit))
plot(fit)
```
99
100 A R Markdown
103
104 B Website Basics
tain element on the web page and selecting the menu item Inspect (or
Inspect Element). In Figure B.1, I right-clicked on the profile image of
my website https://yihui.org and inspected it, and Chrome highlighted
its HTML source code <img src="..." ... /> in the left pane. You can
also see the CSS styles associated with this img element in the right pane.
What is more, you can interactively change the styles there if you know
CSS, and see the (temporary) effects in real time on the page! After you
are satisfied with the new styles, you can write the CSS code in files.
There are a lot of amazing features of Developer Tools, which make them
not only extremely useful for debugging and experimentation, but also
helpful for learning web development. These tools are indispensable to
me when I develop anything related to web pages. I learned a great deal
about CSS and JavaScript by playing with these tools.
B.1 HTML 105
B.1 HTML
HTML stands for Hyper Text Markup Language, and it is the language be-
hind most web pages you see. You can use the menu View -> View Source
or the context menu View Page Source to see the full HTML source of
a web page in your browser. All elements on a page are represented by
HTML tags. For example, the tag <p> represents paragraphs, and <img>
represents images.
The good thing about HTML is that the language has only a limited num-
ber of tags, and the number is not very big (especially the number of com-
monly used tags). This means there is hope that you can master this lan-
guage fully and quickly.
Most HTML tags appear in pairs, with an opening tag and a closing tag,
e.g., <p></p>. You write the content between the opening and closing tags,
e.g., <p>This is a paragraph.</p>. There are a few exceptions, such as the
<img> tag, which can be closed by a slash / in the opening tag, e.g., <img
src="foo.png" />. You can specify attributes of an element in the opening
tag using the syntax name=value (a few attributes do not require value).
HTML documents often have the filename extension .html (or .htm). Be-
low is an overall structure of an HTML document:
<html>
<head>
</head>
<body>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<body>
<h1>A First-level Heading</h1>
<p>A paragraph.</p>
<ul>
<li>An item.</li>
<li>Another item.</li>
<li>Yet another item.</li>
</ul>
<script src="/js/bar.js"></script>
</body>
</html>
In the head, we declare the character encoding of this page to be UTF-8 via
a <meta> tag, specify the title via the <title> tag, and include a stylesheet
via a <link> tag.
B.1 HTML 107
a web server, e.g., only double-click the HTML file in your file
browser and show it in the browser.6
– A very common mistake that people make is a link without
the leading double slashes before the domain. You may think
www.example.com is a valid link. It is not! At least it does not link
to the website that you intend to link to. It works when you type it
in the address bar of your browser because your browser will nor-
mally autocomplete it to http://www.example.com. However, if you
write a link <a href="www.example.com">See this link</a>, you
will be in trouble. The browser will interpret this as a relative link,
and it is relative to the URL of the current web page, e.g., if you are
currently viewing http://yihui.org/cn/, the link www.example.com
actually means http://yihui.org/cn/www.example.com! Now you
should know the Markdown text [Link](www.example.com) is typi-
cally a mistake, unless you really mean to link to a subdirectory of
the current page or a file with literally the name www.example.com.
B.2 CSS
The Cascading Stylesheets (CSS) language is used to describe the look
and formatting of documents written in HTML. CSS is responsible for
the visual style of your site. CSS is a lot of fun to play with, but it can also
easily steal your time.
In the Hugo framework (https://gohugo.io/tutorials/creating-a-new-
theme/), CSS is one of the major “non-content” files that shapes the look
and feel of your site (the others are images, JavaScript, and Hugo tem-
plates). What does the “look and feel”7 of a site mean? “Look” generally
refers to static style components including, but not limited to
• color palettes,
6
That is because without a web server, an HTML file is viewed via the protocol
file. For example, you may see a URL of the form file://path/to/the/file.html in
the address bar of your browser. The path //example.com/foo.png will be interpreted as
file://example.com/foo.png, which is unlikely to exist as a local file on your computer.
7
https://en.wikipedia.org/wiki/Look_and_feel
110 B Website Basics
• images,
• layouts/margins, and
• fonts.
whereas “feel” relates to dynamic components that the user interacts with
like
• dropdown menus,
• buttons, and
• forms.
There are 3 ways to define styles. The first is in line with HTML. For ex-
ample, this code
<p>Marco! <em>Polo!</em></p>
<html>
<style>
#favorite {
font-style: italic;
}
</style>
<ul id="tea-list">
<li>Earl Grey</li>
<li>Darjeeling</li>
<li>Oolong</li>
<li>Chamomile</li>
<li id="favorite">Chai</li>
</ul>
</html>
8
https://stackoverflow.com/q/12013532/559676
B.2 CSS 111
<html>
<link rel="stylesheet" href="/css/style.css" />
</html>
What goes inside the linked CSS document is essentially a list of rules (the
same list could appear internally between the style tags, if you are using
the second method). Each rule must include both a selector or group of
selectors, and a declarations block within curly braces that contains one
or more property: value; pairs. Here is the general structure for a rule9 :
/* CSS pseudo-code */
selectorlist {
property: value;
/* more property: value; pairs*/
}
/* by element type */
li {
color: yellow; /* all <li> elements are yellow */
}
9
https://developer.mozilla.org/en-US/docs/Web/CSS/Reference
10
https://developer.mozilla.org/en-US/docs/Web/CSS/Reference#Selectors
112 B Website Basics
Because each HTML element may match several different selectors, the
CSS standard determines which set of rules has precedence for any given
element, and which properties to inherit. This is where the cascade algo-
rithm comes into play. For example, take a simple unordered list:
<ul id="tea-list">
<li>Earl Grey</li>
<li>Darjeeling</li>
<li>Oolong</li>
<li>Chamomile</li>
<li>Chai</li>
</ul>
Now, let’s say we want to highlight our favorite teas again, so we’ll use a
class attribute.
<ul id="tea-list">
<li>Earl Grey</li>
<li class="favorite">Darjeeling</li>
<li>Oolong</li>
<li>Chamomile</li>
<li class="favorite">Chai</li>
</ul>
We can use this class attribute as a selector in our CSS. Let’s say we want
our favorite teas to be in bold and have a background color of yellow, so
our CSS would look like this:
.favorite {
font-weight: bold;
B.2 CSS 113
background-color: yellow;
}
Now, if you want every list item to be italicized with a white background,
you can set up another rule:
li {
font-style: italic;
background-color: white;
}
If you play with this code (which you can do easily using sites like http://
jsbin.com, https://jsfiddle.net, or https://codepen.io/pen/), you’ll see
that the two favorite teas are still highlighted in yellow. This is because the
CSS rule about .favorite as a class is more specific than the rule about li
type elements. To override the .favorite rule, you need to be as specific
as you can be when choosing your group of selectors:
ul#tea-list li.favorite {
background-color: white;
}
A few one-line examples illustrate how simple CSS rules can be used to
make dramatic changes:
• To make circular or rounded images, you may assign a class img-circle
to images (e.g., <img class="img-circle" src="foo.png" />) and define
the CSS:
.img-circle {
border-radius: 50%;
}
• To make striped tables, you can add background colors to odd or even
rows of the table, e.g.,
tr:nth-child(even) {
background: #eee;
}
B.3 JavaScript
It is way more challenging to briefly introduce JavaScript than HTML and
CSS, since it is a programming language. There are many books and tu-
torials about this language. Anyway, we will try to scratch the surface for
R users in this section.
In a nutshell, JavaScript is a language that is typically used to manipu-
late elements on a web page. An effective way to learn it is through the
JavaScript console in the Developer Tools of your web browser (see Fig-
ure B.1), because you can interactively type code in the console and exe-
cute it, which feels similar to executing R code in the R console (e.g., in
RStudio). You may open any web page in your web browser (e.g., https:
B.3 JavaScript 115
//yihui.org), then open the JavaScript console, and try the code below on
any web page:
document.body.style.background = 'orange';
It should change the background color of the page to orange, unless the
page has already defined background colors for certain elements.
To effectively use JavaScript, you have to learn both the basic syntax of
JavaScript and how to select elements on a page before you can manipu-
late them. You may partially learn the former from the short JavaScript
snippet below:
var x = 1; // assignments
1 + 2 - 3 * 4 / 5; // arithmetic
// function
var sum = function(x) {
var s = 0;
// a naive way to compute the sum
for (var i=0; i < x.length; i++) {
s += x[i];
}
return s;
};
sum(y);
There are many mature JavaScript libraries that can help you select
and manipulate elements on a page, and the most popular one may be
jQuery.14 However, you should know that sometimes you can probably
do well enough without these third-party libraries. There are some ba-
sic methods to select elements, such as document.getElementById() and
document.getElementsByClassName(). For example, you can select all para-
graphs using document.querySelectorAll('p').
Next we show a slightly advanced application, in which you will see
anonymous functions, selection of elements by HTML tag names, regular
expressions, and manipulation of HTML elements.
In Section 2.5.2, we mentioned how to enable MathJax on a Hugo website.
The easy part is to include the script MathJax.js via a <script> tag, and
there are two hard parts:
The easiest solution to the first problem may be adding backticks around
math expressions, e.g., `$x_i$`, but the consequence is that the math ex-
pression will be rendered in <code></code>, and MathJax ignores <code>
tags when looking for math expressions on the page. We can force Math-
Jax to search for math expressions in <code>, but this will still be problem-
atic. For example, someone may want to display inline R code `list$x$y`,
and $x$ may be recognized as a math expression. MathJax ignores <code>
for good reasons. Even if you do not have such expressions in <code>, you
may have some special CSS styles attached to <code>, and these styles will
be applied to your math expressions, which can be undesired (e.g., a light
gray background).
To solve these problems, I have provided a solution in the JavaScript code
at https://yihui.org/js/math-code.js:
(function() {
var i, text, code, codes = document.getElementsByTagName('code');
for (i = 0; i < codes.length;) {
code = codes[i];
if (code.parentNode.tagName !== 'PRE' &&
code.childElementCount === 0) {
text = code.textContent;
if (/^\$[^$]/.test(text) && /[^$]\$$/.test(text)) {
text = text.replace(/^\$/, '\\(').replace(/\$$/, '\\)');
code.textContent = text;
}
if (/^\\\((.|\s)+\\\)$/.test(text) ||
/^\\\[(.|\s)+\\\]$/.test(text) ||
/^\$(.|\s)+\$$/.test(text) ||
/^\\begin\{([^}]+)\}(.|\s)+\\end\{[^}]+\}$/.test(text)) {
code.outerHTML = code.innerHTML; // remove <code></code>
continue;
}
118 B Website Basics
}
i++;
}
})();
It is not a perfect solution, but it should be very rare that you run into
problems. This solution identifies possible math expressions in <code>,
and strips the <code> tag, e.g., replaces <code>$x$</code> with \(x\). Af-
ter this script is executed, we load the MathJax script. This way, we do
not need to force MathJax to search for math expressions in <code> tags,
and your math expressions will not inherit any styles from <code>. The
JavaScript code above is not too long, and should be self-explanatory. The
trickiest part is i++. I will leave it to readers to figure out why the for loop
is not the usual form for (i = 0; i < codes.length; i++). It took me
quite a few minutes to realize my mistake when I wrote the loop in the
usual form.
15
http://optipng.sourceforge.net
B.4 Useful resources 119
1. The title that you select for each page and post is a very im-
portant signal to Google and other search engines telling them
what that page is about.
2. Description tags are also critical to explain what a page is about.
In HTML documents, description tags16 are one way to provide
metadata about the page. Using blogdown, the description may
end up as text under the page title in a search-engine result. If
your page’s YAML does not include a description, you can add
one like description: "A brief description of this page.";
the HTML source of the rendered page would have a <meta> tag
in <head> like <meta name="description" content="A brief de-
scription of this page.">. Not all themes support adding the
description to your HTML page (although they should!)
3. URL structure also matters. You want your post’s slug to have
16
https://www.w3schools.com/tags/tag_meta.asp
120 B Website Basics
While you can use the free subdomain names like those provided by
GitHub or Netlify, it may be a better idea to own a domain name of your
own. The cost of an apex domain is minimal (typically the yearly cost is
about US$10), and you will enter a much richer world after you purchase
a domain name. For example, you are free to point your domain to any
web servers, you can create as many subdomain names as you want, and
you can even set up your own email accounts using the domain or subdo-
mains. In this appendix, we will explain some basic concepts of domain
names, and mention a few (free) services to help you configure your do-
main name.
Before we dive into the details, we want to outline the big picture of how
a URL works in your web browser. Suppose you typed or clicked a link
http://www.example.com/foo/index.html in your web browser. What hap-
pens behind the scenes before you see the actual web page?
First, the domain name has to be resolved through the nameservers as-
sociated with it. A nameserver knows the DNS (Domain Name System)
records of a domain. Typically it will look up the “A records” to point the
domain to the IP address of a web server. There are several other types
of DNS records, and we will explain them later. Once the web server is
reached, the server will look for the file foo/index.html under a directory
associated with the domain name, and return its content in the response.
That is basically how you can see a web page.
121
122 C Domain Name
C.1 Registration
You can purchase a domain name from many domain name registrars. To
stay neutral, we are not going to make recommendations here. You can
use your search engine to find a registrar by yourself, or ask your friends
for recommendations. However, we would like to remind you of a few
things that you should pay attention to when looking for a domain name
registrar:
• You should have the freedom to transfer your domain from the current
registrar to other registrars, i.e., they should not lock you in their system.
To transfer a domain name, you should be given a code known as the
“Transfer Auth Code” or “Auth Code” or “Transfer Key” or something like
that.
• You should be able to customize the nameservers (see Section C.2) of
your domain. By default, each registrar will assign their own name-
servers to you, and these nameservers typically work very well. However,
there are some special nameservers that provide services more than just
DNS records, and you may be interested in using them.
• Other people can freely look up your personal information, such as your
email or postal address, after you register a domain and submit this in-
formation to the registrar. This is called the “WHOIS Lookup.” You may
want to protect your privacy, but your registrar may require an extra
payment.
C.2 Nameservers
The main reason why we need nameservers is that we want to use do-
mains instead of IP addresses, although a domain is not strictly necessary
for you to be able to access a website. You could use the IP address if you
have your own server with a public IP, but there are many problems with
this approach. For example, IP addresses are limited (in particular IPv4),
C.3 DNS records 123
not easy to memorize, and you can only host one website per IP address
(without using other ports).
A nameserver is an engine that directs the DNS records of your domain.
The most common DNS record is the A record, which maps a domain
to an IP address, so that the hosting server can be found via its IP ad-
dress when a website is accessed through a domain. We will introduce
two more types of DNS records in Section C.3: CNAME and MX records.
In most cases, the default nameservers provided by your domain reg-
istrar should suffice, but there is a special technology missing in most
nameservers: CNAME flattening. You only need this technology if you
want to set a CNAME record for your apex domain. The only use case
to my knowledge is when you host your website via Netlify, but want
to use the apex domain instead of the www subdomain, e.g., you want to
use example.com instead of www.example.com. To make use of this tech-
nology, you could consider Cloudflare,1 which provides this DNS fea-
ture for free. Basically, all you need to do is to point the nameservers
of your domain to the nameservers provided by Cloudflare (of the form
*.ns.cloudflare.com).
An apex domain can have any number of subdomains. You can set DNS
records for the apex domain and any subdomains. You can see from
1
https://www.cloudflare.com
2
https://en.wikipedia.org/wiki/List_of_DNS_record_types
3
https://en.wikipedia.org/wiki/Dig_(command)
124 C Domain Name
main set in the CNAME record. Note that this is different from redirec-
tion, i.e., the URL t.yihui.org will not be explicitly redirected to twitter-
yihui.netlify.com (you still see the former in the address bar of your
browser).
Normally, you can set any DNS records for the apex domain except
CNAME, but I set a CNAME record for my apex domain yihui.org, and
that is because Cloudflare supports CNAME flattening. For more infor-
mation on this topic, you may read the post “To WWW or not WWW,”4
by Netlify. Personally, I prefer not using the subdomain www.yihui.org to
keep my URLs short, so I set a CNAME record for both the apex domain
yihui.org and the www subdomain, and Netlify will automatically redirect
the www subdomain to the apex domain. That said, if you are a beginner,
it may be a little easier to configure and use the www subdomain, as sug-
gested by Netlify. Note www is a conventional subdomain that sounds like
an apex domain, but really is not; you can follow this convention or not
as you wish.
For email services, I was an early enough “netizen”,5 and when I reg-
istered my domain name, Google was still offering free email services
to custom domain owners. That is how I can have a custom mailbox
6
[email protected]. Now you will have to pay for G Suite. In Figure C.1 you
can see I have set some MX (stands for “mail exchange”) records that
point to some Google mail servers. Of course, Google is not the only possi-
ble choice when it comes to custom mailboxes. Migadu7 claims to be the
“most affordable email hosting.” You may try its free plan and see if you
like it. Unless you are going to use your custom mailbox extensively and
for professional purposes, the free plan may suffice. In fact, you may cre-
ate an alias address on Migadu to forward emails to your other email ac-
counts (such as Gmail) if you do not care about an actual custom mailbox.
Migadu has provided detailed instructions on how to set the MX records
for your domain.
4
https://www.netlify.com/blog/2017/02/28/to-www-or-not-www/
5
https://en.wikipedia.org/wiki/Netizen
6
https://gsuite.google.com
7
https://www.migadu.com
D
Advanced Topics
In this appendix, we talk about a few advanced topics that may be of in-
terest to developers and advanced users.
127
128 D Advanced Topics
When your website project is under version control in the RStudio IDE,
continuously previewing the site can be slow, if it contains hundreds of
files or more. The default publish directory is public/ under the project
root directory, and whenever you make a change in the source that trig-
gers a rebuild, RStudio will be busy tracking file changes in the public/
directory. The delay before you see the website in the RStudio Viewer can
be 10 seconds or even longer. That is why we provide the option blog-
down.publishDir. You may set a temporary publish directory to generate
the website, and this directory should not be under the same RStudio
project, e.g., options(blogdown.publishDir = '../public_site'), which
means the website will be generated to the directory public_site/ under
the parent directory of the current project.
To understand the option blogdown.new_bundle, you have to know the con-
cept of page bundles2 in Hugo. When this option is set to TRUE, the func-
tion blogdown::new_site() (or the RStudio addin “New Post”) will create
a new post as the index file of a page bundle, e.g., post/2015-07-23-bye-
world/index.md instead of post/2015-07-23-bye-world.md. One benefit of
using a page bundle instead of a normal page file is that you can put re-
source files associated with the post (such as images) under the same di-
rectory of the post. This means you no longer have to put them under the
static/ directory, which is often confusing to Hugo beginners.
D.2 LiveReload
As we briefly mentioned in Section 1.2, you can use blog-
down::serve_site() to preview a website, and the web page will be
2
https://gohugo.io/content-management/page-bundles/
D.2 LiveReload 129
automatically rebuilt and reloaded in your web browser when the source
file is modified and saved. This is called “LiveReload.”
We have provided two approaches to LiveReload. The default approach
is through servr::httw(), which will continuously watch the website di-
rectory for file changes, and rebuild the site when changes are detected.
This approach is relatively slow because the website is fully regenerated
every time. This may not be a real problem for Hugo, because Hugo is
often fast enough: it takes about a millisecond to generate one page, so a
website with a thousand pages may only take about one second to be fully
regenerated.
If you are not concerned about the above issue, we recommend that you
use the default approach, otherwise you can set the global option op-
tions(blogdown.generator.server = TRUE) to use an alternative approach
to LiveReload, which is based on the native support for LiveReload from
the static site generator. At the moment, this has only been tested against
Hugo-based websites. It does not work with Jekyll and we were not suc-
cessful with Hexo, either.
This alternative approach requires an additional R packages to be in-
stalled: processx (Csárdi and Chang, 2019). You may use this approach
when you primarily work on plain Markdown posts instead of R Mark-
down posts, because it can be much faster to preview Markdown posts
using the web server of Hugo. The web server can be stopped by blog-
down::stop_server(), and it will always be stopped when the R session is
ended, so you can restart your R session if stop_server() fails to stop the
server for some reason.
The web server is established via the command hugo server (see its doc-
umentation3 for details). You can pass command-line arguments via the
global option blogdown.hugo.server. The default value for this option is
c('-D', '-F'), which means to render draft and future posts in the pre-
view. We want to highlight a special argument --navigateToChanged in a
recent version of Hugo, which asks Hugo to automatically navigate to the
changed page. For example, you can set the options:
3
https://gohugo.io/commands/hugo_server/
130 D Advanced Topics
Then, when you edit a source file under content/, Hugo will automatically
show you the corresponding output page in the web browser.
Note that Hugo renders and serves the website from memory by default,
so no files will be generated to public/. If you need to publish the pub-
lic/ folder manually, you will have to manually build the website via blog-
down::hugo_build() or blogdown::build_site().
• If your website does not work without the full baseurl, or you do not
want the draft or future posts to be published, you should not pub-
lish the public/ directory generated by serve_site(). Always run blog-
down::build_site() or blogdown::hugo_build() before you upload this
directory to a web server.
• If your drafts and future posts contain (time-)sensitive information,
you are strongly recommended to delete the /public/ directory before
you rebuild the site for publishing every time, because Hugo never
deletes it, and your sensitive information may be rendered by a certain
build_site(local = TRUE) call last time and left in the directory. If the
132 D Advanced Topics
website is really important, and you need to make sure you absolutely
will not screw up anything every time you publish it, put the /public/
directory under version control, so you have a chance to see which files
were changed before you publish the new site.
wc -l ../R/*.R ../inst/scripts/*.R
53 ../R/addin.R
49 ../R/clean.R
59 ../R/format.R
439 ../R/hugo.R
215 ../R/install.R
30 ../R/package.R
165 ../R/render.R
173 ../R/serve.R
38 ../R/site.R
632 ../R/utils.R
107 ../inst/scripts/insert_image.R
102 ../inst/scripts/new_post.R
27 ../inst/scripts/render_page.R
6 ../inst/scripts/render_rmarkdown.R
80 ../inst/scripts/update_meta.R
2175 total
After blogdown compiles each Rmd document to HTML, it will try to de-
tect the dependencies (if there are any) from the HTML source and copy
them to the static/ folder, so that Hugo will copy them to public/ later.
The detection depends on the paths of dependencies. By default, all de-
pendencies, like R plots and libraries for HTML widgets, are generated
to the foo_files/ directory if the Rmd is named foo.Rmd. Specifically, R
plots are generated to foo_files/figure-html/ and the rest of files under
foo_files/ are typically from HTML widgets.
the *.html file and its dependencies will remain the same when they are
copied to public/, so all links will continue to work.
• If you choose to ignore _files$ or have customized the permalinks op-
tion, you need to make sure blogdown can recognize the dependencies.
One approach is to use the path returned by the helper function blog-
down::dep_path() to write out additional dependency files. Basically this
function returns the current fig.path option in knitr, which defaults to
*_files/figure-html/. For example, you can generate a plot manually
under dep_path(), and blogdown will process it automatically (copy the
file and substitute the image path accordingly).
If you do not understand all these technical details, we recommend that
you use the first choice, and you will have to sacrifice custom perma-
nent links and clean URLs (e.g., /about.html instead of /about/). With
this choice, you can also customize the fig.path option for code chunks
if you want.
{{ if not .Params.exclude_jquery}}
<script src="path/to/jquery.js"></script>
{{ end }}
Then if you set exclude_jquery: true in the YAML metadata of a post, the
theme’s jQuery will not be loaded, so there will not be conflicts when your
HTML widgets also depend on jQuery.
D.7 Version control 137
blogdown
public
The blogdown/ directory is used to store cache files, and they are most
likely to be useless to the published website. Only knitr may use them,
and the published website will not depend on these files.
The public/ directory should be ignored if your website is to going to be
automatically (re)built on a remote server such as Netlify.
As we mentioned in Section D.5, R plots will be copied to static/, so you
may see new files in GIT after you render an Rmd file that has graphics
output. You need to add and commit these new files in GIT, because the
website will use them.
Although it is not relevant to blogdown, macOS users should remember
to ignore .DS_Store and Windows users should ignore Thumbs.db.
If you are relatively familiar with GIT, there is a special technique that
may be useful for you to manage Hugo themes, which is called “GIT sub-
modules.” A submodule in GIT allows you to manage a particular folder of
the main repository using a different remote repository. For example, if
you used the default hugo-lithium from my GitHub repository, you might
7
https://github.com/bhaskarvk/widgetframe
138 D Advanced Topics
In general, if you are happy with how your website looks, you do not need
to manage the theme using GIT submodules. Future updates in the up-
stream repository may not really be what you want. In that case, a physi-
cal and fixed copy of the theme is more appropriate for you.
$for(header-includes)$
$header-includes$
$endfor$
$if(highlighting-css)$
<style type="text/css">
$highlighting-css$
</style>
D.8 The default HTML template 139
$endif$
$for(css)$
<link rel="stylesheet" href="$css$" type="text/css" />
$endfor$
$for(include-before)$
$include-before$
$endfor$
$if(toc)$
<div id="$idprefix$TOC">
$toc$
</div>
$endif$
$body$
$for(include-after)$
$include-after$
$endfor$
---
title: "Hello World"
author: "Yihui Xie"
---
Then blogdown will read the YAML metadata of the Rmd source file, and
insert the metadata into the HTML file so it becomes:
---
title: "Hello World"
author: "Yihui Xie"
---
---
title: "Hello World"
D.9 Different building methods 141
8
Honestly, it was originally designed for Yihui himself to build his own website, but
he realized this feature could actually free users from Hugo. For example, it is possible
to use Jekyll (another popular static site generator) with blogdown, too.
E
Personal Experience
Google has provided several tools to help you know more information
about your website. For example, Google Analytics1 can collect visitor
statistics and give speed suggestions for your website. Google Webmas-
ters2 can show you the broken links it finds. I use these tools frequently
by myself.
I firmly believe in the value of writing. Over the years, I have written
more than 1000 posts in Chinese and English. Some are long, and most
are short. The total size of these text files is about 5 Mb. In retrospect,
most posts are probably not valuable to general readers (some are ran-
1
https://analytics.google.com
2
https://www.google.com/webmasters/
143
144 E Personal Experience
dom thoughts, and some are my rants), but I feel I benefitted a lot from
writing in two aspects:
ter going back to that post a few times, finally I can remember how to use
these regular expressions.
Bibliography
Allaire, J., Xie, Y., McPherson, J., Luraschi, J., Ushey, K., Atkins, A., Wick-
ham, H., Cheng, J., Chang, W., and Iannone, R. (2019). rmarkdown: Dy-
namic Documents for R. R package version 1.18.2.
Csárdi, G. and Chang, W. (2019). processx: Execute and Control System Pro-
cesses. R package version 3.4.1.
Karambelkar, B. (2017). widgetframe: Htmlwidgets in Responsive iframes. R
package version 0.3.1.
R Core Team (2019). R: A Language and Environment for Statistical Comput-
ing. R Foundation for Statistical Computing, Vienna, Austria.
Vaidyanathan, R., Xie, Y., Allaire, J., Cheng, J., and Russell, K. (2019). html-
widgets: HTML Widgets for R. R package version 1.5.1.
Wickham, H. and Hesselberth, J. (2019). pkgdown: Make Static HTML Doc-
umentation for a Package. R package version 1.4.1.
Xie, Y. (2016). bookdown: Authoring Books and Technical Documents with R
Markdown. Chapman and Hall/CRC, Boca Raton, Florida. ISBN 978-
1138700109.
Xie, Y. (2018). animation: A Gallery of Animations in Statistics and Utilities to
Create Animations. R package version 2.6.
Xie, Y. (2019a). bookdown: Authoring Books and Technical Documents with R
Markdown. R package version 0.16.1.
Xie, Y. (2019b). knitr: A General-Purpose Package for Dynamic Report Genera-
tion in R. R package version 1.26.1.
Xie, Y. (2019c). servr: A Simple HTTP Server to Serve Static Files or Dynamic
Documents. R package version 0.15.
147
148 E Bibliography
149
150 Index
R Markdown, 13, 99
R Markdown Site Generator, 95
RStudio addins, 5
Shortcode, 34
single.html, 43
Site Migration, 81
Slug, 31
Static Directory, 63
Static Site, 25
Syntax Highlighting, 40, 55
Table of Contents, 56
Tags, 57
Templates, 40
terms.html, 47
Themes, 19, 36
TOML, 28
Travis CI, 74
uglyURLs, 32
Updog, 70
WordPress, 86
XMin Theme, 41