Nuxt3でブログを作る

このブログを Nuxt2 で構築してから3年が経ち,Nuxt2 のサポートが終了するため,Nuxt3 で構築し直しました.

同様の投稿はたくさんあるので,要点のみ記載しています.

プロジェクト構築

下記ページを参考に Nuxt Content v2 のプロジェクトを作成します.

参考:Installation - Nuxt Content

ちなみに,コマンドには nuxinuxt が登場しますが,前者は Nuxt CLI として構築されたものです.

環境変数

Nuxt3 は dotenv がビルドインされています.

参考:.env · Nuxt Directory Structure

.env.local と .env.production など複数ファイルを用意することで,実行環境別に環境変数を設定できます.

ローカル環境では同一ネットワークから接続できるように,以下の環境変数を設定しておきます.

.env.local

HOST=0
PORT=3000

Preprocessors

Pug.js

terminal

$ yarn add pug

<template>lang 属性に pug を指定すると,Pug.js が利用できます.

Sass

参考:# Using Preprocessors

共通化したい Sass は assets ディレクトリ内のファイルに書き出すと良いです.

参考:The CSS Property

ちなみに,Sass には SCSS と Sass が内包されています.SCSS は中括弧あり,Sass は中括弧なしです(なぜプリプロセッサと記述方式の名称を一緒にしてしまったのか…).

Modules

Nuxt3 では,Nuxt2 から引き続き,各機能をモジュールとして提供しています.一般的に必要な機能はほぼカバーされている印象です.

Nuxt UI

Nuxt2 では Bulma を利用していましたが,Bulma や Buefy は Nuxt3 に対応していないようだったので,Nuxt UI @nuxt/ui に切り替えました.

Nuxt UI は Tailwind CSS をベースとしています.また,@nuxtjs/color-modenuxt-icon も自動で入ります.設定ファイルで有効化が必要です.

nuxt.config.ts

export default defineNuxtConfig({
  modules: ['@nuxt/ui', '@nuxtjs/color-mode', 'nuxt-icon'],
})

デフォルトの色を変更したい場合は,既に用意されているカラーマップから選ぶか,カラーマップを作成して指定します.カラーマップを作成するには,以下のサイトが便利です(最高).

参考:uicolors.app/create

設定ファイルは,以下のコマンドを実行するとルートディレクトリ下に作成されます.

terminal

$ npx tailwindcss init --ts

参考:Using ESM or TypeScript

コンポーネントが参照するカラーマップは,ルートディレクトリ下の app.config.ts で指定します.

参考:Theming - Nuxt UI

Nuxt UI Pro にすると多くのコンポーネントが利用できますが,製品版で使うには課金が必要です.使えるコンポーネントは限られますが,課金せずに利用できるコンポーネントもあります.

ダークモード

上記の記述で有効になります.

Chrome ブラウザでは,DevTools の右上3点リーダーから More tools > Rendering > Emulate CSS media features prefers-color-scheme からエミュレートするモードを変更できます.動作確認に便利です.

@nuxtjs/color-mode 単体では,カラーモードを示すクラス名が light-mode or dark-mode ですが,@nuxt/ui を有効にすると light or dark になります.

アイコン対応

<Icon> タグで各種アイコンパッケージを楽に参照できます.

参考:GitHub - nuxt-modules/icon: The <Icon> component, supporting Iconify, Emojis and custom components.

上記では呼び出せないような SVG アイコンを利用したい場合は,nuxt-icons パッケージを利用することもできます.名称は似てますが,別のパッケージです.

参考:Icons · Nuxt Modules

Syntax Highlighting

デフォルトで Shikiji という SyntaxHighlight に対応しています.対応言語や表示テーマは nuxt.config.ts に記述します.

参考:highlight

ライト/ダークモードの切り替えにも対応していますが,defaultを設定すると常に優先して表示されてしまいます.

Nuxt Content v1 では,ファイル名の表示に対応していましたが,v2 ではデフォルトで対応していないようです.表示するには,コンポーネントを上書きする必要があります.

参考:How to Create a Custom Code Block With Nuxt Content v2 | Michael Hoffmann - Senior Frontend Developer (Freelancer)

