The Front Flip: When a DevOps Engineer Enters the JAMStack — Functional Preact/Hugo/AWS Amplify

Florian Dambrine
GumGum Tech Blog
Published in
10 min readDec 18, 2020
Photo: BENNI SCHMID — Copyright by https://www.gibbon-slacklines.com/

Relax and put your seat belt on… You’re about to enter my journey of an intense 2 weeks sprint I recently led, swapping my DevOps hat for a front-end one to build a full JAMStack website from the ground up deployed on AWS!

Foreword & Context

The GumGum Verity team applies machine learning techniques to the text and imagery on web pages to identify the context of the advertising inventory and brand safety threats. It then delivers content-level data over a Rest API that help publishers, DSPs and advertisers place ads in safe and relevant contexts.

This API not only supports page level analysis, but also offers standalone endpoints for text, image and video classification.

It acts as a coordinator, orchestrating the flow of a request within pipelines made of a dozen micro-services until it has a full response that can be sent back to the client.

Visualization of Page processing steps

When a request gets stuck in the pipeline (no success nor response notification), identifying the culprit in such an infrastructure is like trying to find a needle in a haystack:

  • What system did not respond to the request?
  • Did the request fail in the pipeline or did the caller miss the response?
  • Is the request response simply delayed due to lag in the pipeline?

An API developer would go check the different back ends and look for the raw data corresponding to a request to be able to provide a response after investigation…

But for the rest of us, it sounds more like, “I don’t even know where this data is stored…”

It became clear to me that we needed a Web UI that will help visually display all the data for a request and make it easy for anyone to spot the problem rapidly.

The only thing is that our team is made of everything except front-end engineers. With my recent focus on enhancing team productivity, I took on this mission of building a Web UI from scratch.

Front end for non Front end people…

Collection of JavaScript Frameworks : Copyright by Jose Aguinaga

We all know how vast the front-end eco-system is: from build tools and asset managers like Webpack, Yarn and Grunt, to frameworks and libraries such as React, Preact, Vue and Angular, and even static site generators (SSG) like Hugo, Gatsby and Jekyll. You have a lot of options to build a website in 2020!

Given that my JS knowledge stopped at simple vanilla JS and at most JQuery, I had to make wise choices to make sure this project would not go over my sprint and would remain within my skill set. Hence I came up with the following statements:

  • Use a JS framework only when dynamism is required (Authentication / Retrieve data from AWS back ends / Manage complex UI interactions)
  • Use plain HTML when the content is static (modern libraries like Animate.style can be leveraged to make your static HTML look more dynamic)

Choosing the Tech

As a former DevOps Engineer, I am a big fan of the JAMStack principles which I discovered while reading this great free eBook from Netlify:

  • JavaScript: Dynamic functionalities are handled by JavaScript. There is no restriction on which framework or library you must use.
  • APIs: Server side operations are abstracted into reusable APIs and accessed over HTTPS with JavaScript. These can be third party services or your custom function.
  • Markup: Websites are served as static HTML files. These can be generated from source files, such as Markdown, using a Static Site Generator.
Traditional Vs JAMStack — Copyright by https://jamstack.org/

This is something familiar to us as we have built our entire internal documentation using Hugo and the great Hugo-Learn theme !

Now we need to figure out how to plug a JavaScript workflow/framework into something as simple as a static website generator and make both tools co-exist!

Adding an assets pipeline to Hugo

While doing my research on Hugo and Webpack, I came across the Victor Hugo Netlify template on Github. Simple enough for a newcomer to Webpack and JavaScript (like me), it appeared to be a solid starting point working from the get go.

Choosing a JavaScript framework

I always had somewhere in my mind, “I should learn React one day…” and landing on this article “How Smashing Magazine Manages Content: Migration From WordPress To JAMstack” made me discover Preact (a 3kb alternative to React) which seemed really attractive.

Digging further in Google drove me to this article, Preact meets CMS: Building Lightweight Portable Widget Components from Zouhir (Podcast available as well), which literally convinced me with Preact-habitat.

Preact-habitat is just an extension to Preact’s render function where I decided to provide more API. The concept of Preact habitat is pretty straight forward and can be summarised as :

> Scan the DOM when the widget’s script is loaded.
> Re-scan again when DOM is fully loaded.
> Allow host CMS page to pass props via HTML.
> Stay < 1KB.

— Copyright by Zouhir

This was the last piece of the puzzle ! Let’s recap where we are at so far with this stack:

Preact & Hugo stitched together with Webpack

Getting Started — Project Scaffolding

One of the great thing I like about Preact is that it comes with Preact-Cli that helps you get started in seconds and allows you to create a project skeleton from a Github repository template.

I just had to glue together Hugo/Preact and Webpack to get my own template going (originally forked from the Victor Hugo Netlify template).

################################################################
### Gettings Started - Docker way (no setup required)
################################################################
### Create project scaffolding using preact-cli from custom template$ docker run --rm \
-w /app \
-v $(pwd):/app \
lowess/preact-cli create Lowess/preact-hugo demo-app
### List project folder content$ cd demo-app/### Run project using "npm run start"$ docker run --rm \
--entrypoint="" \
-p 3000:3000 \
-e HOST=0.0.0.0 \
-w /app \
-v $(pwd):/app \
lowess/preact-cli npm run start
## Visit http://localhost:3000

Or simply go check it out on Github pages: https://lowess.github.io/preact-hugo

