みなさん、コレクションしていますか?
わが家には鉱物や花瓶、ネイル用品、アクリル絵の具、本、刺繍糸、万年筆インクなどなどがひしめき合っています。
しかし色んなアイテムを集めていると、時折店頭で頭を悩ませることになります。「これ、もう持ってたっけ?」というやつです。前述の中ではネイルや絵の具、糸、インクが特にそういう事態になりがちです。「あれ、この色持ってたっけ?」「この色綺麗だけど、今持っているのと似てない?」「これまだあったような、切らしてたような……」
というわけで、今回は我が家の蒐集品の中でもこんがらがりがちな万年筆インクを、Headless CMS「Newt」とNuxt3、ホスティングサービス「netlify」を用いたJamstackで管理してみようと思います。
まずは新規スペースを作成します。今回は名前をインクコレクションとしていますが、スペースは「複数のAppを束ねる」ものなので、チーム名やプロジェクト名を設定するのがよいかと思います。
次にAppを設定します。「Appを追加」から始めましょう。
「タイプを選択して追加」を選びます。今回はCMSを使うので、「CMS App」を選択しましょう。
App名とApp UIDを設定します。今回はApp名もインクコレクションとしています。ひとつ一つのAppは、例えば一つのコンテンツ、一つのブログなどに当たります。アイコンは今回は使いませんが、好きなものを選んでおきます。
すると今度はモデルの作成を求められました。ここでは各アイテム(記事、コンテンツなど)がどんなフィールドを持つかを設定します。
まずはモデル名とUIDを決めます。ラベルは「2件のアイテム」など、管理画面での表示に関わります。英語で設定する場合は「ラベル(複数形)」に複数形(items)を入れるのがよいでしょう。
そしてフィールドを設定します。今回はインクを管理するので、「インク名、メーカー名、色、ボトル画像、インク画像」をフィールドとして持たせます。
色は選択肢から選ぶようにしました。
次はAppの画面に戻り、ここに今追加したフィールドに沿ってデータを入れていきます。
「アイテムの追加」を押下すると入力画面が開かれます。インクのデータを入力して「保存して公開」しましょう。
無事アイテムが登録できました。
次にCDN APIの設定をします。App名にカーソルを合わせると右にメニューが出るので、ここからApp設定に飛びましょう。
そして左メニューから「APIキー」を選択します。するとTokenを作成する画面に遷移するので、上の「Newt CDN API Token」の「作成」ボタンを押しましょう。
任意の名前を付け、「作成」を押下します。
これでNewt側での設定は終わりました。次に、Nuxtのコードを書いていきます。
最初にNuxtのセットアップを行います。任意のディレクトリで、次のコマンドを叩きます。
npx nuxi init <hogehoge>
<hogehoge>には任意のプロジェクト名を入れてください。途中で出てくる選択肢については、お好みのものを選びましょう。今回はnpmを選択しています。
生成が終わったら、cd <hogehoge>でディレクトリを移動し、npm run devを叩いてみます。http://localhost:3000/が立ち上がるのを確認しましょう。
次に、TypeScriptの型チェックのためにプラグインを入れます。
npm i -D vue-tsc@^1 typescript
nuxt.config.tsファイルを開き、次の設定を追加します。
export default defineNuxtConfig({
devtools: { enabled: true },
typescript: {
strict: true,
typeCheck: true
}
}
これでローカルでも型チェックが行えるようになります。
続いて.envファイルを作成します。nuxt.config.tsがあるのと同じ階層に.envを作成し、次の内容を記載します。
NUXT_PUBLIC_NEWT_SPACE_UID=ink-collection
NUXT_PUBLIC_NEWT_CDN_API_TOKEN=*******
「NUXT_PUBLIC_NEWT_SPACE_UID」に入るのは、Newtで最初にスペースを作成したときに設定したUID名、「NUXT_PUBLIC_NEWT_CDN_API_TOKEN」に入るのは最後に作成したAPI Tokenです。次に、nuxt.config.tsへruntime configを追加します。
,
runtimeConfig: {
public: {
newt: {
spaceUid: '',
cdnApiToken: '',
}
}
}
runtime configを使うと、実行時に内容が対応する.envファイルのものに置き換わります。publicで指定したものは、クライアントサイドとサーバーサイドの双方で利用できます。この値には、Nuxtの「useRuntimeConfig」からアクセスできます。
次に「newt-client-js」をインストールしてください。これはNewtのSDKです。
npm i newt-client-js
そしてnewt-client-jsを利用するためにpluginを作成します。pulginsディレクトリを作成し、newt.tsファイルをその中に作ります。
import { createClient } from 'newt-client-js'
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig()
const newtClient = createClient({
spaceUid: config.public.newt.spaceUid,
token: config.public.newt.cdnApiToken,
apiType: 'cdn'
})
return {
provide: {
newtClient
}
}
})
また、nuxt.config.tsに言語設定も追加しておきます。ついでにcomposablesディレクトリ配下をすべて参照するように書き加えておきましょう。
app:{
head:{
htmlAttrs: {
lang: 'ja'
}
}
},
imports: {
dirs: [
'composables/**'
]
}
次に、取得するアイテムの型を設定します。typesディレクトリを作成し、中にink.tsを作ります。
export interface Ink {
_id: string
name: string
maker: string
color: string
bottleImage: bottleImage
inkImage: inkImage
}
export interface bottleImage{
altText: string
src: string
}
export interface inkImage{
altText: string
src: string
}
型を設定したら、今度はAPIを叩く部分を作ります。composablesディレクトリを作り、中にInksApi.tsファイルを作成してください。
import type { Ink } from '~/types/ink'
export const useInkApi = async () => {
try {
const { data } = await useAsyncData('inks', async () => {
const { $newtClient } = useNuxtApp()
return await $newtClient.getContents<Ink>({
appUid: 'ink',
modelUid: 'item',
query: {
select: ['_id', 'name', 'maker', 'color', 'bottleImage', 'inkImage']
}
})
})
return data.value
} catch (err: any) {
console.log(err)
return false
}
}
composable名は基本的にuseから始まるので、useInkApiとしました。getContents<Ink>のように書くことで、Ink[]型のデータが返ってきます。appUidには、NewtのApp設定画面から確認できる「App UID」を設定します。modelUidも同様に、モデル設定から確認することができます。queryの「select」には、取得したいフィールドを記載します。
それでは実際にアイテムを取得してみましょう。まずapp.vueの記載を次のように修正します。
<template>
<div>
<NuxtPage />
</div>
</template>
そしてpagesディレクトリ配下にindex.vueを作成し、次のように記述します。
<template>
<div>
<ul class="ink-list" v-if="inks">
<li v-for="ink in inks" :key="ink._id" class="ink-list__item">
<div class="ink-list__images">
<p class="ink-list__bottle">
<img :src="ink.bottleImage.src" alt="">
</p>
<p class="ink-list__ink">
<img :src="ink.inkImage.src" alt="">
</p>
</div>
<table class="ink-list__param">
<tr>
<th>インク名:</th><td>{{ ink.name }}</td>
</tr>
<tr>
<th>メーカー名:</th><td>{{ ink.maker }}</td>
</tr>
<tr>
<th>色:</th><td>{{ ink.color }}</td>
</tr>
</table>
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import type { Ink } from '~/types/ink'
const res = await useInkApi()
const inks: Ink[] | null | false = res ? res.items : null
</script>
<style lang="scss">
.ink-list{
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
margin: 0 auto;
width: 720px;
list-style: none;
.ink-list__item{
padding: 10px;
border: 2px solid #999;
border-radius: 10px;
.ink-list__images{
display: flex;
}
.ink-list__bottle{
flex: 1;
img{
width: 100%;
height: auto;
object-fit: cover;
}
}
.ink-list__ink{
flex: 1;
img{
width: 100%;
height: auto;
object-fit: cover;
}
}
}
}
</style>
これでnpm run devすると、画面が表示されるはずです。
これでローカルでは完成……、おっと、色の表示が想定されたものではないですね。まずtemplateの色表示部分を直します。
<th>色:</th><td>{{ colorSet[ink.color] }}</td>
次にscript部分にコードを足します。key側にも型を指定する必要があります。
interface ColorSet {
[key: string]: string
}
const colorSet: ColorSet = {
red: '赤',
orange: '橙',
yellow: '黄',
green: '緑',
blue: '青',
indigo: '藍',
purple: '紫',
pink: '桃',
black: '黒',
blueBlack: 'ブルーブラック'
}
これで色名も反映されました。
ローカルで表示確認できたところで、次はnetlifyでホスティングしましょう。
netlifyでAdd new siteから「Import an existing project」を選びましょう。
今回はソース管理にGitHubを使用しているため、gitHubを選択します。
するとリポジトリ一覧が表示されるので、今回使用するリポジトリを選択します。
サイト名(URL)、デプロイに使うブランチを設定します。
ビルドの設定も行いましょう。
また、環境変数の設定が必要になります。Add Environment variablesから設定しましょう。
.envの内容を転記します。
終わったら「Deploy inkcollection」ボタンを押してみましょう。site overview画面に遷移し、ページの中ほどに進捗情報が表示されます。Publishedになったら完了です。
サイトURLをクリックしましょう。ローカルで見たのと同じページが表示されたかと思います。
これで、自分の持っているインクを登録しておけば、どこからでも確認できるようになりました。店頭で頭を悩ませることもなくなりますね! 今回はシンプルにインクを一覧表示していますが、メーカー名や色でソートできるようにすると、使い勝手がよくなりそうです。