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

Ops-Talks #04

By Florian Dambrine

Ops-Talks #04

OpsTalks #04 - Hugo Static Site Generator & Preact

  • 328
Loading comments...

More from Florian Dambrine