Hugo

Tags: Cloud-Resume

Since I've been using npm for the other parts of this project, I wanted to see if there was a straightforward way to install the Hugo static site generator with npm. The official install instructions don't mention it, but thankfully this post from Blocktrack.io does. It seems it's as simply as the usual npm init, walking through the options, then running

npm install --save-dev hugo-bin

Running npx hugo version lets us see that hugo v0.88.1 was indeed installed. Apparently, since we haven't installed the hugo binaries directly, we'll use the "npx" command to make npm invoke the hugo binary we installed.

Now, we run npx hugo new site . --force to generate the starting hugo sites.

The blocktrack.io module suggests initializing git and using it to download a theme at this point, but since I'm interested in building one up from scratch, I'll just initialize git here with git init. I'll add a .gitignore file to exclude the /node_modules path, and make an initial commit to the repo.

Following along with our Blocktrack friends once more, I'll add a few scripts to the package.json file that will allow us to easily build, serve, and clean up the Hugo site. Of course, building and serving the site now gives us an empty page, since we have no content yet, and also shows a warning in the terminal where we started the server: "found no layout file for "HTML" for kind "home." Makes sense.

So let's create a homepage of some kind. Following along with this video from Giraffe Academy, I'll create an index.html file in at /layouts/index.html.

Well that's better! We have a very small, very bad website on localhost again!

I'll go ahead and replace that with some boilerplate via VSCode, and we'll start figuring out this hugo thing from the beginning. I'm going to create this as a whole separate site, then work out how to merge what I've learned here with my oh-so-beautiful site I was creating using Tailwind.

After repopulating the index.html page with some boilerplate HTML, let's see if we can't get something like the projects pages working.

Let's try the loraDMX project first. If I run npx hugo new .\content\projects\loraDMX.md, I get a new file with just a little bit of what hugo calls "Frontmatter" - metadata about the content of the page. Next, I'll create a projects-page layout at /layouts/projects/single.html with very simply syntax copied from this cloudcannon tutorial:

<main>

  {{ block "main" .}}

  {{end}}

</main>

and I'll fill in my loraDMX.md page with:

<!-- Using a block in a child page -->

{{ define "main" }}

<h1>This will be inserted into the block</h1>

{{ end }}

Now if I run npm run hugo:build... hmmm, it seems to be broken. But what quick google search recommends adding templates to _default.... hmmm, no good either.

Let's go about this another way - let's download a simple hugo theme and see what we can learn by messing with it. I'll delete my content and template for now, and follow the steps on the Codex hugo theme to get it installed. I'll also recreate a very, very basic index.md file as recommending in those install steps, so that something shows up.

Well that's slightly better, there is indeed a webpage here now. Let's see what's going on in these theme files, and where this content is actually coming from.

Under /themes/hugo-theme-codex, it looks like we have index.html and 404.html files. Adding text to the index.html layout adds it to the homepage, so it seems that's what we're looking at. There's also this hilarious comment to give us a clue where to add our content:

We can go directly to localhost:1313/404.html to view what seems to be the 404 page defined in themes/hugo-theme-codex/layouts/404.html, which is good. But it looks like both index.html and 404.html aren't full pages, but rather pieces of content, which define {{ styles }} and {{ main }} blocks to populate into a template somewhere else. Let's see if we can't find those.

The (only?) file with an actual html layout lives in /layouts/_default/baseof.html. That is, it starts with <!DOCTYPE html>, has body and head tags, and seems to be including other page elements within itself. The other members of /layouts/_default (list.hml and single.html) seem to be layouts for single and list page contents, without being full pages in their own right. From what I can glean, from this and the hugo documentation, these are the styles for basic single and list pages, the two fundamental types of content in hugo.

I'm feeling pretty good about trying again to roll my own theme here, but while I've got it installed, let's look at the remaining folders under /layouts/ in the Codex theme. The partials folder contains a bunch of small html/Go pages, while look like they're meant to be pulled into other pages. Things like nav.html, social-icons.html, burger.html, etc. Some are pure HTML, others have hugo-style (Go?) scripts to pull in further details or enumerate pages, I think. Finally, the /layouts/taxonomy folder has a single file, tag.html, while seems like it would list all the pages and present them? Unclear how this is working currently.

Now, let's delete this theme and get back to writing our own. In the layouts folder, I'll create a new _defaults folder, and within that, baseof.html (with boilerplate) and single.html and list.html (both empty for now). The content I've created in these basic pages isn't visible yet, and interestingly, sometimes I'm seeing build warnings about "found no layout file for kind taxonomy" and sometimes I'm not. I notice the example site from the theme calls its homepage content "_index.md"... but changing that in my site makes no difference...

Aha! Some sleuthing through the Hugo Forums indicates that not only do I need to build the draft pages (the index is a draft, currently), but also to serve them. By running, in my case, npx hugo server -D, I can now see some index.html content! Woohoo!


Shortly after the above, I lost patience, and decided to see if there was a faster way to get started. I wiped everything, and went back to the quickstart guide, and installed the "blank" theme from Vimux. Building and serving this locally worked as expected.

