HUGO, Tailwind & TypeScript
The tools behind Skorar.dev

Tags: hugo markdown tailwind typescript
Categories: webdevelopment tools
I thought today we’d talk about the frameworks and tools that power this website.
Let’s take a look at how I came up with the idea of using these frameworks and what advantages they offer me.
Intro
That’s basically it. The website doesn’t consist of anything else. Well, except, of course, for the GitLab CI/CD pipeline, which ensures that the website is automatically rebuilt and deployed with every push. But that’s not what we’re talking about today. And, of course, smaller tools like pagefind for searching and hamburger for the hamburger menu. But those are relatively small tools that don’t really require their own section.
Let’s take a closer look at the core tools and frameworks.
HUGO
HUGO is a so-called Static Site Generator (SSG). This means that the website is generated in advance, and only static files are stored on my server. This, of course, has the advantage that the website loads quickly, even without advanced optimization.
Compared to a typical website created with a CMS like WordPress, for example, the loading time is many times shorter.
This, of course, also depends on whether you optimize your website or not, but that’s a different topic.
What made HUGO particularly interesting for me is simply that I can write both my blog posts and my general pages in Markdown. For me, this combines the best of a CMS and a static, HTML-based website. I don’t have to create a new HTML page for each blog post; I can simply write Markdown files, and HUGO generates the corresponding HTML pages from them. At the same time, I don’t have any extensive system running in the background that could negatively impact the performance of the website.
Getting Started with HUGO
Getting started wasn’t a huge challenge for me, but I still had to familiarize myself with HUGO’s structure and functionality.
I’m used to template engines like Twig, as I work a lot with PHP and Symfony in my job. But I have to admit that I’m not exactly the strongest front-end developer. I’m getting along, but I prefer to stay in the backend 😄.
Generally speaking, after an initial introduction, HUGO is quite easy to use.
Templates are handled using Go templates, which isn’t too unfamiliar to me as a PHP developer.
Here, for example, is how to define a block in a parent file, e.g., the baseof.html
, and extend it in a sub-file, e.g., single.html
:
baseof.html
:
{{ define "main" }}
<main>
{{ block "content" . }}{{ end }}
</main>
{{ end }}
single.html
:
{{ define "content" }}
<article>
<h1>{{ .Title }}</h1>
<p>{{ .Content }}</p>
</article>
{{ end }}
This shouldn’t seem too strange, especially to developers familiar with Twig, for example. It should be said, however, that HUGO also has a few peculiarities. For example, the parameters passed from a post to a template are different than when passing them to a page, for example.
Perhaps I was a bit hasty. If you’re not experienced with HUGO, it might be confusing what I mean by passing parameters.
Every .md file in Hugo has the same structure. The “header” block contains metadata, parameters, and other information. Below that is the actual content.
Here’s an example of the md file used for this blog post while I’m drafting it:
---
title: "Draft-01"
summary: "Summary"
slug: "zwischen-zeilen-und-eindruck.md"
date: 2025-06-18T11:39:00+02:00
updated:
tags: ["hugo", "markdown"]
kategorien: ["webdevelopment"]
draft: false
author: "Skorar"
hero_image: "/images/blog.jpg"
---
We’ll find some of the things we can expect there, such as the title, date, tags, and categories. But also custom parameters like
hero_image
, which I can then use in my template to display an image in the header, for example.
Truly, these are the most important things you need to know about HUGO to decide whether it’s suitable for you or not. Anything else would go beyond the scope of this blog post.
Tailwind CSS
Tailwind is a CSS framework based on utility classes. This means that you use classes that each define a specific property or behavior. Instead of writing 100 classes that do almost identical things, you integrate the classes provided by Tailwind directly into the HTML elements, and the CSS that defines these classes is then generated during the build process.
This is what a simple HTML element with Tailwind CSS looks like, for example, if you want to create a button:
<button class="bg-blue-500 text-white font-bold py-2 px-4 rounded">
Click me!
</button>
I have to admit that I stayed away from systems like Tailwind or, for example, TypeScript for a long time. Why use something new when the tried-and-true things work? Of course, you can definitely learn something by using Tailwind, but I have plain CSS. Isn’t that enough?
Unfortunately, I think the most honest answer to that is: No, it’s not enough. Not because you can’t create a website with plain CSS, but because at some point you should reach the point where it’s more about using systems efficiently than feeling an elitist sense of superiority because you rebel against “this new-fangled nonsense” and prefer “the good old standard” because all this new stuff is “totally unperformant and bloated”.
Yes, those were possibly memorized quotes, but also from old colleagues I’ve worked with in the past. I really went through a phase where I absolutely wanted to learn everything from the smallest possible base. If I’d progressed further, I probably would have eventually learned to program in binary because it’s *“the most performant.” Of course, that’s nonsense. Sure, the origins of software development are important somewhere, but who does that help if you don’t continue your education and aren’t willing to learn new systems?
Okay, now I’ve obviously avoided explaining why I chose Tailwind. So, why Tailwind? It’s actually pretty simple. I wanted to learn something new and have wanted to try Tailwind for a long time.
Getting Started with Tailwind
As I just mentioned, Tailwind works with utility classes. In fact, I didn’t know this when I installed Tailwind in the project. This is partly because I didn’t do the entire project setup, or rather, define what I wanted to use, myself. Instead, I worked with ChatGPT to set up the project.
The use of Generative AI is somewhat controversial, but I have to admit, for me, AI is now an improved version of Google + StackOverflow. And I think that’s where the consensus among many developers is.
I have to admit, getting started was pretty frustrating for me, too. Well, people who know me know that I sometimes have a bit of a short breath when it comes to frameworks or tools I haven’t used before. Because, as I mentioned earlier, I didn’t know that Tailwind works with utility classes. In other words, I was desperately trying to understand how to use standard CSS. It wasn’t until ChatGPT gave me a more detailed explanation that I understood that I should insert the classes directly into the HTML elements, and Tailwind would then generate the CSS during the build process.
Which wasn’t a problem in itself. The only difficulty was figuring out how to define my own classes and how to override CSS, for example.
In the end, it’s a combination of the Tailwind configuration and the use of @apply
in the CSS files.
This is what a simple Tailwind configuration looks like:
module.exports = {
content: [
"./layouts/**/*.html",
"./assets/**/*.{ts,js}"
],
theme: {
extend: {
colors: {
text: '#e0e0e0',
brand: {
DEFAULT: "#1c1c1e",
dark_gray: "#222324",
orange: "#ff7043",
peach: "#ffb347",
gold: "#fffe47",
code: "#263238",
}
},
}
},
plugins: [
require('@tailwindcss/typography'),
]
}
And to customize the hamburger menu, for example, which comes from the Hamburgers package by Jonathan Suh, I use the following CSS:
/* ===== CUSTOM STYLES FOR HAMBURGER MENU ===== */
.hamburger-inner, .hamburger-inner:before, .hamburger-inner:after,
.hamburger--squeeze .hamburger-inner, .hamburger--squeeze .hamburger-inner:before, .hamburger--squeeze .hamburger-inner:after,
.hamburger.is-active .hamburger-inner, .hamburger.is-active .hamburger-inner:before, .hamburger.is-active .hamburger-inner:after {
@apply bg-brand-peach;
}
.hamburger .hamburger--squeeze {
display: none;
}
Of course, this is just a small insight into Tailwind’s configuration, but I think it should be enough to understand why I chose Tailwind and how I use it.
TypeScript
TypeScript is a programming language based on JavaScript and offers static typing. This means that we can define types for variables, functions, and objects in TypeScript. This is especially useful if you have experience with statically typed programming languages, such as C# or Java.
I admit that I also had a little experience with TypeScript, as I used it to develop a GTA roleplay server back in the day. While this isn’t necessarily the finest hour of my development journey, I did at least gain some experience with TypeScript…
Getting Started with TypeScript
Getting started with TypeScript was relatively easy for me, as I already had experience with JavaScript and TypeScript itself. So, I quickly familiarized myself with the syntax and functionality of TypeScript. I actively decided not to write clean code, but to use a Frankenstein-like structure consisting of document.addEventListener
in main.ts
and components.
I don’t want to pretend that this entire page is super cleanly structured. Partly, I was primarily concerned with making it work.
Nevertheless, I’d like to say a few things about using TypeScript.
TypeScript, like Tailwind, isn’t simply loaded into the browser; it must first be compiled. In my case, I use Vite as my build tool. With my configuration, I can simply run npm run build
, and Vite takes care of the rest (or npm run dev
in DEV).
I can’t say too much about the configuration:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["assets/**/*"]
}
As you can see, the configuration is relatively simple. It’s basically just to ensure that TypeScript compiles correctly and the correct files are included.
The Vite configuration is also relatively simple:
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
],
root: '.',
build: {
outDir: 'static/build',
emptyOutDir: true,
rollupOptions: {
input: './assets/main.ts',
output: {
entryFileNames: 'main.js',
assetFileNames: 'style.css'
},
external: [
'/pagefind/pagefind.js'
]
}
}
})
What confused me a bit at first was that Vite only has Tailwind, not TypeScript. But that’s because Vite automatically handles TypeScript files and handles them accordingly. I had to learn that first, though 😄.
Honorable Mentions
Of course, there are a few other tools I use, but they’re more like smaller helpers that don’t really require their own section. For example, I use Hamburgers for the hamburger menu and Pagefind for search.
I’d also like to say something about the use of AI. As already mentioned, I used ChatGPT to set up the project and to help me configure HUGO and Tailwind. I also switched to Claude in part because I felt I was getting better answers there. Which is only partially true, but that’s a topic for another blog post.
The point about AI is very important to me, however, because I want to be transparent from the start about when I use AI and to what extent. This way, for example, I also save myself the trouble of translating texts, as I simply give ChatGPT the German text and then only correct the output.
And I think it’s necessary to be aware of how much AI you use, because, on the one hand, relying too heavily on AI can lead to problems (topic: 15 Risks and Dangers of Artificial Intelligence), and, on the other hand, a false impression is created if we aren’t transparent about when we’ve used AI. However, I’m referring here purely to software development. Topics like image generation with AI, or the use of AI for text, are a completely different and, above all, much more complex topic, one that I don’t really want to discuss publicly, as I don’t have the expertise to have a nuanced opinion on the matter.
I can only say one thing: The persistent theft of artwork and text by AI is incredibly repugnant to me. I admit that I have generated images myself, but more to see what is possible. The trend of selling AI-generated images, using them as content, or even declaring them as art makes me sad and casts a negative light on what AI should be used for, in my opinion. Namely, as a tool to support one’s own creativity, not to replace it. AI is a tool, not a substitute for one’s own thinking or creativity.
And now we’re at the end of the post. I hope I was able to give you a little insight into the tools and frameworks I use for this website. See you next time!
Best regards,
Skorar
