やったこと
ポートフォリオをリニューアルするのに際し、とにかく最初の描画速度を上げたかった、理由は「他にこだわりどころがなかったから」だ
作ったものはこちら(一部未完成)
リポジトリ
環境
- 開発環境:Nuxt(generate)
- ホスティング:firebase
- 多分有名所のホスティングサービスの中で最速だと思っています。
WebPの使用
Chrome系のブラウザでしか描画できないが、ファイルサイズが圧倒的に小さくなるので、それだけパフォーマンスが向上する。
画像自体のホスティングはCloudinaryというCDNを利用した。
Cloudinaryを使うと、WebPに対応していないブラウザでは自動的に別の形式の画像を配信することができます。
AMP対応
AMPを使うと、スマホでGoogleの検索ページからアクセスしたときに自動的にGoogleクローラがキャッシュしたAMP対応HTMLにアクセスしてくれます。検索した時点でキャッシュされるので表示が爆速になります。
今回は、indexのみamp版のページを用意することにしました。
Nuxt generateを使っているので、amp htmlの形式に合わせたHTMLを生成するためにはhooksのタイミングでHTMLを置換する処理を走らせる必要があります。
こんな感じ
import ampify from './src/plugins/ampify'
export default {
...
hooks: {
// This hook is called before saving the html to flat file
'generate:page': page => {
page.html = createAsciiArt(page.html)
if (page.route === '/amp') {
page.html = ampify(page.html)
}
},
// This hook is called before serving the html to the browser
'render:route': (url, page, { req, res }) => {
page.html = createAsciiArt(page.html)
if (page.route === '/amp') {
page.html = ampify(page.html)
}
}
}
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 => {
html = html.replace(/<html/gi, '<html ⚡')
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>`
)
html = html.replace(/<link[^>]*rel="(?:preload|prefetch)?"[^>]*>/gi, '')
html = html.replace(/<link[^>]*rel="(?:amphtml)?"[^>]*>/gi, '')
html = html.replace(/\s*data-(?:[^=>]*="[^"]*"|[^=>\s]*)/gi, '')
html = html.replace(
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
match => {
return /application\/ld\+json/gi.test(match) ? match : ''
}
)
html = html.replace('<link rel="amphtml" href="/">', '')
// Replace img tags with amp-img
html = html.replace(/<img([^>]*)>/gi, (match, sub) => {
return `<amp-img ${sub} layout="responsive"></amp-img>`
})
// Add AMP script before </head>
html = html.replace('</head>', ampScript + ampBoilerplate + '</head>')
return html
}
これでgenerate時に /amp
以下のページだけamp対応に置換してくれます。
imgをamp-imgに置換はしていますが、コーディングの際にタグに直接 width
と height
を指定して上げる必要があります(ちなみに、width
とheight
を直接指定するとサイズの計算を事前に済ませてくれるので画像読み込み時のガクガクがなくなります。普通のimg
でも指定してあげると、体感速度が上がって良いです)
また、Styleは一つのタグでまとめなくては行けないため、generate時に一つのタグ内に収まるように、assetsの配下にcssを集約しましょう。タグ一つにまとめることでレンダリング速度も多少向上します。
また、通常のHTMLとAMP HTMLで相互にリンクを貼る必要があるため、以下のようにします。
amp側からは先程の置換で一緒にリンクも張っています
export default {
link: [
{rel: 'amphtml', href: './amp'}
]
今回はindexのみamp対応にしているため、indexからのみ貼ったほうがいいのですが、めんどくさかったので全ページに貼っています()
generateしたらこちらでテストしてあげると良いです。
imgにdecoding="async"をつける
decoding="async"
と明示的に書くことによって、画像の描画をコンテンツの表示を邪魔しないように非同期的に行ってくれます。
また、Chrome限定ですが loading="lazy"
もつけてあげると良いでしょう。