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>
First, we are going to create a search form. It is going to submit to /search. Create a new partial (layouts/partial/search-form.html). It will allow the user to input and submit a search query.
<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
Awesome Hugo is a curated list of awesome things related to Hugo.
Infinite Ink, #gohugo Portal
cloudcannon, Community resources, Tutorials.
The New Dynamic, articles.
Régis Philibert’s blog
Smashing Magazine, Hugo