Knowledge worth sharing


Florian Dambrine - Principal Engineer - @GumGum

Hugo & Preact


What DOES it DO




DEEP dive



What does it do

/ Static site generators /

Static site generators & HUGO

Think of a static site generatorΒ as a script which takes in data, content and templates, processes them, and outputs a folder full of all the resultant pages and assets.

What is Hugo ?

What are Static Site Generators (SSGs) ?

  • Blazing fast SSG written in Go !Β 
  • General-purpose Website framework


Content Delivery Network (CDN)
Web Server
App Server



Content Delivery Network (CDN)

Optimal performance (CDN)

More Secured (Behind CDN)

Simplified hosting (S3 bucket)

DΓ©ploiement atomique (CDN Invalidation)

Git History and atomique rollbacks

CMS for content management

Static Websites Rock !

Git Commit
Automated Deployment
On Save
Hosting (Github Pages, S3)

Static Websites Deployment

hugo WEb sites examples


/ πŸ™Œ getting Started / Hugo Basics / πŸ™Œ Hands On basics /

Hugo - Getting Started

brew install hugo
hugo new site hugo-tutorial
cd hugo-tutorial
hugo serve
# Visit localhost:1313
git init
git submodule add https://github.com/giraffeacademy/ga-hugo-theme.git themes/ga-hugo-theme
echo 'theme = "ga-hugo-theme"' >> config.toml


Step 1

Step 2

Step 3

πŸ™Œ Hands On


title = "Verity"
description = "Verity Homepage"
date = 2019-09-03T07:43:09Z
weight = 3
chapter = true
pre = "<b><i class='fa fa-caret-square-up'></i> </b>"
draft = false

# Verity

Welcome to the Verity homepage. This documentation aims to deep dive into Verity eco-system.

Verity eco-system is made of **3 major components**:

* `Verity-Api` submit user requests for processing
* CV microservices for images
* NLP for text processing / keywords extraction / threat classification and a lot more

## Global Architecture Diagram

{{< figure src="img/verity-infrastucture-diagram.png" title="Global infrastructure diagram" >}}

{{% children %}}

Hugo Basics - Folder Layout

β”œβ”€β”€ archetypes
β”œβ”€β”€ config.toml
β”œβ”€β”€ content
β”œβ”€β”€ data
β”œβ”€β”€ layouts
β”œβ”€β”€ static
└── themes
  • archetypes content template files in the archetypes directoryΒ of your project that contain preconfigured front matterΒ 
  • config.toml Hugo provides a Β large number of configuration directivesΒ this file usually defines global site parameters
  • content All content for your website will live inside this directory.
  • data This directory is used to store configuration files that can be used by Hugo when generating your website.
  • static Stores all the static content: images, CSS, JavaScript, etc. When Hugo builds your site, all assets inside your static directory are copied over as-is.
  • layouts Stores templates in the form of .html files that specify how views of your content will be rendered into a static website.

Hugo Basics - Leaf and Page bundles

β”œβ”€β”€ content
β”œβ”€β”€ dir1
β”œβ”€β”€ A1.md
β”œβ”€β”€ A2.md
β”œβ”€β”€ B.md
β”œβ”€β”€ C.md
β”œβ”€β”€ dir2
β”œβ”€β”€ index.md

Page bundle


Leaf bundles


Let's Practice

Let's kick off this Hugo-Tutorial website and understand Hugo internals


# Go inside project
cd hugo-tutorial

# Fetch Hugo theme
git submodule update --init --recursive

# Start Hugo server from a terminal
hugo serve

πŸ™Œ Hands On

GO further...

Full series of youtube tutorials with articles and a github project

-- By Mike Dane


/ πŸ™ŒΒ  HUGO CONTINUOUS DEPLOYMENT / πŸ™Œ Static JSON API / πŸ™Œ Headless CMS with forestry /


Hugo Continuous deployment

Create a new SSH Key and store the public one in Github > https://github.com/settings/keys

