nuxt.config.jsで動的なデータを扱う

## 誰に向けての記事か
- Nuxt.jsでSSGもしくはSPAで成果物を生成する際、動的なパスを生成したい
- nuxt.config.jsでaxiosを利用したい
- nuxt.config.jsでプロジェクト内のjsonファイルのデータを取得したい
- nuxt.config.jsのsitemapプロパティもしくはgenerateプロパティのroutesメソッドで動的な値を扱いたい
- Nuxt.jsで作成したサイトのsitemap.xmlにアクセスしても、動的に生成したページが一覧に存在しない
- Nuxt.jsで作成した動的ページをGoogleサーチコンソールに登録したい

## 経緯
Nuxt.jsを使って動的ルーティングを利用してページを作成しました。generateした成果物をサーバーに設置して動的ページにアクセスすると、無事にページが表示されました！全てうまくいった！
これで動的に生成されたページがsitemap.xmlに出力されて、Googleの検索結果にも表示されるようになるぞ！
#### しかし異変に気付く
さて、sitemap.xmlにアクセスして大量に動的に生成されたページでも眺めますか…と。
#### sitemap.xmlに動的に生成したページのパスが記載されていない
URLを入力すればアクセスは出来てページは表示出来るのにサイトマップに記載が無いのはこれ如何に。

## nuxt.config.jsのsitemapプロパティについて
### 静的ページと動的ページ
`pages/sample.vue`や`pages/example.vue`等の静的ファイルはサイトマップ上にパスが記載されています。
しかし`pages/item/_id.vue`や`pages/user/_index.vue`等の動的ファイルはサイトマップ上にパスが記載されていません。

### routesメソッド
調べたところsitemapプロパティのroutesメソッドに手を加える必要がありそうです。

```nuxt.config.js
sitemap: {
  path: "/sitemap.xml",
  hostname: "https://example.com",
  routes() {
    return [];
  },
}
```
とりあえず最低限の型を用意しました。このroutesメソッドのreturnにサイトマップ上に載せたいパスを配列で渡せば良いそうです。
試しに存在しないパスを渡してみました。

```nuxt.config.js
sitemap: {
  path: "/sitemap.xml",
  hostname: "https://example.com",
  routes() {
    return ['/dummy/1', '/dummy/2'];
  },
}
```

そしてsitemap.xmlにアクセスすると
```sitemap.xml
~~~
<url>
    <loc>https://example.com/dummy/1</loc>
</url>
<url>
    <loc>https://example.com/dummy/2</loc>
</url>
```

思い通り！思い通り！！思い通り！！！ 光が見えてきましたね。routesメソッドに動的ページのパスを渡せば問題は解決出来そうです。

## APIを利用して取得した情報をnuxt.config.jsで扱う
axiosを利用してapiを叩くことにします。axiosを利用するにはファイル上部でaxiosをimportします。
```js
import axios from 'axios';
```
axiosを利用してapiを叩いて取得した情報を利用して動的パスを生成、そしてroutesメソッドにパス一覧を渡したものが以下になります。

```nuxt.config.js
// nuxt.config.js上でaxiosをimportする
import axios from 'axios';
~~~
sitemap: {
  path: "/sitemap.xml",
  hostname: "https://example.com",
  routes() {
    // アイテムid一覧を取得するapiのエンドポイント
    const apiUrl = "https://api.example.com/item/id_all";
    axios.get(apiUrl).then(res => {
        // res = {data: ["api1", "api2", "api3", "api4", "api5"]}
        const itemPath = res.data.map(itemId => {
            return '/item/' + itemId;
        })
        // 動的に生成したパスの配列を最後に返す
        return itemPath;
    })
  },
}

```
以上の状態でgenerateし、sitemap.xmlにアクセスすると以下のように表示されました。
```sitemap.xml
~~~
<url>
    <loc>https://example.com/item/api1</loc>
</url>
<url>
    <loc>https://example.com/item/api2</loc>
</url>
<url>
    <loc>https://example.com/item/api3</loc>
</url>
<url>
    <loc>https://example.com/item/api4</loc>
</url>
<url>
    <loc>https://example.com/item/api5</loc>
</url>
```
apiから取得した情報を利用した動的パスがサイトマップ一覧に登録されていますね。

## jsonファイル内の情報をnuxt.config.jsで扱う
今度はプロジェクト内のjsonファイルからデータを取得してroutesメソッドで扱う方法について説明します。
プロジェクト内部のjsonファイルからデータを取得するために、fsモジュールを利用します。
```js
import fs from 'fs';
```

jsonファイルから取得した情報を利用してapiを叩いて取得した情報を利用して動的パスを生成、そしてroutesメソッドにパス一覧を渡したものが以下になります。
```nuxt.config.js
// nuxt.config.js上部でfsモジュールをimportする
import fs from 'fs';
~~~
sitemap: {
  path: "/sitemap.xml",
  hostname: "https://example.com",
  routes() {
    // userId一覧が記載されいているjsonファイル
    const jsonData = JSON.parse(fs.readFileSync('./assets/json/example.json'));
    // jsonData = { userIds: ["json1", "json2", "json3", "json4", "json5"] };
    const userPath = jsonData.userIds.map(userId => {
        return '/user/' + userId;
    })
    return userPath;
  },
}
```
以上の状態でgenerateし、sitemap.xmlにアクセスすると以下のように表示されました。
```sitemap.xml
~~~
<url>
    <loc>https://example.com/user/json1</loc>
</url>
<url>
    <loc>https://example.com/user/json2</loc>
</url>
<url>
    <loc>https://example.com/user/json3</loc>
</url>
<url>
    <loc>https://example.com/user/json4</loc>
</url>
<url>
    <loc>https://example.com/user/json5</loc>
</url>
```
これでjsonファイル内のデータをnuxt.config.js内でも扱えることが確認出来ました。

