どーも、ぐるたか@guru_takaです。
ヘッドレスCMSなどからコンテンツをAPIで取得する場合、静的ジェネレーターでプリレンダリングしたとしても、クライアント側でヘッドレスCMSにアクセスします。
プリレンダリングした私のポートフォリオでネットワークを確認してみると、以下GIFのようにページ遷移すると、ヘッドレスCMSにAPIを叩きにいっているんですよね。
Reactの静的ジェネレーター「Gatsby.js」であれば、良しなにやってくれるようですが、Nuxtの場合、2020/3/16時点では、自分で静的API化する必要があります。
ここでは、Nuxtで静的API化する方法をハマりどころも含め、紹介していきます。
目次
全体の処理の流れ
STEP.1:ビルド前にAPIで取得したデータをJSON化してアセットに出力
まずはコードから紹介!ここでは、ヘッドレスCMS「contentful」をサンプルとして使っています。
nuxt.config.js
と〜/modules/api.js
を編集、作成します。
module.exports = function apiModule(moduleOptions) {
this.nuxt.hook('build:before', async ({ app }) => {
// contentful 初期化
const contentful = require('contentful')
const client = contentful.createClient({
space: process.env.CTF_SPACE_ID,
accessToken: process.env.CTF_PREVIEW_ACCESS_TOKEN,
})
// contentfulからデータ取得
let datas = await client.getEntries({
content_type: process.env.CTF_TUTORIAL_POST_TYPE_ID,
order: 'fields.order'
})
// JSON生成
this.options.build.plugins.push({
apply(compiler) {
compiler.plugin('emit', (compilation, cb) => {
compilation.assets[`api/datas.json`] = {
source: () =>
JSON.stringify({ apiDatas: datas.items.map((item) => item.fields) }),
size: () => {}
}
cb()
})
}
})
})
}
modules: ['~/modules/api'],
すると、ビルド前に~~~~~/_nuxt/api/datas.json
というJSONデータが自動で生成されます。
$npm run dev
で開発環境を立ち上げてみると、http://localhost:3000/_nuxt/api/datas.json
にJSONデータが表示されているはずです!
STEP.2:プリフェッチに設定
続いて、JSONデータをプリフェッチに設定します。プリフェッチとは、リソース先読みのための仕組み「Resource Hints」の1つの手段で、静的コンテンツを事前に取得する技術になります。
超ざっくりいえば、プレフェッチを使うと、JSONデータを描画前に先読みするので、爆速になるわけです。
詳細は以下2つの記事が参考になるので、ぜひチェックしてみて下さい。
参考 ロードを高速化するprefetch - Qiita 参考 link要素によるResource Hintsを使用してリソースの先読みを行う先程のコーディングに追記すると、こんな感じ!
module.exports = function apiModule(moduleOptions) {
this.nuxt.hook('build:before', async ({ app }) => {
// contentful 初期化
const contentful = require('contentful')
const client = contentful.createClient({
space: process.env.CTF_SPACE_ID,
accessToken: process.env.CTF_PREVIEW_ACCESS_TOKEN,
})
// contentfulからデータ取得
let datas = await client.getEntries({
content_type: process.env.SAMPLE,
order: 'fields.order'
})
// JSON生成
this.options.build.plugins.push({
apply(compiler) {
compiler.plugin('emit', (compilation, cb) => {
compilation.assets[`api/datas.json`] = {
source: () =>
JSON.stringify({ apiDatas: datas.items.map((item) => item.fields) }),
size: () => {}
}
cb()
})
}
})
///////////
//追記
// link rel="prefetch"にJSONを追加
///////////
this.options.head.link = [
...this.options.head.link,
{
rel: 'prefetch',
href: `${this.options.build.publicPath}api/datas.json`,
as: 'fetch'
}
]
})
}
STEP3:axiosでJSONデータにアクセスして取得
続いて、生成されたJSONデータを開発環境で取得してみます。
まずはaxiosをインストールします。
$ npm install @nuxtjs/axios
module.exports = {
modules: [
'@nuxtjs/axios',
'~/modules/api'
],
}
後は以下のように、asyncDataでJSONデータを取得するだけです!
~~~~~~~~
async asyncData({ app, env, route }) {
const { data } = await app.$axios.get(`/_nuxt/api/datas.json`)
return {
sample:data
}
}
~~~~~~~~
STEP4:プリレンダリング中にJSONデータから情報を取得できるようにする
最後に、プリレンダリングして、JSONデータを取得して動的にページ生成しなくていはいけません。
まずはコードから紹介します。
module.exports = function apiModule(moduleOptions) {
this.nuxt.hook('build:before', async ({ app }) => {
// contentful 初期化
const contentful = require('contentful')
const client = contentful.createClient({
space: process.env.CTF_SPACE_ID,
accessToken: process.env.CTF_PREVIEW_ACCESS_TOKEN,
})
// contentfulからデータ取得
let datas = await client.getEntries({
content_type: process.env.SAMPLE,
order: 'fields.order'
})
// JSON生成
this.options.build.plugins.push({
apply(compiler) {
compiler.plugin('emit', (compilation, cb) => {
compilation.assets[`api/datas.json`] = {
source: () =>
JSON.stringify({ apiDatas: datas.items.map((item) => item.fields) }),
size: () => {}
}
cb()
})
}
})
// link rel="prefetch"にJSONを追加
this.options.head.link = [
...this.options.head.link,
{
rel: 'prefetch',
href: `${this.options.build.publicPath}api/datas.json`,
as: 'fetch'
}
]
///////////
//追記
///////////
// dev時はここで終了
if (this.options.dev) return
// generate時にexpress立てて、json取得できるようにする
this.nuxt.hook('build:done', (generator) => {
console.log('**[generate]** opening server connection')
const express = require('express')
const app = express()
//dist内でサーバーたてる感じ
app.use(express.static(this.options.generate.dir))
const server = app.listen(process.env.PORT || 3000)
this.nuxt.hook('generate:done', () => {
console.log('**[generate]** closing server connection')
server.close()
})
})
})
}
STEP3でaxiosを使ったように、JSONデータを取得するには、”http://localhost:3000/_nuxt/api/datas.json”にアクセスしなくていはいけません。
しかし今のままでは、プリレンダリング中にローカルサーバーを立ち上げていないので、axiosでアクセスできず、エラーが出ます。
なので、expressを使ってローカルサーバーを立ち上げ、プリレンダリング中もデータ取得できるようにしています!
ここまでのコードは以下の記事を参考にしています!
参考
Firebase、Flamelink、Nuxt、Netlify、PWAを使ってJAMstackなブログを作る - Qiita
STEP5:axiosのbase urlをプリレンダリング中とデプロイ先で分ける
これで一安心かと思いきや、最後に1つやらなくはいけません。
それは、axisoのbase urlを本番環境用のURLでも使えるようにすることです。
今のままでは、どこかにデプロイした後も、クライアント側でhttp://localhost:3000/_nuxt/api/datas.json
にアクセスしてしまい、エラーが生じます。
なので
- プリレンダリング中:localhost:3000
- 本番環境:https://~~~~~~~~.com
に分けれるようにします。
設定方法は以下のとおりです。
// https://qiita.com/penton310/items/4312ddbe015476fd67ff
// nuxt.config.jsでprocess.static && process.serverが使えないので、
export default function({ $axios }) {
$axios.setBaseURL(
(process.static && process.server) || process.env.NODE_ENV !== 'production'
? 'http://localhost:3000'
: `https://~~~~~~~~~~~.com`
)
}
plugins: [
'~/plugins/axios.js',
],
npm run generate
とnpm run dev
のときは、localhostにアクセス、本番環境では設定したURLがaxisoのbase urlになるようなプラグインを作ればOKです。
これで静的API化が完了!!!
参考 プラグイン - NuxtJS
とても、参考になりました。ありがとうございます。blogなどを構築する際に、post以外にも、カテゴリーや固定ページなどのAPIを取ってくる必要があるかと思うのですが、2つ以上のAPIをビルド時に静的化するには、どう書き換えたら良いのでしょうか?教えた頂けましたら助かります。
コメント、ありがとうございます。
以下のように複数のデータを取得→JSON生成→
rel="prefetch"
に追加すれば動くと思います!ありがとうございます!できました〜涙
良かったです!!