Hugo MultiSite workflow?

I’ll be using 3 custom Hugo themes to power about 20 different websites. So I’m trying to understand how to best structure my workflow to maintain some autonomy and sanity when updating the themes. All the websites will be hosted via GitHub Pages.

Should I be trying to set up my own themes repository in the same manner that the default Hugo themes repository is set up?

I tired to symbolic link the theme files from one Hugo copy on my local machine to another, thinking that could be an option, but I’ve not been able to make that work.

I think creating them as your own theme repository makes a lot of sense. That’s the approach I’d use. It’s a lot less overhead than other solutions I can think of.

Could the Content folder play a part in allowing a MultiSite style of setup?

By default Hugo outputs content to the “public” folder…

hugo
// Runs default content folder to default output folder 

Thanks to one of @natefinch’s many support posts, I noticed there’s a Destination flag (-d) available that allows you to override the default “public” output folder, like so…

hugo -d customOutputFolder
// Runs default content folder to custom output folder 

So in the same manner as the Destination flag, could there be a Content flag (-c) that allows you to set a custom content folder which overrides the default “content” folder?

hugo -c customContentFolder
// Runs custom content folder to default output folder

This would allow content for entirely separate websites to sit inside a single Hugo install. The “content” folder could remain the default content folder for when no -c flag is run (just as “public” is the default output folder when -d is not run), and additional content folders could just be named accordingly.

hugo -c website1Content -d website1Output
hugo -c website2Content -d website2Output
hugo -c website3Content -d website3Output
// Runs custom "content" folders to custom output folders

It’s probably not that simple, but who knows. And while most folk probably aren’t trying to run multiple websites with Hugo, requiring one Hugo install per website is a real barrier. And when you’re using the same theme for all those websites, requiring one Hugo install per website is a real pain in the arse. If there could be a way to house separate content in a single Hugo install that would make Hugo even greater than it already is.

Especially when you could then easily run any website content to any output folder via any theme…

hugo -c website1Content -t allHailHugo -d website1Output
// Runs custom "content" folder to custom output folder
1 Like

There is both a content and publish directory setting in the config file. You could switch config files with “hugo --config=${CONFIG}” and use the config options to read and write to different locations.

1st config:

  contentdir = "content"
  publishdir = "public"

2nd config:

  contentdir = "customer002"
  publishdir = "publish002"
1 Like

Wow. Top tip @michael_henderson. Thanks.

I really need to find where all the config files options are listed. They’ll be somewhere in the core files won’t they? I’ll stalk the Github repo.

Look for init() and InitializeConfig() in hugo/commands/hugo.go.

It turns out that MultiSite works out of the Hugo box.

There were a few config settings I hadn’t paid attention to which already make Hugo MultiSite happen. I’m not sure this the best way to do it, so I’d love some feedback. But I have tested that this Hugo MultiSite workflow works…

  1. Create a top-level folder inside your local Hugo install. I’m calling mine “sites”.
  • Inside the “sites” folder, create a new folder for your new website (ie: “websiteA”)
  • Inside your website folder, create a config.toml file, “content” folder, and “publish” folder
  • Set your content directory in config.toml contentdir = "sites/websiteA/content"
  • Set your publish directory in config.toml publishdir = "sites/websiteA/public"
  • Add your content .md files to your new content folder (sites/websiteA/content/)
  • Tell Hugo which website to work with. Now when you run Hugo, you must tell it where your website config file is, like so… hugo --config="sites/websiteA/config.toml". In effect you’re telling Hugo which website to work with, but that’s only possible if the config file contains the correct details for that website.
  • Repeat Steps 2-6 to create any number of new websites, and be sure to use Step 7 so Hugo knows which website you want to work with

The folder structure is entirely flexible, so you can organise it however you want: just be sure to set “contentdir” and “publishdir” accordingly, and tell Hugo which website to work with. Having a top-level “config” folder with the different .toml config files stored there might be a better option for you, or if you want to keep your runtime keystrokes at a minimum, you can keep the .toml config files at the top-level (just as config.toml is by default) and name them accordingly (“websiteA.toml”, “websiteB.toml”, “websiteC.toml”, etc.) which allows you to run Hugo for any specific website a little quicker…