# /!\ Be careful to not override your ~/.ssh/id_rsa
$ ssh-keygen -t rsa -b 4096 -C "drone-hugo@ops-talks.com

# Enter file in which to save the key (/Users/florian/.ssh/id_rsa): drone-hugo
# Enter passphrase (empty for no passphrase): (empty)

# Your identification has been saved in drone-hugo.
# Your public key has been saved in drone-hugo.pub.

πŸ™Œ Hands On

Login and peer with your Github account https://cloud.drone.io/. Once logged in search for hugo-tutorial and activate the project

Add a your Private SSH key as a Drone Secret. Name it github_ssh_key

git commit --allow-empty -m 'πŸš€ Deploy' && git push

/ Static API /

Hugo to serve a static API

Hugo can output content in multiple formats, including calendar events, e-book formats, Google AMP, and JSON search indexes, or any custom text format.



# JSON templates should go in 
# layouts/_default/{pageKind}.{outputFormatName}.{extension}

  home = [ "HTML", "JSON" ]
  page = ["HTML", "JSON"]
  section = ["HTML", "JSON"]

πŸ™Œ Hands On

Hugo to serve a static API (1/2)

{{ define "response" }}
  "kind" : "{{ .Kind }}",
  "count" : "{{ len .Pages }}",
  "items" : [
      {{ range $i, $e := .Pages -}}
          {{ if $i }}, {{ end }}{{ .Render "item" }}
      {{ end }}
{{ end }}

πŸ™Œ Hands On

  "data": {{ block "response" .}}{{ end }}

Hugo to serve a static API (2/2)

{{ define "response" }}
{{ .Render "item" }}
{{ end }}
  "kind": "{{ .Kind }}",
  "title": "{{ .Title }}",
  "permalink" : "{{ .Permalink }}index.json",
  "length": "{{ len .Content }}"

πŸ™Œ Hands On

/ Forestry headless CMS /

Forestry CMS

πŸ™Œ Hands On

Go to Forestry.io and sign in using your Github account

  • Add a new site
  • Pick Hugo as static site generator (leave version as is)
  • Search for hugo-tutorial and choose master branch
  • In Settings > General > Deploy Admin

Deep-Dive (2)

/ πŸ™ŒΒ  HUGO & Preact / Jamstack with aWS Amplify /

HUGO & Javascript Frameworks ?

Image Processing | Asset minification | SASS POSTCSS

Hugo Pipes is Hugo’s asset processing set of functions.

Let's Practice

docker run --rm -it \
    -w /app \
    -v $(pwd):/app \
    lowess/preact-cli \
    create Lowess/preact-hugo hugo-js

Let's kick off this Hugo website and bisect its directory layout

docker run --rm \
    --entrypoint="" \
    -p 3000:3000 \
    -e HOST= \
    -w /app \
    -v $(pwd):/app \
    lowess/preact-cli npm run start

πŸ™Œ Hands On




Backend / API

  • S3
  • CloudFront
  • Lambda@Edge
  • R53
  • Cognito
  • Google Federation
  • Lambda
  • AppSync
  • Lambda

JAMSTACK - Hosting on cloudfront CDN


S3 / CloudFront / Lambda@Edge / R53

Why Lambda@Edge ?

[...] CloudFront does allow you to specify a default root object (index.html), but it only works on the root of the website (such as http://www.example.com > http://www.example.com/index.html). It does not work on any subdirectory (such as http://www.example.com/about/). If you were to attempt to request this URL through CloudFront, CloudFront would do a S3 GetObject API call against a key that does not exist. [...]


# Lambda@edge - subdir-index.js
'use strict';

exports.handler = (event, context, callback) => {
    // Extract the request from the CloudFront event that is sent to Lambda@Edge
    var request = event.Records[0].cf.request;

    // Extract the URI from the request
    var olduri = request.uri;

    // Match any '/' that occurs at the end of a URI. Replace it with a default index
    var newuri = olduri.replace(/\/$/, '\/index.html');

    // Log the URI as received by CloudFront and the new URI to be used to fetch from origin
    console.log("Old URI: " + olduri + " New URI: " + newuri);

    // Replace the received URI with the URI that includes the index page
    request.uri = newuri;

    // Return to CloudFront
    return callback(null, request);

JAMSTACK - Authentication with Cognito


Cognito / Google Federation / Lambda

OAuth Flow

PreSignUp Lambda

- Domain validation - (ensure email address is part of whitelisted domains)

OAuth Challenge

JAMSTACK - Authentication with Cognito


Cognito / Google Federation / Lambda

# Authenticate User with Amplify Cognito Federation
useEffect(() => {
    Hub.listen("auth", ({ payload: { event, data } }) => {
      switch (event) {
        case "signIn":
          setUser({ user: data })
        case "signOut":
          setUser({ user: null })

      .then(user => {
        setUser({ user })
      .catch(() => console.log("Not signed in"));
  }, []);
# Trigger Cognito OAuth Flow 
const SignIn = () => {
  return (
    <div class="flair-signin mx-auto card">
      <div class="card-body">
        <h6 class="card-subtitle"><i>Dive into Verity with elegance...</i></h6>
           class="btn btn-danger w-100"
           onClick={() => Auth.federatedSignIn({provider: 'Google'})}
          <FaGoogle /> Signin with Google
        <hr />
          class="btn btn-default w-100"
           onClick={() => Auth.federatedSignIn()}
          <FaAws /> Sigin with AWS Web UI

JAMSTACK - Backend / api with appsync

Backend / api

AppSync & Lambda Direct resolvers

AppSync VTL
With Direct Resolver

JAMSTACK - Backend / api with appsync

Backend / api

import { API } from '@aws-amplify/api';

const fetchFromAppSync = async ({graphQLQuery, graphQLVars}) => {
    // Run a GraphQL query on AWS AppSync
    const { data } = await API.graphql({
        query: graphQLQuery,
        authMode: 'AMAZON_COGNITO_USER_POOLS',
        variables: graphQLVars,

    // GraphQL results come with the name of the query as a key { "graphQLQuery": {<data>} }
    const result = Object.values(data)[0];

    console.log(`[fetchFromAppSync] received ${JSON.stringify(result)}`);
type Query {
  getVerityPageByPageUrl(data_source: String, page_url: String!, env: String): VerityPage
  getVerityVideoByUuid(uuid: String!, env: String): VerityVideo

type VerityPage {
  page_url: String!
type VerityVideo {
  uuid: String!
(1) Export schema.json
(2) Run aws amplify codegen

JAMSTACK - Backend / api with appsync

Backend / api

AppSync / Lambda

import { API } from '@aws-amplify/api';

const fetchFromAppSync = async (
    {graphQLQuery, graphQLVars}
  ) => {
    const { data } = await API.graphql({
        query: graphQLQuery,
        authMode: 'AMAZON_COGNITO_USER_POOLS',
        variables: graphQLVars,

graphQLQuery = getVerityPageByPageUrl

graphQLVars = { page_url: "http://example.com",

env: "prod" }


JAMSTACK - Backend / api with appsync

Backend / api

AppSync / Lambda

def handler(event, context) -> Dict[Any, Any]:
    # Parse event payload
    arguments = event.get("arguments", {})
    graphql = event.get("info", {})
    query = graphql.get("fieldName", None)

    response = {}

    env = arguments.get("env", "prod")

    # Switch case routing based on GraphQL queries
    if query == "getVerityPageByPageUrl":
        page_response = dynamodb_to_object(
            get_page_by_url(page_url=arguments["page_url"], env=env)

    elif query == "getVerityVideoByUuid":
        response = dynamodb_to_object(
            get_video_by_uuid(uuid=arguments["uuid"], env=env)

    return response





Knowledge worth sharing

By Florian