Next.js + Deck.glを試してみる

[MIERUNE Advent Calendar 2020](https://qiita.com/advent-calendar/2020/mierune) 8日目の記事です。

[7日目](https://qiita.com/nokonoko_1203/items/0491abe96d8a2f476f45)は@nokonoko_1203 さんの「[QGISで3D巨大都市（の一部）を召喚する](https://qiita.com/nokonoko_1203/items/0491abe96d8a2f476f45)」でした！
今度「3Dでベル・ハーバー召喚してーな」ってなったら試してみようと思います！（いつなるんだろう....）
[7日目](https://qiita.com/nokonoko_1203/items/0491abe96d8a2f476f45)は海外でベル・ハーバーだったので、今日は国内の東京でいろいろやろうと思います。

# TL;DR
Next.js + Deck.glをゆるくゆるく試しつつ、[行政区域データ（市区町村の境界のデータ）](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N03-v2_3.html)を表示してみるっていう記事です。

Githubにコードがあります: https://github.com/Nekoya3/nextjs_deckgl_example
Netlifyで公開もしてますので試したい方はぜひ: https://gallant-goodall-a481e2.netlify.app/
ついでにVercelでも公開していますのでぜひ: https://nextjs-deckgl-example.vercel.app
(Vercelのほうが体感早い感じします...後ろで出てくるdata.jsonのせいで重たいですが...通常はAPIとかを使いましょう...）

# はじめに
## Next.jsとは？
Next.jsの[チュートリアル](https://nextjs.org/learn/basics/create-nextjs-app)のはじめにはこうのっています

- 直感的なページベースのルーティングシステム（ダイナミックルートをサポート
- プリレンダリングでは、スタティック生成（SSG）とサーバーサイドレンダリング（SSR）の両方がページ単位でサポートされています。
- ページ読み込みを高速化するための自動コード分割
- 最適化されたプリフェッチによるクライアントサイドルーティング
- 組み込みの CSS と Sass のサポート、および任意の CSS-in-JS ライブラリのサポート
- 高速リフレッシュ対応の開発環境
- サーバーレス関数でAPIエンドポイントを構築するためのAPIルート
- 完全に拡張可能

> https://nextjs.org/learn/basics/create-nextjs-app の Next.js: The React Framework から引用
DeepLで翻訳しました。

**要するに何よ？？？**
[Vercel](https://vercel.com/)によって開発されたJavaScriptフレームワークで、Reactと組み合わせてウェブアプリ開発を強化するフレームワークで。
Reactアプリのサーバーサイドレンダリング（SSR）を可能にするものでもある。
でも今はSSRよりもSSGを推してるっぽい（私もSSG好き)
(VueでいうNuxtみたいなものですね...）

## Deck.GLとは？
[Deck.GL](https://deck.gl)はUberがオープンソースで公開しているWebGLベースの地理情報可視化フレームワーク。
Reactでの使用が推奨されていますが、Vue.jsなどでも使用することができる。
[Deck.GL](https://deck.gl)には様々なLayerと呼ばれるものが用意されているので、簡単にオープンデータを可視化できる。


# 手順
## Next.js の初期設定
`npx create-next-app`で作ってもいいんですが、余計なのがついてくるし、Typescriptで書きたいので手動でInitializeします。

#### YarnのInitialize
``` Shell
> mkdir nextjs_deckgl_example
> cd nextjs_deckgl_example
> yarn init nextjs_deckgl_example -y
```

#### Next.jsのインストール
必要最低限のライブラリをインストールします。

``` Shell
> yarn add next react react-dom
```
必要なフォルダを作ります。

``` Shell
> mkdir public src/pages src/components
```
TypeScriptを使いたいのでTypeScriptと型定義をインストールします。

``` Shell
> yarn add -D typescript @types/node @types/react @types/react-dom
```
確認のために `src/pages/index.tsx` を作ります。

``` tsx:src/pages/index.tsx 
import { NextPage } from "next";

const Page: NextPage = () => <h1>はろーわーるど！こんにちは Next.js!</h1>;
export default Page;
```

`package.json`の`scripts`をNext.js用に編集します。

``` json:package.json
{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  }
}
```

`yarn dev`で開発用サーバが立ち上がり http://localhost:3000 で`はろーわーるど！こんにちは Next.js!`と表示されることを確認します。

この時にNext.jsがTypeScriptを検出して `tsconfig.json`を作ってくれるので、`"strict": false,` を `"strict": true,`に変更します。

#### ESLint と Prettierの初期設定
PrettierとESLintとそのLintルールをインストールします

``` Shell
> yarn add -D eslint prettier eslint-plugin-react eslint-config-prettier eslint-plugin-prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-airbnb
```
`.eslintrc.json` を作成します。

``` json:.eslintrc.json
{
  "extends": [
    "airbnb",
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint"
  ],
  "plugins": [
    "@typescript-eslint",
    "react"
  ],
  "parser": "@typescript-eslint/parser",
  "env": {
    "browser": true,
    "node": true,
    "es6": true
  },
  "parserOptions": {
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "rules": {
    "react/prop-types": "off",
    "react/react-in-jsx-scope": "off",
    "@typescript-eslint/no-explicit-any": "off"
  }
}
```

#### TypeScriptの設定をする
importが相対パスだと気持ち悪いので設定を変更します。
これを

``` tsx
import Hello from "../../../components/Hello";
```

こうしたいので

```tsx
import Hello from "~/components/Hello";
```
`tsconfig.json` をこうします
（pathとbaseUrlはこの設定のためですが、typeRootsに関してはこの後のDeckGLの設定のためです）

``` json:tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "~/*": ["src/*"]
    },
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "typeRoots": ["./types", "./node_modules/@types"],
  "exclude": [
    "node_modules"
  ]
}
```
`next.config.js`も同様に記述します。

``` js:next.config.js
const path = require("path");

module.exports = {
  webpack: config => {
    config.resolve.alias = {
      ...config.resolve.alias,
      "~": path.resolve(__dirname, "./src")
    };
    return config;
  },
};
```
正しく動いているかの確認のためにコンポーネント作って、importしてみます

``` tsx:src/components/Test.tsx
import React from "react";

const Test: React.FC = () => <h1>てすとくん</h1>;

export default Test;
```

``` tsx:src/pages/index.tsx
import { NextPage } from "next";
import Test from "~/components/Test";

const Page: NextPage = () => <Test />;
export default Page;
```

#### .envの設定
開発時は`.env`を使いたいので`dotenv-webpack`を使えるようにします。

``` Shell
> yarn add -D dotenv-webpack
```
それに伴い `next.config.js`も修正します

``` js:next.config.js
const path = require("path");
const Dotenv = require("dotenv-webpack");

module.exports = {
  webpack: config => {
    config.resolve.alias = {
      ...config.resolve.alias,
      "~": path.resolve(__dirname, "./src")
    };
    config.plugins = [
      ...config.plugins,

      // 環境変数を優先して読み込む
      new Dotenv({
        systemvars: true
      })
    ];
    return config;
  }
};
```

#### .gitignoreの作成
gitignoreないことに気づいたので作ります

``` .gitignore:.gitignore
.idea
# next.js build output
.next
# dotenv environment variables file
.env
# Dependency directories
node_modules/
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
```

やっとこれでNext.jsの初期設定が終わりです。

## Deck.GLの初期設定
#### Deck.GLとreact-map-glをインストールする

``` Shell
> yarn add deck.gl react-map-gl
> yarn add -D @danmarshall/deckgl-typings @types/react-map-gl
```
yarnを使っていると `@danmarshall/deckgl-typings`がバグってしまうことがあるので、`deck.d.ts`を追加します

``` Shell
> mkdir types
```

```ts:types/deck.d.ts
import * as DeckTypings from '@danmarshall/deckgl-typings';

declare module 'deck.gl' {
  export namespace DeckTypings {}
}
```

これでDeck.GLの初期設定は終わりです

## react-map-glとNext.jsの動作確認をする
#### _document.tsxを設定する
画面全体にマップを表示したいので、`_document.tsx`で`html`と`body`の`margin`と`padding`を0にします。

```tsx:_document.tsx
import Document, {DocumentContext, Head, Html, Main, NextScript} from 'next/document'

export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const initialProps = await Document.getInitialProps(ctx)
    return {...initialProps}
  }

  render() {
    return (
      <Html>
        <Head>
          <style>{`html,body { margin: 0; padding: 0; }`}</style>
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}
```

#### react-map-glを試すための準備
Deck.GLを試す前に、`react-map-gl`の表示テストをしましょう。
`index.tsx`を`react-map-gl`が表示されるように編集します。
せっかくなのでMaptilerを使いましょう！

https://maptiler.jp の右上の`Sign in`からログインをします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275457/2bb466eb-f780-2dad-9fa3-4ac70cbe2a95.png)
その後、Mapsをクリックし

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275457/2e05d4dc-f753-61b9-9a0f-8e1726909019.png)
`Japan` にある `JP MIERUNE Streets`を選択して
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275457/b932ff51-9e52-44eb-0434-7f97b9c90272.png)
Use Vector StyleにあるURLをどこか（メモ帳とか）にメモしておきます。
(keyを使うのでそれ以降だけでもOKです。）

https://account.mapbox.com からAccess Tokenを取得してどこかにメモしておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275457/c3d24564-656e-07b4-a878-272effdaa24d.png)

`.env`にアクセストークンを書きます（本番環境では環境変数にしましょう）
空欄にしているのでそこにそれぞれを張り付けてください

``` .env:.env
# MapBoxのToken
MAPBOX_ACCESSS_TOKEN =
# MaptilerのKey
MAPTILER_ACCESS_TOKEN =![Something went wrong]()

```

#### react-map-glを試す
いよいよreact-map-glで実際に表示してみます。
`src/pages/index.tsx`を下のコードに帰ると東京周辺のマップが表示されて、マウスでぐりぐり動かせるはずです！

``` tsx:src/pages/index.tsx
import { NextPage } from "next";
import ReactMapGL from 'react-map-gl';
import {useState} from "react";

const Page: NextPage = () => {
    const [viewport, setViewport] = useState({
        width: 400,
        height: 400,
        latitude: 35.681236,
        longitude: 139.767125,
        zoom: 10
    });

    return (
        <ReactMapGL
            {...viewport}
            width={"100vw"}
            height={"100vh"}
            mapStyle={'https://api.maptiler.com/maps/jp-mierune-dark/style.json?key=' + process.env.MAPTILER_ACCESS_TOKEN}
            mapboxApiAccessToken={process.env.MAPBOX_ACCESSS_TOKEN}
            onViewportChange={nextViewport => setViewport(nextViewport)}
        />
    )
}
export default Page;
```

こんな感じ↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275457/bd0a5f1c-49e4-a79c-d247-3de8ceb40917.png)


さて、ここで動作の確認ができたらそろそろ最初の目的である「Next.js+Deck.glで行政区域データ（市区町村の境界のデータ）を表示してみる」をやりましょう！

## Next.js+Deck.glで行政区域データ（市区町村の境界のデータ）を表示してみる
#### データを用意する
[国土数値情報](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N03-v2_3.html) から、データをダウンロードします。
東京の平成31年のデータ（`N03-190101_13_GML.zip`)をダウンロードします。
zipを解凍してから、`N03-19_13_190101.geojson`を`data.json`にリネームして、`src`内に`datas`フォルダを作り入れておきます。

#### Deck.GL用にコードを変更する
上で用意したデータがもとは`geojson`形式なので、`GeoJsonLayer`として読みこみます。
それぞれの表示色をランダムにしたいのでgetFillColorでランダムにしています。

```tsx
import { NextPage } from "next";
import {StaticMap} from 'react-map-gl';
import {useState} from "react";
import {GeoJsonLayer} from "@deck.gl/layers";
import {DeckGL} from "deck.gl";

const Page: NextPage = () => {

    const data = require('~/datas/data.json')
    const [viewport, setViewport] = useState({
        width: 400,
        height: 400,
        latitude: 35.681236,
        longitude: 139.767125,
        zoom: 10
    });

    const layer = new GeoJsonLayer({
        data,
        filled: true,
        stroked: true,
        getLineWidth: 10,
        getLineColor: [255, 0, 0],
        getFillColor: () => {
            const rand = Math.floor(Math.random() * Math.floor(10))
            if (rand <= 5) {
                return [0, 0, 255]
            }
            return [255, 255, 255, 50]
        }
    })

    return (
        <DeckGL
            width={"100vw"}
            height={"100vh"}
            controller
            layers={[layer]}
            viewState={viewport}
            onViewStateChange={(viewState) => setViewport(viewState.viewState)}
        >
            <StaticMap
                width={100}
                height={100}
                mapStyle={'https://api.maptiler.com/maps/jp-mierune-dark/style.json?key=' + process.env.MAPTILER_ACCESS_TOKEN}
                mapboxApiAccessToken={process.env.MAPBOX_ACCESSS_TOKEN}
            />
        </DeckGL>
    )
}
export default Page;
```
下のように表示されたら終了です！
（毎回ランダムなので色の場所は変わります！）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275457/c7035884-eb8a-dc7a-27bb-76a3c44c77b8.png)


#さいごに
思っていた数十倍すんなり動いて感激してます。
通常のcreate-react-appで作ったものよりもカスタマイズがしやすく、ESLintとかPrettierとかの設定がしやすい印象でした。
create-react-appで作ったものよりも動作も心なしかサクサクだったようなので、こっちを使っていこうかなという感じです。

全国でやってみるとかは別な記事で....
読んでいただきありがとうございました！ﾉｼ