Motivation
In the past, I built some portfolios to mainly showcase my small projects when I was learning programming and job hunting. These portfolios were unnecessarily complex, as they were built with React.js or Next.js. After landing my first programming job, I neglected the portfolio, neither updating it nor writing any blog posts. At the beginning of 2025, I decided to make a “fresh start” by rebuilding my website with a simpler UI and without any JavaScript front-end frameworks or libraries.
As I have grown in my programming career, I have become more interested in understanding how technologies work under the hood and sharing my thoughts on topics that intrigue me. This new website will become a space for exploration, experiment and writing.
The source code of my new website can be found at namvnngu/namvnngu.github.io.
Tech stack
Here are the major technologies I have used to build my website:
- HTML defines the website's structure and content.
- CSS specifies the website's styling.
- GNU Bash defines scripts for building the website, deploying the website and updating repeated HTML blocks across pages.
- GNU Make defines custom commands and automates repeated tasks such as running a development server, building the website, deploying the website, and cleaning up.
- GNU sed updates repeated HTML blocks.
- Pandoc converts Markdown to HTML.
- GitHub Pages hosts the website.
Styling
I have been using only pure CSS to style my website.
CSS methodology
There are many CSS methodologies to architect, manage and maintain a website's styling, such as BEM, OOCSS, SMACSS, Atomic CSS, ITCSS or CUBE CSS. Eventually, I chose CUBE CSS. CUBE stands for Composition Utility Block Exception, and obviously, CSS stands for Cascading Style Sheets. I knew this methodology thanks to Kevin Powell's amazing video, A look at the CUBE CSS methodology in action.
The reason why I chose CUBE CSS is because its website mentions:
- “CUBE CSS is a CSS methodology that's orientated towards simplicity, pragmatism and consistency.”
- “This differs somewhat to other popular methodologies like BEM, which are very good in their own right, but also run against the grain of CSS.”
Those made me want to explore and experiment it.
It turns out that using CUBE CSS has been a fun experience, even though I initially had a hard time to understand what Block is and when Block should be used.
I am not entirely confident that I am applying CUBE CSS correctly, but at least I find it fun to work with.
CSS Reset
I referenced Josh Comeau's CSS Reset. His CSS reset is solid, simple, and sufficient in most cases. If you get a chance, I highly recommend checking it out. He explains it in great detail.
Code syntax highlighting
I use a single color for code syntax highlighting. I hope that my choice is not too disappointing for you.
For example:
#include <stdio.h>
int main(void) {
printf("Hello world!");
}
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Content
How is page content managed?
I have been using HTML directly to create and update pages. However, there is a slight exception when creating a writing page, which will be explained in more detail in the below section How is a writing like this created?.
Writing pages in HTML directly introduces one issue. Some areas in all pages
are identical such as navigation bar and footer. If the number of pages grows
into the hundreds (due to the number of writing pages), keeping these areas
consistent and updated in all pages becomes a daunting and time-consuming task.
Therefore, I created Block
built on top of GNU sed
to solve this issue. A block is similar to a component in popular front-end
frameworks/libraries. It is a reusable group of HTML elements.
Block
declaration syntax:
<!-- block-start: block-name -->
<!-- block-end: block-name -->
Let's see Block
in action with the example below.
- Create files as the following tree.
scripts/
└── block.sh
src/
├── blocks/
│ └── header.html
├── home.html
├── index.html
└── projects.html
- Define the
header
block. Note that while the indentation is expected to be formatted properly when theheader
block is placed in HTML page files, indentation does not matter in my case as I do not preserve it.
<header>
<nav>
<a href="/home.html">Home</a>
<a href="/projects.html">Projects</a>
</nav>
</header>
- Define the Home page with
header
's start and end block tag.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Home</title>
</head>
<body>
<!-- block-start: header -->
<!-- block-end: header -->
</body>
</html>
- Define the Projects page with
header
's start and end block tag.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Projects</title>
</head>
<body>
<!-- block-start: header -->
<!-- block-end: header -->
</body>
</html>
- Run
scripts/block.sh
with the block name (i.e.header
). The script can be found in scripts/block.sh.
./scripts/block.sh header
- As a result, the Home page and the Projects page are updated as follows.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Home</title>
</head>
<body>
<!-- block-start: header -->
<header>
<nav>
<a href="/home.html">Home</a>
<a href="/projects.html">Projects</a>
</nav>
</header>
<!-- block-end: header -->
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Projects</title>
</head>
<body>
<!-- block-start: header -->
<header>
<nav>
<a href="/home.html">Home</a>
<a href="/projects.html">Projects</a>
</nav>
</header>
<!-- block-end: header -->
</body>
</html>
- Whenever
src/blocks/header.html
is updated, simply run the script in the step 5 to update theheader
block across all pages.
How is a writing like this created?
A writing is created through the following steps:
- Create a Markdown file, e.g.
writing.md
. - Compose content in
writing.md
. - Run the following Pandoc command to convert Markdown to HTML, i.e.
writing.md
towriting.html
.
pandoc writing.md \
--toc \
--standalone \
--wrap=preserve \
--highlight-style=kate \
--output=writing.html
- Copy content line by line from
writing.html
to the actual HTML page, making adjustments to align with my website's styling.
As you can see, writing pages are first written in Markdown and then converted to HTML. This is the only exception to not using HTML directly to create pages, which is mentioned in the section How is page content managed?.
Interesting points during development
- GNU sed is basically similar to Vim, but non-interactive. It can perform basic text editing operations of an interactive text editor.
- The more I work with Bash, the more I realize that it is extremely powerful.
- Pandoc is extremely powerful. I highly recommend checking out Pandoc website to behold its “God ability in file format conversion”. By the way, it is written in Haskell.
- Living without JavaScript is happy.
- Writing pure CSS is fun.
- Writing pure HTML is fun.