Reactの2大SSGフレームワークを変わった角度から比べてみようとしたデブリ記事

# この記事はデブリです
デブリ
https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%83%87%E3%83%96%E3%83%AA

「なぜGatsby.jsを推すか」の通り、Apache単体で配信できるGatsby.jsが良いという趣旨で記事化したところ
わずか3時間後に「Next.jsもNode.js無しでOK」という情報を頂戴し、めでたくデブリ記事と化しました。

独力で見つけられなかった結構重要な情報が手を動かさずに3時間で得られることもあるので
アウトプットは大事！ということで。

それぞれのフレームワークでHTML生成した場合の流れが知りたい方は
「Next.js の環境を作成する」から読み進めて、最後にnext.js側で以下の手順を追加してください。

```shell:shell
nano docker-compose.yml
```

```yml:dcoker-compose.yml
version: '3'

services:
  next:
    build:
      context: .
      # dockerfile: dockerfile.genepro
      dockerfile: dockerfile
    volumes:
      - ./nextjs/workspace/pages:/usr/node/workspace/pages
    ports:
      - 33000:3000
    container_name: nextjs-trial
    tty: true
    # command: >
    #   npm run dev
```

```shell:shell
nano package.json
```

```json:package.json
{
    "name": "workspace",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start",
        "export": "next build && next export"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "next": "^10.0.6",
        "react": "^17.0.1",
        "react-dom": "^17.0.1",
    }
}
```

```shell:shell
docker-compose up --build
```

```shell:shell
docker-compose exec next bash
```

```shell:shell@next
npm run export
ls out
```

http経由での確認は省略していますが、outディレクトリの中に分かりやすくhtmlファイルが出力されているので様子がわかると思います。

ただどういうわけか pages/articles/index.js は articles.htmlになり、
もう一階層掘って pages/articles/books/index.js を作ると articles/books.htmlに出力されます。
開発サーバとURLが違ってしまうのでこの辺りは変更可能なのか今後調べてみたいところです。

# はじめに
Reactはとても気に入っているのですが、Node.jsはどうもツールの入れ替わりが激しくて
環境のメンテナンスとかを考えると使いたくないなあと思っていたNode.js食わず嫌い勢です。

Node.jsは使いたくないので、ReactとReact-domをJSファイルでダウンロードして、
バックエンドは別の言語でAPIを組むのがベストプラクティスだと信じて生きてきました。

バックエンドに関しては今でも「Node.jsに固定される状況は避けたい」と思っていますが、
Reactフレームワークのトレンドを調べたら、バックエンドとして使わなくてもNode.jsを入れて
Node.js依存のフレームワークを使う機運は高まっているなあ思うようにはなりました。

で、結論は「Gatsby.jsがなかなかいいんじゃない？」という話なんですが、
実際にNext.jsとGatsby.jsでホスティングまで手を進めてみてその結論に至るまでの記録を書こうと思います。

今回は Windows 10 + WSL2 + Dockerが作業環境です。
WSLとDockerが使える状態でない方は"WSL2 Docker VSCode"辺りで検索するとたくさん出ます。
（とても良い記事が2つほどあったのですが、忘れてしまいました。）

# Why did I try Next.js & Gatsby.js ?
まず個人的な好みですが、バックエンドはAPI化してJavascript環境と切り離したいのです。
JSONでやり取りするAPIの形にしておいたほうがバックエンドの使用言語等による制約がなくなります。

バックエンドを別の環境でAPIサーバーとして立てることを考えると、
フロントエンド側は静的ファイルの生成とホスティングができればよいのでSSGが最適解になってきます。
加えて現在トレンド1位と言われているNext.jsがSSGの機能を強化していることもあり、
SSGを使った設計が廃れることはしばらくないのではないかと思います。

https://tsh.io/state-of-frontend/#jamstack
によると、SSGを使ったフレームワークのツートップがNext.jsとGatsby.jsとなっています。
調査方法はよくわかりませんが、2つともダントツですね。

# なぜGatsby.jsを推すか
投稿時点でのNPMでのダウンロード数などを見る限り、Next.jsの方が優勢に見えます。
Next.jsならばSSRにも対応できるので、Gatsby.jsではできないことがあるという考え方はありそうです。
Gatsby.jsは拡張機能をサードパーティープラグインに依存するためリスクがあるという記述も見かけます。

確かにフロントエンドをゴリゴリ作りこんでいくとそういう苦悩にはぶつかるかもしれませんが、
それよりもまず触って違いを感じたのは

