【Nuxt3 × SPA】既存のプロジェクトをNuxt3へアップデートする

Nuxt3へのアップデートをハンズオン形式で説明しています。破壊的変更がこんもりとあるNuxt3ですが、ちょうど最小構成のプロジェクトがあるので対応してみました。

2023年05月28日

前提

  • node v18.0.0
  • 前回の動作環境を引き継いで進めていきます。

あまり影響無さそうな細かいことは割愛しているので、必要あれば変更箇所をご覧ください。

構成を変えていく

詳細に説明するとクソ長くなりそうなので、前回からの変更分だけを抜粋して説明していきます。

公式のコマンド

Nuxt3へのアップデート

$ npx nuxi upgrade

Need to install the following packages:
  nuxi@3.5.1
Ok to proceed? (y) y

いろいろ削除

package.jsonがごっそりと変わるので、既存のパッケージを削除していきます。

 $ rm -rf node_modules 
 $ rm -rf yarn.lock

ついでにYarnのキャッシュを抹消しておく

$ yarn cache clean --force

旧バージョンに依存したファイルやディレクトリを削除

$ rm -rf types/
$ rm -rf store/
$ rm -rf layouts/error.vue
$ rm -rf plugins/firebase.ts
$ rm jsconfig.json
$ rm nuxt.config.js

書き換える

Nuxt3は破壊的アップデートが多いため、書き換えなければBuildも通らない…のようなやばい対応箇所が多いです。
小規模プロジェクトでも、作り直した方が早いんじゃないかというくらいに結構カロリーを使います。

  • package.json
  • firebase.json
  • tsconfig.json
  • pages/index.vue

package.json

dependenciesとdevDependenciesの不用なモジュールを入れ替える

{
  "name": "firebase-study",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "nuxt build",
    "dev": "nuxt dev",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare"
  },
  // ここから
  "dependencies": {
    "firebase": "^9.22.1",
    "nuxt": "^3.5.1",
    "vuetify": "^3.3.1"
  },
  "devDependencies": {
    "@mdi/font": "^7.2.96",
    "firebase-tools": "^12.2.1",
    "sass": "^1.62.1"
  }
}

firebase.json

Nuxt3へのアップデートにより、Nitro Engine という新しいサーバーエンジンが搭載されるようになりました。

Nitro(ナイトロ)の旨みは他記事でもさんざん説明されているようですが、簡潔にどんなものか。

  • 軽量
  • ServiceWorkerでの動作
  • サーバーレス環境での動作
  • ホットリロードの高速化

開発環境が快適になるのはとっても嬉しい。

{
  "hosting": {
    "public": ".output/public",
    "ignore": [
      "firebase.json",
      "**/.*"
    ]
  }
}

tsconfig.json

今回から、Nuxt3側で生成してくれているみたいなので、そちらを参照していきます。
とってもすっきりしました。嬉しい。

※ パフォーマンス上の理由から、nuxi build時に、型をチェックしていないようです

{
  "extends": "./.nuxt/tsconfig.json"
}

pages/index.vue

こちらが変更点です。

setup構文については公式が一番わかりやすいです。
変数 colorSwitch のみ動的な変更があるので ref にしています。

refreactiveなど、動的変更があるもの、無いもので分けることにもパフォーマンスへの影響があるようです。
コード量が減る代わりに、意識することは増えそう。

<template>
  <div>
    <v-container>
      <v-card :style="colorSwitch === 'cool' ? coolColor : cuteColor" class="color-pallet">
        <v-card-text v-html="richText"/>
        <v-card-actions>
          <v-btn color="#08ffc8" @click="colorSwitch='cool'">クール系</v-btn>
          <v-btn color="#ffb6b9" @click="colorSwitch='cute'">かわいい系</v-btn>
        </v-card-actions>
      </v-card>
    </v-container>
  </div>
</template>

<script setup lang="ts">
type ColorType = {
  "--color-h1": string
  "--color-h2": string
  "--color-h3": string
  "--color-back": string
}
const coolColor: ColorType = {
  "--color-h1": '#08ffc8',
  "--color-h2": '#fff7f7',
  "--color-h3": '#dadada',
  "--color-back": '#204969'
}
const cuteColor: ColorType = {
  "--color-h1": '#ffb6b9',
  "--color-h2": '#fae3d9',
  "--color-h3": '#bbded6',
  "--color-back": '#8ac6d1'
}

const colorSwitch = ref<'cool' | 'cute'>('cool')
const richText: string = `<h1>h1タグだよ</h1><p>こんにちは、h1タグです。</p><h2>h2タグだよ</h2><p>こんにちは、h2タグです。</p><h3>h3タグだよ</h3><p>こんにちは、h3タグです。</p>`
</script>

