Last modified 2020-03-16
Design principles
I wanted a very minimal page layout. Nothing fancy; no sidebars, no hidden elements, minimal graphics, and minimal styling. Often in the past I would get bogged down with endlessly tweaking my website’s layout and styling, but despite the time spent doing so I never really felt satisfied with how it looked. This time I’m keeping everything to a minimum and putting the focus on the content.
Tech stack
Hugo
The site is generated using Hugo. I considered Jekyll, Hugo, and Gatsby. Honestly any one of these would have been absolutely fine, but I went with Hugo simply because I work primarily in Go and already have its tooling installed. I wasn’t keen on installing gem or npm simply for this one use-case.
- My theme uses hugo-xmin as the base, though it’s been quite heavily modified.
Netlify
Previously I hosted my site using nginx on a VPS. It worked, but I found it tedious managing things like SSL certificates and setting up automatic code deploys. Netlify has a rather generous free plan that automates many of these concerns, while providing additional capabilities like domain management, capturing form submits, and CDN caching.
- For the custom domain, I opted not to use Netlify’s DNS and instead created
ALIAS
entries on my DNS registrar (provided by Namecheap). - Commits to the
master
branch are automatically deployed.
Netlify CMS
Netlify also provides a CMS framework as part of their ecosystem. I’m used to the workflow of composing in a text editor and pushing using Git, but I found myself rarely writing posts because I didn’t have a good development setup on my desktop (Windows). I figure having a CMS this time around should make it more likely for me to publish.
- The editorial workflow is enabled; posts are created in a draft status (represented by branches in Git).
- Deploy previews are enabled.
- I have the CMS configured to create posts with the path
posts/{{slug}}/index.md
instead of the typicalposts/{{slug}}.md
; this is marked as a beta feature (1, 2).
Feature set
Galleries
Gallery support with a lightbox implementation for viewing photos within a post. The lightbox implementation is provided by PhotoSwipe.
Demo
Setup
First we define a gallery
shortcode that accepts a selector (eg. images/*.jpg
), which is used to select the images for display. HTML markup is generated for each selected image.
Click to expand
<div class="gallery" itemscope itemtype="http://schema.org/ImageGallery">
{{- range (.Page.Resources.Match (.Get "src")) }}
{{ $thumbnail := .Resize "320x" }}
<figure itemscope itemtype="http://schema.org/ImageObject" class="image gallery-item">
<a href="{{ .Permalink }}" itemprop="contentUrl" data-size="{{ .Width }}x{{ .Height }}" >
<img src="{{ $thumbnail.Permalink }}" itemprop="thumbnail" alt="galleryImage" class="galleryImage" />
</a>
<figcaption itemprop="caption description">{{if ne .Title .Name}}{{.Title}}{{end}}</figcaption>
</figure>
{{ end }}
</div>
Second, the following snippet placed in the footer of the page does three things: If a gallery shortcode is defined on the page, then include the PhotoSwipe assets, add the required element to the DOM, and initialize the library.
Click to expand
{{- if .HasShortcode "gallery" -}}
<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
<!-- Background of PhotoSwipe.
It's a separate element, as animating opacity is faster than rgba(). -->
<div class="pswp__bg"></div>
<!-- Slides wrapper with overflow:hidden. -->
<div class="pswp__scroll-wrap">
<!-- Container that holds slides.
PhotoSwipe keeps only 3 of them in DOM to save memory.
Don't modify these 3 pswp__item elements, data is added later on. -->
<div class="pswp__container">
<div class="pswp__item"></div>
<div class="pswp__item"></div>
<div class="pswp__item"></div>
</div>
<!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
<div class="pswp__ui pswp__ui--hidden">
<div class="pswp__top-bar">
<!-- Controls are self-explanatory. Order can be changed. -->
<div class="pswp__counter"></div>
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
<button class="pswp__button pswp__button--share" title="Share"></button>
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
<!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
<!-- element will get class pswp__preloader--active when preloader is running -->
<div class="pswp__preloader">
<div class="pswp__preloader__icn">
<div class="pswp__preloader__cut">
<div class="pswp__preloader__donut"></div>
</div>
</div>
</div>
</div>
<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
<div class="pswp__share-tooltip"></div>
</div>
<button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
</button>
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
</button>
<div class="pswp__caption">
<div class="pswp__caption__center"></div>
</div>
</div>
</div>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"></script>
<script src="/js/init-photoswipe.js"></script>
<script>initPhotoSwipeFromDOM('.gallery');</script>
{{- end -}}
Lastly, to provide a masonry layout for photos the following CSS styles are used.
Click to expand
.gallery {
width: 100%;
display: block;
margin: 0 auto;
line-height: 1;
}
.gallery figcaption {
display: none;
}
/* Extra Small Devices, Phones */
@media only screen and (min-width : 480px) {
.gallery {
column-count: 1;
column-gap: 3px;
}
}
/* Small Devices, Tablets */
@media only screen and (min-width : 768px) {
.gallery {
column-count: 3;
column-gap: 3px;
}
}
/* Medium Devices, Desktops */
@media only screen and (min-width : 992px) {
.gallery { /* Masonry container */
column-count: 4;
column-gap: 3px;
}
}
.gallery-item { /* Masonry bricks or child elements */
display: inline-block;
margin: 0.1rem;
width: auto;
}
Usage
Consider the following directory structure. website-tech-stack
is a page bundle containing this post, and there are two images within the bundle.
> content/post/website-tech-stack/
> images/
> image1.jpg
> image2.jpg
> index.md
Our gallery
shortcode accepts a selector that can be used to select some images. To display a gallery, we invoke the shortcode as follows.
{{< gallery src="images/*.jpg" >}}
Image pre-processing
Using the ImageMagick suite of tools here’s a general-purpose command to bulk resize images.
mogrify -strip -auto-orient -resize 1200x1200 -quality 75 *.jpg
History
In 2015 I wrote a post detailing the tech stack I had then. It’s outdated now but I’ve migrated the post from my old blog for posterity.