webコーダーのsaco @sacocco_sacoya です。
前回に引き続きはじめてつくるNuxtサイト(三好アキさん著書)で学んだことを記事にします。
※ もし記述内容として誤りやアドバイスなどがありましたら、Xかお問合せフォームにてお知らせいただけるととても喜びます!
教材内の目次では第5章の「ブラッシュアップ編」の404ページの追加〜前後のブログページへ移動する、までの記録です!
内容は404ページ、コンタクトフォーム、ブログ記事の前後のページへのリンク作成です。
生じた疑問点やエラー、教材と結果が違う箇所など、私が触ってみた結果も交えて記述していきたいと思います。
↓前回の記事はこちら
404ページの追加
現在作成したまでの状態では、存在しないページのURLへアクセスするとNuxtのデフォルトの404画面が表示されます。
この画面をカスタムで作成するには、ルートディレクトリに error.vueというファイルを作成し、以下のコードを記述します。
<template>
<div class="error container">
<h1>404:Not Found</h1>
<p>ページが見つかりません。</p>
</div>
</template>
再度ブラウザで存在しないページへアクセスすると、先程はNuxtのデフォルトの404ページだったものが、作成した error.vueファイルの内容が表示されるようになりました。
コンタクトフォームの設定
お問い合わせページの設定を、Formspreeというサービスを利用してメッセージを受信できるようにします。
Formspree(https://formspree.io/create/zeit)へアクセスすると、上記のようなページが表示されます。
青枠内に受信したいメールアドレスを入力し、Createボタンで進みます。
上記のような画面が表示されるため、適宜入力して次へ進みます。
入力が完了したら、登録したメールアドレスに認証用メールが届くのでこちらも認証を済ませます。
認証が済むと、「A New Form」画面が開きます。
Integrationタブの下部に「Integrate with your usecase」という項目があり、HTMLコードが記述されています。
この中から<form>の開始タグのみをコピーします。
contact.vue ファイルを開き、作成していたフォームの<form>の開始タグをFormspreeで作成した<form>の開始タグに差し替えます。
保存してGitにプッシュし、Vercelからデプロイするとコンタクトフォームが使えるようになっています。
↓送信完了画面
↓受信画面
前後のブログ記事へ移動するリンクを作成
記事詳細ページに配置する、前後に移動するためのリンクを作成します。
componentsフォルダへ prevNext.vueファイル として配置し、以下のコードを記述しました。
//prevNext.vue コンポーネント
<template>
<div class="pnWrapper">
//リンク先は仮
<NuxtLink :to="前の記事へのリンク">
<img src="/images/arrow-left.svg" alt="arrow-left">
<h3>前の記事のタイトル</h3>
</NuxtLink>
//リンク先は仮
<NuxtLink :to="次の記事へのリンク">
<h3>次の記事のタイトル</h3>
<img src="/images/arrow-right.svg" alt="arrow-right">
</NuxtLink>
</div>
</template>
<NuxtLink>の指定先として、該当する[id].vue(記事詳細ページ)の前後のページへリンク
できるように設定したいです。
この機能を簡単に実装できるのが queryContent の findSurround です。
findSurroundとは
パス周辺の前と次の結果を取得するためのメソッドです。
このメソッドを利用することで、記事ページの前後の情報を取得することができます。
[id].vueファイルの<script>に、以下のコードを追加しました。
//[id].vue 記事詳細ページテンプレート
<script setup>
const { data } = await useAsyncData(useRoute().path, () =>
queryContent(useRoute().path).findOne()
)
//↓追加
const [prev,next] = await queryContent("/blog")
.sort({ id: 1 }) //idを昇順でソート
.findSurround(useRoute().path)
console.log(prev)
//↑追加
</script>
復習も兼ねて、[id.]vueファイルに追記したコードを解読してみます。
queryContentで、contentフォルダ>blogフォルダの中にあるマークダウンファイルのデータを取得します。
そして、取得したマークダウンファイルの中のFrontmatterに指定したidプロパティを基準にして.sortメソッドでソートをかけています。
ここで「1」と指定してあるのは慣習的なもので、「1」とすれば昇順、「-1」とすれば降順になります。
その後、findSurroundの括弧内で useRoute().pathとし、現在のパスを基準として取得した前後のデータが定数の [prev,next]に入るように指定されています。
console.log(prev)として、一つ前の記事の情報が取得されているか確認しました。
2つ目の記事の前の記事として、1つ目の記事のデータがprevに取得できていることが確認できました。
console.log(next)とすれば、3つ目の記事のデータが取得できています。
取得した prev と next のデータを prevNext.vueファイルに渡すため、[id].vueに以下の記述を追加します。
//[id].vue 記事詳細ページテンプレート
<template>
<div>
<div class="hero">
<nuxt-img :src="data.image" alt="blog-image" format="webp" />
</div>
<div class="wrapper">
<div class="container">
<h1>{{ data.title }}</h1>
<p>{{ data.date }}</p>
<ContentDoc />
</div>
//↓追加 prevNext.vueの呼び出し
<PrevNext :prev="prev" :next="next" />
</div>
</div>
</template>
<script setup>
const { data } = await useAsyncData(useRoute().path, () =>
queryContent(useRoute().path).findOne()
)
const [prev,next] = await queryContent("/blog")
.sort({ id: 1 }) //idを昇順でソート
.findSurround(useRoute().path)
console.log(prev)
</script>
<PrevNext〜>として、prevNext.vueファイルを呼び出します。
ここでの:prev=”prev”、:next=”next”は、prevNext.vueで記述した<NuxtLink>のリンク先に指定するための変数の指定です。
この変数([id].vueで取得したprevとnextのデータ)を prevNext.vueで利用するための紐付けとして、prevNext.vueにdefinePropsを利用します。
definePropsとは
Vue.js3で導入されたコンポーネントオプションの1つ。propsとは親コンポーネントから子コンポーネントに渡されるデータの仕組みのこと。
コンポーネントにプロパティを受け取るための方法を提供するもの。
Vue3ではTypeScriptなどの静的型検査を利用する際に、propsの型定義において、型が正確であることを保証するため(型の安全性を向上させるため)に導入されたオプション。
prevNext.vueの<script>に、以下のコードを追記しました。
//prevNext.vue コンポーネント
<template>
<div class="pnWrapper">
//リンク先は仮
<NuxtLink :to="前の記事へのリンク">
<img src="/images/arrow-left.svg" alt="arrow-left">
<h3>前の記事のタイトル</h3>
</NuxtLink>
//リンク先は仮
<NuxtLink :to="次の記事へのリンク">
<h3>次の記事のタイトル</h3>
<img src="/images/arrow-right.svg" alt="arrow-right">
</NuxtLink>
</div>
</template>
//↓追加
<script setup>
//↓追記
//[id].vueで取得したprevとnextのデータを受け取る
const props = defineProps({
prev:Object,
next:Object,
})
//↑追記
</script>
//↑追加
ここでdefinePropsにprevとnextのデータを紐付けして、[id].vueで取得したデータを受け取っています。
prev:Objectという記述のObjectは、definePropsがプロパティの型を厳密にチェックするためのメソッドで、ここでは変数prevとnextの型がオブジェクト型であることを期待しているという指定になります。
そしてprevNext.vueファイルで指定した、前と後の記事に指定するリンク先を以下のように記述しました。(記事のタイトルもprevとnextで取得したデータを利用して該当の記事のタイトルを取得しています)
//prevNext.vue コンポーネント
<template>
<div class="pnWrapper">
//↓リンク指定
<NuxtLink :to="prev._path">
<img src="/images/arrow-left.svg" alt="arrow-left">
<h3>{{ next.title }}</h3>
</NuxtLink>
//↓リンク指定
<NuxtLink :to="next._path">
<h3>{{ prev.title }}</h3>
<img src="/images/arrow-right.svg" alt="arrow-right">
</NuxtLink>
</div>
</template>
現状ではまだリンクが完全に作成できていません。
理由として、1ページ目にはprevが存在せず、6ページ目にはnextが存在しないためです。
そのため、prevもしくはnextが存在する場合のみ表示するという条件をVueのv-ifを利用して記述します。
//prevNext.vue コンポーネント
<template>
<div class="pnWrapper">
//↓ v-ifを追記
<NuxtLink :to="prev._path" v-if="prev">
<img src="/images/arrow-left.svg" alt="arrow-left">
<h3>{{ prev.title }}</h3>
</NuxtLink>
//↓ v-ifを追記
<NuxtLink :to="next._path" v-if="next">
<h3>{{ next.title }}</h3>
<img src="/images/arrow-right.svg" alt="arrow-right">
</NuxtLink>
</div>
</template>
これで最初の記事と最後の記事にはリンクを表示させないという挙動を作成することができました。
↑6つ目の記事(最新の記事)にはprevのリンクのみが表示されています。
これでprevとnext、記事詳細ページのページネーションの機能は完成です!
まとめ
- 404ページを作成しない場合はNuxtのデフォルトの404ページが適用される
- 404ページをカスタムで作成するにはルートディレクトリにerror.vueというファイルを作成する
- コンタクトフォームはFormspreeというサービスを利用し、作成してあったcontact.vueのフォームを一部書き換えて作成した
- 記事詳細ページの前後のリンクはNuxt3のメソッドfindSurroundを使用して作成
- 親コンポーネントから子コンポーネントにデータを渡すにはdefinePropsメソッドを利用する(findSurroundで取得した該当詳細ページの前後のリンクを変数で指定して紐付ける形)
今回は404ページの作成と、コンタクトフォームの作成、記事詳細ページの前後のリンク作成を行いました。
記事詳細ページの前後のリンク設定の理解が少し難しく悩む箇所もありましたが、一つずつコードを読み解いていくことで何とかおおまかな理解には辿り着けたように思います。
次回はページネーションの作成などを行なっていきます。
本日は以上です。