Next.jsのAMPを試してみる - v8.1.0とv8.1.1ではだいぶ違う


Next.jsv8.1.0からAMPが公式サポートになりニュースになりました。
調べると、まだまだ開発中って感じで、canary版でページ構成が変わっていましたのでメモとして残しておきます。

## AMP Tokyo Group

https://amp-tokyo.connpass.com/

AMPのイベントグループを作りました！
どこのイベントサービスを見てもまだAMPにフォーカスしたグループがなかったのでAMPに興味あればぜひ参加してください！
メンバー増えたら、勉強会を企画したいと思っています！

## 概要

AMPでサイトを構築するには、主に2つの構成が考えられます。

- AMP First：AMPのページだけ用意する
- Paired AMP : ページに対して、通常のページとAMPの2つのバージョンを用意する

Next.jsで `/about/` ページの開発を例に見ていきます。
生成されページ構成は、v8.1.0と開発中のcanary版では大きく違います。

## Next.jsのAMP First

### v8.1.0

Next.js@v8.1.0のAMP First構成でaboutページを作成しexportすると、以下2つのページが生成されます。

- /about/index.html : OptimizedしたAMP
- /about.amp/index.html : OptimizedしてないAMP

`/about.amp/index.html`が通常のinvalidなAMPページで、Googleのbotにクローリングされれば、GoogleのAMP Cacheにから配信されます。`/about/index.html`は[ampproject/amp-toolbox](https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer)を利用して最適化を施したAMPページでinvalidにはなりますが、通常のAMPページより高速に表示します。

このような2ページ構成にして、AMP Cacheに対応したサービス(Google、Yahoo Japanの検索結果やTwitter)から遷移したときは、通常のAMPページ(/about.amp/index.html)へ行き、Facebookやその他のサービス（emailのリンク）から遷移したときは、OptimizedしたAMPページ（/about/index.html）へ行くことで、遷移元に適したページで高速表示を目指します。

### Canary版

Next.js@canary版のAMP First構成でaboutをページを作成しexportすると、OptimizedしたAMPページ`のみ`が生成されます。

- /about/index.html : OptimizedしたAMP

## Next.jsのPaired AMP

### v8.1.0

一方、Next.js@v8.1.0のPaired AMP構成では、

- /about/index.html : 通常のページ
- /about.amp/index.html : OptimizedしてないAMP

のような構成になり、OptimizedしたAMPは生成されません。

### Canary版

Next.js@canary版のPaired AMP構成では、

- /about/index.html : 通常のページ
- /about.amp/index.html : OptimizedしたAMP

のような構成になり、OptimizedしてないAMPは生成されません。

## Optimizedとnon-Optimized

先述の通り、AMPはamp-toolboxを用いて最適化(Optimized)することで更に高速表示可能になりますが、欠点として、AMPページとしてinvalid扱いになり、AMP Cacheにのりません。
なので、AMP First構成だとGoogle等のAMP Cacheにのる用のAMPとOptimizedしたAMPの2つを生成しています。

