Markdown is really nice to work with, especially when you’re a developer. This is why it has become the default language choice for document writing among software engineers. Its design emphasizes readability in plain text, allowing you to read and edit raw text without relying on a parser like Pandoc to convert it into HTML or a renderer such as a browser to view the formatted HTML.

The widespread adoption of Markdown really speaks for itself; it has become so prevalent that its usage hardly needs any further explanation. The vast ecosystem has almost everything I need through Remark and Rehype plugins. I have even created custom plugins for this blog due to the ease and how straightforward it is to modify the Markdown syntax tree.

This post demonstrates my customized Markdown-to-post process for this blog, serving as both documentation and a guide for future reference, saving me from revisiting the code to figure out what I implemented.

Markdown, but more powerful

GitHub Flavored Markdown enhances standard Markdown by incorporating additional features such as tables, task list items, and converting plain text URLs into clickable links. I’m so used to writing Markdown on GitHub that I didn’t realize they were extensions, not part of standard Markdown. GFM is the default formatting in Astro, the framework powering this site.

Beyond Markdown, there’s MDX, a superset supporting embedded components from various frameworks. This flexibility ensures I’m well-equipped, especially when creating interactive posts.

Writing code

As a developer, code snippets are a must in my blog posts. I want them to be well-highlighted, akin to how they appear in my IDEs. I opted for rehype-pretty-code, a wrapper of shiki for Markdown so I don’t have to worry much about it.

Apart from syntax highlighting, it has some other nice-to-haves. The inline code can also be colored: var x = 2. With the code block, you can add title, show line numbers or highlight specific lines or words.

example.go
package main
 
type Address struct {
	Street  string `json:"street"`
	Suite   string `json:"suite"`
	Zipcode string `json:"zipcode"`
}

Embedding responsive images

Adding image in Markdown is straightforward and supported by default. However, ensuring they are optimized and performant on the web is a challenge. I don’t want to serve a full-fledged 4K uncompressed image to a phone user, and it probably doesn’t look good too. The ideal image resolution should match the width and height of the rendered element in the browser. For example, if you set an image to display on your site at 1600px wide, it looks the sharpest when the actual image’s width is exactly 1600px. The method to achieve this is called responsive image and MDN has a great article about this.

There’s also something called Cumulative Layout Shift. To prevent this, you need to specify the image’s width and height beforehand. This allows the browser to reserve space for the image while it fetches it, ensuring a smooth layout without sudden shifts.

This is when I need to write my own Markdown plugin to swap out the default image with my custom responsive image component. This component generates different resolutions for an image at build time and provides instructions on which resolution to use for specific screen sizes. It would be easier if you use image hosting services so you can just put a query parameter at the end of the URL like image.service/my-image.png?width=1600 and call it a day. The service will handle image resizing and caching for you. But it’s just a personal blog, I don’t want to pay extra money for a thing that I don’t even know if it would actually have traffic.

Anyway, fortunately, Astro can. They even expose the API that optimizes image behind the scene so it’s really easy to create a custom image component tailored to my need. My site is now 100% static and I can throw it basically anywhere and it can start receiving traffic.

There’s one more aspect to consider: I want the optimization to apply to remote images as well. This way, I can avoid committing large image files to my code repository. During the build process, Astro will download remote images and transform them into custom resolutions. However, the API requires width and height attributes for remote images. To mitigate this, I customized Markdown image’s title syntax to allow the injection of attributes.

Local image, supports paths relative to the markdown file:
![img](./img.jpg)
 
Remote image, width and height attributes are required:
![img](https://share.hhai.dev/IMG_1203.png '#width=1920;height=816')
 
Another way to do remote image, same end result as above:
<img src="https://share.hhai.dev/IMG_1203.png" width="1920" height="816" />
 
![img](./img.jpg)
Text right after the image is automatically recognized as image caption (without
additional line breaks)
 
<img src="https://share.hhai.dev/IMG_1203.png" width="1920" height="816" />
> With the <img> tag, you need to put the caption inside a block quote

Feature section

Keeping everything within the standard reading width can be boring. I want certain elements, like images, to stand out a bit more. To achieve this, I created a Rehype plugin to enclose regular parts of the post within a standard reading width container, while special feature elements are wrapped in a larger width container. These feature elements are identified by a specific featuretype attribute and can be added in a similar manner as the width and height attributes mentioned earlier. Additionally, tables are featured by default because they tend to be wide and require ample horizontal space to display properly. You’ll need to be on desktop to see the below examples in effect.

Remote image with featuretype=sm

Remote image with featuretype=sm

Remote image with featuretype=lg

Remote image with featuretype=lg

Math

I have a love hate relationship with math. I hate it because I’m not good at it, but it has the capability to solves many things that are related to most of the work I do. It feels great when I finally solve something using math, like this piece of asymptotic analysis I did for a blog post:

O(nma1+malog(m))O(n \cdot m^{a-1} + m^{a} \cdot log(m))

This is enabled by the KaTeX library.

I want more

This post might never truly be finished. As I continue to introduce new features, I’ll revisit this space, editing and documenting those additions.

Anyway, these are the features that I want to add in the future:

  • A button to download original images: since the displayed images are already resized and optimized, readers will have the option to download the original images.

  • Image zoom: similar to the functionality on Medium (using something like react-medium-image-zoom).