Create Basic and Placeholder Hugo Pages

Through creating simple pages, we learn how hestiaHUGO operates. A step-by-step guide for hestiaHUGO — ZORALab's Hestia Hugo module.

Learning Objectives

At the end of this tutorial, you will learn:

  1. How hestiaHUGO works with Hugo

  2. How hestiaHUGO governs filesystem pathing and URL

  3. How hestiaHUGO manage all designers' creating freedom via UI components

  4. How hestiaHUGO design to counter the ever-changing W3C changes

  5. How hestiaHUGO design its multiple outputs (HTML & JSON)

  6. How hestiaHUGO handles internationalization by default


This tutorial assumes you have a fresh hestiaHUGO equipped Hugo repository ready for deployment especially being created from the previous setup tutorial page. Otherwise, please complete that lesson and then revert back to this piece:

Setup Hugo Tutorial

Understand How hestiaHUGO Manage URL with Filesystem

hestiaHUGO natively supports its own multi-languages (a.k.a. 'i18n') feature by default. IT USES DIRECTORY NAME AS URL PATH and the 1st level is ISO639-1, ISO639-2, ISO639-3, ISO15924, and ISO3166 LANGUAGE CODE or language independent pages (e.g. usually redirecting shortcuts). The rest are the same as Hugo. We advise you to name your directory according to the URL pattern (no space, small characters, and no funny symbols) and use it eventhough the content is a single language to avoid building broken URL site in the future.

