D.5 Paths of figures and other dependencies

One of the most challenging tasks in developing the blogdown package is to properly handle dependency files of web pages. If all pages of a website were plain text without dependencies like images or JavaScript libraries, it would be much easier for me to develop the blogdown package.

After blogdown compiles each Rmd document to HTML, it will try to detect 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 dependencies, 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.

R plots under content/*/foo_files/figure-html/ are copied to static/*/foo_files/figure-html/, and the paths in HTML tags like <img src="foo_files/figure-html/bar.png" /> are substituted with /*/foo_files/figure-html/bar.png. Note the leading slash indicates the root directory of the published website, and the substitution works because Hugo will copy */foo_files/figure-html/ from static/ to public/.

Any other files under foo_files/ are treated as dependency files of HTML widgets, and will be copied to static/rmarkdown-libs/. The original paths in HTML will also be substituted accordingly, e.g., from <script src="foo_files/jquery/jquery.min.js"> to <script src="/rmarkdown-libs/jquery/jquery.min.js">. It does not matter whether these files are generated by HTML widgets or not. The links on the published website will be correct and typically hidden from the readers of the pages.47

You should not modify the knitr chunk option fig.path or cache.path unless the above process is completely clear to you, and you want to handle dependencies by yourself.

In the rare cases when blogdown fails to detect and copy some of your dependencies (e.g., you used a fairly sophisticated HTML widget package that writes out files to custom paths), you have two possible choices:

  • Do not ignore _files$ in the option ignoreFiles in config.toml, do not customize the permalinks option, and set the option uglyURLs to true. This way, blogdown will not substitute paths that it cannot recognize, and Hugo will copy these files to public/. The relative file locations of 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 option, you need to make sure blogdown can recognize the dependencies. One approach is to use the path returned by the helper function blogdown::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 permanent 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.


  1. For example, a reader will not see the <script> tag on a page, so it does not really matter what its src attribute looks like as long as it is a path that actually exists.