Nuxt3でプレビュー環境を作成する
Table of contents
このチュートリアルでは、Nuxt3 とNewtのプレビュー設定を利用して、プレビュー環境を作成する手順を紹介します。
記事内で使用している主なソフトウェアのバージョン
- Nuxt(
nuxt
): 3.7.4 - newt-client-js(
newt-client-js
): 3.2.7
概要
Nuxt3で静的生成(SSG)されているブログサイトに、プレビュー用のページを追加し、サーバーサイドレンダリング(SSR)でプレビューデータを取得できるようにします。
また、Newtのコンテンツ編集画面から、作成したプレビュー環境にアクセスできるようにします。ホスティングには Vercel を利用します。
ここでは、以下の流れでプレビュー処理を行うものとして、実装を進めていきます。
- Newtの管理画面から「プレビュー」ボタンをクリックする
- プレビュー用のページ
/articles/preview
に、secret
とslug
のクエリパラメータをつけてアクセスする secret
とslug
の値を検証し、問題なければプレビューデータ(下書きデータ)を取得して表示する
ここでは NewtとNuxt3を利用してブログを作成する で作成したブログに対して、プレビュー設定を追加する方法を紹介します。
もし設定したいパスが異なる場合は、適宜読み替えながらチュートリアルを進めてください。
Nuxt3でプレビューをどう実装するか
具体的な方法の紹介に入る前に、Nuxt3で静的生成(SSG)されているサイトにプレビューを追加する場合、どのようなパターンがあるか改めて確認しておきましょう。
まず、Nuxt2の時にあった Preview Mode ですが、Nuxt3では記載がありません(詳細については こちら のissueに記載があります)。
そこで、以下のような方法の中から、サイトの特性や要件に応じて選択する必要があります。
- プレビュー用の環境を用意して、環境に応じてレンダリング方法を制御する
- サイトをサーバーサイドレンダリング(SSR)に変更して、クエリに応じて公開情報を表示するか、プレビューを表示するか決定する
- プレビュー専用ページを用意して、そのページだけサーバーサイドレンダリング(SSR)で表示する
ここでは、サイト自体はプリレンダリングを利用して静的生成したまま、プレビュー用の環境を用意する必要もない「プレビュー専用ページを用意して、そのページだけサーバーサイドレンダリング(SSR)で表示する」方法について紹介します。
通常ページと、プレビュー専用ページの設定が同一となるよう、コードの共通化を行う手間は発生しますが、以下の内容については満たせる方法となっています。
- 通常ページはプリレンダリングを行い、静的に生成される
- NewtのTokenが漏洩しない
- プレビューページが検索エンジンにインデックス登録されない
それでは、具体的なプレビュー環境の作成に進みましょう。
1. Newt API Tokenを作成する
はじめに、Newtの管理画面に入り、スペース設定 > APIキー のページからNewt API Tokenを作成します。
※ 下書き中のコンテンツを取得するためには、Newt APIを利用します。
名前と取得対象を決めて「作成」を押します。
2. プレビュー用のクライアントを作成する
2-1. 環境変数の設定
Nuxtの Runtime Config を設定して、環境変数を利用できるようにします。
まず、.env
ファイルを作成し、以下を実際の値で置き換えて定義してください。
NUXT_NEWT_SPACE_UID
には、プレビューデータの取得対象となるスペースUIDの値を設定します。
NUXT_NEWT_CDN_API_TOKEN
・NUXT_NEWT_API_TOKEN
にはNewtの管理画面で作成したTokenの値を設定します。
NUXT_NEWT_PREVIEW_SECRET
はプレビューリクエストが有効なものであるか検証するために利用します。ご自身で定めたシークレットを入力してください。
1NUXT_NEWT_SPACE_UID=sample-for-docs
2NUXT_NEWT_CDN_API_TOKEN=xxxxxxxxxxxxxxx
3NUXT_NEWT_API_TOKEN=xxxxxxxxxxxxxxx
4NUXT_NEWT_PREVIEW_SECRET=hogehoge
あわせて、nuxt.config.ts
に以下のように runtimeConfig
の設定を追加します。
export default defineNuxtConfig({
(省略)
runtimeConfig: {
newt: {
spaceUid: '',
cdnApiToken: '',
apiToken: '',
previewSecret: ''
}
},
(省略)
})
Runtime Confingは実行時にマッチする環境変数に自動的に置き換えられるため、上記のように定義しておくと、runtimeConfig.newt.apiToken
の値は .env
ファイルの NUXT_NEWT_API_TOKEN
の値に置き換えられ、runtimeConfig.newt.previewSecret
の値は .env
ファイルの NUXT_NEWT_PREVIEW_SECRET
の値に置き換えられます。
また、これらの変数はサーバーサイドでのみ参照できる、プライベートな変数となります。
Runtime Configの詳細については、Nuxtの Runtime Config のドキュメントをご確認ください。
2-2. プレビュー用のクライアントを作成する
プラグインに、Newt API用のクライアントを追加します。
ここでは newtPreviewClient
という名前で追加しています。
プラグインでは、ファイル名に .server
または .client
というサフィックスを付けると、サーバー側またはクライアント側でのみプラグインを読み込むことができます。
ここではサーバーサイドでのみ読み込めれば良いので、plugins
ディレクトリを作成し、その中に newt.server.ts
というファイルを作成します。
1import { createClient } from 'newt-client-js'
2
3export default defineNuxtPlugin(() => {
4 const config = useRuntimeConfig()
5
6 const newtClient = createClient({
7 spaceUid: config.newt.spaceUid,
8 token: config.newt.cdnApiToken,
9 apiType: 'cdn'
10 })
11
12 const newtPreviewClient = createClient({
13 spaceUid: config.newt.spaceUid,
14 token: config.newt.apiToken,
15 apiType: 'api'
16 })
17
18 return {
19 provide: {
20 newtClient,
21 newtPreviewClient
22 }
23 }
24})
3. プレビュー用のページを作成する
通常の詳細ページとして、以下が定義されているとします。
1<script lang="ts" setup>
2import type { Article } from '~/types/article'
3
4const route = useRoute()
5const { slug } = route.params
6
7const { data } = await useAsyncData(`article-${slug}`, async () => {
8 const { $newtClient } = useNuxtApp()
9 return await $newtClient.getFirstContent<Article>({
10 appUid: 'blog',
11 modelUid: 'article',
12 query: {
13 slug,
14 select: ['_id', 'title', 'slug', 'body']
15 }
16 })
17})
18const article = data.value
19
20useHead({
21 title: article?.title,
22 meta: [
23 { name: 'description', content: '投稿詳細ページです' }
24 ]
25})
26</script>
27
28<template>
29 <main class="main">
30 <h2>{{ article?.title }}</h2>
31 <!-- eslint-disable-next-line vue/no-v-html -->
32 <div v-html="article?.body" />
33 </main>
34</template>
この内容を元に、プレビューページを作成しましょう。
パスを /articles/preview
とする場合、pages/articles/preview.vue
を作成します。
クエリパラメータの slug
と secret
の検証を行い、問題がなければ $newtPreviewClient
を利用して、プレビューデータ(下書きデータ)の取得を行います。
検索エンジンによるページのインデックスを防ぐために useHead
の meta
に { name: 'robots', content: 'noindex,nofollow' }
を設定するのも忘れないようにしましょう。
1<script lang="ts" setup>
2import type { Article } from '~/types/article'
3
4const config = useRuntimeConfig()
5const route = useRoute()
6const { slug, secret } = route.query
7
8// slugパラメータの有無を検証する
9if (!slug) {
10 throw createError({ statusCode: 400, statusMessage: 'Invalid slug' })
11}
12
13// secretを検証する
14if (config.newt && secret !== config.newt.previewSecret) {
15 throw createError({ statusCode: 401, statusMessage: 'Invalid secret' })
16}
17
18const { data } = await useAsyncData(`article-${slug}-preview`, async () => {
19 const { $newtPreviewClient } = useNuxtApp()
20 return await $newtPreviewClient.getFirstContent<Article>({
21 appUid: 'blog',
22 modelUid: 'article',
23 query: {
24 slug: slug?.toString(),
25 select: ['_id', 'title', 'slug', 'body']
26 }
27 })
28})
29const article = data.value
30
31useHead({
32 title: article?.title,
33 meta: [
34 { name: 'description', content: '投稿詳細ページです' },
35 { name: 'robots', content: 'noindex,nofollow' }
36 ]
37})
38</script>
39
40<template>
41 <main class="main">
42 <h2>{{ article?.title }}</h2>
43 <!-- eslint-disable-next-line vue/no-v-html -->
44 <div v-html="article?.body" />
45 </main>
46</template>
また、このままだと pages/articles/[slug].vue
を更新する時に pages/articles/preview.vue
も更新することになるため、必要であれば共通のコンポーネントを利用するなどして、コードの共通化を行うといいでしょう。
4. レンダリングの設定を行う
続いてレンダリングの設定を行います。
もともと静的生成されているサイトの場合、nuxi generate を用いている場合が多いかと思いますが、プレビューではサーバーサイドレンダリング(SSR)を行うため、設定を変更します。
プレビュー以外のパスはプリレンダリング、プレビュー用のパスはプリレンダリングなしでビルドを行います。
ビルドコマンドに nuxi build を利用し、configファイルの nitro から nitro.prerender を設定します。
プリレンダリングしたいページが、すべてクロール可能である場合、crawlLinks
のオプションを true
にするのが簡単です。
nuxt.config.ts
に、以下を追加しましょう。
export default defineNuxtConfig({
(省略)
nitro: {
prerender: {
crawlLinks: true
}
},
(省略)
})
crawlLinks
以外に、手動でページ追加したい場合は routes
オプションを利用することも可能です。詳細はNuxtの Selective Pre-rendering のドキュメントをご確認ください。
もし、nitro.prerender
で用意されているオプションでは不十分な場合、hooks
オプションを利用して、より詳細な設定を行うことも可能です。
例えば、blog
モデルの全ての article
について詳細ページをプリレンダリングする場合は、以下のようになります。
1import { createClient } from 'newt-client-js'
2import type { Article } from './types/article'
3
4const newtClient = createClient({
5 spaceUid: process.env.NUXT_NEWT_SPACE_UID + '',
6 token: process.env.NUXT_NEWT_CDN_API_TOKEN + '',
7 apiType: 'cdn'
8})
9
10export default defineNuxtConfig({
11 (省略)
12 hooks: {
13 async 'nitro:config' (nitroConfig) {
14 if (nitroConfig.dev || !nitroConfig.prerender) {
15 return
16 }
17
18 const { items: articles } = await newtClient.getContents<Article>({
19 appUid: 'blog',
20 modelUid: 'article',
21 query: {
22 select: ['_id', 'title', 'slug', 'body']
23 }
24 })
25
26 // 全記事の詳細ページを追加
27 nitroConfig.prerender.routes = articles.map((article) => {
28 return `/articles/${article.slug}`
29 })
30
31 // 一覧ページを追加
32 nitroConfig.prerender.routes.push('/')
33 }
34 },
35 (省略)
36})
5. デプロイの設定を行う
Vercelへのデプロイを設定します。
まず、GitHubのリポジトリを作成し、これまで作成したコードをプッシュします。
詳細はGitHubの リポジトリを作成する のドキュメントをご確認ください。
続いて、作成したリポジトリとVercelを接続します。
※ Vercel のアカウントを持っていない方は、登録をお願いします。
接続方法の詳細については、GitHubのリポジトリとVercelを接続して、ホスティングする のチュートリアルを参考にしてください。
「Framework Preset」に「Nuxt.js」が設定されていることを確認し、「Build Command」に「nuxt build」を指定しましょう。「nuxt generate」ではないのでご注意ください。
また「Environment Variables」に追加した環境変数を設定しましょう。
6. プレビュー設定を行う
最後に、Newtの管理画面に入り、プレビュー設定を行います。
モデル設定の右上から「プレビュー設定」に進みます。
プレビュー用のAPIルート /articles/preview
に secret
と slug
のクエリパラメータをつけてアクセスするよう、プレビューURLを指定します。
サイトのドメインが https://nuxt-preview.vercel.app
、secretが hogehoge
の場合、プレビューURLは以下のように指定します。
https://nuxt-preview.vercel.app/articles/preview?secret=hogehoge&slug={slug}
モデルが slug
というフィールドを持つ場合、{slug}
のように記載することで、各コンテンツのslugの値がプレビューURLに展開されます。
以上ですべての設定ができました。
コンテンツ編集画面からプレビューが見れるか確認しましょう。
もし、プレビューが見れない場合は、プレビューURLが正しく指定されているか、tokenやsecretの値が正しいか確認してみてください。