AMP pages using Nuxt.js

AMP Project pages are consistently fast and accessible and have premium placement in Google search results. With a few tweaks to the rendering process, it's possible to make Nuxt.js pages render as AMP compatible pages either in place of Nuxt.js pages or in addition via the amphtml meta link.

The process for "ampify-ing" a page involves hooking into the render function of Nuxt.js in the hooks configuration options.

// nuxt.config.js
const ampify = require('./plugins/ampify')

module.exports = {
  /*
  ** Hooks configuration
  */
  hooks: {
    // This hook is called before saving the html to flat file
    'generate:page': (page) => {
      if (/^\/amp\//gi.test(page.route)) {
        page.html = ampify(page.html)
      }
    },
    // This hook is called before serving the html to the browser
    'render:route': (url, page, { req, res }) => {
      if (/^\/amp\//gi.test(url)) {
        page.html = ampify(page.html)
      }
    }
  }
}

In this example, only pages under the /amp/ directory will be converted to AMP pages. These methods mutate the state of the page object after rendering, so the changes being applied will be performed on already rendered HTML files using RegEx style mutations utilizing the ampify function we have imported from the ampify.js plugin file.

// plugins/ampify.js
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
}

The ampify script first replaces the opening <html> tag with one including the AMP signifier . The script then takes all <style> tags and combines them into a single <style> with the amp-custom attribute. The script then strips out all preload and prefetch tags, any <link rel="amphtml"> tags, and all <script> tags except for those used for JSON-LD structured data. The script then converts any <img> tags to <amp-img> tags, and finally adds imports for the two required AMP boilerplate snippets.

With the Javascript removed from the page some features will become unavailable, but most Nuxt.js projects should function fine. Links will no-longer be preloaded and rendered in place as an SPA, but will instead cause an actual page location change and re-render. Analytics and ad services will no longer function, but there are AMP plugins available to solve these issues in a highly performant manner (AMP Analytics, AMP Ads).

This script does the heavy lifting on a few of the more programmatic AMP modifications, but there are still a few additional modifications that will likely be required. For instance, while the <img> tag is converted to an <amp-img> tag, it will fail validation without a width attribute. Also, CSS specificity exception !important will cause your CSS to fail AMP validation.

To test what other steps will be required to make your page 100% AMP compliant, run it through the AMP Project validator. The next step is to head over to the AMP Project site and read through the "Create your first AMP Page" guide where you will find many useful tips for improving usability and SEO.