Nuxt SSG(Full Static) で外部 css/js を一度だけ Lazy Load する
Published: 2022/6/25
問題
例えば Katex の css であったり、 gtag.js などのように、一度だけ <head>
に記述して外部からロードする前提となっているようなライブラリないしリソースは、割と存在する。
そのようなリソースを読み込みたいとなったとき、単純な実装方法としては、 nuxt.config.ts
の head
にその読み込みを記述することではあるが、これをやってしまうとすべてのページにおいてこのスクリプトがロードされることになり、これは SEO ないし UX 的な観点では好ましくない。
// nuxt.config.ts
export default {
head: {
script: [
{ src: 'https://examle.com/foo.js', async: true }
]
}
}
そのリソースを実際に利用するコンポーネントの head
にこれを移動するという手もあるが、その場合は、記述された <script>
は vue の reactivity による管理の下に入るため、コンポーネントが unmount されたタイミングで head
から再度消えてしまい、次にコンポーネントが mount されるタイミングでまた head
に追加される、という処理になる。
What happens if you use a <script> tag with the same "src" attribute multiple times within a single HTML document?
Although I am almost certain the answer to this question will be browser specific, do any of the browsers define behavior for when multiple &lt;script&gt; tags are used and have the same src attrib...
stackoverflow.com

上記のページにある通り、 <script>
は document に attach されるたびにその中身が評価されるので、単純にその評価に使う計算資源が無駄であるということと、ロード対象がリソースが二度実行されることを想定した作りになっていないかもしれないということから、この実装はあまり好ましいとは言えない。
また、ページの表示速度もろもろを高速化することを考えると、 initial render のタイミング、つまりサーバーから html として送られてきたタイミングで既に、 <head>
にその読み込み記述があることが望ましい。
まとめると、外部 js や css は、以下の要件を満たすように <script>
や <link>
として <head>
の下に記述したいことになる。
- それを必要としないページでは追加されない
- ただし、一度ロードしたならば、それを再度ロードする処理は無駄なので、それ以降はロードされた状態になっている(
<head>
に追加された<script>
ないし<link>
は、それ以降はそのままになる) - さらに、もしそのページにて必要なリソースであったならば、 SSR 時点の html に
<head>
の中で読み込む対象として記述される。
これを Nuxt.js の、特の Full Static (SSG) の場合ではどのように実現するべきかについて、調べた際の記録をまとめる。
回答: store でフラグ管理し、 created でそのフラグを true にする
まず、 Nuxt は SSR つまりサーバー側の記述においては、以下の制約がある。
beforeMount
とmounted
のフックは実行されない。- reactivity は動作しない
また、 SSG におけるクライアント側でのページ遷移では、 nuxt generate
時の asyncData
の戻り値が payload.js
としてダウンロードされ、それが直でコンポーネントに代入されることになる。
これらの制約を考えると、以下の方針に行きつく。
- lazy load する外部 css ないし js は、
layouts/default.vue
など、すべてのページで利用されるコンポーネントのhead
の値として、記述する。 - ただし、読み込む必要がない場合は読み込みを行いたくないため、それを行うかどうかを store の中でフラグとして管理し、その値は一度
true
になったならば、それ以降はそのままにする。
それを記述して、例えば以下。
<!-- layouts/default.vue -->
<template>
<Nuxt />
</template>
<script lang="ts">
import Vue from "vue";
import { useStore } from "~/store/some-resource"
export default Vue.extend({
head() {
const store = useStore(this.$pinia)
return store.load ? {
script: [{ src: 'htts://example.com/foo.js, async: true }]
} : {}
}
})
</script>
そして、どこで loaded を true にする処理を記述するかというと、これにはそのリソースを利用するコンポーネントの、 created
が利用できる。
// 例
import Vue from "vue";
import { useStore } from "~/store/some-resource.ts"
export default Vue.extend({
created() {
const store = useStore(this.$pinia)
store.load = true
}
})
というのも、 SSR では reactivity がたしかに動作しないが、 head()
は、実装としては computed
が利用されていて、かつ実際にそれが呼び出されるのは、ページコンポーネントの render が完了しすべての vm が生成された後に行われる、head の要素の計算を行うタイミングにおいてであるので、コンポーネントが created の中 store の値を書き換えていたとすると、それはもれなく head の計算に反映されることになる。
また、 asyncData
とは違いこのコードはクライアント側でも実行されるので、対象のリソースを必要とするコンポーネントが render されることになったならば、そのタイミングでこのリソースは <head>
に reactivity によって追加されていくことが期待できる。
まとめ
外部リソースをただ一度だけロードしながらも、そのロードを必要になるまで遅延したいとなったときには、以下の実装を行えば良い。
- 対象外部リソースをロードするかどうかを store にてフラグで管理する。
- 共通レイアウトの
head()
にて、 store のフラグの値を見てロードするかどうかを決める - そのリソースを必要とするコンポーネントが、
created()
のタイミングで store のフラグをオンにする - store のフラグは一度オンになったならば、それ以降はオンのままになるようにする
Tags: nuxt
関連記事
Nuxt 3 (vite) で SVG を import する方法
2023/2/21
Nuxt Content V2 で RSS フィードを作成する
2023/2/19
useAsyncData とそれを実現する Nuxt 3 の非同期処理の機構
2022/9/16
Nuxt Content V2 は Nuxt Bridge では動かない
2022/8/13