hugo --config="websiteA.toml" //Runs websiteA
hugo --config="websiteB.toml" //Runs websiteB
hugo --config="websiteC.toml" //Runs websiteC

Hugo is seriously insane, in all the best ways. Thanks @spf13 for giving it to us, and to @natefinch & all the contributors for making it better again. Thanks also to @michael_henderson for the hugo --config="" tip.

10 Likes

I was searching for the a way to handle a multilingual site, when, you have a separate domain per language, like mysite.com and mysite.jp.

Now I just need to figure out how to specify a given content piece’s translations, say in yaml frontmatter. I’m thinking something like:

---
  ...
[translations]
- Japanese
     http://mysite.jp/post/japanese-content-for-this/
- Chinese
     http://mysite.cn/post/chinese-content-for-this/
---

My guess most translations would be site-wide, so a good place to put them would in YAML/TOML/JSON files inside /data:

/data/translations
Japanese.yaml
Chinese.yaml

There are good i18n resources out there that talks about how to structure these files - but having one file per language makes life easier.

In the end you would use these string like:

{{ translations.Japanese.currency.yen }}
1 Like

Ok, but does that mean using those data files for all text? Or only certain strings like date strings?

For the above, I was thinking to have a separate folder for two sites, each uploaded to a different site structure in apache. And, when there is a suitable translation doc in the other site, to use that sort of pseudo-yaml I showed to simply make a list, say, in the sidebar, for what’s available, pulling the URL from the frontmatter.

I might have misunderstood what you’re after.

I have a site which is both in English (lang code en) and Norwegian (lang code no), each with its own domain.

So I have this structure:

config_no.toml
config_en.toml

content/no
content/en
layouts/no
layouts/en

/data/translations
en.toml
no.toml

themes/mytheme

  • So, both languages gets their own content files (md).
  • All UI strings go into /data: Button texts etc.
  • If the above gets too funky, I just put the text directly into the layout file and duplicate that file for each language (Hugo’s theme overrides work great for this).
4 Likes

That’s exactly what I was looking for. Thanks!
Is that one of your repos?

No, bepsays is one language only (or there is a mix, but all mingled together).

The site i talk about above is in a private GitHub repo.

For total separation of the data folders, you could have one data dir per language (and set datadir in config.toml); this will make the lookups easier … but prevents sharing data files.

The big win having these text strings in /data vs config.toml is livereload.

@bep the technique you cite above is working for me quite well, and I’m re-writing my company’s site that way.

Given a locale set in a given language’s config file, I wonder if I can take advantage of the fact with a nested structure:

{{ $.Site.Data.translations.{{ .Site.Params.locale }}.somestring }}

So locale would be en_US or ja_JP, and if I used that same string in my data file, it seems like an easy way to point at a specific string…

Getting late in Japan so I have to crash. Appreciate your opinion, thank you.

Yes, that is a good idea.

Something like this should work:

{{ ( index $.Site.Data.translations  $.Site.Params.locale ).somestring }}

Or

{{ index ( index $.Site.Data.translations  $.Site.Params.locale ) "somestring" }}

Or

{{ $translations := index ( index $.Site.Data.translations  $.Site.Params.locale )  }}
{{ index $translations "somestring" }}

1 Like

Appreciate that @bep; I’ll give it a go.

@bep that worked just fine; thank you once again indeed. I used the first example, with a data/translations.yaml that looks like:

en-US:
  moreinfo: More Info
ja-JP:
  moreinfo: つづきを読む

Then just:

{{ ( index $.Site.Data.translations  $.Site.Params.locale ).moreinfo }}

… in the template.

I wonder, would it be more efficient to have one file per language, named as the locale:

en-US.yaml
ja-JP.yaml

Could I then access it like this:

$.Site.Data.$.Site.Params.locale.moreinfo

? Somehow that seems like it wouldn’t work.

I would prefer one file per lang. Not more effective as in CPU-effective – but easier to maintain.

The lookup would be exactly the same.

Put en-US.yaml in /data/translations.

1 Like