2月頃に Next.js を学ぶために Next.js Learn (Basic) を試した.詳しくは以下の記事にまとめてある.
今回はその続きと言える Next.js Learn (Excel) を試して学んだことをまとめる.Basic と比較すると,一歩踏み込んだレベルだと思う.だからこそ Next.js Learn (Basic) とセットで終わらせておくと学習効率が1番高そう.僕自身,Excel を試すまでに少し間を空けてしまったことを後悔している.
Next.js Learn (Excel)
Excel では「計4種類」のコンテンツが用意されている.正確には,最近まで Lazy Loading 関連のコンテンツもあったけど,Outdated であるという理由から2月末に削除されていた.なお,削除されたプルリクエストを見ると「Next.js チュートリアルを作り直している」という記載もあり,楽しみだ!気になる!
- Export into a Static HTML App
- TypeScript
Lazy Loading Modules⚠️ 2月末に削除されたLazy Loading Components⚠️ 2月末に削除された- Create AMP Pages
- Automatic Static Optimization
環境
今回は Next.js 9.3.2 を使えるようにローカル環境を構築した.Next.js Learn (Basic) を試した前回は Next.js 9.2.1 だった.
$ npm view next version 9.3.2
1. Export into a Static HTML App
まず「Export into a Static HTML App」では,Next.js でウェブアプリケーションを静的コンテンツとしてエクスポートできる Static HTML Export 機能を学ぶ.Next.js を動かすために Node.js 環境を作る必要がなく,例えば nginx さえあれば動かせるようになる.当然ながら,良し悪しはあるため,機能として知っておくことに価値がありそう.
今回も Next.js Learn (Basic) で使ったリポジトリ zeit/next-learn-demo
を使って,サンプルアプリケーションを動かす.
$ cd E1-static-export
$ npm install
$ npm run dev
npm run dev
を実行すると,前回と同じ Batman TV Shows の画面にアクセスできる.現時点では,まだ普通の Next.js アプリケーションとなり,SSR (Server-Side Rendering) も使っている.画面遷移をしたり,URL 直接アクセスをすると,挙動は調べられる.
次に next.config.js
を以下のように実装する.関数から返却している paths
には,エクスポートするパス情報を含めている.今回の例で言うと /
と /about
と /show/[id]
で,/show/[id]
は実際に TVmaze API を実行した結果から自動的にエクスポートされる.
const fetch = require('isomorphic-unfetch'); module.exports = { exportTrailingSlash: true, exportPathMap: async function() { const paths = { '/': { page: '/' }, '/about': { page: '/about' } }; const res = await fetch('https://api.tvmaze.com/search/shows?q=batman'); const data = await res.json(); const shows = data.map(entry => entry.show); shows.forEach(show => { paths[`/show/${show.id}`] = { page: '/show/[id]', query: { id: show.id } }; }); return paths; } };
そのまま npm run export
を実行すると out
ディレクトリにエクスポートされる.今回は軽量な HTTP サーバとして serve
を使って配信すると,今までと同じ Batman TV Shows の画面にアクセスできる.
$ npm install -g serve $ npm run export (中略) [== ] Exporting (1/14)Fetched show: Batman Show data fetched. Count: 10 Fetched show: The Batman [== ] Exporting (4/14)Fetched show: Batman Beyond Fetched show: The Adventures of Batman Fetched show: Batman: The Animated Series [ ==] Exporting (7/14)Fetched show: Batman Unlimited Fetched show: Batman: The Brave and the Bold Fetched show: The New Batman Adventures [ ===] Exporting (12/14)Fetched show: Beware the Batman Fetched show: Batman: Black and White Exporting (14/14) $ cd out $ serve -p 8080
実際に out
ディレクトリを確認すると,HTML や JavaScirpt など,たくさんエクスポートされている.特に out/show
を見ると,id
ごとに index.html
がある.next.config.js
に実装した通り,エクスポート時に自動的に index.html
を作っていると確認できる.
$ tree out/show out/show ├── 11464 │ └── index.html ├── 22309 │ └── index.html ├── 33618 │ └── index.html ├── 3557 │ └── index.html ├── 481 │ └── index.html ├── 504 │ └── index.html ├── 5951 │ └── index.html ├── 757 │ └── index.html ├── 900 │ └── index.html └── 975 └── index.html 10 directories, 10 files
2. TypeScript
Next.js は TypeScript をサポートしている.特に Next.js 9 から,より便利に使えるようになった.
検証用に新規ディレクトリを作り,React と Next.js と TypeScript をインストールする.
$ npm init -y $ npm install --save react react-dom next $ npm install --save-dev typescript @types/react @types/node
さらに pages/index.tsx
を以下のように実装する.
const Home = () => <h1>Hello world!</h1>; export default Home;
現時点だと,以下のような構成になっている.
$ tree -L 1 . ├── node_modules ├── package-lock.json ├── package.json └── pages 2 directories, 2 files
ここで npm run dev
を実行し,ローカルサーバを起動すると,なんと tsconfig.json
を自動的に作ってくれる!最終的にカスタマイズは必要だとしても,Next.js のオススメ設定をベースに実装を進められるのは特に初学者にとって良さそう.
$ npm run dev (中略) [ wait ] starting the development server ... [ info ] waiting on http://localhost:3000 ... We detected TypeScript in your project and created a tsconfig.json file for you. Your tsconfig.json has been populated with default values.
自動的に tsconfig.json
が作られたよね?という驚きを確認するクイズもある.前回の記事にも書いたけど,手順通りに進めるだけではなく,動作確認をしながらクイズに回答する体験は Next.js Learn の良いところだと思う.
なお,自動的に作られる tsconfig.json
は,デフォルトだと "strict": false
になっているため,ここを "strict": true
にすると型エラーになる.既に型解決をした実装も用意されているため,以下のように変更するとうまく動く.最近は TypeScript を書く機会も増えているし,試せて良かった.
import { NextPage } from 'next'; const Home: NextPage<{ userAgent: string }> = ({ userAgent }) => ( <h1>Hello world! - user agent: {userAgent}</h1> ); Home.getInitialProps = async ({ req }) => { const userAgent = req ? req.headers['user-agent'] || '' : navigator.userAgent; return { userAgent }; }; export default Home;
3. Create AMP Pages
Next.js なら React アプリケーションを簡単に AMP に対応させることもできる(当然ながら Valid にするためには工夫も必要になる).「Create AMP Pages」では Next.js でサポートしている2種類の設定 amp: true
と amp: 'hybrid'
を試す.
手順は今までとは違って,突然「pages/index.js
に amp: true
を実装する」と書かれている.少し突き放されたような雰囲気もあるため,今回はさっき使った zeit/next-learn-demo
リポジトリの E1-static-export
ディレクトリを再利用することにした.まず pages/index.js
を以下の実装に書き換えて,npm run dev
を実行する.AMP の設定を config
にしている.
export const config = { amp: true }; export default function Index(props) { return <p>Welcome to the AMP only Index page!!</p>; }
Chrome 拡張機能「AMP Validator」を使って確認すると,data-ampdevmode
関連のエラーは1件出るけど,ちゃんと AMP に対応していた.具体的には,HTML の html
タグに AMP 関連の設定が入っていた.
<html amp="" data-ampdevmode="" i-amphtml-layout="" i-amphtml-no-boilerplate="" transformed="self;v=1" amp-version="2003261442330" class="i-amphtml-singledoc i-amphtml-standalone" style="padding-top: 0px !important;"> (中略) </html>
次に pages/index.js
を以下の実装に変更し,AMP の設定を hybrid
にする.http://localhost:3000/
にアクセスすると通常のレンダリングになり,http://localhost:3000/?amp=1
にアクセスすると AMP のレンダリングになる.isAmp()
で判定できる.実際に使うなら hybrid
で使うことが多そう.
import { useAmp } from 'next/amp'; export const config = { amp: 'hybrid' }; export default function Index(props) { const isAmp = useAmp(); return <p>Welcome to the {isAmp ? 'AMP' : 'normal'} version of the Index page!!</p>; }
4. Automatic Static Optimization
最後は Next.js 9 の新機能「Automatic Static Optimization」を試す.簡単に言うと,ビルドをするときに getInitialProps
や getStaticPaths
など動的な実装があれば Server としてビルドし,完全に静的な実装なら Static にビルドしてくれる機能となる.ただし,Next.js 9.3 で getStaticProps
など,Automatic Static Optimization を改善する機能がリリースされたことにより,コンテンツ内容と画像は古くなっていて,進めながら少し困った⚠️今回は Next.js 9.3 を前提に進める.
まず,pages/index.js
に以下の実装をして,npm run build
を実行する.
const Index = () => <h1>Hello World</h1>; export default Index;
すると,特に /
に動的な実装はなく ○ (Static)
と判断されていた.
$ npm run build (中略) Automatically optimizing pages Page Size First Load ┌ ○ / 266 B 58.2 kB └ ○ /404 3.15 kB 61.1 kB + shared by all 57.9 kB ├ static/pages/_app.js 957 B ├ chunks/87dfcf810f5ac5b1c4a6c5a9e347b3d4582d9ddb.92878c.js 10.3 kB ├ chunks/framework.0f140d.js 40 kB ├ runtime/main.b9d743.js 5.95 kB └ runtime/webpack.b65cab.js 746 B λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps) ○ (Static) automatically rendered as static HTML (uses no initial props) ● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)
次に,新しく pages/agent.js
に以下の実装をして,npm run build
を実行する.これは getInitialProps
を使ってレンダリング時に UserAgent を取得している.
const Agent = ({ userAgent }) => <h1>Your user agent is: {userAgent}</h1>; Agent.getInitialProps = async ({ req }) => { const userAgent = req ? req.headers['user-agent'] : navigator.userAgent; return { userAgent }; }; export default Agent;
すると,/agent
に動的な実装があるため λ (Server)
と判断されていた.
$ npm run build (中略) Automatically optimizing pages Page Size First Load ┌ ○ / 266 B 58.5 kB ├ ○ /404 3.15 kB 61.4 kB └ λ /agent 400 B 58.7 kB + shared by all 58.3 kB ├ static/pages/_app.js 959 B ├ chunks/842ebe18e826b71a6d34d074da5a709ac83a664a.79e44c.js 8.28 kB ├ chunks/db577d455de274a7f6e1386d96fe5570d4bd24c0.a3bfbe.js 2.4 kB ├ chunks/framework.0f140d.js 40 kB ├ runtime/main.ee8296.js 5.95 kB └ runtime/webpack.b65cab.js 746 B λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps) ○ (Static) automatically rendered as static HTML (uses no initial props) ● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)
最初はビルド時に表示される結果を見てもよくわからなかったけど,少しは理解できるようになった.Next.js Learn に感謝!
まとめ
Next.js Learn (Excel) を試した.Static HTML Export 機能や Automatic Static Optimization 機能を学べて,Next.js Learn (Basic) を試したときよりも,理解を一歩前進できたように思う.フィードバックとしては Next.js 9.3 を前提にコンテンツが更新されると良いかなと!とは言え,実際に Next.js を使ってアプリケーションを実装しないとチュートリアルレベルから抜け出せないため,プロトタイプを実装し始めた.Next.js をもっと学ぶぞ!