はじめに

Nuxt 4でStatic Site Generation (SSG) を行う際、ローカルのJSONファイルからデータを読み込んで静的ページを生成したいケースがあります。しかし、Next.jsのgetStaticPropsのようにシンプルにはいかず、いくつかのハマりポイントがあります。

本記事では、試行錯誤の末に見つけた正しいアプローチを紹介します。

問題:なぜ単純なfsの読み込みでは動かないのか

最初に試したアプローチ(失敗)

c}o;ni}crsfoetccccrnt(ooooesufinnnnttremssssuntpttttrrconeahrfpfdswLtsauaJpao.tltSoicm=hlaOntaePNslta=a=.erDawtpea.aahfa=stsiwsrpaeta=.saorirewn=vitpe(asemaadiearpitdat.s)omhFtjyrp.iafsn{torl)eoc(ree;tn'tsSc((f(oyh)fs'ln(;i'pvc`l)ae(/e;t(fdPhpuaa'rltt)olah;cP/:ea$st{sshft.,ircliw'enduPg(ta))ft,-h=8}>''`p)){u;;blic/data',filePath);

このアプローチには以下の問題があります:

  1. process.cwd()がビルド環境で異なる: ローカル開発とVercel等のビルド環境では作業ディレクトリが異なる
  2. Nitroのプリレンダリング時にファイルが見つからない : SSG時、Nitroは独自のコンテキストで動作する
  3. useAsyncDataなしでは、クライアントサイドでも実行される : SSGの意味がなくなる

解決策:Nitro Storage API + Server APIルート + useAsyncData

アーキテクチャ

[PSp[_SaeupSgrbaGevlyeilCrcoouu/HamsAsdTd]pePeaM.oAIStLjnsta]seyRo/onnor*ntcua.JDtgjSaeesOtoNa(n/Naiptir/oloSctaolr-adgaetaA/P[I.)..path])

Step 1: nuxt.config.tsでserverAssetsを設定

e}x)pn}r};noi,o,urtp}s}uxtrr,e]ttoecfrbd,e.d:rraNvaiRceeaiiesru'of{nwltre:l:nadlOrANefueLnosas{ilriEsm:gt:nrSee/p.krtt:p{rtd{soosuese:rr:'brf:adleitg[ainnrfe{tcdeuaaeNelA'dru,sP,a:xeItt,atCp'rouunbeflii}gc,(/{data

Step 2: Server APIルートを作成(Nitro Storage + fsフォールバック)

iie}mmx)pppci}ci}cci}ci}t;soooofofoofofherrrntntnncrncrrrttts(hs(hNnssS(oes(oeovt!rtfriutttanttentwe{{dpoiotxowsuxsurepawflwrtssratrfitrc/rrfatieo.ttainssnraeeathclPccoogtdPtdepasuhPre:arSorreadasaJaidolPaePtetnaasta:tStSt/FltaraahaofggAtathyaOelivratt.trieePoanNEoledamehieagKIr=;=c=.rceem)EnEg.=ea(praS}fr=cretygarfraolyi={rlrsueweserr-nfnoAuoAs=.asPas(dcregrrdrPsehioade{a,oEe(re(IeSfatfltF(tmvt{as{rtissvhidsaeeRy(使vo/lIse)lat/x'nos.'serett()eta[iptuti.tra:PeopSat.saHtas.aAgamrr{y)u.ttaetA'tset(aon;s.shnrur)us(hsgccCpS'dPsr)se'.tee(oay;laCaCtaro.sfdtneroy{ossergssehcrad(dspae.P:](mepeelgtca.}a(:a:taeIwt4tsetscKtdh0sfyv4h4:eee(,4rne0P0'd(ym),ocn0a0da/)(,'mt,r,at\)sum(,ata/t'te'emmma'/{opfsfv'e)e')gru-ssepss;,ab8a'nas?sgl'g;ttaa'ei)e)hgpg:Kc;:'eae'e/=):t:)yd`>;h;)aF'P';ti{PaIalarn'etav,hman.lfoijiitsodliefrnpPoe(aauq'ttnu/hhdi''):r);e}$d:){';fpi}al)te;hPPaatrha}m`;});

Nitro Storage APIを使うメリット:

  • Vercel等のビルド環境で安定動作
  • OSごとのパスセパレータ問題を回避
  • Nitroが環境に応じてファイルアクセスを最適化

fsフォールバックが必要な理由:

開発モード(npm run dev)ではserverAssetsが完全に機能しないケースがあるため、fs直接読み込みをフォールバックとして追加しています。ビルド時にはStorage APIが優先されます。

Step 3: Composableでラップ

e}x;pc}rcoo;eornt}}tmtsruptyi}}ccrrocfaoensof{recirtntane(elofecsu{bstitsnthorltcmSues(ulnfehpSrt!r(eesuLoGn{rne.nt/sor/rereucuectSaesarrlhsLa.SwspworlLeolmRapoaro;oLcDeioni)rcoaat:tnst(aclta:Sse{`laDa.S$3e.rEDlasefpoeraDt=ereuN=ksrtaarvtbe)poatavecltaora=serhilwrn}.yr(ciaesl;tn)A`fiteoscP/ytu.a{Iarjd=(pfnsi>f使ieoni/tnng{llScu(:eoehl)Pcr(l;$aav`;{tle/fh-rdi:dalaAtestPaPtaI/ar/$ti${hn{f}gfi`)il,le=ePe>Paratr{thoh}r}`)`););;

クライアントサイドフォールバックが重要な理由:

SSGで生成した静的サイトをS3やNetlify等にデプロイする場合、Server APIルートは存在しません。そのため、クライアントサイドナビゲーション時には/data/から直接JSONを取得する必要があります。

Step 4: ページコンポーネントでuseAsyncDataを使用

<cc}c<soo)o/t/cnncr;nse<trssoecscmd/eittntotrpi{dmpusumilv{ipt{s{trpppavlenuottp>asfAdrts>e-oteesaeret>fsettytsedsot>ucnausr.phc:lu==tLDtl"iloaptcptacta=;oolnaagmsegleapt=DDwu}"aaati}tttiensaatd"(p>}}f(oe)s==ttc=suah>"swLeaop:LicakotagecleyauDD=lsaa"DettpaAaaots(.say'vt(nja.)csli;Doudane"ta?>ap.(id'/amntyoa-dpea/gpeo[-s]dt)as;t.aj's,ona's)y;nc=>{

重要なポイント

1. useAsyncDataは必須

useAsyncDataなしでawaitすると、クライアントサイドでも実行されてしまいます。

ccoonnssttd{atdaat=aa}wa=itawfaeittchuLsoecAaslyDnactDaa(t'ap(o'sktesy.'j,son')=;>fetchLocalData('posts.json'));

2. キーはユニークに

useAsyncDataの第一引数のキーは、ページ/コンポーネント間でユニークにする必要があります。

c}o)nr;settu{rndaatwaai}t=fe使atwcahiLtocuasleDAastyan(c`Dpaotsat(s`/p$o{srto-u$t{er.opuatrea.mpsa.riadm}s..jisdo}n``,);async=>{

3. computedで安全にアクセス

useAsyncDataの戻り値はRefなので、テンプレートやロジックで使う際はcomputedでラップすると安全です。

cccooonnnssstttc{otimitdpteaulmttesae:d==pccaoogmmeppDuuattteeadd((}(())===a>>wappiaatggeeuDDsaaettAaas..yvvnaacllDuuaeet??a..(ti.it.te.lm)es;[]));;

4. 変数スコープに注意

useAsyncDataのコールバック内で定義した変数は、コールバック外では使用できません。

c}ccc}co)ooo)oncr;nnnr;nsoesssestnttttttsuu{trit{rintyntdtepdeayameaamtpwsstwsaeaaasi==i=}t}t=[[[=f''=f'[exaexa't''at'wac,,wc,a'hahi,D''iD'taybtay't''t'uba,]ua,s'(;s(e])e)A;;A;s.s.ytytnynycpcpDeDeasast]t]a;a;((''kkeeyyO''K,,:aastsyyynpncecsis==>n>o{t{defined

結果

この方法で生成された静的サイトでは:

  1. HTMLにデータが埋め込まれる : 初回表示が高速
  2. _payload.jsonにデータが保存される : クライアントサイドナビゲーション時に使用
  3. 元のJSONファイルへのリクエストは発生しない : ネットワーク負荷が軽減
[PSp[_SaeupSgrbaGevlyeilCrcoouu/HamsAsdTd]pePeaM.oAIStLjnsta]seyRo/onnor*ntcua.JDtgjSaeesOtoNa(n/Naiptir/oloSctaolr-adgaetaA/P[I.)..path])

0

別のアプローチ

小さなJSONファイルの場合: 直接import

ファイルサイズが小さく(50KB以下)、動的なパスが不要な場合は、直接importが最もシンプルです。

[PSp[_SaeupSgrbaGevlyeilCrcoouu/HamsAsdTd]pePeaM.oAIStLjnsta]seyRo/onnor*ntcua.JDtgjSaeesOtoNa(n/Naiptir/oloSctaolr-adgaetaA/P[I.)..path])

1

コンテンツ管理が主目的の場合: Nuxt Content

マークダウンやJSONコンテンツを扱うサイトの場合は、Nuxt Contentモジュールが適しています。

[PSp[_SaeupSgrbaGevlyeilCrcoouu/HamsAsdTd]pePeaM.oAIStLjnsta]seyRo/onnor*ntcua.JDtgjSaeesOtoNa(n/Naiptir/oloSctaolr-adgaetaA/P[I.)..path])

2

まとめ

Nuxt 4でSSGとローカルJSONを組み合わせるには:

  1. Nitro Storage API を使ってServer APIルートでJSONを読み込む
  2. useAsyncData で必ずラップする
  3. computed で安全にデータにアクセスする
  4. クライアントサイドフォールバック を忘れずに実装する

この方法により、ビルド時にすべてのデータがプリフェッチされ、クライアントサイドでの追加のAPI呼び出しを防ぐことができます。

参考リンク