- Next.jsは静的ファイルを生成しても、結局Node.jsでサーバーを立てる必要がある
- Gatsby.jsは生成されたファイルを配置するだけでApacheなどのWebサーバーで配信できる

という部分です。
これは利用可能なリリース環境や、その環境で与えられる権限にとらわれないという点で有利です。

# Next.js の環境を作成する

まずはWSL2のUbuntu上のホームにディレクトリを切っていきます。

```shell:shell
cd ~
mkdir nextjs-trial
cd nextjs-trial
mkdir -p nextjs/workspace/pages
```

pagesという名前はnext.jsのソースを格納するディレクトリに合わせてあります。実際に開発する際は
このディレクトリの中のソースを変更していきます。次にdocker-compose.ymlを作成します。
node.jsの環境を作成する場合、何度かやってみて考えたのですが最終版と途中までのビルドスクリプトは
分けて作ったほうが楽だなあと思います。
なぜかというとnode.js環境を新しく作る場合は npm init -> npm install が必要で、
一度完成させた環境を再現する場合は package-lock.json から npm ci でビルドするためです。

この2つを分けて残しておくために
途中までの部分は
dockerfile: dockerfile.genepro
tty: true
最終版は
dockerfile: dockerfile
commands: >
  npm run dev
としています。
ちなみにgeneproというのはオーケストラとかの楽団が本番前に行う最終リハーサルのことです。
一般的にどんな名前が使われるのかは知りません。なんかいい名前があったら教えてください。

```shell:shell
nano docker-compose.yml
```

```yml:dcoker-compose.yml
version: '3'

services:
  next:
    build:
      context: .
      dockerfile: dockerfile.genepro
      # dockerfile: dockerfile
    volumes:
      - ./nextjs/workspace/pages:/usr/node/workspace/pages
    ports:
      - 33000:3000
    container_name: nextjs-trial
    tty: true
    # command: >
    #   npm run dev
```

genepro版のdockerfileを作成します。

```shell:shell
nano dockerfile.genepro
```

```dockerfile:dcokerfile.genepro
FROM node:lts
WORKDIR /usr/node/workspace
RUN npm init -y
RUN npm install react react-dom next
```
本当はnode:ltsではなくて、ちゃんとバージョン指定をしたほうがいいかもしれませんね。
next.jsのダミーソースを作っておきます。中身はnext.jsのチュートリアルページと同じです。
https://nextjs.org/docs/getting-started

"npm init"のコマンドに"-y"オプションをつけ忘れると入力待ちの状態になって失敗します。
npmに慣れていない人（わたしです）は特に要注意。

```shell:shell
nano nextjs/workspace/pages/index.js
```

```js:index.js
function HomePage() {
  return <div>Welcome to Next.js!</div>
}

export default HomePage
```

ついでにもう1つ、階層を作るとどうなるかの確認用に

```shell:shell
nano nextjs/workspace/pages/articles/index.js
```

```js:artcles/index.js
function ArticlePage() {
  return <div>Here's Article page!</div>
}

export default ArticlePage
```

コンテナを立ち上げます。

```shell:shell
docker-compose up
```

別のシェルでコンテナへ接続してbashを操作します。

```shell:shell
docker-compose exec next bash
```

中にnode_module, package.json, package-lock.json (と、pages)があることを確認します。
このpackages.jsonとpackage-lock.jsonはビルドに使うファイルになるので、ローカルにコピーします。
コピーの仕方は何でもよいですが、ローカルと接続されているディレクトリがあるので

```shell:shell@next
cp -a package*.json pages/
```

```shell:shell
cp -a nextjs/workspace/pages/package*.json ./
```

とすると早いと思います。
ここでpackage.jsonに開発サーバーの起動コマンドを登録しておきましょう。

```shell:shell
nano package.json
```

```json:package.json
{
    "name": "workspace",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "next": "^10.0.6",
        "react": "^17.0.1",
        "react-dom": "^17.0.1",
    }
}
```

さて、このjsonを使う最終版のビルドスクリプトを作ります。

```shell:shell
nano docker-compose.yml
```

```yml:docker-compose.yml
version: '3'

services:
  next:
    build:
      context: .
      # dockerfile: dockerfile.genepro
      dockerfile: dockerfile
    volumes:
      - ./nextjs/workspace/pages:/usr/node/workspace/pages
    ports:
      - 33000:3000
    container_name: nextjs-trial
    # tty: true
    command: >
      npm run dev
```

```shell:shell
nano dockerfile
```