The Static Content navigation drives you to a list of posts that are written in simple Markdown and generated by Hugo.

Hugo statically built content

The Dynamic Content navigation loads a dynamic Preact-habitat widget that is built with our JavaScript tool chain and packaged along with the app.

Preact widget injection in Hugo thanks to Preact-habitat

Hugo JavaScript widget injection with Preact-habitat ?

At this stage you might be wondering how Hugo injects the dynamic content as part of the build process. It’s pretty simple:

  • Hugo searches for the layout to use for a given page in a well defined order. In our case, the layouts/dynamic/ section should override the default site behavior to do whatever is necessary to load the JavaScript widget. Here is an example of Hugo layout to do so:
layouts
├── _default // Default layout for static pages and homepage
│ ├── baseof.html // Main website structure
│ ├── list.html // List of articles (eg. /static/)
│ └── single.html // Single article (eg. /static/getting-started)

├── dynamic // Override templates for /dynamic section
│ └── list.html // Defines the logic to load JS widget
...
  • The default template is overridden by dynamic/list.html which defines the main content of the page:
{{ define "main" }}
{{- "<!-- Preact component injection -->" | safeHTML }}
{{ partial "preact.html" (dict
"widget" .Site.Data.webpack.helloWidget
"data" (dict "name" "I am built with Preact")
)
}}
{{ end }}

This file calls a partial template with proper arguments like the widget to load (path to <widget>.js) and the data (props passed as JSON to the widget component).

The partial template preact.html is simply implementing Preact-habitat logic to load and render the widget:

<div class="preact">
{{- "<!-- Preact component -->" | safeHTML }}
{{- $script := .widget }}
{{- $data := .data }}
{{ with $script.css -}}
<link href="{{ relURL . }}" rel="stylesheet">
{{- end }}
{{ with $script.js -}}
{{ with $data -}}
<script type="text/props">
{{ . | jsonify }}
</script>
{{- end }}
<script async src="{{ relURL . }}"></script>
{{- end }}
</div>

Here is what the final render of this HTML page looks like:

<div class="content container-fluid p-0">      
<!-- Preact component injection -->
<div class="preact">
<!-- Preact component -->
<script type="text/props">
{"name":"I am built with Preact"}
</script>

<script async src="/preact-hugo/helloWidget.e5579.js"></script>
</div>
</div>

A Serverless back end with AWS Amplify

Authentication and GraphQL API

One important criteria that really mattered to us was to have a fully managed authentication service that we could rapidly plug into and would help us perform operations in our AWS back ends securely.

AWS Amplify — An AWS SDK to build full stack application

The AWS Amplify SDK seemed to be a great fit as it offers OAuth authentication using Cognito and supports federation with social providers such as Google.

It also comes with GraphQL API features that let you plug into AWS AppSync to handle the heavy lifting of securely connecting to data sources like AWS DynamoDB or Lambda. It also has a built in feature that will ensure that the user performing the request is currently authenticated in AWS Cognito.

Wow! Lots of concepts to digest! But watching AWS Amplify with Amazon DynamoDB Single Table and Direct AWS Lambda Resolvers with Rick Houlihan was helpful and confirmed this technical choice.

The major key takeaway I learned from this video is the fact that AppSync now seamlessly integrates with Lambda using direct resolvers.

The lambda function will likely act as a giant SWITCH CASE that will, based on the name of the GraphQL query, perform a certain action (query DynamoDB records, fetch content from RDS, pre-sign an S3 URL…). The sky is the limit as long as you are within your VPC and you likely have access to anything you may want!

This means that your back end remains quite simple: A public and secured API Gateway (AppSync endpoint) and your business logic (in a single Lambda).

Here is a diagram that recaps how we deployed this Serverless infrastructure in order to leverage AWS Amplify SDK on the front end:

AWS Amplify — AppSync Back end / Cognito Authentication layer

We now have all the pieces our application requires to serve the different features we want to implement, so now it’s time to actually write code.

Thinking in JavaScript…

I have to admit, it’s something. Coming from the back-end/infra world and mostly working with Python or Terraform on a daily basis, it sounded quite unnatural to write JavaScript at first…

As usual, when you don’t know, you look for best practices in documentation. I quickly understood that the (P)React world offered two ways of doing things:

  • The former way of writing classes
  • The newer way introduced in React v16.8 (2018) of using hooks (which also exist in Preact)

Watching 90% Cleaner React With Hooks by Ryan Florence — React Conf 2018 was the trigger that led me to adopt a functional style using hooks over the former class definitions.

Here an implementation of Amplify authentication using Preact written in a functional style:

Conclusion

Web development in 2020 offers tons of possibilities no matter what your skill set is! Remember that the good old SPA (single page application) is not the only choice and other approaches do have their pros as well (SEO, editable content from a CMS…).

This blog post also showcases the fact that static site generators like Hugo can actually be leveraged in more complex JAMStack projects or extended to use cases you might not think of (like turning your Hugo website into a static API according to this Forestry.io blogpost, which is something we also did in this project).

👉 With that, I will let you go JAM on your stack now

Resources

We’re always looking for new talent! View jobs.

Follow us: Facebook | Twitter | Linkedin | Instagram

--

--

Florian Dambrine
GumGum Tech Blog

Principal Engineer & DevOps enthusiast, building modern distributed Machine-Learning eco-systems @scale