OK. So let's start from where I think is the beginniing, with the baseof.html layout. I can see, in the <head> section, the <title>{{ .Title }}</title> inclusion. Changing the title value in config.toml does indeed change the value of the page title, so at long last, I'm able to make little changes. Somewhere. To something. Similarly, adding <p>Hello World</p> to the body makes it show up in the page. And adding in some plain content to index.html makes it show up on this index page as well. Ok.

So, now to try to add some CONTENT! Following strictly along with the quickstart, I'll run npx hugo new pots/my-first-post.md, and create some basic content in there. Re-serving the page:

Let's try to understand what's going on here and where each of these pieces comes from

  • The link at the top is created in the header.html partial, which auto-generates the URL and text based on the config file. This header is included in the baseof.html template.
  • The Hello, world text is hardcoded into the baseof.html template itself
  • The words "This content is in index.html" is hardcoded into the index.html template, and included as the "main" block of the baseof.html template in this case
  • The index.html also includes the $paginator method... which I don't entirely understand yet.
    • I'm also realizing I don't entirely understand the difference between a black include and a partial. It seems like the blocks are pieces of relevant content included from content pages, whereas partials are separate pieces of HTML etc. included from the separate partials folder?

  • The "LATEST POST" section is actually from the sidebar.html partial, which seems to list the last 5 latest posts using the {{ range }} function, which I'll need to learn more about
  • Finally, the footer partial is responsible for the copyright indication and final link, also included via the baseof.html layout.

And now, some experiments.

  • Making changes in the /themes/blank/static/style.css file does reflect on the page, but does require a rebuild.
  • Let's see about overriding some of the theme files using our own. If I make a /layouts/partials/header.html file, copy and paste the header.html file's contents into it and make changes... we have changes! Similarly with the _default/baseof.html file.

  • It looks like the "blank" template I installed is including the header, main, and footer blocks within the "baseof" file. Which I might or might-not want - the individual project pages (and blog pages, if I ever port those over) probably want a different header. So I may want to move those includes over to different templates.


So, the process from here looks like taking the rough layout I built with tailwind, and breaking some of its components apart into blocks to fit better within the Hugo mentality. For now, I'm doing to just manually copy my css built with Tailwind into the proper place (build/css) and then work on integrating Hugo and Tailwind later on

First, I'll make it so baseof.html only really includes some basic metaconent, a reference to the CSS file, and an include to the {{ main }} block on a given page. Then I'll slim down the index.html file some as well by commenting out the pagination... and then blow it up again by copying everything from my <body> tag in my current version of the site over. After figuring out that the Tailwind-built CSS should go into static/css.style.css, it's mostly kinda working! Well, the images in the project cards are broken, but that's not surprising. Let's see if I can figure out where those should go.

Huh! So just copying my existing /images folder into /static/images worked! So the html tag with src="./images/workshop.jpg" is referencing the image at /static/images/workshop.jpg. Good to know. I imagine there's a way to dynamically work with file names - as in, for a project file, load the image from such-and-such folder with the same name? I'll get to that later.

Let's see about breaking the header out into it's own partial. I think this may be the only page that uses this header, but it still seems like a good process to learn. I also see that Hugo has a "Menus" functionality... perhaps also good to look into at some point. But for now, I'll add {{ partial "header.html }} to the very top of my index.html file, just inside the {{ define "main }} tag. Then I'll copy my "Green Navbar" html code over from my Index.html file, and seeing that it works, removing it from Index.html....

And hey! Everything looks the same! So the header is now broken out into its own partial file, ready for re-use if I want to - neat! I'll do the same for the footer in the footer.html partial.

Let's see about how we would use Hugo to make the management of our Projects easier. The ideal setup would allow:

  • Writing up a project page in markup or html for each project
  • Having each project page have consistent formatting via a common template
  • Having each project page contain the data necessary to generate a card on the index.html page
  • Have a pretty list page containing all the projects, generated automatically from the project pages. Possibly categorized?

I'm not quite sure how to proceed... so let's mess around!

If I make an empty loradmx.md file in a new content/projects folder, and look at the sitemap.xml, I can see two new pages have been generated: /projects/ and /projects/loradmx. They are... very barebones at the moment, but at least they appear. I hear the right way to do this is to use hugo new, so let's see what that creates:

Just a little bit of what Hugo calls "Frontmatter" - metadata about the content in question. I'm thinking this is where I can stash the slug text for the cards that appear on the homepage and the projects page. Rebuilding the page with this frontmatter causes it to disappear from the default hugo server build, unless draft is set to false or I run npx hugo serve -D.

Let's try figuring out how these pages are being generated by adding our footer to them. As a place to start, at least.

It seems the content in /projects/loradmx is currently based on the theme/layouts/_defaults/single.html file. So, like before, I'll make a copy of it in my own layouts/_defaults folder. Similarly, I'll copy /layouts/_defaults/list.html. By making small changes to those files, I can confirm that the template files are indeed taking effect.

