Generating Jekyll Web-site for Both Domain and Subdomain

When building a web-site one can rely on manual editing of each page HTML and CSS that much, so it was only a matter of time before it became more practical to use web-site generators. Jekyll seemed like a popular choice, offering a lot of flexibility and ease in creating own templates, utilities and plug-ins, so I’ve started fleshing out basic pipeline, layouts and tools with the following requirements:

  1. The main page is located under www.volgar.name, the blog correspondingly goes to blog.volgar.name,Mainly to take advantage of owning a domain. and in order for both to share assets an additional subdomain cdn.volgar.name or media.volgar.name is used.

  2. The web-site is generated as a single-domain version in development environment.Test in production? No thanks.

  3. The templates and the plug-ins are shared between domain and subdomain.Obviously nobody wants to write same things twice or let things get out of sync.

And while there is a variety of plug-ins for Jekyll available in the wild, I wasn’t able to find ones fulfilling these requirements, so I started exploring the options.

The Problem

The web-site directory structure is largely similar to most basic Jekyll sites:

.
├── _config.yml
├── _config_development.yml
├── _data
│   └── ...
├── _includes
│   └── ...
├── _layouts
│   └── ...
├── _plugins
│   └── ...
├── _site
│   ├── blog
│   │   ├── en
│   │   │   ├── 2021
│   │   │   │   └── 10
│   │   │   │       └── 25
│   │   │   │           └── okonomiyaki-recipe
│   │   │   │               └── index.html
│   │   │   ├── 2022
│   │   │   │   └── 06
│   │   │   │       └── 07
│   │   │   │           └── domain-and-subdomain-jekyll
│   │   │   │               └── index.html
│   │   │   └── index.html
│   │   └── ru
│   │       ├── 2021
│   │       │   └── 10
│   │       │       └── 25
│   │       │           └── okonomiyaki-recipe
│   │       │               └── index.html
│   │       └── index.html
│   ├── cdn
│   │   ├── hamburger.svg
│   │   ├── style.css
│   │   ├── style.css.map
│   ├── en
│   │   ├── feed.xml
│   │   ├── index.html
│   └── ru
│       ├── feed.xml
│       └── index.html
├── blog
│   ├── en
│   │   ├── _posts
│   │   │   ├── 2021-10-25-okonomiyaki-recipe.md
│   │   │   └── 2022-06-07-domain-and-subdomain-jekyll.md
│   │   └── index.md
│   └── ru
│       ├── _posts
│       │   └── 2021-10-25-okonomiyaki-recipe.md
│       └── index.md
├── cdn
│   ├── hamburger.svg
│   ├── pygments.scss
│   ├── style.scss
├── en
│   ├── feed.xml
│   ├── index.md
└── ru
    ├── feed.xml
    └── index.md

The folders blog and cdn are to be served from corresponding subdomains, since like many hosting providers mine allows setting subfolders of the main domain folder to be served as a subdomain. Hence the only difference between development and production is linking:

  page at www… page at blog… page at cdn…
link to www… relative absolute absolute
link to blog… absolute relative absolute
link to cdn… absolute absolute relative

I.e., in production links within the same domain or subdomain (with some exceptions, e.g., in <link rel="canonical"> tags) are relative and links from one domain or subdomain to another domain and subdomain are absolute. In development all links are relative to be served from localhost.

Failed Solution

My initial thought was to take advantage of Jekyll’s permalinks as well as site destination and url configuration options in order to coordinate where pages and files go in the web-site structure:

Additionally each link pointing from the main domain to a subdomain and vice versa would need to be changed in production environment to include domain or subdomain it points to. Custom Liquid filters are easiest to help with this task (e.g., let’s create ./_plugins_/transform_url.md):

module Jekyll
module SubdomainFilters
def to_blog(page_url)
from_page = @context['page']
from_subdomain = from_page['subdomain'] || 'www'
if ENV['JEKYLL_ENV'] == 'production'
blog_url = 'https://blog.volgar.name'
end
locale_prefix = '/' + from_page['locale']
if from_page['locale'] && !page_url.start_with?(locale_prefix + '/')
page_url = locale_prefix + page_url
end
if !blog_url.nil?
if from_subdomain != 'blog'
blog_url + page_url
else
page_url
end
else
'/blog' + page_url
end
end
end
end
Liquid::Template.register_filter(Jekyll::SubdomainFilters)