AMP Firstなのに2ページ分生成するはちょっとわずらわしいですよね。
この件に関しては、[AMP Conf 2019 @ Tokyo](https://youtu.be/UDqTVMQeucQ)でも言及されており、数ヶ月の間にOptimizedしたAMPページもvalidとして判定されるようになります。
そうなれば、AMPページはOptimziedしたページのみ生成すればよくなります。

AMP-toolboxでは、[このPR](https://github.com/ampproject/amp-toolbox/pull/291)でvalidなOptimized AMPを生成する変更を行っていて、v1.2.0-alpha系のバージョンで試すことができます。
またNext.jsでは、それを利用してOptimized AMPを生成するように[このPR](https://github.com/zeit/next.js/pull/7465)で変更しています。v8.1.1-canary.41以降で試すことができます。

よって、Next.js@canary版とv8.1.0版で生成されるページ構成が大きく変わる結果になってます。

## 試してみる (AMP Frist版)

それぞれまだalpha/canary versionですが、試すことができますので実際に作って試してみましょう。
validなOptimized AMPを生成する変更を含んだnext@v8.1.1-canary.43で試します。

```
$ npm init
$ npm install --save next@v8.1.1-canary.43 react react-dom
```

下記のようにpackage.jsonのscript欄を修正します。

```js
{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "export": "npm run build && next export"
  }
}
```

/about/ページを作成します。

```
$ mkdir -p pages/about
$ vi pages/about/index.js
```

下記をpages/about/index.jsに書き込みます。

```js
import { withAmp } from 'next/amp';

export default withAmp(function About() {
  return <h3>My AMP About Page!</h3>
});
```

これでhtmlを生成してます。

```
$ npm run export
```

生成されたOptimized AMPのhtml(`out/about/index.html`)はこんな感じで、OptimizedしてないAMPは生成されません。

```html
<!DOCTYPE html><html amp="" i-amphtml-layout="" i-amphtml-no-boilerplate="" transformed="google;v=1"><head><style amp-runtime="" i-amphtml-version="011905292322390">html{overflow-x:hidden!important}html.i-amphtml-fie{height:100%!important;width:100%!important}html:not([amp4ads]),html:not([amp4ads]) body{height:auto!important}html:not([amp4ads]) body{margin:0!important}html.i-amphtml-inabox-preserve-height-auto,html.i-amphtml-inabox-preserve-height-auto body{height:auto!important}html.i-amphtml-inabox-preserve-height-auto body{margin:0!important}body{-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}[hidden]{display:none!important}html.i-amphtml-singledoc.i-amphtml-embedded{-ms-touch-action:pan-y;touch-action:pan-y}html.i-amphtml-fie>body,html.i-amphtml-singledoc>body{overflow:visible!important}html.i-amphtml-fie:not(.i-amphtml-inabox)>body,html.i-amphtml-singledoc:not(.i-amphtml-inabox)>body{position:relative!important}html.i-amphtml-webview>body{overflow-x:hidden!important;overflow-y:visible!important;min-height:100vh!important}html.i-amphtml-ios-embed-legacy>body{overflow-x:hidden!important;overflow-y:auto!important;position:absolute!important}html.i-amphtml-ios-embed{overflow-y:auto!important;position:static}#i-amphtml-wrapper{overflow-x:hidden!important;overflow-y:auto!important;position:absolute!important;top:0!important;left:0!important;right:0!important;bottom:0!important;margin:0!important;display:block!important}html.i-amphtml-ios-embed.i-amphtml-ios-overscroll,html.i-amphtml-ios-embed.i-amphtml-ios-overscroll>#i-amphtml-wrapper{-webkit-overflow-scrolling:touch!important}#i-amphtml-wrapper>body{position:relative!important;border-top:1px solid transparent!important}#i-amphtml-wrapper+body{visibility:visible}#i-amphtml-wrapper+body .i-amphtml-lightbox-element,#i-amphtml-wrapper+body[i-amphtml-lightbox]{visibility:hidden}#i-amphtml-wrapper+body[i-amphtml-lightbox] .i-amphtml-lightbox-element{visibility:visible}html.i-amphtml-ios-embed-sd{overflow:hidden!important;position:static!important}html.i-amphtml-ios-embed-sd>body,html.i-amphtml-singledoc.i-amphtml-ios-embed-sd>body{position:absolute!important;top:0!important;left:0!important;right:0!important;bottom:0!important;overflow:hidden!important}.i-amphtml-element{display:inline-block}.i-amphtml-blurry-placeholder{-webkit-transition:opacity 0.3s cubic-bezier(0.0,0.0,0.2,1)!important;transition:opacity 0.3s cubic-bezier(0.0,0.0,0.2,1)!important}[layout=nodisplay]:not(.i-amphtml-element){display:none!important}.i-amphtml-layout-fixed,[layout=fixed][width][height]:not(.i-amphtml-layout-fixed){display:inline-block;position:relative}.i-amphtml-layout-responsive,[layout=responsive][width][height]:not(.i-amphtml-layout-responsive),[width][height][sizes]:not(.i-amphtml-layout-responsive){display:block;position:relative}.i-amphtml-layout-intrinsic{display:inline-block;position:relative;max-width:100%}.i-amphtml-intrinsic-sizer{max-width:100%;display:block!important}.i-amphtml-layout-container,.i-amphtml-layout-fixed-height,[layout=container],[layout=fixed-height][height]{display:block;position:relative}.i-amphtml-layout-fill,[layout=fill]:not(.i-amphtml-layout-fill){display:block;overflow:hidden!important;position:absolute;top:0;left:0;bottom:0;right:0}.i-amphtml-layout-flex-item,[layout=flex-item]:not(.i-amphtml-layout-flex-item){display:block;position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.i-amphtml-layout-fluid{position:relative}.i-amphtml-layout-size-defined{overflow:hidden!important}.i-amphtml-layout-awaiting-size{position:absolute!important;top:auto!important;bottom:auto!important}i-amphtml-sizer{display:block!important}.i-amphtml-blurry-placeholder,.i-amphtml-fill-content{display:block;height:0;max-height:100%;max-width:100%;min-height:100%;min-width:100%;width:0;margin:auto}.i-amphtml-layout-size-defined .i-amphtml-fill-content{position:absolute;top:0;left:0;bottom:0;right:0}.i-amphtml-layout-intrinsic .i-amphtml-sizer{max-width:100%}.i-amphtml-replaced-content,.i-amphtml-screen-reader{padding:0!important;border:none!important}.i-amphtml-screen-reader{position:fixed!important;top:0px!important;left:0px!important;width:4px!important;height:4px!important;opacity:0!important;overflow:hidden!important;margin:0!important;display:block!important;visibility:visible!important}.i-amphtml-screen-reader~.i-amphtml-screen-reader{left:8px!important}.i-amphtml-screen-reader~.i-amphtml-screen-reader~.i-amphtml-screen-reader{left:12px!important}.i-amphtml-screen-reader~.i-amphtml-screen-reader~.i-amphtml-screen-reader~.i-amphtml-screen-reader{left:16px!important}.i-amphtml-unresolved{position:relative;overflow:hidden!important}#i-amphtml-wrapper.i-amphtml-scroll-disabled,.i-amphtml-scroll-disabled{overflow-x:hidden!important;overflow-y:hidden!important}.i-amphtml-select-disabled{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.i-amphtml-notbuilt,[layout]:not(.i-amphtml-element){position:relative;overflow:hidden!important;color:transparent!important}.i-amphtml-notbuilt:not(.i-amphtml-layout-container)>*,[layout]:not([layout=container]):not(.i-amphtml-element)>*{display:none}.i-amphtml-ghost{visibility:hidden!important}.i-amphtml-element>[placeholder],[layout]:not(.i-amphtml-element)>[placeholder]{display:block}.i-amphtml-element>[placeholder].amp-hidden,.i-amphtml-element>[placeholder].hidden{visibility:hidden}.i-amphtml-element:not(.amp-notsupported)>[fallback],.i-amphtml-layout-container>[placeholder].amp-hidden,.i-amphtml-layout-container>[placeholder].hidden{display:none}.i-amphtml-layout-size-defined>[fallback],.i-amphtml-layout-size-defined>[placeholder]{position:absolute!important;top:0!important;left:0!important;right:0!important;bottom:0!important;z-index:1}.i-amphtml-notbuilt>[placeholder]{display:block!important}.i-amphtml-hidden-by-media-query{display:none!important}.i-amphtml-element-error{background:red!important;color:#fff!important;position:relative!important}.i-amphtml-element-error:before{content:attr(error-message)}i-amp-scroll-container,i-amphtml-scroll-container{position:absolute;top:0;left:0;right:0;bottom:0;display:block}i-amp-scroll-container.amp-active,i-amphtml-scroll-container.amp-active{overflow:auto;-webkit-overflow-scrolling:touch}.i-amphtml-loading-container{display:block!important;pointer-events:none;z-index:1}.i-amphtml-notbuilt>.i-amphtml-loading-container{display:block!important}.i-amphtml-loading-container.amp-hidden{visibility:hidden}.i-amphtml-loader-line{position:absolute;top:0;left:0;right:0;height:1px;overflow:hidden!important;background-color:hsla(0,0%,59.2%,0.2);display:block}.i-amphtml-loader-moving-line{display:block;position:absolute;width:100%;height:100%!important;background-color:hsla(0,0%,59.2%,0.65);z-index:2}@-webkit-keyframes i-amphtml-loader-line-moving{0%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{-webkit-transform:translateX(100%);transform:translateX(100%)}}@keyframes i-amphtml-loader-line-moving{0%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{-webkit-transform:translateX(100%);transform:translateX(100%)}}.i-amphtml-loader-line.amp-active .i-amphtml-loader-moving-line{-webkit-animation:i-amphtml-loader-line-moving 4s ease infinite;animation:i-amphtml-loader-line-moving 4s ease infinite}.i-amphtml-loader{position:absolute;display:block;height:10px;top:50%;left:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%);-webkit-transform-origin:50% 50%;transform-origin:50% 50%;white-space:nowrap}.i-amphtml-loader.amp-active .i-amphtml-loader-dot{-webkit-animation:i-amphtml-loader-dots 2s infinite;animation:i-amphtml-loader-dots 2s infinite}.i-amphtml-loader-dot{position:relative;display:inline-block;height:10px;width:10px;margin:2px;border-radius:100%;background-color:rgba(0,0,0,0.3);box-shadow:2px 2px 2px 1px rgba(0,0,0,0.2);will-change:transform}.i-amphtml-loader .i-amphtml-loader-dot:first-child{-webkit-animation-delay:0s;animation-delay:0s}.i-amphtml-loader .i-amphtml-loader-dot:nth-child(2){-webkit-animation-delay:.1s;animation-delay:.1s}.i-amphtml-loader .i-amphtml-loader-dot:nth-child(3){-webkit-animation-delay:.2s;animation-delay:.2s}@-webkit-keyframes i-amphtml-loader-dots{0%,to{-webkit-transform:scale(.7);transform:scale(.7);background-color:rgba(0,0,0,0.3)}50%{-webkit-transform:scale(.8);transform:scale(.8);background-color:rgba(0,0,0,0.5)}}@keyframes i-amphtml-loader-dots{0%,to{-webkit-transform:scale(.7);transform:scale(.7);background-color:rgba(0,0,0,0.3)}50%{-webkit-transform:scale(.8);transform:scale(.8);background-color:rgba(0,0,0,0.5)}}.i-amphtml-element>[overflow]{cursor:pointer;position:relative;z-index:2;visibility:hidden}.i-amphtml-element>[overflow].amp-visible{visibility:visible}template{display:none!important}.amp-border-box,.amp-border-box *,.amp-border-box :after,.amp-border-box :before{box-sizing:border-box}amp-pixel{display:none!important}amp-instagram{padding:64px 0px 0px!important;background-color:#fff}amp-analytics,amp-story-auto-ads{position:fixed!important;top:0!important;width:1px!important;height:1px!important;overflow:hidden!important;visibility:hidden}html.i-amphtml-fie>amp-analytics{position:initial!important}amp-iframe iframe{box-sizing:border-box!important}[amp-access][amp-access-hide]{display:none}[subscriptions-dialog],body:not(.i-amphtml-subs-ready) [subscriptions-action],body:not(.i-amphtml-subs-ready) [subscriptions-section]{display:none!important}[visible-when-invalid]:not(.visible),amp-experiment,amp-live-list>[update],amp-share-tracking,form [submit-error],form [submit-success],form [submitting]{display:none}.i-amphtml-jank-meter{position:fixed;background-color:rgba(232,72,95,0.5);bottom:0;right:0;color:#fff;font-size:16px;z-index:1000;padding:5px}amp-accordion{display:block!important}amp-accordion>section{float:none!important}amp-accordion>section>*{float:none!important;display:block!important;overflow:hidden!important;position:relative!important}.i-amphtml-accordion-content,.i-amphtml-accordion-header,amp-accordion,amp-accordion>section{margin:0}.i-amphtml-accordion-header{cursor:pointer;background-color:#efefef;padding-right:20px;border:1px solid #dfdfdf}amp-accordion>section>:last-child{display:none!important}amp-accordion>section[expanded]>:last-child{display:block!important}amp-list[resizable-children]>.i-amphtml-loading-container.amp-hidden{display:none!important}amp-list[load-more] [load-more-button],amp-list[load-more] [load-more-end],amp-list[load-more] [load-more-failed],amp-list[load-more] [load-more-loading]{display:none}amp-story-page,amp-story[standalone]{min-height:1px!important;display:block!important;height:100%!important;margin:0!important;padding:0!important;overflow:hidden!important;width:100%!important}amp-story[standalone]{background-color:#202125!important;position:relative!important}amp-story-page{background-color:#757575}amp-story .i-amphtml-loader{display:none!important}amp-story-page:not(:first-of-type):not([distance]):not([active]){-webkit-transform:translateY(1000vh)!important;transform:translateY(1000vh)!important}amp-autocomplete{position:relative!important;display:inline-block!important}amp-autocomplete>input{padding:.5rem;border:1px solid rgba(0,0,0,0.33)}.i-amphtml-autocomplete-results,amp-autocomplete>input{font-size:1rem;line-height:1.5rem}[amp-fx^=fly-in]{visibility:hidden}@media only screen and (max-width:979px){amp-addthis[data-widget-type=floating]{position:fixed!important;width:100%!important;height:50px;bottom:0}}@media only screen and (min-width:979px){amp-addthis[data-widget-type=floating]{position:fixed!important;width:70px!important;height:320px!important;top:200px}}
/*# sourceURL=/css/ampdoc.css*/
/*# sourceURL=/css/ampelement.css*/</style><meta charset="utf-8" class="next-head"><link rel="preload" as="script" href="https://cdn.ampproject.org/v0.js"><meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"><script async="" src="https://cdn.ampproject.org/v0.js"></script><style amp-custom=""></style><link rel="canonical" href="/about"></head><body><h3>My AMP About Page!</h3></body></html>
```

ちなみに、next@v8.1.0でexportすると、`out/about.amp/index.html`にOptimizedしてないAMPも生成されます。

```html
<!DOCTYPE html><html amp=""><head><meta charSet="utf-8" class="next-head"/><meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"/><link rel="canonical" href="/about"/><link rel="amphtml" href="/about.amp"/><link rel="preload" as="script" href="https://cdn.ampproject.org/v0.js"/><style amp-custom=""></style><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><script async="" src="https://cdn.ampproject.org/v0.js"></script></head><body><div id="__next"><h3>My AMP About Page!</h3></div></body></html>
```