<style lang="sass">
.color-pallet
  h1
    background: var(--color-h1)
  h2
    background: var(--color-h2)
  h3
    background: var(--color-h3)
  p
    color: var(--color-back)
</style>

いろいろ追加する

nuxt3に対応したファイルを追加していきます。

pluginsディレクトリ内のファイル名にサフィックスをつけることで、クライアントサイドもしくはサーバーサイドどちらでロードするかを指定できます。

いままで process.browser などで分けていましたが、若干きもかったので嬉しい。

$ touch app.vue
$ touch nuxt.config.ts
$ touch plugins/firebase.client.ts
$ touch plugins/vuetify.ts

app.vue

layouts 内の各コンポーネントを適用させるため、app.vueで記述していきます。

<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

nuxt.config.ts

がっつり書き変わってますが、記述量はだいぶ減りました。
環境変数の呼び出し方は以下のとおりです。

const { apiKey, authDomain } = useRuntimeConfig().public
// もしくは
useRuntimeConfig().public.apiKey

前回の書き方

変更後↓

export default defineNuxtConfig({
  ssr: false,
  build: {
    transpile: ["vuetify"]
  },
  runtimeConfig: {
    public: {
      apiKey: process.env.FB_API_KEY,
      authDomain: process.env.FB_PROJECT_ID + '.firebaseapp.com',
      projectId: process.env.FB_PROJECT_ID,
      storageBucket: process.env.FB_PROJECT_ID + '.appspot.com',
      messagingSenderId: process.env.FB_MESSAGING_SENDER_ID,
      appId: process.env.FB_APP_ID,
    }
  },
  app: {
    head: {
      titleTemplate: '%s - firebase-study',
      title: 'firebase-study',
      htmlAttrs: {
        lang: 'en'
      },
      meta: [
        { charset: 'utf-8' },
        { name: 'viewport', content: 'width=device-width, initial-scale=1' },
        { name: 'description', content: '' },
        { name: 'format-detection', content: 'telephone=no' }
      ],
      link: [
        { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
      ]
    }
  }
})

firebase.client.ts

今回はSPAなので関係ありませんが、SSRもしくはSSGにてFirebase Authentication を利用すると、ちょっとめんどくさいので注意が必要です。

前回の書き方

import { FirebaseApp, getApp, getApps, initializeApp } from 'firebase/app'
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig().public;
  const firebaseConfig = {
    apiKey: config.apiKey,
    authDomain: config.authDomain,
    databaseURL: config.databaseURL,
    projectId: config.projectId,
    storageBucket: config.storageBucket,
    messagingSenderId: config.messagingSenderId,
    appId: config.appId,
    measurementId: config.measurementId
  };
  const firebaseApp: FirebaseApp = !getApps().length ? initializeApp(firebaseConfig) : getApp();

  return {
    provide: {
      firebase: firebaseApp,
    }
  }
});

vuetify.ts

コンポーネントのデフォルト設定などもここでできるようですが、今回は割愛します。
※ 今後書いていくつもり

import { createVuetify, IconOptions } from "vuetify";
import * as directives from "vuetify/directives";
import { aliases, mdi } from 'vuetify/iconsets/mdi'
import * as components from 'vuetify/components'

export default defineNuxtPlugin((nuxtApp) => {
  const icons: IconOptions = {
    defaultSet: 'mdi',
    aliases,
    sets: {
      mdi
    }
  }

  const vuetify = createVuetify({
    icons: icons,
    components,
    directives
  });

  nuxtApp.vueApp.use(vuetify);
});

静的ファイルの設置

nuxt3へのアップデートにより、今までのstaticディレクトリを認識しなくなりました。
かわりに静的ファイルはpublicディレクトリへ移行することになります。

EOL

私の契約している会社でも慌ただしくNuxt3移行を行なっております。
加えてEOLがありました。
2023年5月22日 時点の発表によると、EOLは2023年中のようです。
この規模のアップデートを大規模プロジェクトで行うのは、ちょっと震えますね。
死人が出そうです。

Nuxt 2 will reach End of Life (EOL) on December 31st, 2023 at the same time as Vue 2 does. All supported versions should run on all currently supported Node.js releases.

共有

【今回の作業ブランチ】
https://github.com/yutahhh/firebase-study/tree/feature/%235

【Hosting】
https://fir-study-42747.web.app/

実際、middlewareやcomposablesなど、説明しきれていない箇所はまだあるので、追々別記事にてアウトプットしていこうかと思います。

お疲れ様でした。

筆者情報
IT業界経験6年目のフルスタックエンジニア。
フロントエンドを軸として技術を研鑽中でございます。