Open Graph 画像の自動生成

モジュールとして画像生成機能が提供されています.

参考:Og-image · Nuxt Modules

デフォルトでは日本語非対応です.非英語圏の言語を利用する場合は,フォントを指定する必要があります.

参考:Non-English Locales · Nuxt SEO

多少制限はありますが,コンポーネントを作成する容量でイメージ画像を生成でき,Nuxt Devtools を併用するとリアルタイムで確認もできるので便利です.

フォントを日本語対応にすることはできますが,改行位置は調整されません.調整したい場合は,Google の BudouX などを用いて制御する必要があります.

参考:GitHub - google/budoux

前提としては,HTML タグの指示通りに画像を生成します.ただし,変換に対応していないスタイルも存在します.そのため,HTML 表示と画像生成時の見た目が異なることがよくあります.経験的に,改行の処理は認識されないように感じます.

(Og-image モジュールの内部処理では,複数のパッケージを用いて ReactNode → SVG → PNG と変換されていきます.)

generate コマンドが画像を生成するとき,.nuxt ディレクトリのキャッシュを参照するため,記事を変更しても画像が変更されません.キャッシュを削除すれば,次回実行時に再生成されます.

参考:nuxi cleanup · Nuxt Commands

Sitemap

参考:Sitemap · Nuxt Modules

Google Analytics

参考:Gtag · Nuxt Modules

多言語対応

参考:GitHub - nuxt-modules/i18n: I18n module for Nuxt

プロジェクト編集

ディレクトリ構造

Directory Structure を参照してください.

参考:Nuxt Guide

layouts

複数のページで同一のレイアウトを利用したい場合に使います.プロジェクト作成時に生成されませんが,Nuxt2 から変わらず利用できます.

有効化には,app.vue の編集が必要です.

参考:layouts/ · Nuxt Directory Structure

composables と utils の使い分け

Nuxt3(Vue3)では, Vue の Composition API を利用する composable を用意できます.Composition API とは,状態を管理するロジックをカプセル化して再利用するためのものです.

参考:Composables | Vue.js

composables 下のスクリプトは自動インポートされますが,utils ディレクトリ下のスクリプトも同様に自動インポートされます.utils には comspsables 用途以外のカプセル化した処理を格納します.

参考:utils/ · Nuxt Directory Structure

script setup

<template>, <script>, <style> の3要素を含む .vue ファイルを Single File Component (SFC) と呼びます.

Vue3 から <script setup> が使えるようになりましたが,<setup> とは記述方法が異なります.従来よりも簡潔に記述できる場合が多く,型も定義しやすくなったようです.

<script> はコンポーネントがインポートされた最初のタイミングで実行されますが,<script setup> はコンポーネントのインスタンスが作成されるたびに実行されます.

参考:<script setup> | Vue.js

雰囲気は以下の通りです.Nuxt2 を触ったことがあると,記述方法が大きく変わったことがわかると思います.詳しくは,公式ドキュメントを参照してください.

<template>
  <div>
    <p v-if="hasName">{{ name }}</p>
  </div>
</template>

<script setup>
const props = defineProps({
  name: {
    type: String,
    default: '',
  },
})

const { name } = props

const hasName = computed({
  get() {
    return name === 'alice'
  },
})
</script>

参考:Writable Computed

Markdown の取得

Nuxt Content v1 で利用できた $content はなくなり,useAsyncData() 関数を利用する形になりました.

<script setup>
const { data } = await useAsyncData('home', () => queryContent('/').findOne())
</script>

この処理を複数並列して実行する場合,第一引数で管理されています.つまり,同じキーを指定すると結果が共有されます.

複数のコンポーネントが並列されているとき,親コンポーネントと子コンポーネントで上記の処理を行うときなどは,同じキーを利用していると最終的に変数に代入される値が同じになるため,注意が必要です.

参考:queryContent() - Nuxt Content

画像パスの動的な指定

Nuxt2 では,require() 関数で動的に指定していました.

<img :src="require(`~/assets/image/${name}.jpg`)" />

