Was this article helpful?
Thanks for your feedback
Google Accelerated Mobile Pages, or simply Google AMP, is an open-source HTML web development framework designed to help web pages load faster, especially on mobile devices.
By using Google AMP, you can easily create optimized content for mobile, so they open instantly on almost all devices.
In this guide, we will learn how to implement Google AMP on top of your Contentstack-powered websites to help you deliver information to your end-users instantly.
To help you understand how AMP works, we have created a sample app and the code. You can download it from our GitHub repository.
Once you have the code, you’ll find a zip containing the content types needed for this example. Unzip content-type.zip and import them into your stack.
Open the code in any code editor of your choice. Let's look into the routes for header & footer of our Express.js sample app (for a single entry).
/** * Module dependencies. */ const express = require('express'); const router = express.Router(); router.get('*', require('./partials')); module.exports = router;
If you want to fetch the content of the header and footer data from Contentstack, you can use the async library to parallelly fetch data. The following code shows an example of how you can use the async library:
/** * Module dependencies. */ const async = require('async'); const Contentstack = require('contentstack'); const configVars = require('../config'); // Initialize stack const Stack = Contentstack.Stack(configVars.apiKey, configVars.accessToken, configVars.env); // Below method will fetch header & footer data module.exports = (req, res, next) => { async.parallel( [ // Here in this function we get the data for header (callback) => { const Query = Stack.ContentType(configVars.contentTypeUid.headerContentTypeUid).Query(); Query.toJSON() .find() .then((result) => { callback(null, result[0][0]); }) .catch((err) => { console.log(err); }); }, // Here in this function we get the data for footer (callback) => { const Query = Stack.ContentType(configVars.contentTypeUid.footerContentTypeUid).Query(); Query.toJSON() .find() .then((result) => { callback(null, result[0][0]); }) .catch((err) => { console.log(err); }); }, ], (error, success) => { // Here the variable header & footer is used into the template directly for your header and footer if (error) return next(error); res.locals.header = success[0]; res.locals.footer = success[1]; next(); }, ); };
You can create templates for your app. For our example, we have fed the data of the variables (“header” & “footer”) into the header template, you can create a code similar to the one shown below:
<!-- Header/nav bar --> <!-- Here the variable header is fed into template which consist the data fetched from contentstack --> <amp-sidebar id="sidebar" class="cid-qMPDXEs8Ea" layout="nodisplay" side="right"> <div class="builder-sidebar" id="builder-sidebar"> <button on="tap:sidebar.close" class="close-sidebar"> <span></span> <span></span> </button> {% for menuItems in header.navigation_group.navigation_option %} <ul class="navbar-nav nav-dropdown" data-app-modern-menu="true"> <li class="nav-item"> <a class="nav-link link text-white display-7" href="{{menuItems.link}}">{{menuItems.title}}</a> </li> </ul> {% endfor %} </div> </amp-sidebar> <section class="menu horizontal-menu cid-qMPDXEs8Ea" id="menu1-7"> <nav class="navbar navbar-dropdown navbar-fixed-top navbar-expand-lg" style="box-shadow: 0px 10px 5px #a09f9f80; -webkit-box-shadow: 0px 5px 5px #27272780; -moz-box-shadow: 0px 5px 5px #27272780;"> <div class="navbar-brand"> <span> <a href="/"> <amp-img src="{{header.brand_name.logo.url}}" alt="AMP Marketing website template" width="40" height="40"> </amp-img> </a></span><span style="padding-left: 7px;" class="navbar-caption-wrap"> <a style="font-weight: bold;" class="navbar-caption text-white display-5" href="/">{{header.brand_name.title}}</a></span> </div> <div class="collapse navbar-collapse" id="navbarSupportedContent"> {% for menuItems in header.navigation_group.navigation_option %} <ul class="navbar-nav nav-dropdown" data-app-modern-menu="true"> <li class="nav-item"> <a class="nav-link link text-white display-7" style="font-weight: bold;" href="{{menuItems.link}}">{{menuItems.title}}</a> </li> </ul> {% endfor %} </div> </nav> <!-- AMP plug --> <button on="tap:sidebar.toggle" class="ampstart-btn hamburger sticky-but"> <span></span> <span></span> <span></span> <span></span> </button> </section>
Similarly, you can create a template for the footer:
<!-- Footer html --> <section class="footer1 cid-qMPDT0TXHG" id="footer4-6"> <div class="container"> <div class="mbr-col-sm-12 align-center"> <p class="mbr-text mbr-fonts-style mbr-white display-7"> {{footer.footer_section}} </p> </div> </div> </section>
Let's now discuss how the routes are defined through the code in our example app. The following code depicts the Home page route:
/** * Module dependencies. */ const express = require('express'); const router = express.Router(); const Contentstack = require('contentstack'); const configVars = require('../config'); // Initialize stack const Stack = Contentstack.Stack(configVars.apiKey, configVars.accessToken, configVars.env); // Below method will fetch & render home page router.get('/', (req, res) => { const Query = Stack.ContentType(configVars.contentTypeUid.homeContentTypeUid).Query(); Query .toJSON() .find() .then((result) => { res.render('pages/home.html', { homeData: result[0][0] }); }).catch((err) => { console.log(err); }); }); module.exports = router;
The homeData variable in the above code is used to provide values to the home-page template as shown below:
<!-- Home html --> {% extends "pages/index.html" %} {% block content %} <!-- Below is the template for your home-page where the variable "home" is fed to the tag <h1>, <p>,<amp-youtube> etc --> <section class="header11 cid-qMPCctt0uS mbr-fullscreen" id="header11-0" style="background-image: url({{homeData.hero_banner.banner_image.url}});"> <div class="mbr-overlay"></div> <div class="container"> <h1 class="mbr-section-title mbr-fonts-style mbr-white align-center mbr-bold display-1"> <div style="font-size: 70px;">{{homeData.hero_banner.title}}</div> </h1> </div> </section> <section class="features2 mbr-section cid-qMPCzM7s2W" id="features2-1"> <div class="container"> <div class="mbr-row mbr-justify-content-center"> <div> <h1 style="font-size: 40px;">{{homeData.introduction_section.title}}</h1> <p>{{homeData.introduction_section.description}} </p> <amp-youtube data-videoid="{{homeData.introduction_section.videoid}}" width="450" height="220" layout="responsive"> </amp-youtube> </div> </div> </div> </section> {% endblock %}
In the above code, we have used the
In the downloaded code, there's a content-type.zip file. It contains the content type for the blog page (multiple entries). We have added a Modular Block and Group fields in our content type for blog entries (multiple Content Type). So with the help of Modular Block, we can use a single content-type for both “blog-list” page & individual blog pages.
Below is the sample router method created for the blog list page. We have used the “blogData” variable to feed the template for the blog list page to render all blogs.
/** * Module dependencies. */ const express = require('express'); const router = express.Router(); const Contentstack = require('contentstack'); const configVars = require('../config'); // Initialize stack const Stack = Contentstack.Stack(configVars.apiKey, configVars.accessToken, configVars.env); // Below middleware will render the blog list page router.get('/', (req, res) => { Stack.ContentType(configVars.contentTypeUid.blogContentTypeUid).Query() .toJSON() .find() .then((result) => { const blogList = result[0].find((blog) => blog.url === '/blogs'); res.render('pages/blog-list.html', { blogData: blogList }); }) .catch((err) => { console.log(err); }); }); module.exports = router;
The following code represents the router method which fetches individual blogs from the modular block i.e blog_page and render that specific blog into a new template using the variable “blogDetail” as shown below:
// Below code is the router method for an individual blog page` /** * Module dependencies. */ const express = require('express'); const router = express.Router(); const Contentstack = require('contentstack'); const configVars = require('../config'); // Initialize stack const Stack = Contentstack.Stack(configVars.apiKey, configVars.accessToken, configVars.env); // Below router method will fetch & render a specific blog from modular block i.e blog_page router.get('/', (req, res) => { Stack.ContentType(configVars.contentTypeUid.blogContentTypeUid).Query() .toJSON() .find() .then((result) => { const blogContent = result[0].find((blog) => `/blogs${blog.url}` === `${req.originalUrl}`); if (blogContent) { res.render('pages/blog-page.html', { blogDetail: blogContent }); } }) .catch((err) => { console.log(err); }); }); module.exports = router;
Now let’s see the blog-list & blog-page templates where the variables (“blogData” & “blogDetail”) are used to feed the template, similar to what we did for header and footer.
Blog-list template:
<!-- Blogs list page html {% extends "pages/index.html" %} {% block content %} <section class="header11 cid-qMPCctt0uS mbr-fullscreen" id="header11-0" style="background-image: url({{blogData.banner_section[0].banner_content.banner_gourp.banner_image.url}});"> <div class="mbr-overlay"></div> <div class="container"> <h1 class="mbr-section-title mbr-fonts-style mbr-white align-center mbr-bold display-1"> <div style="font-size: 70px;">{{blogData.banner_section[0].banner_content.banner_gourp.heading}}</div> </h1> </div> </section> {% for blogList in blogData.modular_blocks %} <section class="features22 cid-qMPD3jJ5kk" id="features22-3"> <div class="container"> <div class="mbr-row mbr-justify-content-center"> <div class="mbr-col-lg-6 mbr-col-md-12 mbr-col-sm-12 resize vcenter"> <div class="img_wrapper vcenter"> <amp-img src="{{blogList.blog_list.image.url}}" alt="AMP Marketing website template" width="540" height="390" layout="responsive"> </amp-img> </div> </div> <div class="mbr-col-lg-6 mbr-col-md-12 resizeblock vcenter"> <div style="margin-top: 30px;"> <h2 class="tab-title mbr-fonts-style align-center display-5" style=" font-weight: 600; font-size: 25px; color: rgb(33, 37, 41); text-align: justify; margin-bottom: -3px;"> {{blogList.blog_list.heading}} </h2> <p class="mbr-fonts-style align-center display-7" style="margin-bottom: -13px; text-align: justify; font-size: 20px; color: rgb(141, 138, 138);">{{blogList.blog_list.date}}</p> <p class="mbr-fonts-style align-center display-7" style="margin-bottom: -13px; text-align: justify;">{{blogList.blog_list.description}} </p> <p class="mbr-fonts-style align-center display-7" style="text-align: justify;"><a href="/blogs{{blogList.blog_list.cta.href}}">{{blogList.blog_list.cta.title}}</a></p> </div> </div> </div> </div> </section> {% endfor %} {% endblock %}
In the above code, the “blogData” & "blogdetail" variables are used in a "for-loop" to iterate over the modular block entries and feed into your tags. When you click on the See more… option, the page will get directed to the in-detail blog-page for more information on the blog.
Below is the template for blog-page:
<!-- Blog content page html --> {% extends "pages/index.html" %} {% block content %} <section class="header11 cid-qMPCctt0uS mbr-fullscreen" id="header11-0" style="background-image: url({{blogDetail.modular_blocks[0].blog_page.image.url}});"> <div class="mbr-overlay"></div> <div class="container"> <h1 style="font-family:Verdana, Geneva, Tahoma, sans-serif ; color: white;"> <div style="font-size: 60px; ">{{blogDetail.modular_blocks[0].blog_page.heading}}</div> </h1> </div> </section> <section class="features2 mbr-section cid-qMPCzM7s2W" id="features2-1"> <div class="container"> <div class="mbr-row mbr-justify-content-center"> <div> <p class="mbr-fonts-style align-center display-7" style="margin-bottom: -13px; text-align: justify; font-size: 20px; color: rgb(141, 138, 138);"><span>Date : </span>{{blogDetail.modular_blocks[0].blog_page.date}}</p> <p>{{blogDetail.modular_blocks[0].blog_page.description}} </p> </div> <div class="returnTag"> <a style="text-decoration: none; padding: 0px;" href="{{blogDetail.modular_blocks[0].blog_page.cta.href}}"> {{blogDetail.modular_blocks[0].blog_page.cta.title}}</a> </div> </div> </div> </section> {% endblock %}
As mentioned above, the “blogDetail” variable is used in to feed the template tag for your individual Blog-page. This happens by parsing the URL and fetching only the required content/object (for that specific blog itself).
Before adding and publishing your entries, you will need to create an environment. For this exercise, we have created 4 content types as you must have noticed after importing them to your stack.
Add entries in them and publish them on your environment. Now that you are done publishing the entities, let's make changes in the configuration file of your project.
Open the downloaded project in any code editor of your choice. Then, open the config.js file and add your stack details, port number, and content type UIDs as shown below:
Now that you have configured the project, let's move ahead and start the app.
To start the app, you need to install the required node dependencies:
npm install
npm start
You should see the app running on localhost:4000 as shown below (desktop version):
The following image shows the mobile version of the app:
With this, you now have the AMP-powered app running on top of Contentstack. Let's now validate whether your website is AMP powered.
To validate whether your website is AMP-powered or not, you can use any of the following methods:
Using Chrome DevTools Console
You can verify this as depicted in the following image (on the right-hand side of the console):
Using AMP Validator Chrome Extension
If you are using the AMP Validator Chrome extension, you will see a green icon on the top right corner of your Chrome browser extension bar as shown below:
If it's green, it indicates that the page was compiled with all AMP tags used in the project and if there is any error, it will turn red. The AMP Validator basically checks whether the tag/component used for building the website complies with AMP regulation tags, structure & styling guide.
Additional resources: For more information on AMP Page validation, you can check the Validate AMP pages, AMP validation errors, Create your AMP HTML page, The AMP Component Catalog, AMP Websites Guides & Tutorials.
Now let’s compare the performances of a non-AMP website and an AMP-powered website using Contentstack.
To determine the performance of your AMP-powered site, you can use the following tools:
To test your site’s performance using Google Test my site & Page Insights, you can host your site globally through ngrok.
npm i ngrok
Was this article helpful?
Thanks for your feedback