Jan 27, 2021

NuxtのWebサイトをFull Static Generationで再構築してみました。

Nuxt の Web サイトを Full Static Generation で再構築してみました。私のサイトの場合、問題になったのはサーバサイドの処理をどのように回避するかでした。またリンクされていないページは、クローニング処理されないので、従来通り一手間かけてファイルを生成します。

Nuxt は、バージョンをアップするたびに機能が充実してきます。今回は、Full Static Generation を使ってみたいと思います。やはり目玉は、nuxt generate コマンドのみで動的ページを作成することでしょうか。今まで Nginx フロントエンドサーバと Nuxt サーバのバックエンドサーバの構成でした。全て Nginx のフロントエンドサーバ側にコンテンツを配置し、バックエンドサーバは廃止したいと思います。

IPv6 接続されたときは connected IPv6 と表示させる。

IPv6 で接続されたときは、ヘッダーメニューの右側に connected IPv6 と表示させていました。この情報は、最初にクライアントから送られてくるヘッダ情報の x-forwarded-for に設定されている IP アドレスをみて判断していました。このヘッダー情報が取れるのはサーバサイド側のみですので、今回はこの方式は使えなくなります。試しに generate するとやっぱりエラーとなりました。

/store/index.html
export const state = () => ({
  ipv6: false,
});
 
export const actions = {
  nuxtServerInit({ commit }, { req }) {
    if (req.headers['x-forwarded-for']) {
      const ips = String(req.headers['x-forwarded-for']).split(',');
      const ip = ips[0];
      const ipv6 = checkIPv6(ip);
      commit('IPV6', ipv6);
    }
    const unit = process.env.UNIT;
  },
};
export const mutations = {
  IPV6(state, ipv6) {
    state.ipv6 = ipv6;
  },
};
export const getters = {
  IPV6(state) {
    return state.ipv6;
  },
};
function checkIPv6(ip) {
  let resutl = false;
  const pattern =
    '((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*$';
  if (ip.match(pattern)) resutl = true;
  return resutl;
}

静的なサイトにするために困りました。結局どうやって判断するか、そういえば、IPv6 に対応した時、Nginx の設定に IPv6 アドレスからも受け付けるように listen に追加しました。今回発想を変え、シンプルに Niginx の listen で IPv6 用のコンテンツと IPv4 のコンテンツに分けることにしました。当初、画像のみ IPv6 かそうでないかに切り分けることを考えてましたが、IPv6 または IPv4 で接続したか Google アナリティクスで分析したいので、各ページに GID を設定するので、シンプルに IPv6 のサイトと IPv4 のサイトを用意することにしました。

当初の Nginx の設定を改造することにしました。

/etc/nginx/vhost.d/akiboi-blog.conf
## IPv6&IPv4
upstream akiboi-blog {
    server localhost:3000;
}
 
server {
    listen      443 ssl http2;
    listen	[::]:443 ssl http2;
    server_name www.akiboi.duckdns.org;
    location / {
        location / {
        proxy_pass http://akiboi-blog/;
        }
    }
}

=$Nuxt のバックエンドサーバを廃止と共に IPv6 と IPv4 用にサイトを分けました。

/etc/nginx/vhost.d/akiboi-blog.conf
## Unit1 IPv6
server {
    listen	[::]:443 ssl http2;
    listen	[::]:80;
    server_name www.akiboi.duckdns.org duffy.akiboi.duckdns.org duffy;
    location / {
        root /usr/share/nginx/akiboi-blog/unit1;
        index index.html;
    }
}
 
## Unit1 IPv4
server {
    listen      443 ssl http2;
    listen      80;
    server_name www.akiboi.duckdns.org duffy.akiboi.duckdns.org duffy;
    location / {
        root /usr/share/nginx/akiboi-blog/unit2;
        index index.html;
    }
}

記事をリロードすると 404 のエラーが出てしまう。

generate コマンドで静的ファイルを出力した後に nginx に配置してテストしてみると記事一覧や記事一覧のリンクからの記事は読めますが、記事表示した後にリロードしてみると 404 not found とエラーが出てしまいます。記事は json ファイルで db_xxx に格納されていて記事一覧からハッシュキーで読むようです。リロード後や直接リンクでは記事のファイルを検索する script がないので記事を取得できないと思われます。流石にリンクされていないページは作成されていなかったので手動で articles 以下にある記事を generate するようにしました。

nuxt.config.js
  :
  generate: {
    async routes() {
      const { $content } = require('@nuxt/content')
      const articles = await $content('articles').only(['path','updatedAt']).fetch()
      return articles.map(article => article.path == '/articles' ? '/' : article.path)
    }
  }