## APIから取得した情報とjsonファイルから取得した情報どちらも扱う
今まではAPIから取得した情報を扱う場合と、jsonファイルから取得した情報を扱う場合単体のやり方でやってきました。最後にAPIから取得した情報とjsonファイルから取得した情報どちらも扱う場合を試してみます。

といっても特別なやり方はなく、routesメソッドの返り値に最後に全てのパスを結合した配列を返すだけで良いです。
以下サンプルになります。

```nuxt.config.js
import fs from 'fs';
import axios from 'axios';
~~~
sitemap: {
  path: "/sitemap.xml",
  hostname: "https://example.com",
  routes() {
    const getItemPath = new Promise(resolve => {
      // アイテムid一覧を取得するapiのエンドポイント
        const apiUrl = "https://api.example.com/item/id_all";
        axios.get(apiUrl).then(res => {
            // res = {data: ["api1", "api2", "api3", "api4", "api5"]}
            const itemPath = res.data.map(itemId => {
                return '/item/' + itemId;
            })
            resolve(itemPath);
        })  
    })

    const getUserPath = new Promise(resolve => {
        // userId一覧が記載されいているjsonファイルからjsonデータを取得
        const jsonData = JSON.parse(fs.readFileSync('./assets/json/example.json'));
        // jsonData = { userIds: ["json1", "json2", "json3", "json4", "json5"] };
        const userPath = jsonData.userIds.map(userId => {
            return '/user/' + userId;
        })
        resolve(userPath);
    })

    return Promise.all(getItemPath, getUserPath).then(arrays => {
        // 取得したパスの配列を最後に結合
        arrays.forEach(value => {
          routes = routes.concat(value);
        })
        return routes;
    })
  },
}
```
Promise.allを利用して、すべてのパスの配列がresolveで返されるまで待機して、最後にすべての配列を結合する方法を取りました。この方法なら他に動的なパスを生成する必要があっても、同様にPromise.allに渡せば最後にまとめて配列が結合されるようになります。

以上の状態でgenerateし、sitemap.xmlにアクセスすると以下のように表示されました。
```sitemap.xml
~~~
<url>
    <loc>https://example.com/item/api1</loc>
</url>
<url>
    <loc>https://example.com/item/api2</loc>
</url>
<url>
    <loc>https://example.com/item/api3</loc>
</url>
<url>
    <loc>https://example.com/item/api4</loc>
</url>
<url>
    <loc>https://example.com/item/api5</loc>
</url>
<url>
    <loc>https://example.com/user/json1</loc>
</url>
<url>
    <loc>https://example.com/user/json2</loc>
</url>
<url>
    <loc>https://example.com/user/json3</loc>
</url>
<url>
    <loc>https://example.com/user/json4</loc>
</url>
<url>
    <loc>https://example.com/user/json5</loc>
</url>
```
これでapiから取得した情報とjsonファイル内のデータを使用して動的にnuxt.config.js内でも扱えることが確認出来ました。

## nuxt.config.jsのgenerateプロパティで動的なデータを扱う
nuxt.config.js上でAPIからデータを取得したりjsonファイルからデータを取得して動的なデータを扱う方法は、sitemapプロパティだけでなくgenerateプロパティでも同様に使用できます。

以下generateで今までの方法を使用した場合のサンプルです。

```nuxt.config.js
import fs from 'fs';
import axios from 'axios';
~~~
generate: {
  routes() {
    const getItemPath = new Promise(resolve => {
      // アイテムid一覧を取得するapiのエンドポイント
        const apiUrl = "https://api.example.com/item/id_all";
        axios.get(apiUrl).then(res => {
            // res = {data: ["api1", "api2", "api3", "api4", "api5"]}
            const itemPath = res.data.map(itemId => {
                return '/item/' + itemId;
            })
            resolve(itemPath);
        })  
    })

    const getUserPath = new Promise(resolve => {
        // userId一覧が記載されいているjsonファイルからjsonデータを取得
        const jsonData = JSON.parse(fs.readFileSync('./assets/json/example.json'));
        // jsonData = { userIds: ["json1", "json2", "json3", "json4", "json5"] };
        const userPath = jsonData.userIds.map(userId => {
            return '/user/' + userId;
        })
        resolve(userPath);
    })

    return Promise.all(getItemPath, getUserPath).then(arrays => {
        // 取得したパスの配列を最後に結合
        arrays.forEach(value => {
          routes = routes.concat(value);
        })
        return routes;
    })
  },
}
```
やっていることはsitemapプロパティの時と変わらないですね。

---
以上がnuxt.config.jsのsitemapプロパティとgenerateプロパティのroutesメソッドでAPIとjsonファイルから取得した情報を扱う方法になります。開発の参考になったら幸いです。

また、ご指摘やもっと良い方法があれば是非コメントください！

[Twitter](https://twitter.com/manzoku_bukuro)もやってます。