Best way to include JavaScript libraries in Hugo sites

I would like to use the PhotoSwipe library on my Hugo site and I’m wondering what the best way to include this library is. I could of course just download the files from the git repo and include them in my project, but I would like to know if there’s anyone using a different method.

I would like to retain some link to the project to easily update to newer versions. Preferably, I would also like to group all JavaScript libraries in a vendor or lib folder, e.g. /static/js/vendor/photoswipe/, separate from my own JavaScript.

So far, I have thought of or found a few options.

  • Using npm and a symlink
    As described in Reference js file outside assets directory, install using npm and create a symlink inside /static/js to /node_modules.
  • Using npm and a build tool
    It’s always possible to use gulp or grunt to move js files around before starting Hugo, but I’d preferably have as few dependencies as possible, and use the new Hugo pipes feature where applicable.
  • Clone the library’s repo into /static/js
    Either using git clone or git submodule. The disadvantage of this is that the entire repo is cloned, where usually only the dist folder is needed.

I’m curious if anyone uses or knows any other options. Thanks!


Yet another option: supporting JS/CSS includes in your template based on frontmatter variables. This would allow you to use CDN links so you could forego including the libraries in your source code all together if you like. Otherwise you can use relative links if you want to drop the javascript or CSS files in your static directory.


title: Some catchy title

Template partial

You could also support multiple includes if you want to provide scripts as an array in your front matter; you’d just loop through and add a script tag for each entry under js. The same process works for CSS.

CSS Partial

{{ if .Params.css }}
<link rel="stylesheet" href="{{ .Params.css }}">
{{ end }}

JS Partial

{{ if .Params.js }}
<script src="{{ .Params.js }}"></script>
{{ end }}

I like this solution! Besides using a CDN, this method allows for only loading the js and css on pages where it’s needed as well. Thanks a lot!

I’d recommend fetch injecting the assets and inlining the data URIs so you can deep link to gallery images without blocking the page load, leaving the user wait staring at a blank page until libs load. I’ve left an example of how to download this for PhotoSwipe here (requires some changes to data URIs):

Also, if you’d like a theme component which adds PhotoSwipe to Hugo PM me for the link.

1 Like

For JS files included in the site itself, it would be best to include them in the assets/ dir instead of static/, as assets/ allows for automatic minification and fingerprinting, among other things.

I’ve spent some time trying to optimize the inclusion of assets on pages in my own theme, as part of a plugin structure I’ve implemented. If you’d like, some related files are linked below:

head.html - loads CSS files in the <head> of the website.
foot.html - loads JS files at the end of the <body> of the website.
styles - partial that generates <link> tags based on items in a dict.
script - partial that generates <script> tags based on items in a dict.
katex/foot.html - a partial included into foot.html when the katex shortcode appears on a page. Uses script.
katex/head.html - a partial included into head.html when the katex shortcode appears on a page. Uses styles.

The global head.html and foot.html partials also leverage a parameter $.Site.Params.http2 to determine whether common assets get bundled into one file (http2 = false) or not (http2 = true).

The theme hasn’t been released yet, as I have a lot of documentation left to write regarding the plugins system. If there are questions regarding how I’ve implemented any of this, though, I’m happy to answer them.

Quick edit: Forgot to mention that the partials load configurations from the theme and from the site to determine things like which partials to load for which plugin and whether the partial can be cached or not. This would allow e.g. someone to create their own PhotoSwipe shortcode for their site and fully integrate it into the theme without modifying any theme files.



You can clone locally a library’s entire repo but with some rules in .gitignore you can have everything from that repo ignored except for what you need.

For example I am using Lazy Sizes to lazy load images etc.

I have cloned its repo in a Hugo project under /assets/js/lazysizes

And then in the .gitignore file under the root of my Hugo project I have the following rules:


And only the lazysizes.min.js will be committed to my remote.

P.S.1. Also one could clone a library’s repo outside of a Hugo project and then create symlinks under /assets/ to whatever resources are needed. However if this approach is favored then this tip is very useful if one is going to deploy to a service like Netlify.

P.S.2. I have used NPM in the past but I got tired of the constant Node updates, it was too much for me. I have a minimalistic approach to web development. So I prefer using git and submodules to keep dependencies up to date.


How you get it working? I create in myproject/⁨themes⁩/mytheme/⁨assets folder a symbolic link to myproject/⁨node_modules but it doesn’t work. It says:

Symbolic links for directories not supported, skipping " ... assets/node_modules"

This is ERROR has been around forever but before Hugo 0.50 it didn’t really cause any problems.

Now with the recent in-browser ERROR messages it blocks the rendering of a site. See this thread for a workaround:

1 Like

Ok but doing so I also lose all the other “real” errors? That’s not so good. There is another solid solution?

No. But there is a GitHub issue that hopefully will allow symlinks under /assets/

Best way from hugo 0.56