File path :          content/[i18n]/url-compatible-pathing/file.extension
URL       : [http://baseURL]/[i18n]/url-compatible-pathing/file.extension?query=value#tags

content/ (
	redirects ➔ content/en (
	redirects ➔ content/zh-hans (

content/releases (
	redirects ➔ content/en/releases/ (
	redirects ➔ content/zh-hans/releases/ (

content/en/releases/v1-0-0/ (
content/en/releases/v1-1-0/ (
content/en/releases/v1-2-0/ (
content/en/releases/v1-2-0/index.html (
content/en/releases/v1-2-0/index.json (
content/en/releases/v1-2-0/ (

Create Hugo's Content Directory

We can now start by creating the Hugo's content directory. It is named inside your config/_default/config.toml under the field contentDir (usually called content). You may alter it as per your need. For this tutorial, we will just stick to the default content. After creating the directory, you will need to restart your hugo server (hint: server.cmd command). A complete list of commands would be:

	[ Kill the server with CTRL+C ]
	$ cd sites/        # your hugo repository if you're not inside it
	$ mkdir -p content
	$ ./server.cmd     # refer pervious tutorial if you had forgotten

	[ Kill the server with CTRL+C ]
	> cd sites/        # your hugo repository if you're not inside it
	> mkdir content
	> server.cmd       # refer pervious tutorial if you had forgotten

Create Placeholder Page

We will begin by creating a single language placeholder landing page. We won't be building this page now but we need it for the page we want to build: the 404 page. hestiaHUGO is designed in the way to be natively compatible with Hugo. Since this is an English language tutorial, we can proceed to create content/en page with hugo command. Remember this important step because we won't repeat it again and you will be using it a lot. The command is as shown below:

$ hugo new --kind hestia [filesystem path]

$ hugo new --kind hestia content/en

Understand How hestiaHUGO Manages Page Configurations

Before we continue, let's understand how hestiaHUGO manage page compilations. If you take a look at the content/en page, there are a lot of double underscore TOML files. Due to the complexities of web connectivities across the Internet, we do not have a choice but to split them into various pieces of config data. One constant to remember: never touch the Hugo's original _index.html file. It is now ONLY USE for hugo to discover and map the site directories. The way hestiaHUGO is designed will rarely have any conflict with other Hugo themes. You're still allowed to place any page associated files (e.g. images only this page uses, docs, .csv, etc) in the same directory. Each known files' roles and responsibilities are listed below:

__assets.toml            ➤ manage all CSS and JS files' inclusion or compilation
__components.toml        ➤ list out all hestiaHUGO UI components for compilation
__content.hestiaCSS      ➤ the page's final CSS asset for inlining into HTML
__content.hestiaHTML     ➤ the page's HTML output
__content.hestiaJS       ➤ the page's final JS asset for inlining into HTML
__content.hestiaJSON     ➤ the page's JSON output
__content.hestiaLDJSON   ➤ the page's LD+JSON output for inlining into HTML
__contributors.toml      ➤ designate list of contributors to this page
__data.toml              ➤ a page-level data file listed in __page.toml
__i18n.toml              ➤ a page-level data file listed in __page.toml
_index.html              ➤ hugo discovery file (**DO NOT TOUCH THIS AT ALL**)
__languages.toml         ➤ the page's alternate languages URL (translated pages)
__page.toml              ➤ the page's primary configurations
__robots.toml            ➤ the page's robots instructions for inlining into HTML
__thumbnails.toml        ➤ the page's thumbnails used in social media sharing
__twitter.toml           ➤ the page's Twitter configuration for sharing
__wasm.toml              ➤ the page's WASM configurations

Update the __page.toml

Don't worry, for placeholding page, you only need to deal __page.toml file. This is to setup the page's critical metadata. A few critical fields you have to update are shown below:


Created   = 'Sat, 05 Mar 2023 11:22:21 +0800'  # update to today's time obviously
Published = 'Sat, 05 Mar 2023 11:22:21 +0800'  # update to today's time obviously


Title = 'My App Page'
Keywords = [
        'My App Page',


Pitch = '''
Summary = '''
Will be back later.


Git Commit Placeholder Page

Now that everything is in place, you can proceed to git commit the page and we will proceed to deal with the real basic one: /en/404 page. If you need assistance with Git, you can check out their offical book in the following URL. Basically, the command is as follows:

Git Manual
$ git add .
$ git commit -s

sites: added /en/ page as placeholder

Since we need to build /en/404/ page, we have to create the /en/ page as
placeholder for now. Hence, let's do this.

This patch adds /en/ page as placeholder in sites/ directory.

Signed-off-by: Name <EMAIL>

$ git push

Repeat Page Creation for 404 Page

Now that you get the hang of how to create a hestiaHUGO page and updating the critical file, as a re-cap, please create /en/404 page (hint: content/en/404 path). For this page, please DO NOT commit as we will begin this page creation in the next step.

(1) $ hugo new --kind hestia [filesystem path]
(2) $ update __page.toml with the appropriate values for 404 page
(3) $ git add .

Update Page-level Twitter Settings

Unlike placeholder page, this time, if you have Twitter account, you can update the content/en/404/__twitter.toml config file. Since 404 page is just a system notice page (and no one insane enough to visit it on purpose), you only need to update the following:


Handle = 'yourTwitterHandle'    # with or without @ is ok


Update Page-level Robots Settings

Due to this page being a 404 page, we do not want any search engine robots to index it. Hence, let's update the content/en/404/__robots.toml config file. These robot values will be compiled as the page's HTML output's meta tags. An example would be as follows:


Name = 'googleBot'
Content = 'noindex, nofollow'

Name = 'robots'
Content = 'noindex, nofollow'

Update Page-level Languages Settings

As a good practice and to prevent massive copy-paste error in the future, it is better we update the page-specific language settings in the content/en/404/__languages.toml config file. You're strongly advised to use RFC3986 compliant relative URL. We will explain further in the next step. Here's the data to be updated:


URL = '/en/404'


Understand How hestiaHUGO Process URL

hestiaHUGO deploys its own URL data processor (called hestiaURL/Sanitize partial function). We strongly encourage using relative URL for pre-compilations (e.g. your page templates and etc) while letting hestiaHUGO to compile it to absolute URL as output. Visitor should always get the absolute URL in no matter what condition. The function is RFC3986 compliant so be careful with your relative URL construction. Here are the explanation examples:

RFC3986 URL Specification
INPUT    :

INPUT    : /my-page

INPUT    : my-page

Create Page HTML Content

For hestiaHUGO, the corresponding file is specified in the __page.toml control file's Sources.HTML field. The default is the relative file __content.hestiaHTML in the same directory. Since we're beginner, we should use the default __content.hestiaHTML file. Let's open it and take a look. If you're a seasoned Hugo developer, you will immediately notice that the Go template (and Hugo's partial) functions are used instead of Hugo specific Markdown and shortcodes. When using any ZORALab's Hestia product, we strive to be as portable and nimble as possible across them with minimal learning cost. If you're already familiar with Go template from Go Programming Language, then every piece of your rendering knowledge is automatically and fully transferred here. For this tutorial, let's update it with the following contents. Once done, try visit the URL + /en/404/ web page presented by the Hugo server. You can verify the HTML-only rendering of the page.


{{- /* render outputs */ -}}
	<section id='introduction' class='banner'>
		<h1>{{- .Titles.Page -}}</h1>
		<p>{{- .Descriptions.Page.Pitch }} {{ .Descriptions.Page.Summary -}}</p>

		{{- $ret := merge . (dict "Input" (dict "Data" "/en/")) -}}
		{{- $ret = partial "hestiaURL/Sanitize" $ret -}}
		<a class='button' href='{{- $ret -}}'>Back HOME</a>

Understand How hestiaHUGO Renders Page Data

Unlike Hugo requiring designer to guess a data field, hestiaHUGO employs its own page data structure for designers to utilize. In fact, we have our some design-adaptive debugger that shows the entire page data structure for debugging or rendering purposes when Hugo is operating in server mode. Have you notice a color button at the bottom right of the page? Try click on it and you can browse your current page's entire usable datasets. Example, the rendering part of {{- .Titles.Page -}} was rendered as the value of .Titles.Page when viewed in the debugger console.

The Screenshot of the Debugger Console

Understand hestiaHUGO Data Path

hestiaHUGO finalized its design by having content directory supplying the site URL and having each of its directory delivers the page data. Remember the __i18n.toml and __data.toml in the page directories? Those data files are listed in __page.toml's [[Data]] array list. hestiaHUGO maintains absolute freedom for page designer to construct the Page level data structure. After parsing and merging all the data files, all the data can be accessed via hestiaHUGO.Page data field. Other config files are obviously provide various different values for different data fields. Feel free to explore around and check them via the on-screen debugger console. This is how you source your data key:value when designing your page. The link below is the technical specification of the hestiaHUGO.Page data structure (browse when you're free, we will move forward for now).

Data Structure & Specs
__data.toml + __i18n.toml + ...   (NOTE: follows __page.toml data files list)
     {{- .Field.[...] -}}

Understand How hestiaHUGO Empowers Hugo

hestiaHUGO supplies its own standard libraries just like other ZORALab's Hestia components. Notice the use of hestiaURL.Sanitize partial function above? It converts a given ambigious URL into a proper full absolute URL consistently in accoradance to hestiaHUGO URL management explained in the 1st step. The sole purpose of preparing our own standard libraries is to make sure we have absolute consistency as we grow ZORALab's Hestia and not Hugo's output inconsitencies AND maintaining inter-operability with other components. All ZORALab's Hestia libraries are documented in the same context across all components in our specifications section. You should explore all available hestiaHUGO API to advance your development at your own time. For this tutorial, we will proceed further.


Create Page UI Components Styling List

Now that we verified the data shown in HTML-only page is correct, we can now style it. hestiaHUGO uses the UI components approach to compile all necessary proceed to edit /en/404/__components.toml and add the following components after the commented guide. Give the server a few moment and refresh the web page. You can see it's being compiled with styling (especially the call-to-action anchor link). Check out the CSS codes in the CSS.Inline section of the debugger console. It's filled with the compiled CSS codes.

Name = "zoralabCORE"
Include = true
Variables = [
        { "--body-scroll-behavior" = "smooth" },

Name = "zoralabFONT_NOTOSANS"
Include = true
Variables = []

Name = "zoralabANCHOR"
Include = true
Variables = []

Name = "zoralabBUTTON"
Include = true
Variables = []

Understand How hestiaHUGO Manage Page Styling

Like Backbone, Angular, and React frameworks, hestiaHUGO componentized each UI but unlike them, hestiaHUGO only integrate each component's HTML, CSS, and JS codes as 1 set rather than split them and overloadingly manage everything via JavaScript. That way, via Hugo, hestiaHUGO can compile only the required CSS, CSS variables, and JavaScript codes into the assets and then embedded them into the HTML output file. This output HTML file is self-contained and not requiring external remote framework, fulfilling the vision where AMP project originally wants. It also seamlessly support PWA's offline feature as well and simplify the UI component designer maintenance workflow by only focusing on applying non-backward compatible updates introduced by W3C. 3 birds, 1 stone ⥤ definitely worth the effort.

UI Components Catalog

Understand Why hestiaHUGO Containerized Page Styling

This is mainly for preventing a site-wide UI update from breaking pages like a whack-a-mole nightmare from happening again especially large content website. We tested it and find this compilation approach is the most optimized way. So, to scale the same UI across other pages, you just have to copy-paste the __components.toml config file. For page designer regardless of experiences, please feel safe and free to express your creativities and make a safer mistake to gain experience with a site development.

Understand How hestiaHUGO UI Component Works

As stated earlier, the CSS codes are for defining the styling, the CSS variables are for UI customziation, and the JS codes are for behavior control. When you add an UI component, you are provided with an Include switch for design debugging purpose (better then delete large chunk of codes). At the same time, you're allowed to overwrite existing CSS variables, making hestiaHUGO to only generate 1 list of CSS variables for your HTML output will do.


Name = "zoralabCORE"
Include = true
Variables = [
        { "--body-scroll-behavior" = "smooth" },   # this overrides the default value 'none'


Understand Why ZORALab's Hestia Only Prioritize HTML & CSS

For 2 primary reasons:

  1. Supporting privacy-first content rendering (JS disabled). In year 2022 during the Covid19 pandemic, everyone was concerned about privacy and data leak so having a complete JavaScript disabled use case is totally unsurprising. In that situation, your famous JS Frameworks and our WASM are completely unsable but the site is still expected present correctly; AND

  2. ZORALab's Hestia intend to replace JavaScript entirely with WASM compiled using proper programming languages like Go or Nim in the future. It is a lot easier and seamless to port the HTML+CSS only UI component into the WASM counterpart (as in no JavaScript interference).

That being said, you can still use JavaScript (as shown in later steps). In ZORALab's Hestia, we don't do idiotic and autocratic goverance like some technology's governors did that restrict anyone freedom of use and artistic expression.

Understand ZORALab and JavaScript Relationship

Let's face it: JavaScript is the only programming language for web UI behavior packed with weird and inconsistencies. It's problematic weaknesses gave birth to various frameworks like Angular and Typescript of itself trying to workaround them but introduced their own. With the birth of WASM, ZORALab wants to solve the problem heads-on. We do not hate the language but finding development with it comes is very risky. A lot of outdated JS frameworks in the past trying to replace one another had already proven the point.

Understand hestiaHUGO Future

In order to realize our ultimate goal for web technologies, there are 3 specific use cases:

  1. Discovery page like SEO & SMO

  2. Single App - 1 WASM implements the entire site

  3. Reactive Rendering (each page has its own WASM renderer)

hestiaHUGO fulfills primarily on use case: while facilitating WASM hosting for use cases (2) and (3). That's why we are still facilitating Hugo and so far it is still the best frontend static site generator (and now, a compiler).

Customize Page Styling with CSS

Now that the page is styled (that looks common across all users), you can customize on top the currently compiled CSS codes. Simply edit the file __content.hestiaCSS and place your page-level CSS codes in it. For this tutorial, we will be using the following CSS customization codes. hestiaHUGO will recompile your CSS file as the last inline-type asset. Once done, refresh the page and you'll observe your 404 page is disco-ing with colors.

main {
	height: 100vh;
	width: 100vw;

	 isplay: flex;
	justify-content: center;
	align-items: center;

	background: linear-gradient(245deg,
	background-size: 1200% 1200%;

	animation: BgDiscoFlasher 4s ease infinite;

@keyframes BgDiscoFlasher {
	0%{background-position:0% 86%}
	50%{background-position:100% 15%}
	100%{background-position:0% 86%}

.banner {
	width: 50%;
	max-height: fit-content;

	padding: 5rem;
	border-radius: 2rem;

	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;

	background: rgba(255, 255, 255, .75);

Customize Page with JavaScript

Likewise, you can also customize the page with JavaScript. The responsible file is __content.hestiaJS. While the page is no longer needing further styling, for this education purpose, let's temporarily add the following code block in and check out the browser' inspect console (F12 button). You should observe a cat meowing. Same case: this JavaScript is the last inline JavaScript to be included in the page.

window.onload = function() {
	console.log("\nMeow~~~~ ₍⌯ᴖⱅᴖ⌯ ^₎◞ ̑̑ෆ⃛ \n\n");

Update LD+JSON Data for SEO

hestiaHUGO has a built-in support for complex data structuring of your page. The responsible file is __content.hestiaLDJSON. Since this is a 404 page, we will add the following code block will do. Once the Hugo server is fully updated, you can proceed to obtain the output from our on-screen debugger console and validate at validator site.

LD+JSON Validator
{{- /* prepare variables for function */ -}}

{{- /* execute function */ -}}
{{- $dataList = merge $dataList (partial "hestiaJSON/schemaorgLDJSON/WebPage" .) -}}

{{- /* render output */ -}}

Understand How hestiaHUGO Handles LD+JSON

To maintain balance between freedom of expression and rigidity for consistent output, there are 2 things to remember:

  1. hestiaHUGO can only quickly help you process the base level dataset of known common data types (e.g. authors, page type, etc); and

  2. you need to add in the page context-specific dataset (e.g. recipe steps, etc). Data Structures

Stage the Current State

Now the HTML output is done deal, we should git stage it. However, we are not quite done yet. Proceed to next step:

$ git add .

Create JSON Output

By default, hestiaHUGO supports JSON output type as secondary output format. This is using the hestiaHUGO internal feature; not the Hugo one. Like LD+JSON, you can construct a customer usable dataset using the Go template function. Unlike LD+JSON, this is a pure JSON output (viewable by appending index.json at the end of the page URL) unrestricted by schematic. The responsible file defined by __page.toml's .Source.JSON value (default is the relative __content.hestiaJSON). Since this is 404 page, we can leave it as it is. You can study the default codes for educational purposes.

Understand Why hestiaHUGO Natively Supports JSON Output

5 main reasons:

  1. Allows one to use hestiaHUGO as a CDN-driven open data API server; AND

  2. A backup reason for using Hugo after search engine giants nearly destroyed SEO in the past solely for SMO ad revenues; AND

  3. Provides alternatives and freedom compared to LD+JSON implementations; AND

  4. making sure hestiaHUGO can support multiple output feature natively in a very consistent manner; AND

  5. Facilitate a way to feed AI training data.

  6. The default processor is configured in a way as an option for those who do not want to use it so if you're not using it, don't bother touching __content.hestiaJSON file at all.

Commit and Push

At this point, we had completed designing a basic and simple 404 page. You can proceed to Git commit and push out.

$ git add .
$ git commit -s
... write a good git message as shown in previous tutorial ...
$ git push

Remember the Strength of hestiaHUGO

If you haven't realize, hestiaHUGO only takes 1 set of input dataset and generated to various outputs type while supporting multiple languages at the same time. In fact, it generates more like page-level 'sitemap.xml' and 'sitemap-page.xml' as well. This is one of the problem we're trying to solve when using Hugo: 1 consistent input dataset to multiple outputs consistently. These are just a scratch of the surface. We will explore more as you go through the tutorials walkthrough.

What's Next?

Now that we had completed the hestiaHUGO setup, the next step is to make sure we support multilingual pages from the get go. Here's the URL for the next step:

Using Multilingual Pages from the Get Go


Looks like we have arrived to the last station. Intrigued to get in touch? Please feel free to start contacting us via the following public channels:

GitHub Discussion Portal