β³
6 mins
read time
Everyone wants that their web page appears naturally at the first position on Google search. Googleβs sorting algorithm takes into account the structure of the page, the metadata, the contents of the page, the mobile compatibility and also how fast the page loads. Google worked on a web standard called AMP, which imposes some restrictions to the web page, but guaranties fast page loading.
These restriction are:
Not having external javascript calls makes really difficult to include some dynamic functionalities like fetching external content or calling API services. Itβs the case for a search bar which needs to fetch some content and render the results. This gets more complicated for static web content that is hosted in servers like Netlify, Gitlab or Github Pages. These options donβt provide any backend and thus no dynamic content.
Even if it seems complicated, itβs not impossible. One can adapt a website to AMP format and include dynamic functionalities. For example, this post shows how to implement a javascript call in AMP pages and implement a search bar which allows searching for content in a static website hosted on Github pages.
There are various alternatives to integrate a search function in a static website:
I personally prefer the approach from SimpleJekyllSearch which exposes an endpoint with the posts information and which can be then used in AMP components. The front-matter informs Jekyll that is a special page so it takes into account the defined parameters in order to render it. The front-matter defines the following properties:
/api/github-pages.json
.Jekyll allows looping over the posts using the site.posts
array and get data from each post.
Hereβs how my endpoint file looks like:
---
limit: 100
permalink: /api/github-pages.json
---
[{% for post in site.posts limit: page.limit %}{"title": "{{ post.title }}", "description": "{{ post.description }}", "image": "{{ post.image.path }}", "thumb": "{{ post.thumb.path }}", "date": "{{ post.date | date: "%B %d, %Y" }}",{% if post.source %}"source": "{{ post.source }}",{% endif %}{% if post.link %}"link": "{{ post.link }}",{% endif %}{% if post.categories %}"categories": [{% for category in post.categories %}"{{ category }}"{% if forloop.last %}{% else %},{% endif %}{% endfor %}],{% endif %}{% if post.categories == nil %} "categories" : [], {% endif %} "url": "{{ post.url }}", {% if post.tags %}"tags": [{% for tag in post.tags %}"{{ tag }}"{% if forloop.last %}{% else %},{% endif %}{% endfor %}]{% endif %}{% if post.tags == nil %}"tags": []{% endif %}}{% unless forloop.last %},{% endunless %}{% endfor %}]
The content of the file uses the liquid syntax to iterate between the posts. The result is an array that contains the following information for each post:
There are AMP components that can use the static API:
For example, if we define an HTML input element, itβs possible to add a listener that takes the input value and filters the results using some javascript functions (not all ES6 are allowed). The javascript indexOf function returns an int
larger than -1 if the input element is found, so one can use it as a simple search like this:
allArticles.filter(a => a.title.indexOf(event.value) != -1)
For more complex research, in multiple fields, the components looks like the following:
<amp-state id="allArticles"
src="/api/github-pages.json"></amp-state>
<input class="input" type="text" on="input-throttled:AMP.setState({filteredArticles: allArticles.filter(a => ((a.title.toLowerCase().indexOf(event.value.toLowerCase()) != -1) || (a.description.toLowerCase().indexOf(event.value.toLowerCase()) != -1) || (a.tags.join(' ').toLowerCase().indexOf(event.value.toLowerCase()) != -1) || (a.categories.join(' ').toLowerCase().indexOf(event.value.toLowerCase()) != -1) ) ), inputValue: event.value }), top-blog-page.scrollTo(duration=200)" placeholder="π Search" [value]="inputValue">
AMP provides some components to process the JSON object that is binned with amp-bind:
The amp-list component loads the filteredArticles object, the height is controlled dynamically with the number of elements in the object.
By default amp-list expects an object with the format {"items": [obj1, obj2]}
,
itβs possible to configure the location of the items list using the items parameter.
Mustache syntax allows to filter properties of the component using the conditional notation. Here are the definition of the special characters:
Please note that static websites generators like Jekyll need to escape mustache templates using { raw } and { endraw } elements. Otherwise the results would not be rendered. Here is how looks like the final combination of the two elements:
<amp-list height="500"
[height]="(400) * filteredArticles.length"
layout="fixed-height"
items="."
src="/api/github-pages.json"
[src]="filteredArticles"
binding="no">
<div placeholder>Loading ...</div>
<div fallback>Failed to load data.</div>
<template type="amp-mustache">
{% raw %}
<div class="box">
<div class="columns is-multiline">
<div class="column is-3-desktop is-12-mobile">
<a class="image" href="{{#link}}{{ link }}{{/link}}{{^link}}{{ url }}{{/link}}" data-proofer-ignore>
<amp-img
width="300" height="200"
src="{{ thumb }}"
srcset="{{ thumb }} 400w, {{ image }} 900w"
alt="{{ title }}" layout="responsive"></amp-img>
</a>
</div>
<div class="column is-9-desktop is-12-mobile">
<div class="content">
<a class="title is-6" href="{{#link}}{{ link }}{{/link}}{{^link}}{{ url }}{{/link}}" data-proofer-ignore>{{ title }}</a>
<p class="is-size-7">
{{ description }}
</p>
<div class="tags has-addons">
<span class="tag"><i class="fas fa-calendar-alt"></i> {{ date }}</span>
{{#tags}}<a class="tag" href="/blog/tags#{{.}}" data-proofer-ignore> {{.}}</a>{{/tags}}
{{#categories}}<a class="tag is-link" href="/blog/categories#{{.}}" data-proofer-ignore> {{.}}</a>{{/categories}}
{{#source}}
<span class="tag is-danger"><i class="fas fa-external-link-alt"></i> {{ source }}</span>
{{/source}}
</div>
</div>
</div>
</div>
</div>
{{% endraw %}}
</template>
</amp-list>
The final version of the search bar can be seen in the following video. The search bar filters the posts at every key stroke. An additional AMP event is called to place the window at the top of results list and the amp list height is controlled by the number of posts that are shown.
The search bar is always visible using a position: fixed
css property.
The code can be found at the github repository of this page.
Your browser doesn't support HTML5 video.
I have seen different opinions on the effect of using AMP format. For my personal point of view, my SEO has increased since I started using this format and also the page load speed. The AMP rules impose good web format that guaranties the best performances. The traffic data of my website can be found at this github page.
AMP integrates smoothly with API endpoints defined in static website build tools like Jekyll. The endpoint with the contents of the blog is a small json, which can be filtered using simple javascript functions allowed in AMP.
Iβm happy with the results since, the json is still very small (less than 19kb) and I think that when I write more posts I can add more advanced functions of amp-list to include a pagination.
This posts shows how to include bulma css classes inside a jekyll website but keeping good performance from AMP websites
This article shows how to implementent Algolia searching engine in a static website and update the entries using Github Actions.
Vim is a simple and ubiquitous text editor that I use daily. In this article I show how to use Vim to take and publish diary notes using Vimwiki and Hugo.
Services like Mopidy and Snapcast are ideal to create a multiroom streaming audio player using devices like the RaspberryPi or android telephones. This posts presents a web interface that uses the state of the art web technologies and integrates nicely with Mopidy and Snapcast.