20 min read

Getting started with blogdown on Synology

Setting up a website using blogdown/RStudio, hosted on a Synology NAS.


  • RStudio.
  • blogdown and dependencies: Yihui Xie et al.’s Creating Websites with R Markdown (the “blogdown book”) is well worth reading in its entirety, but this post assumes you have read at least the Installation section (and followed the instructions therein).
  • A Synology NAS1.

Initial setup

We will broadly follow setup suggested in the blogdown book getting started section.

First, though, we set up/initialise a remote git repository. If you don’t have the Synology git package installed already, install it from Package Center, then ssh into the server with admin privileges and execute a git init (repalcing /volume1/git/michaelbolgernet.git with your desired repository path/name; the --bare option is required for a remote repository):

git init --bare /volume1/git/michaelbolgernet.git

Alternatively, any other git host will do (or none, though then disregard other git-relevant instructions below).

Clone the empty repository onto your local machine in RStudio via File > New Project > Version Control > Git, then enter the repository URL etc:

Or clone the repository directly in the Terminal:

git clone ssh://[email protected]:2222/volume1/git/michaelbolgernet.git michaelbolger.net

In either case, the URL format is the same:

protocol://[email protected][domain or IP address][:port if non-standard]/path/to/repository.git /path/to/localrepository

Next, run File > New Project again, but this time selecting the New Directory > Website using blogdown option to create the basic site framework:

Generally, a new RStudio project should be created in an empty directory. For these purposes, however, the project directory we have just created by cloning the bare remote repository is sufficiently empty.

  • The ‘Install Hugo automatically’ option does nothing if Hugo is already installed, and so can be left enabled.
  • ‘Add sample blog posts’ adds a sample R Markdown post from the blogdown package, which may be useful in helping understand the capabilities of R Markdown, but can be ignored if you are already familiar with this.
  • ‘Add the example site of the theme’ copies the example site for the theme into the main site directory. While optional–these files are anyway available within the themes/<theme name>/exampleSite directory–it is nevertheless helpful, especially when first setting up/exploring a site.

Regarding selecting a theme, don’t get side-tracked by shiny things–you will waste a lot of time before you end up back with the blogdown default theme (Lithium) or another one of the blogdown-recommended themes (see the getting started section of the blogdown book)2. See Themes, below, for further discussion on selecting a Hugo theme.


The config.toml file in the site’s root directory is the main configuration file. Edit this file as required3. My config.toml ended up like this (material deviations from the default Lithium config.toml are commented):

baseurl = "http://michaelbolger.net/"
languageCode = "en-us"
title = "michaelbolger.net"
theme = "hugo-lithium"
author = "Michael Bolger"
# googleAnalytics = ""
# disqusShortname = "michaelbolger"
ignoreFiles = ["\\.Rmd$", "\\.Rmarkdown$", "_files$", "_cache$"]

# Amend default Lithium permalink behaviour.
   post = "/post/:year-:month-:day-:slug/"

    name = "Home"
    url = "/"
    weight = 1
    name = "About"
    url = "/about/"
    name = "Projects"
    url  = "/projects/"
# [[menu.main]]
#     name = "GitHub"
#     url = "https://github.com/rstudio/blogdown"
    name = "Twitter"
    url = "https://twitter.com/michaelbolger"

    description = "Tech and other musings, primarily relating to Apple, Synology, and R."

    # options for highlight.js (version, additional languages, and theme)
    highlightjsVersion = "9.12.0"
    highlightjsCDN = "//cdnjs.cloudflare.com/ajax/libs"
    highlightjsLang = ["r", "yaml", "ini", "bash"]
    highlightjsTheme = "github"

    # MathJaxCDN = "//cdnjs.cloudflare.com/ajax/libs"
    # MathJaxVersion = "2.7.5"

    # path to the favicon, under "static"
    favicon = "favicon.ico"

    url = "logo.png"
    width = 50
    height = 50
    alt = "Logo"

I changed the permalink behaviour as I feel, for a low-volume site, having nested directories for each month/year is too crufty. See the configuration section of the blogdown book for further details on these settings.