Nuxt3 では,Vite がこの関数に対応していないため,インポート方法を変える必要があります.試していませんが,下記のような方法があるようです.

参考:Nuxt3 で img タグの src 属性に変数を使えない問題の回避策 #JavaScript - Qiita

public ディレクトリ下の画像はパスが変わらないため,通常通りの記述で読み込めます.

<img :src="`/image/${name}.jpg`" />

上記は v-bind の記載(省略形)になっていますが,文字列の中に変数を埋め込むように書くことも技術上は可能です.しかし,変数のバインディングが有効にならないため,undefined になります.

実行環境

nuxt dev or preview

Nuxt3 では nuxt preview が利用できますが,build 後に実行するコマンドで,start と同一の処理を行います.

nuxi preview · Nuxt Commands

同一ネットワーク内からの接続

ローカルネットワークから接続できるように,起動スクリプトを追記します.ローカルサーバを起動しているパソコンだけでなく,同じネットワークに接続する他のパソコンやスマホからも接続できます.

package.json

{
  "scripts": {
    "local": "yarn dev --dotenv .env.local"
  }
}

terminal

$ yarn local

検索エンジンやブラウザへの対応

favicon

Google のガイドラインによれば,48px の倍数で用意しなければならないとのこと.

参考:Define Website Favicon for Search Results | Google Search Central  |  Documentation  |  Google for Developers

しかし,Apple のガイドによれば,iPhone/iPad(Pro/mini/無印)/Mac ごとに異なるサイズを用意するようにともあります.

参考:App icons | Apple Developer Documentation

多数のアイコンを用意し,記述するのも面倒なので,サイズ指定が不要な SVG で対応します(サイトを作るたびに思う,各社はなぜサイズを細分化してしまうのかと).IE 他,一部ブラウザは対応しない可能性があります.

また,iOS/iPad OS の最高画質向けに,180x180 サイズの apple-touch-icon を別途用意します.Google のドキュメントに apple-touch-icon-precomposed も設定項目として表記されていますが,これは古い iOS 端末でアイコンに光沢が出ないようする項目のようです.

参考:apple-touch-icon と apple-touch-icon-precomposed - chocotaka の日記

rel="shortcut icon" は古い記述のため,不要そうです.

参考:HTML attribute: rel - HTML: HyperText Markup Language | MDN

ちなみに,ICO ファイルは複数のサイズに対応できるため,単一ファイルで記述できる利点があります.

参考:sizes

Head 情報

OGP やその他メタデータなどを記載します.app.vue に記述すると,共通のタグとして利用されます.個別ページで設定すると,上書きされます.favicon などの <link> もここで設定します.

参考:useSeoMeta

Feed

Nuxt3 では,フィードを作成するためのモジュールも公開されていますが,日本語記事を扱う Nuxt Content との相性が悪かったため,以下記事を参考にスクリプトを書きました.

参考:Nuxt Content V2 で RSS フィードを作成する - Yukii's Blog

最後に

Nuxt2 で作成したブログを Lighthouse で評価すると,以下のスコアでした.

Mobile

91Performance
83Accessibility
95Best Practices
90SEO
0-49
50-89
90-100

Reported by Lighthouse

PC

99Performance
83Accessibility
100Best Practices
90SEO
0-49
50-89
90-100

Reported by Lighthouse

Nuxt3 で構築し直した結果,スコアは少し減少しました.減少要因は,JS ライブラリの大きさや AdSense の仕様上の問題です.

Mobile

85Performance
81Accessibility
100Best Practices
87SEO
0-49
50-89
90-100

Reported by Lighthouse

PC

90Performance
81Accessibility
100Best Practices
90SEO
0-49
50-89
90-100

Reported by Lighthouse

Nuxt2 と比べてスクリプトは描きやすくなったと感じます(特に,props や method 周り).

Nuxt Content v1 は,開発サーバで起動しているとダブルクリックで Markdown 編集モードに入れましたが,v2 では無くなってしまったので軽微な編集はしづらくなってしまいました.

モジュールの拡充もあり,総じて見るとサイト構築がかなり楽になったと思います.