Next.js Learn (Excel)で静的コンテンツ生成などを学んだ


以前、Next.js Learn (Basics)でNext.jsの基礎を学んだでNext.jsのチュートリアルを実施した。今回は続きのNext.js Learn (Excel)を実施したので、まとめた。

内容

Next.js Learn (Excel)は4つのレッスンで構成されている。

Export into a Static HTML App

Next.js Learn (Basics)で作成したTV Showsを静的コンテンツとしてエクスポートする。TV Showsのルートディレクトリに下記内容をnext.config.jsとして保存する。これは、エクスポート時に呼ばれる関数で、//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を実行すると、TV Showsの各ページが静的コンテンツとして/outに出力される。npm install -g serveでHTTPサーバをインストールし、serveで配信するとBatman TV Showsへアクセスできる。

$ npm run export

> hello-next@1.0.0 export /Users/mshr/next-learn-demo/E1-static-export
> next export

> using build directory: /Users/mshr/next-learn-demo/E1-static-export/.next
  copying "static build" directory
  launching 3 workers
[   =] Exporting (1/14)Fetched show: The Batman
Show data fetched. Count: 10
[    ] Exporting (1/14)Fetched show: Batman
[ ===] Exporting (4/14)Fetched show: Batman: The Animated Series
Fetched show: The New Batman Adventures
Fetched show: Batman Beyond
[=   ] Exporting (7/14)Fetched show: Beware the Batman
Fetched show: Batman: Black and White
[  ==] Exporting (9/14)Fetched show: Batman Unlimited
[==  ] Exporting (12/14)Fetched show: The Adventures of Batman
[====] Exporting (13/14)Fetched show: Batman: The Brave and the Bold
Exporting (14/14)

Export successful
mshrs-MacBook-Pro:out mshr$ ls
404             _next           index.html
404.html        about           show
mshrs-MacBook-Pro:out mshr$ serve -p 8080

   ┌────────────────────────────────────────────────────┐
   │                                                    │
   │   Serving!
   │                                                    │
- Local:            http://localhost:8080        │
- On Your Network:  http://192.168.0.1:8080      │
   │                                                    │
   │   Copied local address to clipboard!
   │                                                    │
   └────────────────────────────────────────────────────┘

TypeScript

Next.jsはTypeScriptに対応している。このレッスンでは、新しくプロジェクトを作り、その中で作業を進める。

mkdir next-ts
cd next-ts
npm init -y
npm install --save react react-dom next
npm install --save-dev typescript @types/react @types/node

package.jsonを開き、scriptを以下に書き換える。

"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start"
}

pages/index.tsxを作り、以下を記述する。

const Home = () => <h1>Hello world!</h1>;

export default Home;

npm run devを実行すると、開発サーバが起動するとともにtsconfig.jsonが自動生成された旨のメッセージが端末に表示される。

次に、pages/index.tsxを下記に書き換え、

const Home = ({ 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;

tsconfig.json内のstrict"strict": trueへ書き換え、http://localhost:3000へアクセスすると、型エラーが表示される。

ERROR in /Users/mshr/next-ts/pages/index.tsx(1,17):
1:17 Binding element 'userAgent' implicitly has an 'any' type.
  > 1 | const Home = ({ userAgent }) => <h1>Hello world! - user agent: {userAgent}</h1>;
      |                 ^
    2 | 
    3 | Home.getInitialProps = async ({ req }) => {
    4 |   const userAgent = req ? req.headers['user-agent'] : navigator.userAgent;

index.tsxを下記に書き換え、型情報を付与することで、エラーが解消される。

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;

Create AMP Pages

アプリケーションをAMP対応させる。configオブジェクトのamp: trueをエクスポートすることで、常にAMP対応ページになる。

// pages/index.js
export const config = { amp: true };

export default function Index(props) {
  return <p>Welcome to the AMP only Index page!!</p>;
}

amp: 'hybrid'とすることで、URLへ?amp=1を付けるとAMPページが、付けないと通常のページがレンダリングされる。

// pages/index.js
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>;
}

Automatic Static Optimization

Next.js 9以降のバージョンでは、動的な実装を含むページはServerとして、静的な実装のみならStaticとしてビルドしてくれる。

以下の実装をindex.jsとして保存する。

const Index = () => <h1>Hello World</h1>;
export default Index;

npm run buildでビルドすると、Staticとしてビルドされたことが確認できる。

mshrs-MacBook-Pro:hello-next mshr$ npm run build

> hello-next@1.0.0 build /Users/mshr/hello-next
> next build

Creating an optimized production build

Compiled successfully.

Automatically optimizing pages

Page                                                           Size     First Load JS
┌ ○ /                                                          266 B          58.2 kB
└ ○ /404                                                       3.15 kB        61.1 kB
+ First Load JS shared by all                                  57.9 kB
  ├ static/pages/_app.js                                       957 B
  ├ chunks/bd49b44e65de90cbf4f977910e0c706119f124ab.92878c.js  10.3 kB
  ├ chunks/framework.0f140d.js                                 40 kB
  ├ runtime/main.43a0bb.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)

次に、agent.jsとして以下を保存し、

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;

同様にnpm run buildでビルドすると、今度はServerとしてビルドされる。getInitialPropsを含むため、動的ページとして認識されている。

mshrs-MacBook-Pro:.next mshr$ npm run build

> hello-next@1.0.0 build /Users/mshr/hello-next
> next build

Creating an optimized production build

Compiled successfully.

Automatically optimizing pages

Page                                                           Size     First Load JS
┌ ○ /                                                          266 B          58.6 kB
├ ○ /404                                                       3.15 kB        61.4 kB
└ λ /agent                                                     400 B          58.7 kB
+ First Load JS shared by all                                  58.3 kB
  ├ static/pages/_app.js                                       959 B
  ├ chunks/f7db141d5c375743d3f7fa783807f86c90c92554.a3bfbe.js  2.4 kB
  ├ chunks/fbdfa5f92fe41f664c609f7a50034ba170386183.79e44c.js  8.28 kB
  ├ chunks/framework.0f140d.js                                 40 kB
  ├ runtime/main.9a37d4.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 (Excel)では、静的コンテンツとしてエクスポートする方法、TypeScript、アプリケーションをAMP対応する方法、Automatic Static Optimizationによるページの自動静的/動的ビルドを学んだ。今回学んだことをベースに、このブログをカスタマイズしていきたい。