NextJSを扱い慣れる目的もありブログサイトを制作中です。
components/layout.tsx へ microCMS から引っ張ってきたカテゴリー別のメニューを静的レンダリング(プレレンダリング)することが目的でした。
Nextのバージョンは 11系 です。
page/ の階層でないとgetStaticPropsが使えない
しかしLayoutコンポーネントは components/* 階層にあるため呼び出すことができませんでした。
そのためLayoutコンポーネントを呼び出している各ページで getStaticProps してLayoutに渡さないといけなくなりました。
ちなみに GatsbyJS を1年ほど使っていたのですが GatsbyJS の場合は useStaticQuery と呼ばれる GraphQL によるオブジェクトデータをどこからでも引っ張ってこれる機能がありました。
NextJS もてっきりどこからでもデータを呼び出せるのかな、と思っていたのですがまだ対応していないようです。
繰り返し同じコードを書くのはイケテナイので initialize pages (_app.tsx) にLayoutコンポーネントを設置してグローバル化に
しようと思い getStaticProps を _app.tsx で使ったのですが呼び出すことができませんでした。
そのため、仕方がないなということで公式では非推奨となっていますが getInitialProps を使ってみると無事にデータをPropsすることができました。
export default function App({ Component, pageProps, test }) {
console.log(test); // helloと表示される
return (
<>
<Layout>
<Component {...pageProps}/>
</Layout>
</>
);
}
App.getInitialProps = async () => {
return { test: 'hello' }
}
getInitialPropsを使うとSSGではなくなる罠
NextJSには Automatic Static Optimization と呼ばれる静的への最適化処理の機能があります。
引用
Next.js automatically determines that a page is static (can be prerendered) if it has no blocking data requirements. This determination is made by the absence of getServerSideProps and getInitialProps in the page.
Next.jsでは、ブロックデータの要求がない場合、ページが静的(プリレンダリング可能)であると自動的に判断します。この判断は、ページ内にgetServerSidePropsやgetInitialPropsがないことで行われます。
とあるように逆に言えば getServerSideProps や getInitialProps があるとSSRになってしまうようです。
といった形で特殊な機能があるわけでもないブログサイトをわざわざこれのためだけに SSR化 させるのも癪に障るため諦めることにしました。
いっそ長期Cacheと一緒にfetch や axios を一緒に使うパターンなども考えられますがその手間を考えれば各ページで getStaticProps してLayoutへデータを渡した方がましかなと。
各コンポーネントやグローバルでgetStaticPropsが使えるようになって欲しい
とNextJSをOSSするつよつよマンの方々に淡い期待を抱いております。
追記:ビルド前に取得したmicroCMSのデータでJSONファイルを作成することでグローバル上で扱えるデータにしてみた
自己満足のおまけです。この記事を書いた後にもやはり各ページ毎で同じ処理で getStaticProps を使うのはちょっとなあと思いました。
特に [id].tsxページ で1ページ毎に getStaticProps でGETリクエストを投げてデータを取得してプレレンダリングするやり方は数百ページ程度であれば気にする必要はないかもしれませんが数千、数万ページあるような規模になるとさすがに無駄な気がしました。
(もっとも数十記事しか書かない予定のブログなのでオーバーエンジニアリングな発想です)
そこで NextJS のビルド前に microCMSのデータ を取得して JSONファイル として生成するという原始的な方法?と useContext で取得したデータをグローバル化させました。
dataディレクトリにJSON生成用のファイルを作成
const fs = require('fs');
const axios = require('axios').default;
require('dotenv').config();
const getMicroCMSdata = async() => {
const url = `https://hoge.microcms.io/api/v1/hoge`;
const apiKeyHeaderOption = { headers : { "X-API-KEY": process.env.MICROCMS_API_KEY }};
const getUrlOption = (number, url) => {
const UrlandOption = url + `?limit=${number}`;
return String(UrlandOption);
}
// microCMSのコンテンツを引っ張ろうとするとデフォルトでlimit=10のオプションがついており全てのコンテンツを引っ張ってこれない。totalCountでコンテンツ総数をチェック
const getTotalCountUrl = getUrlOption(0, url),
totalCountUrlData = await axios.get(getTotalCountUrl, apiKeyHeaderOption).then(r => r.data),
{ totalCount } = await totalCountUrlData;
const getContentUrl = getUrlOption(totalCount, url),
contentUrlData = await axios.get(getContentUrl, apiKeyHeaderOption).then(r => r.data),
{ contents } = await contentUrlData
return contents;
}
const createJSONdata = async() => {
const contents = await getMicroCMSdata();
const JSONdata = JSON.stringify(contents, null, 2);
fs.writeFileSync('data/microcms.json', JSONdata);
}
createJSONdata();
省略
"scripts": {
"dev": "node ./data/create/createCMSdata.js && next dev",
"build": "node ./data/create/createCMSdata.js && next build",
"start": "next start"
},
省略
これで build(develop)のタイミングで dataディレクトリ層に microcms.json を生成した段階で処理できるようになりました。
次に _app.tsx で createContext と useContext を使いグローバル化させます。このことでJSONデータのimportを一回だけに済ませました。
import microCMSdata from 'data/microcms.json'
import { MicroCMS } from 'components/util/context'
export default function App({ Component, pageProps }) {
return (
<MicroCMS.Provider value={{microCMSdata}}>
<Component {...pageProps}/>
</MicroCMS.Provider>
);
}
import { MicroCMSType } from 'model/type'
import { createContext } from "react"
export const MicroCMS = createContext<MicroCMSType>({
microCMSdata: null,
});
あとは読み込みたいコンポーネントで useContext(MicroCMS) を呼び出せば使えるようになったのでLayoutコンポーネント(の孫)でバケツリレーさせずに呼び出せました。
(まあ普通に使いたいところで生成した JSONファイル を import するだけでいいんですがこの記事で意図していたことを実現させることを優先)
参考にした記事
stackoverflowの人の質問: https://stackoverflow.com/questions/67110186/how-to-add-global-data-to-app-tsx-in-next-js-without-causing-the-whole-app-to-b