So now what? Well, let's start by adding our header and footer templates to both pages. I'll probably want to change this around some later, but it's an alright place to start.

And now is the post where I'd really like to have the ability to work with Tailwind inside this project, so as I'm making changes to styling these pages with Tailwind I can see the results live on the Hugo pages. Following the post from Praveen Juge, I'll run

npm install tailwindcss@latest postcss@latest postcss-cli@latest autoprefixer@latest --save

To install the requisite Tailwind packages in via npm. I'll use npx tailwind init -p to create the basic tailwind.config.js and postcss.config.js files, then copy the varients over from my other working project. Finally, again as suggested by Praveen, I'll set my purge settings to

content: ["./layouts/**/*.html", "./content/**/*.md", "./content/**/*.html"],

So that all the relevent css is captured.

Sometime later after much poking around...

So, here's how you integrate Tailwind CSS into the Hugo build process after the above. Any relevant tailwind-style CSS wants to go in your /assets folder, which is where Hugo looks for resources involved in Hugo Pipes, which may be transformed upon rebuild. The above commands take care of installing the necessary dependencies. Then, in wherever you reference your CSS file (in my case, in baseof.html), use:

    {{ $css := resources.Get "css/style.css" }}

    {{ $style := $css | resources.PostCSS }}

    <link rel ="stylesheet" type="text/css" href="{{ $style.RelPermalink }}">

To dynamically link to your generated CSS. Now you can write all the tailwind code you want and, so long as you're not working with custom classes in your css, it will all just work. There seems to be an outstanding issue working with Tailwind JIT compilation in Hugo, so I'm not going to attempt that now.

Now that the projects page is rendering ok, I'll see about how to apply formatting to it. Looking at the Paginator documentation is a bit confusing, so let's dig around a bit. The list template I pulled out of the blank theme has the following:

            {{ range .Paginator.Pages }}

                {{ .Render "summary" }}

            {{ end }}

Which I take to mean - using the built-in Pagination function, get all the pages (assumedly only the projects pages?), then render them using the alternative view "summary" (which is in /layouts/_default/summary.html). Making a copy of that them file in my /layouts folder, I can see that that is indeed the case. Creating a few more project files use npx hugo new project/gooddog.md (for example), all of the new project pages show up in the paginator, which is good. And now I can go through formatting the summary.html file to apply formatting to all of the listed projects!

One curious thing is that Hugo is doing its own truncation of longer bits of Lorem Ipsum and adding a "read more" link. Presumably that's what this structure does:

        {{ .Summary }}

        {{ if .Truncated }}

            <a href="{{ .Permalink }}">Read more...</a>

        {{ end }}

So let's learn more about that. It seems that the page .Summary functionality is one of Hugo's built in page variables. It seems Hugo will default to using the first 70 words of a piece of content as a summary (or another value, if specified in the configuration). Or, one can override this with a custom summary in Frontmatter. OR, one can override THAT with a <!--more--> tag in the content to specify where the content should be split. Let's try out that second option on the Demilight page. By adding a summary key to the frontmatter of demilight.md, we can override the generated page summary so it shows up more cleanly.

A post from the Hugo forums suggests that it's possible to use one's own custom keys in the frontmatter as well. So, by adding the slug key to some frontmatter in a couple of the content files, and rewriting the summary section like so:

    <div>

        {{ if .Slug}}

            {{ .Slug }}

        {{ else }}

            {{ .Summary }}

            {{ if .Truncated }}

                <a href="{{ .Permalink }}">Read more...</a>

            {{ end }}

        {{ end }}

    </div>

It's possible to have the list page display the hand-written slug if one was provided, or the generated summary if one wasn't! Neat!

(We can also point the "more projects" card on the homepage to "/project" to link it to this generated page.

Now to associate an image with each of these individual project cards, we can do something like:

 {{ $img := resources.Get "images/goodogs.jpg" }}

<img src="{{ $img.RelPermalink }}" alt="" class="col-span-1">

Which makes the LoraDmx image associated with each and every card. Which is effective, but not exactly communicative. To improve this, we can use an additional front matter parameter called "slug_image" which specifies an image file path like "images/loradmx.jpg".

HOLD THE PHONE. After another much fiddling around, I realize I made a silly error in the previous sentence. Did you spot it? Using a path like "images/loradmx.jpg" means the browser will look for the resource relative to our current page. If we want to look in the images directory, we should specify the image as /images/loradmx.jpg (note the leading slash). Oof. 25 minutes gone on that one. I found this post on how to access your image resources in Hugo very helpful.


Now that we have a barebones project list page, it would probably be nice to format some actual project pages, yeah? Currently they're just using the default single.html template, which isn't terribly attractive. Let's create a new layout for project pages specifically, which should go in (according to the template lookup order page) /layouts/project/single.html

After much fiddling with Tailwind, something like a decent layout starts to emerge. I also added an additional piece of project content in HTML, just to verify that yes, indeed, that's possible.

fghfghfghg