Naturally the site would need to be built in two or more passes, one for each subdomain plus one for the main domain using a combination of include, exclude and keep_files configuration options and the ability of Jekyll generator to merge multiple configurations. And that’s exactly why the solution failed: the posts under the blog subdomain had to be excluded when generating for the domain making it impossible to refer to or list them. But as it often goes with coding without bad implementations it’s hard to see a better one, so the custom Jekyll tags for conditionally including domain or subdomain is in fact the only mechanism necessary.

Working Solution

Let’s make the site subdomains configurable by listing the folders that must be treated as subdomains via an option in the configuration (./_config.yml), e.g.:

url: "https://www.volgar.name"
host: 0.0.0.0
subdomain_folders: ["cdn", "blog"]

Since folder structure of the Jekyll output already matches the desired one, instead of trying to convince Jekyll to get linking between subdomains right by configuring links-related options, let’s put each link though a custom Jekyll filter (e.g., by replacing the ill-fated to_blog method in ./_plugins_/transform_url.md):

def rewrite_url(page_url)
return_url = page_url.dup
if ENV['JEKYLL_ENV'] == 'production'
site = @context.registers[:site]
site_url = site.config['url']
if page_url.start_with?('https://')
for subdomain in site.config['subdomain_folders']
if page_url.start_with?(site_url + '/' + subdomain + '/')
return_url['www.volgar.name/' + subdomain + '/'] = subdomain + '.volgar.name/'
break
end
end
elsif page_url.start_with?('/')
from_page = @context['page']
for subdomain in site.config['subdomain_folders']
if from_page['path'].start_with?(subdomain + '/')
from_subdomain = subdomain
end
if page_url.start_with?('/' + subdomain + '/')
to_subdomain = subdomain
end
end
if !to_subdomain.nil?
return_url.slice!(0, to_subdomain.length + 1)
end
if from_subdomain != to_subdomain
return_url = site_url.gsub('www', to_subdomain || 'www') + return_url
end
end
end
return_url
end

This way it’s now possible to chain the custom filter with other URL filters, e.g., to get a relative URL for the RSS feed in the header file common for all site pages:

<link rel="alternate" href="{{ '/feed.xml' | relative_url | rewrite_url }}" type="application/atom+xml">

In production the result would look the following on the main domain (given the feed file is located at the root of the site):

<link rel="alternate" href="/feed.xml" type="application/atom+xml">

On pages located under subdomains:

<link rel="alternate" href="https://www.volgar.name/feed.xml" type="application/atom+xml">

While in developer enironment it would produce the same reasult regardless where it is:

<link rel="alternate" href="/feed.xml" type="application/atom+xml">

Another example is a canonical URL link that must be always absolute:

<link rel="canonical" href="{{ page.url | absolute_url | rewrite_url }}">

It would look like the following for this page in production:

<link rel="canonical" href="https://blog.volgar.name/en/2022/06/07/domain-and-subdomain-jekyll/">

And in development:

<link rel="canonical" href="http://0.0.0.0:4000/blog/en/2022/06/07/domain-and-subdomain-jekyll/">

Let me know, if there is a better way to solve the problem of maintaining Jekyll site on multiple subdomains. At the moment the easiest way to reach out is to ping me on Mastodon.


Notes

  1. Mainly to take advantage of owning a domain. ↩︎

  2. Test in production? No thanks. ↩︎

  3. Obviously nobody wants to write same things twice or let things get out of sync. ↩︎

  4. Especially, considering that there is no problem to set _plugins and _layouts to point outside the source directory. ↩︎

Okonomiyaki Recipe

Okonomiyaki is a Japanese cabbage pancake containing a comparable with pizza variety of ingredients. I’ve eaten the dish for the first time while travelling in 2015, in a small cosy London restaurant called “Abeno Too” that, unfortunately had closed in 2018.However, “the older brother” of it is still open and operational, though I hadn’t had a chance to visit it. Upon returning home, my husband and I began to regularly prepare okonomiyaki, gradually improving and perfecting the recipe.

