Hugo is one of the most popular open-source static site generators written in Go. It is fast, ease of use, and flexible. Hugo supports markdown JustToThePoint, Markdown and LaTeX.
Installing Hugo. If you are in Arch (GNU/Linux): sudo pacman -Syu go hugo. I prefer a software package manager because it makes it easier to install, update, or uninstall Hugo. Another alternative: brew install hugo.
To verify your install type: hugo version.
Create a new site: hugo new site justtothepoint
Create a new (skeleton) theme: hugo new theme anawim. Then, copy the layout file folders into your Hugo files directory,
Add some content. Create a new home page: hugo new _index.md. Create a new post: hugo new posts/my-new-post.md
A layout is a page used as a “frame”, “skeleton” or “basic template” to display your content. It consists of all the parts that remain consistent across all of the pages. Hugo uses a layout for the home page, a second one for content pages, and a third one that shows or displays lists of pages.
The main home page layout will live in themes/anawim/layouts/index.html, where anawim is my theme. This file defines the HTML structure or skeleton for the site’s home page:
{{ define "main" }}
<div class="container mx-auto">
<h1> {{ .Site.Title }} </h1>
{{ .Site.Title }} specifies that we want to pull the title field out of the config.yaml, the main Hugo’s configuration file.
{{if .Params.subtitle }}
<h2 class="text-secondary h3"> {{ .Params.subtitle }} </h2>
{{ end }}
<p> {{ .Description }} </p>
{{ .Content | markdownify }}
</div>
The {{ .Content | markdownify }} line displays the content of the page. This content will be in a Markdown document in our content folder, more specifically, in the “scope” or context" that contains the data we want to access. The current context is represented by a dot “.” and the content’s file is a file named _index.md in the content directory. It takes this content and runs it through Hugo’s Markdown processor.
The layouts directory contains all the HTML files that are used for generating HTML from the Markdown files. In other words, they specify how your content will be rendered. The _default directory is the place where Hugo will search for the base page layout file, that is, baseof.html:
<!doctype html>
<!-- prefix="og: http://ogp.me/ns#" is the required prefix for open graph protocol meta tags. -->
<html prefix="og: http://ogp.me/ns#" lang="{{ .Site.Language.Lang }}" >
<!-- Schema.org provides a collection of shared vocabularies webmasters can use to mark up their pages
in ways that can be understood by the major search engines! -->
<head itemscope itemtype="https://schema.org/WebPage">
{{- partial "mymeta" . -}} <!-- SEO: meta tags -->
{{- partial "head" . -}} <!-- Google Analytics -->
</head>
<body>
{{- partial "search-form" . }} <!-- Search with Lunr -->
{{- partial "navbar" . -}} <!-- Menu, Navbar -->
{{- block "main" . -}} {{ end }}
{{- partial "script-footer" . -}} <!-- Javascript -->
{{- partial "footer" . -}} <!-- Social Media, Copyright, Privacy Policy and Terms of Use -->
{{- partial "style" . -}} <!-- Css -->
{{- partial "search-index.html" . }} <!-- Search with Lunr -->
</body>
</html>
Observe that we define blocks and partial where we define the different “sections” or “pieces” of our content, e.g., the first two partials (mymeta, head) contain the code that will appear in the HEAD. The dash “-” removes whitespace characters in the output, so we are using it in every partial to reduce the number of blank lines that Hugo generates.
Under the layouts folder, you can see there is a folder called partials. It contains all our partial templates.
list.html is the “frame” or “html template” that Hugo uses to list posts of your site under a particular directory. single.html is the templated used to display a single page.
The layouts folder is where you define your layouts and templates, i.e., your site’s look and feel. They are used to render your content files written in Markdown (.md) in HTML webpages (.html).
The static directory contains your static assets, that is, your CSS, JavaScripts, images, and any other assets that are not generated by Hugo. Everything inside of Hugo’s static/ directory goes to the root of the built site (public/) by default, so there’s no static directory in the build/deployable site.
The themes directory holds our theme “anawim”. It could contain a third-party theme to style your website.
We will also create a folder called data to access data formatted in JSON. It acts like a read-only database for your site.
Besides, there are an archetypes folder where you can define parameters and content for new pieces of content; a config.yaml, Hugo’s main configuration file where you can set your site’s configuration options; and contents. content contains all the markdown files with the content for your site.
Syntax Highligher. Generate a syntax highlighter CSS: hugo gen chromastyles –style=monokai > syntax.css.
Next, we will include this CSS in our /static/css/ directory and link it (head.html).
<link rel="stylesheet" href="{{ "css/syntax.css" | absURL }}" />
Awesome Fonts. Firstly, you need to download the FontAwesome zip and extract the folder. Second, copy the all.min.css file in our /static/css/ directory and link it.
<link rel="stylesheet" href= "{{ "css/all.min.css" | absURL }}" />
Finally, copy the webfonts folder from the downloaded zip file and place it into the project one directory above the all.min.css file.
RAW HTML
If you want to add raw html to your markdown content, add a shortcode in layouts/shortcode/raw.html with the following content:
{{.Inner}}
It tells Hugo to render the content passed to the shortcode directly into HTML. Use:
{{< raw >}}
2<sup>3</sup>
<p class="my_custom_class">
This is <strong>raw HTML</strong>.
</p>
{{< /raw >}}
Inline Images. Let’s place an image in the text of a paragraph.
{{< raw >}}
<img src="../images/heart.png" style="width:10%;border:0;display:inline;margin:0 2px;box-shadow: none">
{{< /raw >}} respiration,
Escaping in Hugo
If you want to write about shortcodes, you need to replace the opening greater-than sign “<” with < or “%” with %.
{{ < img src="../images/best-friends.webp" title=“best-friends” src2="../images/best-friends.jpg" >}}
By default, Hugo replaces two hyphens for a single dash, “–” the solution is this: ‐‐ ‐‐
The same goes with emojis, instead of :star: ⭐️, you use an HTML entity for one of the colons in the emoji code: :star:
If you want that Hugo does not turn ‐ into “‐”, you need to replace the ampersand “&” with & e.g., content_new = pattern.sub('&hyphen;&hyphen;', content_new)
content_new = pattern.sub("‐‐", content_new)
I also use this shortcode -spam.html-, htmlEscape returns the given string with the reserved HTML coded escaped:
<span {{ with .Get "style"}} style="{{ . | safeCSS }}"{{ end }}>{{ .Get "text" | htmlEscape }}</span>
First, we use the .Get function to retrieve the style parameter passed to our shortcode (e.g., “color:red;”). The dot’s value is different from the value inside the “with” block, it changes based on context, which is our style in this case. safeCSS declared the provided string as a “safe” CSS string. Next, we retrieve the text parameter and pipe it into htmlEscape.
Use:
{{< span style="color:red;" text="‐" >}}
Result: ‐
Alert box
We can add support for bootstrap-style alert boxes by adding the following shortcode, alert.html
<div class="alert alert-{{ .Get 0 }}">
{{ .Inner | markdownify }}
div>
We are using .Get to gain access to our positional parameters passed to our shortcode. More specifically, we are retrieving the first parameter: {{ .Get 0 }} We are taking the .Inner variable that is populated with all the content between the opening and closing square bracket, and run it through the Markdown processor. As a result, we can format this content in Markdown as usual. From now on, you can use it like this:
{{< alert "primary">}}This is a **primary** alert.{{</alert >}}
Styling Paragraphs
Let’s create another shortcode to styling paragraphs with your own CSS classes. Create a file in /layouts/shortcodes/pborder.html and add the following content:
{{ printf "<p class=\"bordes_redondeados\">"}}
{{- .Inner | safeHTML -}}
{{ printf "</p>"}}
safeHTML declares the inner content passed to the shortcode as “safe” to avoid escaping by Go templates. Use:
{{% pborder %}}
This is how you style text with custom css class.
{{% /pborder %}}
The end result is that it will output:
<p class="bordes_redondeados">
This is how you style text with custom css class.
</p>
Adding YouTube Videos.
The YouTube shortcode embeds a responsive video player for YouTube videos. You only need to copy the YouTuve video ID, e.g., “ji5_MqicxSo”, https://www.youtube.com/watch?v=ji5_MqicxSo&ab_channel=CarnegieMellonUniversity (it follows v= in the video’s URL) and pass it to the YouTube shortcode:
{{< youtube ji5_MqicxSo >}}
Using Webp Images.
Let’s focus on the “Google’s WebP image format, and how you can take advantage of it to serve images that have all the visual fidelity your images have now, but at a fraction of the file size,” Using WEbP Images, CSS-Tricks, by Jeremy Wagner.
We need to create a new shortcode, /layouts/shortcodes/img.html with the following code:
<picture>
{{ with .Get "link" }}<a href="{{ . }}">{{ end }}
<source srcset="{{ .Get "src" }}" type="image/webp" />
<source srcset="{{ .Get "src2" }}" />
<img class="img-fluid" src="{{ .Get "src" }}" />
{{ if .Get "link" }}</a>{{ end }}
{{ if .Get "title" }}
<figcaption>
<h4>{{ .Get "title" }}</h4>
</figcaption>
{{ end }}
</picture>
We are using the .Get function to retrieve named parameters passed to our shortcode (src, src2, title, link). We also want it to be responsive and images in Bootstrap are made responsive with .img-fluid. Use:
{{< img src="../images/mycomputerSEODigital.jpeg" title="Programming in Hugo" src2="../images/mycomputerSEODigital.webp" link="/code/">}}
It will be rendered as an image using the HTML picture tag:
<picture>
<a href="/code/">
<source srcset="../images/mycomputerSEODigital.jpeg" />
<source srcset="../images/mycomputerSEODigital.webp" />
<img class="img-fluid" src="../images/mycomputerSEODigital.jpeg" />
</a>
<figcaption>
<h4>Programming in Hugo</h4>
</figcaption>
</picture>
Let’s open config.yaml:
baseURL: https://justtothepoint.com/ # url address of the website | required
theme: "anawim"
pygmentsUseClasses: true # We are going to use a style sheet for highlighting.
pygmentsCodefences: true # It enable syntax highlighting in code fences with a language tag (html, python, css, json, md, etc.) in markdown: ``` html [ my code html ] ```
PygmentsStyle: "monokai"
languageCode: en-us
title: JustToThePoint # The title of the site.
copyright: "Copyright © 2022 JustToThePoint. All rights reserved." # Copyright notice for your site, typically displayed in the footer.
googleAnalytics: YourCodeHere # It enables Google Analytics by providing your tracking ID.
defaultContentLanguage: en # Content without language indicator will default to this language.
enableEmoji: true # It enables Emoji emoticons support.
enableRobotsTXT: true # It enables generation of robots.txt file. It tells search engine crawlers which URLs the crawler can access on your site.
assetDir: "assets/"
Without adding a single line to our config file, Hugo will automatically create taxonomies for tags and categories. I don’t want Hugo to create any taxonomies, so I set disableKinds in config.yaml as follows:
disableKinds:
- taxonomy
- term
markup: # Configure Markup
goldmark: # Goldmark is the default library used for Markdown.
renderer:
unsafe: true # By default, Goldmark does not render raw HTML.
highlight: # This is the highlight configuration. Hugo uses Chroma as its code highlighter.
codeFences: true # It enables syntax highlighting with GitHub flavored code fences: ``` html [ my code html ] ``` By default, Highlighting is carried out via the built-in shortcode highlight.
guessSyntax: true # It tries to do syntax highlighting on code fenced blocks in markdown without a language tag.
lineNos: false # It configures line numbers.
tabWidth: 4
params:
author: Máximo Núñez Alarcón, Anawim
og_image: https://www.justtothepoint.com/myImages/logotipoMagotipoCabecera.png
description: Free bilingual e-books, articles, resources, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun.
params:
social:
profiles:
facebook: "https://www.facebook.com/nmaximo7"
github: "https://github.com/nmaximo7"
twitter: "http://Twitter.com/justtothepoint"
instagram: "http://instagram.com/justtothepoint"
linkedin: "https://www.linkedin.com/in/justtothepoint"
youtube: "https://youtube.com/justtothepoint"
NPM is a package manager for JavaScript. It consists of a command line client (npm) and an online database of public and paid-for private packages, called the npm registry.
Node.js (Node) is a run time open source development platform. It allows us to write JavaScript code that runs directly in a computer process instead of in a browser, so it is used to write server-side applications with access to the operating system, file system, and everything else required to build fully-functional applications.
Initialize the project: npm init (project’s name, initial version, description, etc.) It will generate a package.json file in the current directory. A package.json file is your project’s manifest. It includes the packages and applications it depends on, information about its unique source control, and specific metadata.
You can install modules with npm install myModule or install modules and save them to your package.json as a dependency: npm install –save-dev npm-check. We are going to install npm-check and hugo-installer.
npm-check checks for outdated, incorrect, and unused dependencies. hugo-installer installs Hugo into your repository: npm install hugo-installer –save-dev
Edit the package.json file. JSON does not support comments.
{
{
"name": "justtothepoint", // The name of the project.
"version": "1.0.0", // The version of the project.
"description": "Free resources, bilingual e-books and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun.", // The description of the project
"main": "index.js",
"scripts": {
"dev": "exec-bin node_modules/.bin/hugo/hugo server --gc --disableFastRender",
// It runs Hugo's webserver: -gc remove unused cache files after the build; --disableFastRender enables full re-renders on changes.
"build": "exec-bin node_modules/.bin/hugo/hugo --gc -cleanDestinationDir --minify",
// --cleanDestinationDir removes files from destination not found in static directories. --minify reduces CSS code size and make your website load faster.
"prebuild": "exec-bin node_modules/.bin/hugo/hugo --gc --cleanDestinationDir",
"npm-check": "npm-check -u",
"start": "exec-bin node_modules/.bin/hugo/hugo server -d-disableFastRender --gc --renderToDisk --cleanDestinationDir",
// --renderToDisk serve all files from disk. Please notice that the default is to serve all files from memory.
"postinstall": "hugo-installer --version otherDependencies.hugo --extended --destination node_modules/.bin/hugo"
// hugo-installer is recommended as part of our postinstall hook within our project's package.json. It installs Hugo into the repository. --destination is the path to the folder into which the binary will be put. --extended download the extended version of Hugo.
},
"author": "Máximo Núñez Alarcón, Anawim", // The author of the project.
"license": "ISC", // The license of the project.
"devDependencies": {
"npm-check": "^5.9.2"
},
"dependencies": {
"exec-bin": "^1.0.0",
"hugo-installer": "^3.1.0"
},
"otherDependencies": {
"hugo": "0.96.0"
}
}
We are going to add all our meta tags under the mymeta partial. It is one of the partials included in the baseof.html file. You may want to read the article Perfect SEO Meta Tags with Hugo, SKCRIPT:
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>{{ .Title }}{{ if ne .Title .Site.Title }} | {{ .Site.Title }}{{ end }}</title>
<meta name="application-name" content="{{ .Title }}{{ if ne .Title .Site.Title }} | {{ .Site.Title }}{{ end }}" />
<meta itemprop="name" content="{{ .Title }}{{ if ne .Title .Site.Title }} | {{ .Site.Title }}{{ end }}" />
<meta name="description" content="{{ if .Params.Description }}{{ .Params.Description }}
{{ else if .Site.Params.Description }}{{ .Site.Params.Description }}
{{ else }}Free bilingual e-books, articles, resources, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun.{{ end }}" />
<meta itemprop="description" content="{{ if .Params.Description }}{{ .Params.Description }}
{{ else if .Site.Params.Description }}{{ .Site.Params.Description }}
{{ else }}Free bilingual e-books, articles, resources, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun.{{ end }}" />
<meta name="keywords" content="{{with .Params.Keywords }}{{ delimit . " "}}{{ end }}" />
<meta name="language" content="{{ if .Params.Language }}{{ .Params.Language }}{{ else }}en_US{{ end }}" />
<!-- Open Graph Tags -->
<meta property="og:title" content="{{ .Title }}{{ if ne .Title .Site.Title }} | {{ .Site.Title }}{{ end }}" />
<meta property="og:locale" content="{{ if .Params.Language }}{{ .Params.Language }}{{ else }}en_US{{ end }}" />
<meta property="og:site_name" content="{{ .Site.Title }}" />
<meta property="og:url" content="{{ .Permalink }}" />
<meta property="og:description" content="{{ if .Params.Description }}{{ .Params.Description }}
{{ else if .Site.Params.Description }}{{ .Site.Params.Description }}
{{ else }}Free bilingual e-books, articles, resources, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun.{{ end }}" />
<meta property="og:article:author" content="https://www.facebook.com/nmaximo7" />
<!-- Twitter Tags -->
<meta name="twitter:title" content="{{ .Title }}{{ if ne .Title .Site.Title }} | {{ .Site.Title }}{{ end }}" />
<meta name="twitter:description" content="{{ if .Params.Description }}{{ .Params.Description }}
{{ else if .Site.Params.Description }}{{ .Site.Params.Description }}
{{ else }}Free bilingual e-books, articles, resources, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun.{{ end }}" />
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@justtothepoint" />
<meta name="twitter:creator" content="@justtothepoint" />
<meta name="twitter:domain" content="https://www.justtothepoint.com/" />
<meta name="twitter:url" content="{{ .Permalink }}" />
<!-- Link Tags -->
<base href="{{ .Permalink }}" />
<!-- A canonical URL is the URL of the page that Google thinks is most representative
from a set of duplicate pages on your site. -->
<link rel="canonical" href="{{ .Permalink }}" itemprop="url" />
<meta name="url" content="{{ .Permalink }}" />
<!-- Image Tag -->
{{ with .Params.featured_image }}
<meta itemprop="image" content="{{ . | absURL }}" />
<meta property="og:image" content="{{ . | absURL }}" />
<meta name="twitter:image" content="{{ . | absURL }}" />
<meta name="twitter:image:src" content="{{ . | absURL }}" />
{{ else }}
<meta itemprop="image" content="{{ .Site.Params.ogimage | absURL }}" />
<meta property="og:image" content="{{ .Site.Params.ogimage | absURL }}" />
<meta name="twitter:image" content="{{ .Site.Params.ogimage | absURL }}" />
<meta name="twitter:image:src" content="{{ .Site.Params.ogimage | absURL }}" />
{{ end }}
RealFaviconGenerator is a favicon generator for all platforms and browsers. After downloading the generated favicon package, unzip it, and copy all its files into the Hugo’s static folder (/static).
<!-- Favicon Tags -->
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#AADFE1">
<!-- It indicates a suggested color that user agents should use to customize the display of the page. ColorZilla is a tool picker that could be useful for that purpose. -->
<meta name="theme-color" content="#AADFE1">
<meta name="google-site-verification" content="YourCodeHere" />
<meta name="msvalidate.01" content="YourCodeHere" />
<meta name="p:domain_verify" content="YourCodeHere" />
<meta name="yandex-verification" content="YourCodeHere" />
<meta property="article:publisher" content="https://www.facebook.com/nmaximo7" />
<meta property="article:author" content="https://www.facebook.com/nmaximo7" />
<meta property="fb:app_id" content="YourCodeHere" />
<!-- Sitemap and RSS -->
<link rel="sitemap" type="application/xml" title="Sitemap" href='{{ "sitemap.xml" | absURL }}' />
{{ with .OutputFormats.Get "RSS" }}
<link href="{{ .Permalink }}" rel="alternate" type="application/rss+xml" title="{{ $.Site.Title }}" />
<link href="{{ .Permalink }}" rel="feed" type="application/rss+xml" title="{{ $.Site.Title }}" />
{{ end }}
<!-- Search Engine Crawler Tags -->
<meta name="robots" content="index,follow" />
<meta name="googlebot" content="index,follow" />
To add Twitter card and Open Graph metadata, you can use two internal templates (opengraph, twitter_cards) by including the following lines between the HEAD tags in your templates:
{{ template "_internal/opengraph.html" . }}
{{ template "_internal/twitter_cards.html" . }}
Firstly, you need to enable Google Analytics by editing Hugo’s configuration file (config.yaml) and add the following code: googleAnalytics = “Add Your Google Analytics code”
Then, we will use a Hugo template (_internal/google_analytics_async.html) to enable Google Analytics on our site. It will be written on the head.html’s last line. When Hugo generates your site, the appropriate JavaScript code will be added to your pages.
<!-- Google Analytics. Credits: JANNE KEMPPAINEN, https://pakstech.com/, Let's Create a New Hugo Theme. -->
{{ if not (in (string .Site.BaseURL) "localhost") }}
<!-- We don't want Google Analytics when we are deploying Hugo locally (localhost) -->
{{ template "_internal/google_analytics_async.html" . }}
{{ end }}
The idea is to use Hugo’s asset pipeline to bundle all the css into a single one (css/bundle.css). Next, we minify it (it removes whitespace, strips comments, etc.) to reduce CSS code size and make our website load faster.
I will put it in layout/partials/style.html that is referenced in _default/baseof.html.
{{ $csBundle := slice }}
<!-- Bootstrap CSS. -->
{{ $csBundle := $csBundle | append (resources.Get "css/bootstrap.min.css") }}
We are going to build our website using Bootstrap, a free and open-source CSS framework so we can design and customize responsive mobile-first websites.
Our styles will live in a new directory, the assets folder, under a “css” directory. The resources.Get functions uses the assets directory as its base path.
<!-- My main CSS -->
{{ $csBundle := $csBundle | append (resources.Get "css/main.css") }}
<!-- Monokai CSS. hugo gen chromastyles --style=monokai > syntax.css. -->
{{ $csBundle := $csBundle | append (resources.Get "css/syntax.css") }}
<!-- Awesome CSS Fonts. -->
{{ $csBundle := $csBundle | append (resources.Get "css/all.min.css") }}
{{ $style := $csBundle | resources.Concat "css/bundle.css" | minify | fingerprint }}
<link rel="stylesheet"
href="{{ $style.Permalink | absURL }}"
Cache Busting is the process of appending some query parameter in the URLs of static resources such as JS, CSS or image file. It is useful because it allows your visitors to receive the most recently updated files without having to perform a hard refresh or clean their browser cache. Otherwise, your users might not notice the changes unless they clear their local caches.
JavaScriptThere is no much to say here. We will bundle, minify, and fingerprint our Javascript files. They will live under the assets directory in a js folder. I will put it in layout/partials/scrip-footer.html that is referenced in _default/baseof.html.
{{ $jsBundle := slice }}
<!-- Bootstrap Js -->
{{ $jsBundle := $jsBundle | append (resources.Get "js/bootstrap.min.js") }}
<!-- My main Js -->
{{ $jsBundle := $jsBundle | append (resources.Get "js/main.js") }}
{{ $globalJS := $jsBundle | resources.Concat "js/global.js" | minify | fingerprint }}
<script src="{{ $globalJS.Permalink }}">script>
The footer is a section of the site that appears at the very bottom of every page. It typically contains a copyright notice and links to your privacy policy, terms of use, and social media. To reduce maintenance, we are going to use Hugo’s configuration file, config.yaml, to place these links and information:
params:
author: PhD. Máximo Núñez Alarcón, Anawim
og_image: https://www.justtothepoint.com/myImages/logotipoMagotipoCabecera.png
description: Free bilingual e-books, articles, resources, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun.
social:
profiles:
facebook: "https://www.facebook.com/nmaximo7"
github: "https://github.com/nmaximo7"
twitter: "http://Twitter.com/justtothepoint"
instagram: "http://instagram.com/justtothepoint"
linkedin: "https://www.linkedin.com/in/justtothepoint"
youtube: "https://youtube.com/justtothepoint"
share:
facebook: true
linkedin: true
twitter: true
whatsapp: true
email: true
line: true
# My footer
websiteTermsEs: "/contactes/terminos-y-condiciones-de-uso/"
privacyPolicyEs: "/contactes/politica-de-cookies/"
websiteTermsEn: "/contact/website-terms-and-conditions-of-use/"
privacyPolicyEn: "/contact/cookies-policy/"
copyrightStart: "2011"
# Related posts. Hugo provides a sensible default configuration of Related Content, but you can fine-tune it in Hugo's configuration file.
related:
threshold: 70
includeNewer: true
toLower: false
indices:
- name : "keywords"
weight: 150
- name: "categories"
weight: 80
- name : "date"
weight: 10
pattern: "2006"
Next, we will create a new partial under layout/partials/footer.html that is referenced in _default/baseof.html:
<div class="site-info">
<div class="site-info-container">
<center>
<img src="{{ "myImages/nenes2.png" | absURL }}" width="60%" ></center>
<p><a href="https://justtothepoint.com/">JustToThePoint</a> Copyright © {{ .Site.Params.copyrightStart }} - {{ dateFormat "2006" now }} {{ .Site.Params.author }}. ALL RIGHTS RESERVED. Bilingual e-books, articles, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun.</p>
Let’s add Copyright information to our footer. We are adding {{ dateFormat "2006" now }}, so we don’t need to update our copyright info every year. now returns the current local time. We use the .Format function to display only the current year.
{{ range $key, $url := site.Params.social.profiles }}
Please observe that Hugo variables are always prefixed with a $sign. To look through a list of items, we use the Range function. We are initializing two variables at once, a key-value pair, e.g., $key (facebook) and $url (https://www.facebook.com/nmaximo7).
{{ if eq $key "facebook"}}
Observe that if is the condition, and eq is a function. It compares the first parameter ($key) with the following one, in this particular case, facebook. If the key is Facebook, then we will show the corresponding icon (we are using Awesome fonts) and href will point to our Facebook link (it is provided in config.yaml).
<a class="btn btn-default" href="{{ $url }}"><i class="fab fa-facebook fa-2x" ></i></a>
{{ else if eq $key "twitter" }}
<a class="btn btn-default" href="{{ $url }}"><i class="fab fa-twitter fa-2x" ></i></a>
{{ else if eq $key "github" }}
<a class="btn btn-default" href="{{ $url }}"><i class="fab fa-github fa-2x" ></i></a>
{{ else if eq $key "instagram" }}
<a class="btn btn-default" href="{{ $url }}"><i class="fab fa-instagram fa-2x" ></i></a>
{{ else if eq $key "youtube" }}
<a class="btn btn-default" href="{{ $url }}"><i class="fab fa-youtube fa-2x" ></i></a>
{{ else if eq $key "linkedin" }}
<a class="btn btn-default" href="{{ $url }}"><i class="fab fa-linkedin fa-2x" ></i></a>
{{ end }}
{{ end }}
{{ if eq (string .Params.language) "es" }}
<p>Esta web utiliza 'cookies' propias y de terceros para ofrecerte una mejor experiencia y servicio. <br>Al navegar o utilizar nuestros servicios, estas aceptando nuestra <a href="{{ .Site.Params.privacyPolicyEs }}">Política de Cookies</a>, así como, nuestros <a href="{{ .Site.Params.websiteTermsEs }}">Términos y condiciones de uso.</a></p>
{{ else }}
<p>This website uses cookies to improve your navigation experience.<br> By continuing, you are consenting to our use of cookies, in accordance with our <a href="{{ .Site.Params.privacyPolicyEn }}">Cookies Policy</a> and <a href="{{ .Site.Params.websiteTermsEn }}">Website Terms and Conditions of use.</a></p>
{{ end }}
</div> <!-- .site-info-container -->
</div><!-- .site-info -->
We are going to create another partial to show our social icons and share our links on social media. You can copy the below code snippet as a reference and save it under layout/partials/socialshare.html:
<!-- Social Share Button HTML -->
<style> <!-- We can add a separate .css file to Hugo, but we can also add an internal stylesheet. Adapt your own CSS style based on your theme -->
.myButtonShare {
padding: 0;
border: 0;
background: transparent;
}
.myImageShare {
display: block;
}
.containerBitcoin {
background: url( "/myImages/myBitcoin.png" ) no-repeat;
display: inline-block;
background-size: 100px 100px; /* image's size */
height: 100px; /* image's height */
padding-left: 100px;
}
.containerBitcoin span {
height: 100px; /* image's height */
display: table-cell;
vertical-align: middle;
}
</style>
<section class="social-share">
{{ $title := .Title }}
{{ $url := printf "%s" .Permalink }}
{{ $body := print .Site.Title " " .Params.description }}
<!-- We create and initialize three variables: title, url, and body. Observe that we use "printf" to format our URL (.Permalink), and "print" to concatenate the Site's title with the page's description. -->
<!-- Twitter -->
{{ if .Site.Params.social.share.twitter }}
We have added: params, social, share, twitter: true in our main Hugo’s configuration file, config.yaml, so we can enable and disable our social media icons and links site-wide.
<a href="https://twitter.com/intent/tweet?hashtags=justtothepoint&url={{ .Permalink }}&text={{ $body }}&" target="_blank" rel="noopener" aria-label="Share on Twitter">
<button class="myButtonShare" >
<img class="myImageShare" src="/myImages/twitter.png">
</button>
</a>
{{ end }}
<!-- Facebook -->
{{ if .Site.Params.social.share.facebook }}
<a href="https://facebook.com/sharer/sharer.php?u={{ .Permalink }}" target="_blank" rel="noopener" aria-label="Share on Facebook">
<button class="myButtonShare" >
<img class="myImageShare" src="/myImages/facebook.png">
</button>
</a>
{{ end }}
<!-- LinkedIn -->
{{ if .Site.Params.social.share.linkedin }}
<a href="https://www.linkedin.com/shareArticle?mini=true&url={{ $url }}&source={{ $url }}&title={{ $title }}&summary={{ $title }}" target="_blank" rel="noopener" aria-label="Share on LinkedIn">
<button class="myButtonShare" >
<img class="myImageShare" src="/myImages/linked-in.png">
</button>
</a>
{{ end }}
<!-- WhatsApp -->
{{ if .Site.Params.social.share.whatsapp }}
<a href="whatsapp://send?text={{ $body }}-{{ $url }}" target="_blank" rel="noopener" aria-label="Share on WhatsApp">
<button class="myButtonShare">
<img class="myImageShare" src="/myImages/whatsapp.png">
</button>
</a>
{{ end }}
<!-- Email -->
{{ if .Site.Params.social.share.email }}
<a href="mailto:?subject={{ .Site.Title }} - {{ $title }}.&body={{ $body }} - {{ $url }}" target="_self" rel="noopener" aria-label="Share by E-Mail">
<button class="myButtonShare">
<img class="myImageShare" src="/myImages/email.png">
</button>
</a>
{{ end }}
<!-- Line -->
{{ if .Site.Params.social.share.line }}
<a href="https://social-plugins.line.me/lineit/share?url={{ $url }}" target="_blank" rel="noopener" aria-label="Share on Line">
<button class="myButtonShare">
<img class="myImageShare" src="/myImages/line.png">
</button>
</a>
{{ end }}
<!-- Buy me a coffee -->
<a href="https://buymeacoffee.com/justtothepoint" target="_blank" rel="noopener">
<button class="myButtonShare">
<img class="myImageShare" src="/myImages/buyMeCoffee.png">
</button>
</a>
Hugo supports a solution for related content Hugo, Related Content out of the box.
<!-- Related Posts -->
<div id="relatedposts">
<h2>Related Posts</h2>
{{ $related := .Site.RegularPages.Related . | first 5 }}
{{ with $related }}
<ul>
{{ range . }}
<li>
<a href="{{ .Permalink }}">
<img src="{{ .Params.featured_image | absURL }}" alt="{{ .Title }}"/>
<p>{{ .Title }}</p></a>
</li>
{{ end }}
</ul>
{{ end }}
</div>
Finally, we have added our socialshare partial to the single.html file:
{{- define "main" }}
[....]
{{ partial "socialshare.html" . }}
{{- end }}
Even though Hugo is a static site generator, i.e., a tool that generates a full static HTML website based on HTML templates and Markdown content, we can still have access and pull data from external sources, such as APIs or local data files.
First, let’s create a folder called data in the root of your project and a file “quotes.json” in it with the following content:
[
{
"author": "Douglas Adams, The Hichhikers Guide to the Galaxy",
"quote": "Flying is learning how to throw yourself at the ground and miss."
},
{
"author": "Richard Feynman",
"quote": "The first principle is that you must not fool yourself -- and you are the easiest person to fool."
},
[...]
]
It is basically a list of quotes formatted in JSON. The data folder is where you can store additional data for Hugo. The data is accessible as a map in the .Site.Data variable.
We want to pull this information. Next, we will create a new partial, layouts/partials/quotes.html, and add this content:
{{ range (.Site.Data.quotes) | shuffle | first 2 }}
<blockquote>{{ .quote}}
<cite>{{ .author}}</cite>
</blockquote>
{{ end }}
It retrieves our quotes: .Site.Data.quotes. Remember that the dot “.” refers to the current context, and this context includes site-wide variables, e.g., .Site.Data.quote is a map, an associative array, meaning it has key and value pairs. Our list of quotes, stored in the file /data/quotes.json, is accessible as a map in this variable.
It could loop over all the collection of quotes using range: range (.Site.Data.quotes), but we only want two random quotes. Then, we loop over these two quotes and access their two parameters: .quote, and .author, and render them in a blockquote.
You can use .Site.Data.quotes to pull all the quotes from the data file. However, we only want to retrieve two random quotes from the list, so we pipe the list to shuffle. Shuffle returns a random permutation of our quotes. Finally, we extract the first two quotes: first 2.
Finally, we will output this two quotes in layouts/_default/list.html:
{{ define "main" }}
{{- partial "quotes" . -}}
[...]
There are three options:
<form role="search" method="get" action="https://www.google.com/search">
<input type="search" placeholder="Search" value="" name="q" title="Search for:">
<input type="hidden" name="sitesearch" value="justtothepoint.com">
<button type="submit" value="Search"/>
</form>
<form id="search"
action='{{ with .GetPage "/search" }}{{.Permalink}}{{end}}' method="get" >
<div class="col-12 justify-content-end d-flex">
<label hidden for="search-input">Search site</label>
<input type="text" id="search-input" name="query" placeholder="Search">
<input type="submit" value="search"></div>
</form>
Create a new search page (content/search/_index.md) with its layout (layouts/search/list.html) that responds to a GET request and displays or render the search results:
{{ define "main" }}
<ol id="results">
<li>
Please enter a keyword or keywords in the input above to search this site.
</li>
</ol>
{{ end }}
Let’s build a search index (layouts/partials/search-index.html) for Lunr:
<script>
window.store = {
// Search all pages in our site:
{{ range .Site.Pages }}
"{{ .Permalink }}": {
// Define your searchable fields using any .Page parameters
"title": "{{ .Title }}",
"tags": [{{ range .Params.Tags }}"{{ . }}",{{ end }}],
"description": "{{ .Description }}",
"content": {{ .Content | plainify }},
plainify strips out any HTML tags and returns the plain text version of the provided string
"url": "{{ .Permalink }}"
},
{{ end }}
}
</script>
{{ $jsBundle2 := slice }}
<!-- Lunr Js -->
{{ $jsBundle2 := $jsBundle2 | append (resources.Get "js/lunr.js") }}
<!-- Search Js -->
{{ $jsBundle2 := $jsBundle2 | append (resources.Get "js/search.js") }}
{{ $globalJS2 := $jsBundle2 | resources.Concat "js/global2.js" | minify | fingerprint }}
<script src="{{ $globalJS2.Permalink }}"></script>
Finally, let’s perform the search (assets/js/search.js). This is our javascript to retrieve the value entered by the user and generate a list of search results, if any.
//3. Display the results
function displayResults (results, store) {
// Remember the line: <ol id="results"> (layouts/search/list.html)
const searchResults = document.getElementById('results')
if (results.length) { // Are there any results?
let resultList = ''
// If there are some results, we iterate over them.
for (const n in results) {
const item = store[results[n].ref]
resultList += '<li><a href="' + item.url + '">' + item.title + '</a>. ' + item.description + '</li>'
}
searchResults.innerHTML = resultList
} else {
searchResults.innerHTML = 'No results found.'
}
}
//1. Get the query parameter(s). The search property of the Location interface is a search string that is a string contaning a '?' followed by the parameters of the URL.
const params = new URLSearchParams(window.location.search)
// URLSearchParams makes it very easy to parse out the parameters from the querystring.
const query = params.get('query')
//2. Perform a search if there is a query
if (query) {
// It keeps the search input value in the form after displaying the results
document.getElementById('search-input').setAttribute('value', query)
// It intializes lunr with the fields it will be searching on.
const idx = lunr(function () {
this.ref('id')
this.field('title', {
boost: 15 // We are going to "boost" title, description, and content.
})
this.field('tags')
this.field('description', {
boost: 10
})
this.field('content', {
boost: 5
})
// We add the data from the search index to Lunr
for (const key in window.store) {
this.add({
id: key,
title: window.store[key].title,
tags: window.store[key].category,
description: window.store[key].description,
content: window.store[key].content
})
}
})
// Next, we get lunr to perform the search
const results = idx.search(query)
// Finally, we get lunr to display the search results
displayResults(results, window.store)
}
Hugo Documentation
Infinite Ink, #gohugo Portal
cloudcannon, Community resources, Tutorials.
The New Dynamic, articles.
Régis Philibert’s blog
Smashing Magazine, Hugo