Website tech stack

2020-03-16
Table of contents

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.

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.

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.

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.