By default, the Lithium theme has support for Google Analytics, Disqus comments, highlight.js syntax highlighting, and MathJax math expressions. I will not use Google Analytics and so commented it out of config.toml4.

To enable Disqus comments, you have to register to install Disqus on your site (select ‘Install Disqus on my site’ if prompted). Enter your website name, customising the ‘Shortname’ as required. This is the name that should be entered against the disqusShortname in config.toml5. Amend other site settings in Disqus as appropriate, although Disqus will work with default values.

highlight.js works out of the box. The only change I made to config.toml was to add support for toml and bash as additional languages (highlightjsLang = ["r", "yaml", "ini", "bash"]–in highlight.js, toml shares a syntax highlighting scheme with ini, which is listed first, so add ini to get toml syntax highlighting without any build errors.

I’m unlikely to use math expressions on my site, so I have commented out the MathJax parameters, although the related JavaScript is still copied across to the public folder when the site is built.


Create source documents for any pages you specified in config.toml–there is an About page in the Lithium example site, but you’ll need to edit its content, and the Projects page does not exist. From the RStudio Addins toolbar button, select New Post then amend as appropirate:

Note, to create a top-level page, ensure ‘Subdirectory’ is blank.

Create a new post in the same way:

When you create a post in this way, the date you include for the post is included as part of the filename of the file (and in the YAML metadate). You can amend the suggested filename when creating a post, but I like to keep the date in there so that sorting the files by name in a directory listing also sorts them by date.

You can use a date in the future for a post that is a work-in-progress. Similarly, you can add draft: true to the post’s YAML metadata. In each case, such posts are rendered locally (e.g. when using the Serve Site add-in), but are not rendered when building the site for deployment6.

You can use the Update Metadata add-in to, well, update a post’s metadata:

As noted in the blogdown book, this is primarily useful for updating categories and tags as it prompts you with those you have already used in other posts. Changing the date or slug (or title, if it is used to define your permalinks), or selecting the ‘Rename file if the date is changed’ option, should be done with caution, as these actions will likely break any existing links to the post. In general, if a post is new and there are no other references to it in your blog, it’s safe to change these metatdata at will (though see caveat in Images and static files, below), but it is best avoided post publication.

(R) Markdown

When creating a post in blogdown, you have a choice of Markdown versions–the blogdown book has a section on this. My primary motivation for using blogdown is R Markdown support, so .Rmd is a natural choice. However, as explained in the blogdown book, posts in .Rmd format create intermediate .html files in the content directory and so, when writing entirely text-based posts with simple formatting, I will sometimes use .md files. However, even if a post does not contain R code that is intended to be run, it may be useful to use the .Rmd format so as to take advantage of formatting options that are only available when using .Rmd, such as syntax highlighting and proper footnotes (you can have footnotes in .md posts but, at least with the Lithium theme, you don’t get the cute return arrow (↩) and in the source .md file, you can’t break your footnote over multiple lines).

Files using the alternative R Markdown file format, .Rmarkdown, have certain limitations compared to .Rmd files, and still create intermediate files (in this case, .md files), so I will use .Rmd or .md.

Categories versus tags

Hugo (and blogging software more generally) allows you to categorise and tag your posts. In practice (at least, for me) these are functionally identical, so semantically it helps to think of categories as high-level topics–a post would fall into perhaps one or two topic areas–whereas it could have many tags. Also, in other blogging systems, categories are hierarchical, though this is not currently the case with Hugo.

Images and static files

To insert an image into a post, use the Insert Image add-in (Addins > Insert Image):

Leave the ‘Alternative text’ field blank–blogdown (and/or the Lithium theme) seems to insert the text as body text, rather than creating a HTML alt text tag.

When using the Insert Image add-in, the image is copied to a location in the static directory. By default, the directory is the filename of the post (without extension) plus _files, and the relevant file path is written into the post. If the filename of the post is later changed (perhaps when updating the metadata and selecting the ‘Rename file if the date is changed’ option), the image file in the static directory is not updated. As the paths to the images in the post are also not updated, the links to the images will still work, but if any further images are inserted, a new _files directory will be created for the post.

A further consequence of this workflow is that these are one-time actions, not part of the automated process of building the site. Accordingly, if you have images (or other assets) in static that were put there for a now-deleted post, they will be orphaned. These can be tidied manually, but there is no straight-forward automated way of clearing them out7.

Code chunks

The great power of R Markdown comes when using code chunks. Code chunks can be used to run any supported code (R, bash, as well as Python/SQL/etc. if supported on your system). Typically, in the context of a blogdown site, code will be used to create output to be included in the post, e.g. a chart–the code is run when the site is built, and the output is copied as part of the generated static files to the public directory. Another use for code chunks is to display code with syntax highlighting, which is great for readability, particularly for longer sections of code.

However, by default, code included in a code chunk is run as-is, and there is no restriction on what it can do. This is potentially Very Important, depending on what code you include in your code chunks. Say you want to display the bash command echo Hi > ~/hi.txt, nicely formatted in a code block with proper bash syntax highlighting:

echo Hi > ~/hi.txt

You might include the following code chunk in your source R Markdown file:

echo Hi > hi.txt

If you did this with default settings, you would be leaving a message to yourself in your root user directory, every time you built your site:

Imagine the mess you could get yourself into if you were writing a post on dangerous terminal commands, say8.

Chunk options

To protect against such issues, we set appropriate chunk options9. When writing a tutorial-type post such as this one, I often include code that I ran when implementing the project that I’m documenting, but which, as in the examples above, I don’t want to run each time the post is rendered (for example, the git commands in Initial setup). To display sample code in a chunk option, but not run it, set the chunk option eval=FALSE:

```{bash eval=FALSE}
echo Hi > hi.txt

To be on the safe side, I set global chunk options for .Rmd posts by including the following code chunk immediately after the YAML metadata, right at the top of the post.

```{r include=FALSE}
knitr::opts_chunk$set(eval=FALSE, warning=FALSE, message=FALSE)

In this chunk:

  • include=FALSE supresses any output from the code in this chunk being included in the rendered web page (even though opts_chunk doesn’t return anything anyway–better safe than sorry).
  • eval=FALSE is the important bit: when set to FALSE, code in chunks is not run. Once this is set as a global option, the code in any code chunks is not run by default. To run the code in a particular chunk, set the chunk option eval=TRUE for that chunk.
  • warning=FALSE, message=FALSE: these chunk options suppress warnings and messages (messages are warning-like messages emitted by message()) in the chunk output. If using R code to create a chart, for example, we don’t want warnings about unknown database field types being imported as character, say.

Finally, for syntax highlighting to work correctly, add the relevant language code to the chunk. The ‘Insert’ drop-down in a .Rmd document’s toolbar already populates this for the languages it supports, but you can also add additional languages; for example, the chunk options for the config.toml code block above look like this:

baseurl = "http://michaelbolger.net/"

Be sure that any languages you use are also specified in the config.toml highlightjsLang parameter. As noted in the config.toml section, use ini in place of toml to indicate the apropriate language for syntax highlighting10.

Site preview

To preview your site, use the Serve Site add-in–this will start an internal web server and serve your site locally on your computer. The console output will tell you the IP address and port to view your site, e.g.


This completes the basic setup but before we commit our changes, we should ignore unnecessary files to keep the remote repository clean. Amend the root .gitignore file as follows:

# RStudio defaults

# macOS

# blogdown generated files

We can ignore the public directory because its contents are generated automatically. As noted, .Rmd files clutter up the contents directory with corresponding .html files, so these can be ignored too. The /**/ syntax ignores matching files in the contents directory and any sub-directories.

Commit changes using RStudio’s git functionality, or directly at the Terminal:

git add -A
git commit -m "Initial commit of blogdown site."
git push origin master

Hosting setup

To host a website on a Synology, it must be running a web server–this is managed using the Web Station package. On your Synology, install the Web Station package from the Package Center. The default Web Station settings are fine to leave as is.

Deploying your site

To deploy your site:

  • Session > Restart R in RStudio.
  • Delete the local copy of your site’s public directory (don’t worry–it’s automatically re-created).
  • Run build_site() to build the site for deployment.
  • Delete the default contents of the web shared folder (index.html and web_images directory), and copy all of the contents of your site’s public directory to here. The easiest way to copy across your site is to log in to your Synology and use the File Station app–you can drag and drop from the Finder directly in web folder in File Station.

You can now access and browse your site, while on the same network, by entering the IP address of your Synology:


Your site is now on your Synology, but nobody outside of your network can see it because your domain does not yet point to it. To fix this:

  • Your Synology must be accessible from the internet. This means that the appropriate ports must be forwarded from your router to your Synology, and you need to have a way of keeping track of your public IP address (unless you have a fixed IP address, which is generally not the case for most home broadband services).
  • The DNS settings for your domain name must ultimately resolve to your Synology.

Port forwarding and DNS

The easiest way forward the relevant ports (80 and 443) is to use Synology’s EZ-Internet wizard11. This should work if you have Universal Plug and Play (UPnP) enabled on your router.

If you choose not to enable UPnP, or if the wizard doesn’t work, you need to manually forward ports 80 and 443 on your router to your Synology–how to do this is router-specific, but there should be the facility to forward ports somewhere within your router’s management page. Port 80 is for regular (http) traffic, whereas 443 is for encrypted (https) traffic. Even though encryption is not (yet) setup, it’s fine to forward both ports.

Once port forwarding is complete, your Synology is accessible from the internet, at the IP address allocated to you by your ISP (you can check what this is with Google). Confirm this by navigating to your public IP address–you should see your site:

Dynamic DNS

IP addresses assigned for home broadband services are generally not static. A dynamic DNS service will keep track of your public IP address and let you access your Synology using an unchanging domain name. Synology supports several such services, and offers its own. The walk-through below uses the Synology service, but the steps should be equivalent for others.

First, register for a Synology account12. Follow the prompts to register an account (home user is fine). While registering, I recommend signing up for the NAS-related security advisory emails. I also recommend turning on two-factor authentication, and of course, using a strong, unique password (and a password manager).

Once registered, log into your account on your Synology (Control Panel > Info Center > Synology Account tab). Then go to Control Panel > External Access > DDNS tab and select Add. Complete as appropriate, then select ‘Test Connection’–you should get a ‘Status: Normal’ result. Your public IP address is auto-detected.

Once complete, you should be able to connect to your Synology at your_subdomain.synology.me.

Domain name routing

You now need to connect your actual domain name with your DDNS domain name. First of all, you need a domain name. If you don’t have one already, I strongly recommend Hover. Once you have registered your domain, select the DNS tab and then ‘Add a Record’:

This adds a ‘CNAME’ record to your domain name’s DNS settings. In this example www.michaelbolger.net points to michaelbolger.synology.me, which through the Synology DDNS service resolves to your home network’s current public IP address (and hence to your Synology, via port 80 that you forwarded from your router). Your site should now be accessible at www.yourdomain.net.

Note that you need to include the www part of the domain. Forwarding an apex domain (i.e., michaelbolger.net, without any subdomain) requires using Cloudflare to manage your domain’s DNS settings, which will be the subject of a later post.


To update, follow the steps set out in Deploying your site, above, bearing in mind that any changes you deploy are now available for all the world to see13. To recap:

  • Session > Restart R in RStudio.
  • Delete the local copy of your site’s public directory.
  • Run build_site() to build the site for deployment.
  • Copy all of the contents of your site’s public directory to the web directory of your site (select ‘Overwrite’).

If you have made any changes that change filenames/paths or delete files (e.g. removing an inserted image), make sure you manually delete the corresponding files in your web directory, otherwise they will still be available, even if they are not linked from anywhere else on your site. A belt-and-braces approach is delete the entire contents of your web directory and re-upload the whole site.

This overwrite/delete and re-upload deployment workflow is ineffiient, and may become cumbersome as your site grows larger–it won’t all be available for the period between the delete and the completion of the upload, for example–but should be manageable for sites with a modest number of pages/posts. We will look at more efficient deployment methods in a later post.

One gotcha to look out for is, if you are making edits to a post that has already been published, when you re-upload your site and navigate to the post in your browser, you may still see the old version that was cached by your browser. If you reload, it should load the new version (unless you are being served a cached-version from elsewhere, in which case you might just have to wait until that cache is refreshed).


This is down here because I think your first priority should be getting a working site up and running, rather than agonising over selecting and tweaking a theme.

When selecting a theme, consider what you want, and in what order. I wanted:

  • blogdown/R Markdown/RStudio compatability
  • Responsive design
  • Top navigation elements (i.e., no side-bar)
  • Syntax highlighting
  • Disqus support
  • Sparse design aesthetic
  • Custom fonts

Then, focus on finding a theme that matches the functionality you want, rather than one you find aesthetically pleasing. I initially settled on themes that matched the design aesthetic I was looking for, for example Introduction, but this proved far too complex for me to get it to play well with blogdown. I then tried Black & Light which is a much simpler theme, but even then I spent fruitless hours trying to get it to work seamlessly with blogdown. So finally I came back to the default Lithium theme, which supports all of the core functionality I want, and I will work on tweaking its aesthetics to make it more to my liking.

Next steps

  • Add TLS encryption with Let’s Encrypt.
  • Use git functionaltiy to maintain link to upstream theme code, while allowing for tweaks specific to this site.
  • Use custom fonts.
  • Tweak Lithium theme to make the look and feel more to my liking.

  1. I have a DS916+ Synology, with DSM version 6.2.1, but any modern Synology with a recent version of DSM should work.

  2. I know: I did.

  3. If you did not enable the ‘Add the example site of the theme’ option when creating the new website project, copy the theme config.toml (e.g. themes/hugo-lithium/exampleSite/config.toml) to your site’s root directory, overwriting the minimal defaults file that was generated during the New Project process.

  4. There is a references to Google Analytics in partials/footer.html template, but this is inactive without a valid Google Analytics ID.

  5. I have disabled Disqus for now because, by default, the Lithium theme shows comments on every page, including the About and Project pages, where I don’t want people commenting.

  6. Note, however, that any images for your draft post that have been placed in the static directory (by using the Insert Image add-in) are copied to the public/static directory, and so can be found on your deployed site, although they will not be linked anywhere.

  7. In the RStudio Build tab, in the More dropdown, there is a ‘Clean All’ command, however beware of this. This calls the clean_site function from the rmarkdown package, which deletes unnecessary files (the public directory and the .html file counterparts to any .Rmd posts in the contents directory), but it also deletes the contents of the static directory. If you used the ‘Insert Image’ add-in, this is where the image files are copied, so you will lose all your images. More generally, the RStudio Build tab is an interface to the rmarkdown package rather than to blogdown, and so is best ignored.

  8. It’s actually even worse if you are using the Serve Site add-in to live preview your site–the site is built (and hence, any code in code chunks executed) every time you save, so a stray rm -rf / in a code chunk could delete your whole filesystem the next time you save.

  9. Chunk options are an essential part of what makes R Markdown so useful, but they are extensive and can be a bit overwhelming, so I always keep a tab with the chunk options reference page handy.

  10. Using toml instead of ini in the highlightjsLang parameter and in chunk options seems to work insofar as syntax highlighting is applied, but I get a warning when building the site (Unknown language engine 'toml' (must be registered via knit_engines$set())). Using ini, which is listed together with toml on the highlight.js page, results in the same syntax highlighting and no error, so I’ll just use that instead.

  11. There is another way to access Synologys from the internet, which is to use the Quickconnect feature, however this does not seem to work for websites hosted with Web Station–it’s primarily intended for accessing built-in Synology services like Drive without having to deal with port forwarding, DDNS etc.

  12. The Synology account registration page can be a bit flakey–the registration process would not complete for me with Safari on a Mac, but worked OK with Chrome on a PC.

  13. See Hosting multiple sites on one Synology for details on how to set up a staging version of your website alongside your live one.