```dockerfile:dockerfile
FROM node:lts
WORKDIR /usr/node/workspace
COPY package.json /usr/node/workspace/
COPY package-lock.json /usr/node/workspace/
RUN npm ci
```

最初にdocker-compose.ymlにcommandを書かず、dockerfile側に"RUN npm run dev"
を書いていたんですが、これだとdocker-composeで管理された構成のコンテナは落ちてしまって
無名のコンテナの中で"npm run dev"が走っている状態になってつながらず、長時間スタックしてました。

```shell:shell
docker-compose up --build
```

再ビルドをかけつつコンテナを起動します。これでコマンドが使える状態のpackage.jsonが配置され、
package-lock.jsonから環境が再現される完成状態のコンテナになります。

"npm run dev"で"next dev"が実行されているので、コンテナの3000番ポートが開発サーバにつながります。
コンテナの3000番はdocker-compose.ymlでlocalhostの33000番と接続してあるので
ブラウザから localhost:33000 にアクセスして
"Welcome to Next.js!"が表示されればOKです。
articles/index.jsは localhost:33000/articles のURLからルーティングされるみたいですね。

# Next.js のホスティングについて調べる
Next.jsの場合、先ほどdocker-composeで最後に"npm run dev"を実行するようにしていました。
これを一旦とりやめて、"tty: true"にします。

```yml:dcoker-compose.yml
version: '3'

services:
  next:
    build:
      context: .
      # dockerfile: dockerfile.genepro
      dockerfile: dockerfile
    volumes:
      - ./nextjs/workspace/pages:/usr/node/workspace/pages
    ports:
      - 33000:3000
    container_name: nextjs-trial
    tty: true
    # command: >
    #   npm run dev
```

コンテナを立ち上げて接続します。

```shell:shell
docker-compose up
```

```shell:shell
docker-compose exec next bash
```

next.jsのSSG機能を使ってソースのビルドを実行し、完成品を見てみます。

```shell:shell@next
npm run build
cd .next/server/pages
ls
```

ビルド済みファイルが隠しディレクトリになってます。
index.html, article.html が同じ層に入っているし、ブラウザからURLを叩いた感じとは明らかに違います。
Web検索で調べてもnext.jsのビルド済みコンテンツをnode.jsでサーバを立てずにホスティングする例は見当たりませんし、このディレクトリ構造を見る限りnode.jsのサーバーなしで動かすのは難しそうです。

# Gatsby.js の環境を作成する

次はGatsby.jsを構築していきます。ディレクトリを切っていきます。

```shell:shell
cd ~
mkdir gatsby-trial
cd gatsby-trial
mkdir gatsby/workspace/src
```

next.jsはソースディレクトリのルートがpagesでしたが、Gatsby.jsではsrcになっています。
srcの下にNext.js同様pagesが入りますが、pages以外にcomponentsディレクトリを切って
テンプレートを格納しておけるディレクトリ構造になっているみたいです。

docker-compose.ymlを作成します。next.jsの時と基本的に同じ作り方をしています。

```shell:shell
nano docker-compose.yml
```

```yml:dcoker-compose.yml
version: '3'

services:
  gatsby:
    build:
      context: .
      dockerfile: dockerfile.genepro
      # dockerfile: dockerfile
    volumes:
      - ./gatsby/workspace/src:/usr/node/workspace/src
    ports:
      - "38000:8000"
    container_name: gatsby-trial
    tty: true
    # command: >
    #   npm run develop
```

genepro版のdockerfileを作成します。

```shell:shell
nano dockerfile.genepro
```

```dockerfile:dcokerfile.genepro
FROM node:lts
WORKDIR /usr/node/workspace
RUN npm init -y
RUN npm install gatsby react react-dom
```

gatsby.jsのダミーソースを作っておきます。中身はまたも公式からパクります。
https://www.gatsbyjs.com/docs/using-gatsby-professionally/setting-up-gatsby-without-gatsby-new/

```shell:shell
nano nextjs/workspace/pages/index.js
```

```js:index.js
import React from "react"
export default function Home() {
  return <h1>Hello Gatsby!</h1>
}
```

今回も階層を作るとどうなるかの確認用に

```shell:shell
nano nextjs/workspace/pages/articles/index.js
```

```js:artcles/index.js
import React from "react"
export default function ArticlePage() {
  return <h1>Here's Article page!</h1>
}
```

コンテナを立ち上げます。

```shell:shell
docker-compose up
```

別のシェルでコンテナへ接続してbashを操作します。

```shell:shell
docker-compose exec next bash
```

中にnode_module, package.json, package-lock.json (と、src)があることを確認します。
packages.jsonとpackage-lock.jsonをローカルにコピーします。

```shell:shell@gatsby
cp -a package*.json src/
```

```shell:shell
cp -a gatsby/workspace/src/package*.json ./
```

package.jsonにコマンドを登録します。

```shell:shell
nano package.json
```

```json:package.json
{
  "name": "workspace",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "develop": "gatsby develop -H 0.0.0.0",
    "build": "gatsby build"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "gatsby": "^2.32.3",
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  }
}
```

Next.jsとちょっと違うのは、開発サーバーのコマンドにオプションが必要な点です。
Next.jsは開発サーバーに外からアクセスすることが許可されていますが、
Gatsby.jsはデフォルトで許可されていない仕様です。
このため、"-H 0.0.0.0"をオプションに与えてコンテナの外から接続することを許可する必要があります。

最終版のビルドスクリプトを作ります。

```shell:shell
nano docker-compose.yml
```

```yml:dcoker-compose.yml
version: '3'

services:
  gatsby:
    build:
      context: .
      # dockerfile: dockerfile.genepro
      dockerfile: dockerfile
    volumes:
      - ./gatsby/workspace/src:/usr/node/workspace/src
    ports:
      - "38000:8000"
    container_name: gatsby-trial
    # tty: true
    command: >
      npm run develop
```

```shell:shell
nano dockerfile
```

```dockerfile:dcokerfile
FROM node:lts
WORKDIR /usr/node/workspace
COPY package.json /usr/node/workspace/
COPY package-lock.json /usr/node/workspace/
RUN npm ci
```

```shell:shell
docker-compose up --build
```

ブラウザから localhost:38000 にアクセスして
"Hello Gatsby!"が表示されればOKです。
articles/index.jsは localhost:38000/articles/ で確認できます。
Next.jsと同じ見え方ですね。

# Gatsby.js のホスティングについて調べる

ここまでだとNext.jsもGatsby.jsも同じように見えますが、コンテナに入って中身を調べてみます。

```yml:dcoker-compose.yml
version: '3'

services:
  next:
    build:
      context: .
      # dockerfile: dockerfile.genepro
      dockerfile: dockerfile
    volumes:
      - ./gatsby/workspace/src:/usr/node/workspace/src
    ports:
      - 38000:3000
    container_name: nextjs-trial
    tty: true
    # command: >
    #   npm run dev
```

コンテナを立ち上げて接続します。

```shell:shell
docker-compose up
```

```shell:shell
docker-compose exec next bash
```

ビルドして生成物を確認します。

```shell:shell@gatsby
npm run build
cd public
ls
```

publicの中にindex.htmlとarticles/index.htmlがあり、URLと対応していることがわかります。
node.jsのないapache環境で配信ができるか確認します。

```yml:dcoker-compose.yml
version: '3'

services:
  gatsby:
    build:
      context: .
      dockerfile: dockerfile
      # dockerfile: dockerfile.genepro
    volumes:
      - ./gatsby/workspace/src:/usr/node/workspace/src
      - public:/usr/node/workspace/public
    ports:
      - "38000:8000"
    container_name: gatsby-trial
    tty: true
    # command: >
    #   npm run develop

  hosting:
    image: httpd:latest
    volumes:
      - public:/usr/local/apache2/htdocs
    ports:
      - "81:80"
    container_name: gatsby-trial-hosting
    tty: true

volumes:
  public:
```

開発環境(gatsby)側の/usr/node/workspace/publicを
ホスティング側の/usr/local/apache2/htdocs
とつなぎ、ビルドファイルがホスティング用コンテナのApacheドキュメントルートに入るようにします。
ホスティングコンテナの80番ポートはlocalhostの81番ポートに接続します。

```shell:shell
docker-compose up
```

```shell:shell
docker-compose exec next bash
```

```shell:shell@gatsby
npm run build
```

ブラウザからhttp://localhost:81とhttp://localhost:81/articles/にアクセスすると
"Hello Gatsby!"と"Here's Article page!"が表示されています。

Gatsby.jsはApacheのみの環境でも表示できることが確認できました。

# 終わりに
実際に技術選定をする場合ですが、先進的な開発環境が使える現場では
ホスティングサービスを使ってサーバーレスの構成を使う事が多いかもしれません。

なので通常あまりこういう比較の仕方をしないと思いますが、
いざとなったらLinuxサーバ立ててサービスが維持できるというのは泥臭くて好きですね。

今回は開発効率とか表示速度とか、本当はもっと大事な話をガン無視して触っただけなので
この後ちゃんと触ってみようと思います。