There are many regional variations of okonomiyaki, and the common ones are Tokyo-style, in which a watery batter is poured over hot finely chopped ingredients right before serving, Hiroshima-style, which is cooked in layers, and Osaka- or Kansai-style, the most common version, in which the ingredients are mixed with the batter before frying. The recipe below is the closest to the latter, with the exception of the noodles, which are commonly added to Hiroshima-style okonomiyaki.

The original recipe is from BBC Good Food and includes a step where the batter must be refrigerated for two hours. In 2019, while visiting Tokyo, we went to a cooking class, having selected okonomiyaki as one of the dishes to learn in order to see firsthand the steps followed in Japan. We hadn’t put the batter for two hours in the fridge during the class, so we’ve crossed it out from our recipe. We also used to chop cabbage with a blender, that practically resulted in a cabbage puree.To be fair, this is how cabbage is cut in the wonderful Stockholm restaurant “MamaWolf”, but, by the look of it, the chefs in the restaurant prepare another kind of okonomiyaki called osakayaki. However, in one of the episodes of “Terrace House” someone was cooking okonomiyaki as well. We noticed that the cabbage was chopped very roughly and changed the recipe once more.

Ingredients

For the sauce

To garnish

Cooking steps

  1. Put the noodles to boil. Meanwhile, cut the chicken (or any other preferred meat or meat substitute) into small cubes and fry until half cooked.

    Frying pan with slices of vegan bacon.
  2. While the noodles and the meat are cooking, combine the flour and baking powder.

    A bowl of ingredients for the okonomiyaki batter.
    You can mix equal proportions of wheat and rye flour.
  3. Pour in chilled broth and add eggs. Mix well, whisking out any lumps.

    A bowl of okonomiyaki batter.
    The batter should come out relatively thin.
  4. Cut the cabbage not very finely so that individual pieces are recognisable in the finished dish.

    Cut cabbage on the board.
    Both large chunks of cabbage itself and the chunks of cabbage stump will only add nice texture to the dish.
  5. Cut off and set aside the stems of the chives for decoration. Cut the bulbs and the dense part of the stems into rings.

  6. Put the noodles and the meat into the batter, add cabbage, onions and corn.

    A bowl of okonomiyaki batter with ingredients.
  7. Stir everything together to thoroughly coat the mix in the batter.

    Okonomiyaki mix ready to fry.
  8. Heat a thin layer of oil in a frying pan on a medium heat. Pour the mixture into the pan, keeping it as circular as possible. The mixture is usually enough for 4 small cakes.

    Okonomiyaki pancake in a frying pan.
  9. While the pancake is fried, carefully mix all the ingredients for the sauce in a cup.

  10. Fry the pancake for 3-4 minutes, turn it over (the easiest way is with a plate), sprinkle with grated cheese. Cover the pan with a lid to melt the cheese and fry the other side for another 3-4 minutes.

    Okonomiyaki pancake sprinkled with cheese in a frying pan.
  11. Transfer the finished pancake to a plate, spread the sauce to a relatively thin layer.

    Okonomiyaki with sauce on a plate.
  12. Chop previously set aside stems of chives, sprinkle them on the pancake.

    Okonomiyaki pancake with sauce and onions on a plate.
  13. Decorate with the mayonnaise.

    Okonomiyaki flatbread with sauce, onions and mayonnaise on a plate.
    Okonomiyaki is often decorated with thin zigzag lines.
  14. Sprinkle with katsuobushi and serve while the flakes move in the steam of the hot pancake.

    Decorated pancake on a plate.

Notes

  1. However, “the older brother” of it is still open and operational, though I hadn’t had a chance to visit it. ↩︎

  2. To be fair, this is how cabbage is cut in the wonderful Stockholm restaurant “MamaWolf”, but, by the look of it, the chefs in the restaurant prepare another kind of okonomiyaki called osakayaki. ↩︎

What!? Lego and glue?

🖼️

Greener Grass and Pink(ish)er Walls