IPv6 と IPv4 用サイト作成の準備

今回、dotenv も標準で簡単に使えるようになってました。今回 IPv6 と IPv4 用に.evn ファイルを設定してそれに合わせたサイトを作るようにしました。

.env ファイル

env/unit1.env
#UNIT 1
UNIT=1
IPV6=True
GAID=G-xxxxxxx
COPYRIGHT="© 2020 "
TITLE="akibo.I Blog "
VERSION="v1.3.6"
env/unit2.env
#UNIT 2
UNIT=2
IPV6=False
GAID=G-yyyyyyyy
COPYRIGHT="© 2020 "
TITLE="akibo.I Blog "
VERSION="v1.3.6"

.env で設定した値は以下のように$config.変数で利用することができます。IPv6 が True のときは、IPv6 用の画像を出力するようにしています。

components/Menu.vue
<template>
  :
      <div class="connectIP">
        <div v-if="ipv6">
          <img src="/images/ipv6.png" />
        </div>
      </div>
  :
</template>
<script lang="ts">
  :
export default defineComponent({
  setup() {
  :
    const { $config } = useContext();
    const ipv6 = $config.IPV6;
  :
  }
  return { menus, active, navi, naviOpen, ipv6 };
  },
});
</script>

それぞれ IPv6 サイトと IPv4 サイト別に出力したいので nuxt.config.js ファイルをそれぞれ用意します。出力先コマンドでできないのかなぁ・・・・多分やり方あるんですよね。

nuxt.conif.unit1.js
  :
  generate: {
    dir: 'unit1',
    async routes() {
        const { $content } = require('@nuxt/content')
        const articles = await $content('articles').only(['path','updatedAt']).fetch()
        return articles.map(article => article.path == '/articles' ? '/' : article.path)
    }
  }
};
nuxt.conif.unit2.js
  :
  generate: {
    dir: 'unit2',
    async routes() {
        const { $content } = require('@nuxt/content')
        const articles = await $content('articles').only(['path','updatedAt']).fetch()
        return articles.map(article => article.path == '/articles' ? '/' : article.path)
    }
  }
};

package.json ファイルにサイト出力用コマンドを追加しときます。合わせて start コマンドもそれぞれ用意します。

package.json
{
  :
 "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "generate": "nuxt generate",
    "start": "nuxt start",
    "export": "nuxt export",
 
    "unit1": "nuxt generate -c nuxt.config.unit1.js --dotenv env/unit1.env",
    "unit2": "nuxt generate -c nuxt.config.unit2.js --dotenv env/unit2.env",
    "start1": "nuxt start -c nuxt.config.unit1.js",
    "start2": "nuxt start -c nuxt.config.unit2.js"
  :
 }

IPv6 と IPv4 用のサイトの作成とテスト

準備ができましたので、generate コマンドで IPv6 と IPv4 用のサイトを作成したいと思います。

% yarn unit1
yarn run v1.22.10
warning package.json: No license field
$ nuxt generate -c nuxt.config.unit1.js --dotenv env/unit1.env
	:
 Full static generation activated
 Generating output directory: unit1/
 Generating pages with full static mode
 Generated route "/"
	:
 Client-side fallback created: 200.html
 Static manifest generated
 Generating sitemaps
 Generated /sitemap.xml
  Done in 16.75s.
 
% yarn unit2
yarn run v1.22.10
warning package.json: No license field
$ nuxt generate -c nuxt.config.unit2.js --dotenv env/unit2.env
	:
	:
 Full static generation activated
 Generating output directory: unit1/
 Generating pages with full static mode
 Generated route "/"
	:
 Client-side fallback created: 200.html
 Static manifest generated
 Generating sitemaps
 Generated /sitemap.xml
  Done in 17.26s.

IPv6 用サイト unit1 と IPv4 用サイト unit2 が作成されました。ローカル環境で静的ファイルのチェックすることも Nuxt start で可能となりました。

% yarn start1
yarn run v1.22.10
warning package.json: No license field
$ nuxt start -c nuxt.config.unit1.js
 WARN  mode option is deprecated. You can safely remove it from nuxt.config
 % yarn start1
yarn run v1.22.10
warning package.json: No license field
$ nuxt start -c nuxt.config.unit1.js
 WARN  mode option is deprecated. You can safely remove it from nuxt.config
 
      Nuxt @ v2.14.12
 
 Environment: production
 Rendering:   server-side
 Target:static
            Listening: http://localhost:3000/
 
 Serving static application from unit1/
 

本当に楽になりました。通常ならば nuxt generate だけで静的コンテンツが作成できて、nuxt start でそのまま内容も確認できます。あとは、Nginx のサイトに配置して完了です。