Generate AMP Pages in Nuxt.js
About AMP
AMP (Accelerated Mobile Pages - amp.dev ↗️) is Google’s iniisatif for helping publishers, developers and website owner to serve website with fast first load time even nearly instant in mobile devices. For achieving that instant loading is not free, this can be done by AMP with applying so many strict rule for websites. We can say that AMP is strip down all your custom JavaScript to guarantee your website have instant load time.
AMP is recommended by Google because it is very user centrict. Google also will prioritize the contents with AMP support with adding a badge in the search results to indicate AMP. Google also support with adding the caches to boost the first load speed into the limit which make AMP is the best choice for first user landing from the search result.
Basics for Creating AMP
AMP is seperate pages with the normal one. This is because AMP have different strict rule that make us harder to implement in our existing technology stack that usually become very depends on JavaScript.
For creating new AMP pages, first we need to add an identifier in html
tag to make browser know that the pages is AMP version.
If in normal HTML we can have this below code:
<html>
<head></head>
<body></body>
<html></html>
</html>
In AMP we will add emoji ⚡ in our html
tag, see below example:
<html ⚡>
<head></head>
<body></body>
<html></html>
</html>
Or we can just change the ⚡ emoji with attribute amp
without any values if we didn’t support emoji.
After adding the identifier, we need to add the main AMP engine script with this below code:
<script async src="https://cdn.ampproject.org/v0.js"></script>
We also need to add CSS boilerplate for all AMP pages:
<style amp-boilerplate>
body {
-webkit-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
-moz-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
-ms-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
animation: -amp-start 8s steps(1, end) 0s 1 normal both;
}
@-webkit-keyframes -amp-start {
from {
visibility: hidden;
}
to {
visibility: visible;
}
}
@-moz-keyframes -amp-start {
from {
visibility: hidden;
}
to {
visibility: visible;
}
}
@-ms-keyframes -amp-start {
from {
visibility: hidden;
}
to {
visibility: visible;
}
}
@-o-keyframes -amp-start {
from {
visibility: hidden;
}
to {
visibility: visible;
}
}
@keyframes -amp-start {
from {
visibility: hidden;
}
to {
visibility: visible;
}
}</style
><noscript
><style amp-boilerplate>
body {
-webkit-animation: none;
-moz-animation: none;
-ms-animation: none;
animation: none;
}
</style></noscript
>
The things we need to be noted is AMP is not allowing any custom JavaScript or any external third party JavaScript link.
So do with CSS file, we can’t load any external CSS file. All style need to be loaded internally using tag amp-custom
like below example code:
<style amp-custom>
.custom-class {
}
</style>
In AMP, we can use any !important
in our CSS code.
Basically, we can use any HTML tag inside our <body>
tag. But AMP already creating many components to help us in creating a common layout or functionality. Better for us to read in AMPs official documentations about the components that already created by AMP teams so we can have better visibility when creating AMP pages in the future.
Generate AMP in Nuxt.js
Creating AMP pages in JavaScript framework was like an impossible things to do. This is because JavaScript is forbidden in AMP, while in JavaScript framework become the main technology to be used.
Today, JavaScript framework already have power to generate static pages like Nuxt.js already done. In my blog itself, I use Nuxt.js for generate static pages to simplify my deployment process. Unfortunately, the static files that generated bu Nuxt.js is bloated with many external JavaScript that need to be clean up first. So the hardest task to generate AMP in Nuxt.js is cleaning process itself.
These are steps that I have been done in generate AMP for my blog:
1. Creating new pages
First of all, I need to create new pages under the same root url /amp/**
with nearly same content with the normal pages. This is for helping us to differentiate AMP pages with the normal pages so we can running clean up process is only for AMP pages an skip the normal pages.
2. Remove unused codes
Because AMP is not allowing any custom JavaScript actions, so any codes in our AMP pages which placed under methods
in Vue’s single file components file are need to be removed. This code become unused because we need to strip down all interactions that require any custom Javascript.
3. Adding hooks in generate
I use generate
command in Nuxt.js to creating static files for my blog so I need to add hooks to detect when this command is executed. In Nuxt, this task can be done with adding this below code in nuxt.config.js
file like this below example:
module.exports = {
hooks: {
'generate:page': (page) => {
if (/^\/amp/gi.test(page.route)) {
console.log('processing amp file: ', page.route);
}
},
},
};
The code above is for inserting a process when Nuxt execute command generate
and we adding Regular Expressions to detect only the AMP pages route that have added process and skip all the normal pages.
4. Manipulate HTML output
From our sample code above, we have page
object as parameter in our generate hooks. If we get property page.html
we can get the HTML string as our final output as HTML file.
The good thing from Internet is there are many good people there. I found the article from toor.co ↗️ that share about cleaning up HTML string from Nuxt to support AMP.
We can just copy-paste this below code:
const ampScript = '<script async src="https://cdn.ampproject.org/v0.js"></script>';
const ampBoilerplate =
'<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>';
module.exports = (html) => {
// Add ⚡ to html tag
html = html.replace(/<html/gi, '<html ⚡');
// Combine css into single tag
let styleConcat = '';
html = html.replace(/<style[^>]*data-vue-ssr[^>]*>(.*?)?<\/style>/gi, (match, sub) => {
styleConcat += sub;
return '';
});
html = html.replace('</head>', `<style amp-custom>${styleConcat}</style></head>`);
// Remove preload and prefetch tags
html = html.replace(/<link[^>]*rel="(?:preload|prefetch)?"[^>]*>/gi, '');
// Remove amphtml tag
html = html.replace(/<link[^>]*rel="(?:amphtml)?"[^>]*>/gi, '');
// Remove data attributes from tags
html = html.replace(/\s*data-(?:[^=>]*="[^"]*"|[^=>\s]*)/gi, '');
// Remove JS script tags except for ld+json
html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, (match) => {
return /application\/ld\+json/gi.test(match) ? match : '';
});
// Replace img tags with amp-img
html = html.replace(/<img([^>]*)>/gi, (match, sub) => {
return `<amp-img ${sub} layout=intrinsic></amp-img>`;
});
// Add AMP script before </head>
html = html.replace('</head>', ampScript + ampBoilerplate + '</head>');
return html;
};
You can check the detail of each process with reading the comments in that code. Basically we just replace the characters we didn’t need with RegEx and adding AMP support script. We adding emoji ⚡, remove all external JavaScript, combine all internal CSS into one tag under <style amp-custom>
and any other tasks.
This functions is put in plugins/ampify.js
directory that will be called in generate hook generate like this below sample:
const ampify = require('./plugins/ampify');
module.exports = {
hooks: {
'generate:page': (page) => {
if (/^\/amp/gi.test(page.route)) {
console.log('processing amp file: ', page.route);
page.html = ampify(page.html);
}
},
},
};
5. Change all scoped style to global style
Because I am not good enough to playing with RegEx code, it’s very risky to update the RegEx when something unexpected happened.
And I realize that this below code make weird result:
// Remove data attributes from tags
html = html.replace(/\s*data-(?:[^=>]*="[^"]*"|[^=>\s]*)/gi, '');
The above code made all my scoped style become invalid because wrong replace process. The easiest part for me to solve this thing is move all scoped style into global style. But this task can be done easily because with unscoped style I need to apply CSS module architecture to keep the CSS from one components is not affecting any other component. I prefer use BEM CSS because of my experience of using this CSS convention.
This step is optional for you if you can update the Regex part that made weird replace process.
6. Change all lazy load images
In non-AMP pages, I use VueTinyLazyloadImg ↗️ library to creating lazy load image. For AMP we don’t need this library anymore because amp have amp-img
component that already support lazy load by default.
Using amp-img
make my existing function in lazy load become invalid for AMP because with in existing function I put attribut data-src
to load the original source of image and using src
for placeholder image. This thing need to be adjust for AMP with replacing data-src
attribute with src
and delete placeholder image.
You can see the RegEX function in this below sample:
function replaceLazyloadImg(str) {
return str && str.replace(/"src":(?:[^=>][^"]*","data-src"|[^=>"]*)/gi, '"src"');
}
7. Adding fix size for images
This is the step that I haven’t done when this article published. All the image tag should have height
and width
attribute.
8. Adding canonical
We need to linking our AMP with each respective normal pages that have same content with our AMP.
You can put link canonical under <head>
tag in AMP page like in this below code :
<head>
<link rel="canonical" href="url-to-normal-page"></link>
</head>
In our normal page we can add amphtml
link, see this below code:
<link rel="amphtml" href="url-to-amp-page"></link>
In Nuxt.js, we can add this meta tag via head()
in our .vue files, see this below sample:
export default {
head() {
const url = `${this.productionUrl}/${this.meta.slug}`;
return {
link: [{ hid: 'canonical', rel: 'canonical', href: url }],
};
},
};
And this below code is sample to insert tag amphtml
in normal page:
export default {
head() {
const ampUrl = `${this.productionUrl}/amp/${this.meta.slug}`;
return {
link: [{ hid: 'amphtml', rel: 'amphtml', href: ampUrl }],
};
},
};
9. Adding Google Analytics
Adding Google Analytics in AMP is quite different with the normal page. In short, we just need to add this below script to adding Google Analytics engine in our AMP pages:
<script
async
custom-element="amp-analytics"
src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"
></script>
Also placing this below simple tracker to tracking page view state:
<amp-analytics type="googleanalytics">
<script type="application/json">
{
"vars": {
"account": "UA-12345678-X"
},
"triggers": {
"trackPageview": {
"on": "visible",
"request": "pageview"
}
}
}
</script>
</amp-analytics>
I update the ampify
script to insert analytics into our AMP pages:
const ampScript = `<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>`;
We also insert page view tracker before close tag </body>
like this below code:
// Add AMP analytics
html = html.replace(
'</body>',
`<amp-analytics type='googleanalytics'>
<script type='application/json'>
{
"vars": {
"account": "UA-12345678-X"
},
"triggers": {
"trackPageview": {
"on": "visible",
"request": "pageview"
}
}
}
</script>
</amp-analytics>
</body>`,
);
Validate AMP Pages
Not all AMP can be shown in Google Search result, only the valid pages that can have badge in search result. So we need to test the validity first before publishing our AMP pages. There are some alternatives outside to test AMP validation, two website that I can mention are: