Nuxt3(SSG) + prismic + bunで無料ブログ作成(ミニマム実装編)
Nuxt3(SSG) + prismic + bunで無料ブログ作成の知識編はこちらにまとめています
ここからは、新規でブログ構築するまでのメモです。
※現時点で、SSG(bun run generateで固めてデプロイした環境)でreview機能の動作がうまくいってません。
うまく動いている方いらっしゃいましたら、「自分はいけてます」だけでもコメントいただけると嬉しいです。
ブログページを作成してみる
ここから、ブログのコンテンツを扱えるページを実装していきます。
手順
slice machine(ローカルGUI)上で
① Slice(ブログページを構成する部品の最小構成)の作成
② Page(ブログページの構成)の作成
prismic管理画面(GUI)で、
③ document(ブログの実際の記事・コンテンツデータ)の作成
を行い、
④ Nuxtのコンポーネント、ページを実装
⑤ SSGに固めてデプロイ
という流れです。
prismic利用の方針
Webサイトによっては、
ほとんどのサイト上のコンテンツをprismicで制作することもできそうですが、
今回はミニマムにprismicを使うという方針で、「ブログコンテンツ部分のみ」利用します。
理由は、CMS依存を小さくし、今後CMS基盤を変更するときの負荷を小さくするためです。
ざっくりした構成
ブログパーツは
・タイトル
・コンテンツ
の2つのみ。
コンテンツの中身は、画像やテキスト、様々な組み合わせでパーツを選択できるようにします。
Sliceの作成
ブログコンテンツに使いそうな、以下を作成。
Image(サイズで分けられるように、2バリエーション作成)
Hero(タイトル)
Text(各種装飾などができる、リッチテキスト)
Quote(引用に使う)
Image
Default variation
※フリーサイズで、アップ画像の全体を表示させる
Banner variation
※3000px × 1000px でサイズ指定する
Text
1フィールド、装飾は全部許可のシンプルな構成
Quote
Textと同じリッチテキストフィールド(h2,B,I装飾のみ許可)と、引用元用のテキストフィールド
Pageを作成
ブログの汎用ページ用に、blog_postというラベルで、1つだけPageを作成します。
Static Zoneには、必須のUIDの他に、Titleフィールド(リッチテキスト)を追加。
Slice Zoneには、作成した全てのSliceを登録しておき、必要に応じて部品として使えるようにしておく。
document (実データ)を作成
作成したsliceをPushで本番に反映させた後、
実データをセットしていきます。
タグ
テストタグ
というタグを追加
Static Zoneのデータ
UID: first_sample
Title: テストタイトル
Slice Zoneのデータ
Imageと、Textの2つのSlice構成
Nuxtのページ作成
Sliceを作成したタイミングで、基本的なファイル群は、
自動的にNuxtのソースコードとして生成されます。
他のフレームワークを選択している場合、それぞれに応じたファイルが生成されているはずなので(未確認です)、
それぞれの環境に置き換えてみてください。
nuxt.config.tsの設定
modulesで、prismicモジュールの利用を宣言し、
prismicで、細かい設定をしてきます。
endpoint:prismicのリポジトリ名
preview:サンプルのデフォルト値のまま(今回未使用)
clientConfig.routes: ここに、prismicを使うパス(ブログポスト)を指定します。
export default defineNuxtConfig({
modules: [
"@nuxtjs/prismic",
],
prismic: {
endpoint: 'sampleproject',
preview: '/api/preview',
clientConfig: {
routes: [
//topics blog
{
type: "blog_post",
path: "/topics/blog/:uid",
},
],
},
},
ページを実装
Nuxtはディレクトリ構造がそのままURLのパスになります。
ブログ記事に指定したいパスに、ページファイルを作成します。
[uid].vueとすることで、uid部分を変数で取得できます。
具体的には、
http://localhost:3000/topics/blog/first_sample
とすると、
first_sample
という文字列を、ページ内で変数として取得できるということです。
今回は、この部分がprismicのdocumentで指定した、各ページのUIDの相当するようにしています。
uidを取得して、prismicのデータ(document)を呼び、
SliceZoneコンポーネントを使って、そのまま展開しています。
<script setup>
import { usePrismic } from "@prismicio/vue";
import { components } from "~/slices";
// const { client } = usePrismic();
const prismic = usePrismic();
const route = useRoute()
const { data: page } = useAsyncData(route.params.uid, () =>
prismic.client.getByUID('blog_post', route.params.uid)
);
useHead({
title: prismic.asText(page.value?.data.title)
})
</script>
<template>
<SliceZone wrapper="main" :slices="page?.data.slices ?? []" :components="components" />
</template>
この状態で、http://localhost:3000/topics/blog/first_sample
を表示すると、
以下のように表示されます。
documentで1つずつ作成したImage,TextそれぞれのSliceに対応した部分は表示できていそうですが、
画像や文字列が取得できません。
各sliceのコンポーネントを実装
sliceをGUIで作成すると、それに応じたコンポーネントファイルは自動的に生成されるものの、
デフォルトでは、以下のように中身が実装されていません。
ここから、表示したい内容に編集していきます。
初期状態
上記の画面の通り、
<script setup lang="ts">
import { type Content } from "@prismicio/client";
// The array passed to `getSliceComponentProps` is purely optional.
// Consider it as a visual hint for you when templating your slice.
defineProps(
getSliceComponentProps<Content.TextSlice>([
"slice",
"index",
"slices",
"context",
]),
);
</script>
<template>
<section
:data-slice-type="slice.slice_type"
:data-slice-variation="slice.variation"
>
Placeholder component for text (variation: {{ slice.variation }}) Slices
</section>
</template>
表示させるときは、slice machineのGUIを見ながら実装すると分かりやすいです。
例えばTextの部分であれば、
GUI画面で、Show code snippets?
部分をONにすると、
記述すべきNuxtのコードが表示されます。
snippets部分はこちら↓
<PrismicRichText :field="slice.primary.text" />
編集後
snippetsに置き換えてみると、、、
<script setup lang="ts">
import { type Content, type HTMLRichTextMapSerializer } from '@prismicio/client'
// The array passed to \`getSliceComponentProps\` is purely optional.
// Consider it as a visual hint for you when templating your slice.
defineProps(
getSliceComponentProps<Content.TextSlice>([
"slice",
"index",
"slices",
"context",
]),
);
</script>
<template>
<section>
<PrismicRichText :field="slice.primary.text" />
</section>
</template>
テキスト部分が表示されました。
同様に、Imageも修正してみます。
初期状態
<script setup lang="ts">
import { type Content } from "@prismicio/client";
// The array passed to `getSliceComponentProps` is purely optional.
// Consider it as a visual hint for you when templating your slice.
defineProps(
getSliceComponentProps<Content.ImageSlice>([
"slice",
"index",
"slices",
"context",
]),
);
</script>
<template>
<section
:data-slice-type="slice.slice_type"
:data-slice-variation="slice.variation"
>
Placeholder component for image (variation: {{ slice.variation }}) Slices
</section>
</template>
編集後
<script setup lang="ts">
import { type Content } from "@prismicio/client";
// The array passed to `getSliceComponentProps` is purely optional.
// Consider it as a visual hint for you when templating your slice.
defineProps(
getSliceComponentProps<Content.ImageSlice>([
"slice",
"index",
"slices",
"context",
]),
);
</script>
<template>
<PrismicImage :field="slice.primary.image" />
</template>
画像も表示されました。
prismicは"headless" CMSなので、デザイン、cssは自分で実装する必要が有ります。
逆に言えば、どんなデザイン・環境でも柔軟にカスタマイズできて、
データ部分のみを管理できるのが、headlessの良い所です。
Nuxt3 SSGによるファイル出力
NuxtでSSG用の静的ファイル出力は、
bun run generate
で出力できるが、ブログ記事のような動的なページについては、
事前にルーティングを指定しなければいけません。
※こちらの記事がとても参考になりました
※公式のissueブログのpathは、
https://xxxxx/topics/blog/[uid].vue
で、[uid]の部分はprismicで作成したdocumentのUIDになるため、
過去作成した全てのファイルを取得する必要があります。
routingの動的設定
prismicから、ブログページのUIDを含むパスを取得する関数を用意する
async function getAllBlogRoutes(): Promise<string[]> {
const client = prismic.createClient(PRISMIC_REPOSITORY);
const pages = await client.getAllByType("blog_post");
return pages.map((page) => {
return `/topics/blog/${page.uid}`;
});
// return [];
}
hookとして、ルーティング情報を事前に追加する
export default defineNuxtConfig({
hooks: {
// fetch async routes to prerender
'prerender:routes': async (ctx: any) =>{
const routes = await getAllBlogRoutes();
if (routes && routes.length) {
routes.forEach((route) => {
ctx.routes.add(route);
});
}
},
},
これで、対象の静的ファイルが出力されるようになります。
$ bun run generate
・・・省略・・・
ℹ Initializing prerenderer
ℹ Prerendering 8 initial routes with crawler
├─ /200.html (83ms)
├─ /404.html (86ms)
├─ / (157ms)
├─ /api/preview (154ms)
├─ /api/preview/_payload.json?302213b5-2cf0-4cf5-b14c-c012295fd514 (3ms) (skipped)
├─ /api/preview/_payload.json (3ms)
├─ /slice-simulator (184ms)
├─ /slice-simulator/_payload.json?302213b5-2cf0-4cf5-b14c-c012295fd514 (0ms) (skipped)
├─ /slice-simulator/_payload.json (1ms)
├─ /topics/blog/first_sample (1226ms)
├─ /topics/blog/first_sample/_payload.json?302213b5-2cf0-4cf5-b14c-c012295fd514 (2ms) (skipped)
├─ /topics/blog/first_sample/_payload.json (2ms)
prismicプレビューの利用(現在、動作エラー中)
ブログ記事を投稿するときは、公開前にページの表示を確認する必要が有ります。
prismicでもプレビュー機能が用意されているので、利用するための設定を行います
公式
公式 Nuxt3でのプレビュー実装
プレビュー設定画面で、サイト名、applicationのドメイン(https://<domain名>)、
必要であればプレビューのrouteパス(デフォルトはpreview)を入力し、保存します。
今回は、nuxt.config.tsで以下のようにpreview pathを指定しているので、これに合わせます
prismic: {
endpoint: 'xxxxx',
preview: '/api/preview',
Nuxtでプレビューを設定
上記、prismicのpreview設定画面で、前画面にscriptの設置が必要と記載があるので、
nuxt.config.tsにコピペ追記して対応
app: {
head: {
script: [
{
src: 'https://static.cdn.prismic.io/prismic.js?new=true&repo=xxxxx',
async: true,
defer: true,
}
]
}
}
(previewがSSGで使えない問題、未解決です。分かる方いらっしゃったらコメントください!)
SSGだと、preview動作しないかも・・・?
I just got my problem solved by using the Route Resolver :)
link resolver、Router Resolverで解決したとの報告が。
link resolver, router resolverとは?何を設定すれば良い?
chatGPTにも聞いてみた
- Link Resolver: PrismicにおけるLink Resolverは、APIから返されるドキュメントのIDを実際のURLに変換する機能を持つ関数です。つまり、それはあなたのアプリケーションのルーティングシステムとPrismicのコンテンツをリンクする方法です。例えば、ブログ投稿のドキュメントがある場合、Link ResolverはそのドキュメントIDを"/blog/[投稿のID]"といった形式のURLに変換します。
- Router Resolver: Nuxt.jsにおけるRouter Resolverは、URLのパスからそれがどのページコンポーネントに対応するかを決定する機能を持つ関数です。Nuxt.jsでは、"pages"ディレクトリ内の.vueファイルの構造に基づいて自動的にルーティングが生成されますが、カスタムのルーティングロジックを制御するためにRouter Resolverを使用することができます。
両者は似ていますが、Link ResolverはAPIからのレスポンスをURLに変換する役割を持ち、一方、Router ResolverはURLを適切なページコンポーネントにリダイレクトする役割を持ちます。
簡単に言うと、prismicのdocumentについて、
そのページのURLがどうなるかを紐付ける仕組みのこと。
この設定は、nuxt.config.ts内で定義しているが、
prismic: {
routes: [
//topics blog
{
type: "blog_post",
path: "/topics/blog/:uid",
},
],
}
linkResolver.jsファイルを配置することでも指定することができ、
linkResolverがある場合は、ここで返り値が返ってきたらそのパスを、
nullが返ってきたら、nuxt.config.ts内のパスを取得する、という優先度の設定になっているらしい。
export default (doc) => {
if (doc.type === "blog_post") {
return `/topics/blog/${doc.uid}`;
}
return null;
};
linkResolverでの動作確認
上記linkResolver.jsがあり、なしどちらもNGだった。
previewのURLをローカルに設定
ローカルに設定して、ローカルでアプリを稼働しておくと、
ちゃんとプレビューが起動して、Publish前のdocumentが確認できた。
上記issueで議論されている、SSGでpreviewが閲覧できない原因と別の原因っぽいが、
原因わからないのでローカルで作業することで運用回避し、保留とする。
中級以上の情報
公式から、必要な項目をピックアップしていく