NewtとQwik Cityを利用してブログを作成する
Table of contents
- 記事内で使用している主なソフトウェアのバージョン
- 概要
- 1. Qwikのセットアップ
- 2. Newtのセットアップ
- 2-1. Appを追加する
- 2-2. スペースUID・App UID・モデルUIDを確認する
- 2-3. Newt CDN API Tokenを作成する
- 3. リクエストの準備
- 3-1. 環境変数の設定
- 3-2. newt-client-jsのインストール
- 3-3. APIクライアントの作成
- 4. 一覧ページの作成
- 4-1. 言語の設定をする
- 4-2. 投稿の型を定義する
- 4-3. 投稿一覧の取得メソッドを作成する
- 4-4. 投稿一覧を表示する
- 5. 詳細ページの作成
- 5-1. 投稿詳細の取得メソッドを作成する
- 5-2. 投稿詳細を表示する
- 6. Cloudflare Pagesにデプロイする
- 6-1. Cloudflare Pages Adapterを設定する
- 6-2. axiosにadapterを設定する
- 6-3. GitHubのリポジトリを作成し、Cloudflare Pagesと接続する
このチュートリアルでは、Newtと Qwik City を利用して、ブログを作成する手順を紹介します。
具体的には、Newtで管理しているコンテンツの一覧ページと詳細ページを作る手順を紹介した後、Cloudflare Pages にデプロイして、サーバーサイドレンダリング(SSR)でページを表示します。
記事内で使用している主なソフトウェアのバージョン
- Qwik(
@builder.io/qwik
): 1.2.11 - Qwik City(
@builder.io/qwik-city
): 1.2.11 - newt-client-js(
newt-client-js
): 3.2.6 - axios-fetch-adapter(
@vespaiach/axios-fetch-adapter
): 0.3.1
概要
Qwik Cityでプロジェクトを作成し、Newtのコンテンツ情報を取得できるようにします。
コンテンツの一覧ページ(パス: /
)と詳細ページ(パス: /articles/:slug
。slugがarticle-1の場合は /articles/article-1
)を作成し、まずローカル環境で表示できるようにします。
続いて、Cloudflare Pagesにデプロイし、サーバーサイドレンダリングでページを表示します。
1. Qwikのセットアップ
はじめに、Qwikのセットアップを行います。qwik-create-cli-build を利用することで、簡単にQwikのプロジェクトを作成できます。
以下のどちらかのコマンドを入力します。
npm create qwik@latest
# or
yarn create qwik
コマンドを入力すると、以下の質問を聞かれるので、お好きな設定を選びましょう。
- どこにプロジェクトを作成するか(ここでは
./qwik-blog
と入力) - スターターの選択(ここでは
Empty App
を選択) - dependenciesをインストールするか(ここでは
No
を選択) - gitリポジトリを初期化するか?(ここでは
Yes
を選択)
以下のように表示されます。
$ npm create qwik@latest
┌ Let's create a Qwik App ✨ (v1.2.11)
│
◇ Where would you like to create your new project? (Use '.' or './' for current directory)
│ ./qwik-blog
│
● Creating new project in /Users/foo/bar/qwik-blog ... 🐇
│
◇ Select a starter
│ Empty App
│
◇ Would you like to install npm dependencies?
│ No
│
◇ Initialize a new git repository?
│ Yes
│
◇ App Created 🐰
│
◇ Git initialized 🎲
│
○ Result ─────────────────────────────╮
│ │
│ 🦄 Success! Project created in qwik-blog directory │
│ │
│ 🤍 Integrations? Add Netlify, Cloudflare, Tailwind... │
│ npm run qwik add │
│ │
│ 📄 Relevant docs: │
│ https://qwik.builder.io/docs/getting-started/ │
│ │
│ 💬 Questions? Start the conversation at: │
│ https://qwik.builder.io/chat │
│ https://twitter.com/QwikDev │
│ │
│ 👀 Presentations, Podcasts and Videos: │
│ https://qwik.builder.io/media/ │
│ │
│ 🐰 Next steps: │
│ cd qwik-blog │
│ npm install │
│ npm start │
├─────────────────────────────────╯
│
└ Happy coding! 🎉
作成したプロジェクトに移動して、開発サーバーを立ち上げます。
$ cd qwik-blog
$ yarn install
$ yarn start
http://localhost:5173
にアクセスして、以下のような画面が表示されることを確認します。
2. Newtのセットアップ
次にNewtにコンテンツとAPIトークンを用意し、コンテンツの取得を行うための準備を行います。
2-1. Appを追加する
「Appを追加」をクリックして「テンプレートから追加」を選択します。
表示されるテンプレートの中から「Blog」を選択して、「このテンプレートを追加」をクリックします。
テンプレートが追加されると、「投稿データ」「タグデータ」「著者データ」が追加されます。
2-2. スペースUID・App UID・モデルUIDを確認する
スペースUIDは「スペース設定」から確認できます。
上記の例だと、スペースUIDは sample-for-docs
となります。
この値は3-1で環境変数として定義します。
また「Blog」テンプレートを追加した場合、App UIDは blog
、「投稿データ」モデルUIDは article
となります。
これらの値は、4-3や5-1で投稿情報を取得する際に利用します。
2-3. Newt CDN API Tokenを作成する
続いて、APIリクエストに必要なトークンを発行します。
スペース設定 > APIキー のページからNewt CDN API Tokenを作成します。
名前と取得対象を決めて「作成」を押します。
ここで作成したトークンの値は3-1で環境変数として定義します。
3. リクエストの準備
Newtの SDK を利用することで、NewtのAPIをより簡単に利用できます。
ここではSDKを利用して、NewtのAPIクライアントを作成します。
3-1. 環境変数の設定
Qwik Cityの環境変数には Build-time variables と Server-side variables の2種類があります。
「Build-time variables」はビルド時に読み込まれ、ブラウザでもサーバーサイドでも利用可能な環境変数です。先頭に PUBLIC_
を付ける必要があります。
「Server-side variables」はサーバーサイドでのみ利用可能な環境変数です。ビルド時には読み込まれず、ブラウザでも利用できません。プライベートな変数の場合はこちらを利用します。
このチュートリアルでは、サーバーサイドレンダリング(SSR)を行うため、「Server-side variables」を利用します。静的生成(SSG)ではないため、ビルド時に環境変数がわかる必要もありません。
.env.local
ファイルを作成し、2-2で確認したスペースUID、2-3で作成したトークンの値を定義します。以下を、実際の値で置き換えて定義してください。
NEWT_SPACE_UID=sample-for-docs
NEWT_CDN_API_TOKEN=xxxxxxxxxxxxxxx
上記のように定義しておくと、RequestEvent オブジェクトから、requestEvent.env.get('NEWT_SPACE_UID')
や requestEvent.env.get('NEWT_CDN_API_TOKEN')
として利用できます。
3-2. newt-client-jsのインストール
次に newt-client-js をインストールします。
npm install newt-client-js
# or
yarn add newt-client-js
3-3. APIクライアントの作成
CDN APIのクライアントを作成する関数を用意します。
スペースUIDとトークンの値を引数として渡すことで、クライアントを取得できるようにします。
ここではCDN APIを利用するので、apiType
には cdn
を指定します。
// src/libs/newt.ts
import { createClient } from 'newt-client-js'
export const generateClient = (spaceUid: string, token: string) => {
return createClient({
spaceUid,
token,
apiType: 'cdn',
})
}
これで、NewtにAPIリクエストを送るための準備ができました。
4. 一覧ページの作成
4-1. 言語の設定をする
src/entry.ssr.tsx
の Containers Attributes で設定されている lang
属性を修正します。ここでは日本語 ja
を指定します。
(省略)
export default function (opts: RenderToStreamOptions) {
return renderToStream(<Root />, {
manifest,
...opts,
// Use container attributes to set attributes on the html tag.
containerAttributes: {
- lang: 'en-us',
+ lang: 'ja',
...opts.containerAttributes,
},
})
}
また、src/root.tsx
にある lang 属性も修正します。
(省略)
return (
<QwikCityProvider>
<head>
<meta charSet="utf-8" />
<link rel="manifest" href="/manifest.json" />
<RouterHead />
</head>
- <body lang="en">
+ <body lang="ja">
<RouterOutlet />
<ServiceWorkerRegister />
</body>
</QwikCityProvider>
)
})
4-2. 投稿の型を定義する
投稿の型 Article
を定義します。
このチュートリアルでは、_id
・title
・slug
・body
のみを使うため、以下のように定義しておきます。
// src/types/article.ts
export interface Article {
_id: string
title: string
slug: string
body: string
}
4-3. 投稿一覧の取得メソッドを作成する
Qwik Cityはファイルシステムベースのルーティングを採用しており、src/routes
ディレクトリの配下にファイルを作成すると、自動的にルートとして利用できるようになります。
例えば、以下のようにルーティングされます。
src/routes/blog/index.tsx
→/blog
src/routes/blog/first-post/index.tsx
→/blog/first-post
※ ルーティングの詳細については、Qwik Cityの Routing のドキュメントをご確認ください。
ここではトップページ(パス: /
)で投稿一覧を表示したいので、src/routes/index.tsx
のファイルを修正します。
Qwik Cityでは routeLoader$ を利用して、外部のCMSやデータベースからデータの取得を行います。
また、routeLoader$
は RequestEvent を通じて環境変数を取得できます。routeLoader$(async ({ env })
のように引数に env
を指定し、env.get('NEWT_SPACE_UID')
や env.get('NEWT_CDN_API_TOKEN')
とすると、環境変数を取得できます。
// src/routes/index.tsx
import { routeLoader$ } from '@builder.io/qwik-city'
import { generateClient } from '../libs/newt'
export const useArticles = routeLoader$(async ({ env }) => {
const spaceUid = env.get('NEWT_SPACE_UID') || ''
const token = env.get('NEWT_CDN_API_TOKEN') || ''
const client = generateClient(spaceUid, token)
})
続いて、投稿一覧を取得するために、SDKが提供している getContents メソッドを利用します。getContentsはNewtのコンテンツ一覧を取得するためのメソッドです。getContentsのパラメータに Article
の型を渡すことで、返却される items
の型として Article[]
が指定されます。
また、selectパラメータを利用して、取得するフィールドを _id
・title
・slug
・body
のみに制限します。
// src/routes/index.tsx
import { routeLoader$ } from '@builder.io/qwik-city'
import { generateClient } from '../libs/newt'
import type { Article } from '~/types/article'
export const useArticles = routeLoader$(async ({ env }) => {
const spaceUid = env.get('NEWT_SPACE_UID') || ''
const token = env.get('NEWT_CDN_API_TOKEN') || ''
const client = generateClient(spaceUid, token)
const { items: articles } = await client.getContents<Article>({
appUid: 'blog',
modelUid: 'article',
query: {
select: ['_id', 'title', 'slug', 'body'],
},
})
return articles
})
4-4. 投稿一覧を表示する
次に、投稿一覧を表示します。src/routes/index.tsx
は以下のようになります。
useArticles()
で取得した articles
は Readonly<Signal<Article[]>>
という型になっているので、articles.value
のようにすると投稿一覧の情報を取得できます。
// src/routes/index.tsx
import { component$ } from '@builder.io/qwik'
import { routeLoader$ } from '@builder.io/qwik-city'
import { generateClient } from '../libs/newt'
import type { DocumentHead } from '@builder.io/qwik-city'
import type { Article } from '~/types/article'
export const useArticles = routeLoader$(async ({ env }) => {
const spaceUid = env.get('NEWT_SPACE_UID') || ''
const token = env.get('NEWT_CDN_API_TOKEN') || ''
const client = generateClient(spaceUid, token)
const { items: articles } = await client.getContents<Article>({
appUid: 'blog',
modelUid: 'article',
query: {
select: ['_id', 'title', 'slug', 'body'],
},
})
return articles
})
export default component$(() => {
const articles = useArticles()
return (
<main>
<ul>
{articles.value.map((article) => {
return (
<li key={article._id}>
<a href={`articles/${article.slug}`}>{article.title}</a>
</li>
)
})}
</ul>
</main>
)
})
export const head: DocumentHead = {
title: 'Newt・Qwik Cityブログ',
meta: [
{
name: 'description',
content: 'NewtとQwik Cityを利用したブログです',
},
],
}
ここでは、getArticles
で取得した投稿一覧を表示しています。
また、メタデータを設定するために head
をエクスポートしています。詳細についてはQwik Cityの Pages のドキュメントをご確認ください。
http://localhost:5173/
にアクセスして、以下のように投稿一覧が表示されれば成功です。
5. 詳細ページの作成
5-1. 投稿詳細の取得メソッドを作成する
Qwik Cityで 動的ルーティング を設定するためには、src/routes/blog/[slug]/index.tsx
や src/routes/user/[username]/index.tsx
のように、角括弧を使って動的なパラメータを定義します。
ここでは /articles/:slug
(/articles/article-1
など)のパスで投稿の詳細を表示したいので、src/routes/articles/[slug]/index.tsx
のファイルを作成します。
投稿一覧の取得と同様に、まず環境変数を取得し、Newtクライアントを作成します。
続いて、指定したスラッグのコンテンツを取得するために getFirstContent メソッドを利用しています。
動的パラメータへのアクセスも Request Event を経由してできるため、routeLoader$(async ({ env, params, fail })
のように指定することで、params.slug
で動的パラメータ slug
を取得できます。
さらに failメソッド を使うことによって、投稿が見つからなかった場合は404のステータスを返すようにしています。
// src/routes/articles/[slug]/index.tsx
import { routeLoader$ } from '@builder.io/qwik-city'
import { generateClient } from '~/libs/newt'
import type { Article } from '~/types/article'
export const useArticle = routeLoader$(async ({ env, params, fail }) => {
const spaceUid = env.get('NEWT_SPACE_UID') || ''
const token = env.get('NEWT_CDN_API_TOKEN') || ''
const client = generateClient(spaceUid, token)
const article = await client.getFirstContent<Article>({
appUid: 'blog',
modelUid: 'article',
query: {
slug: params.slug,
select: ['_id', 'title', 'slug', 'body'],
},
})
if (!article) {
return fail(404, {
errorMessage: 'Not found',
})
}
return article
})
5-2. 投稿詳細を表示する
次に、投稿詳細を表示します。
src/routes/articles/[slug]/index.tsx
は以下のようになります。
// src/routes/articles/[slug]/index.tsx
import { component$ } from '@builder.io/qwik'
import { routeLoader$ } from '@builder.io/qwik-city'
import { generateClient } from '~/libs/newt'
import type { DocumentHead } from '@builder.io/qwik-city'
import type { Article } from '~/types/article'
export const useArticle = routeLoader$(async ({ env, params, fail }) => {
const spaceUid = env.get('NEWT_SPACE_UID') || ''
const token = env.get('NEWT_CDN_API_TOKEN') || ''
const client = generateClient(spaceUid, token)
const article = await client.getFirstContent<Article>({
appUid: 'blog',
modelUid: 'article',
query: {
slug: params.slug,
select: ['_id', 'title', 'slug', 'body'],
},
})
if (!article) {
return fail(404, {
errorMessage: 'Not found',
})
}
return article
})
export default component$(() => {
const article = useArticle()
if (article.value.errorMessage) {
return <h1>{article.value.errorMessage}</h1>
}
return (
<>
<h1>{article.value.title}</h1>
<div dangerouslySetInnerHTML={article.value.body} />
</>
)
})
export const head: DocumentHead = ({ resolveValue }) => {
const article = resolveValue(useArticle)
return {
title: article?.title,
meta: [
{
name: 'description',
content: '投稿詳細ページです',
},
],
}
}
※ bodyの表示で利用されている dangerouslySetInnerHTML はXSSの危険性があるため、利用には注意が必要です。ここでは、Newtで管理している投稿情報を表示するものであり、不特定多数のユーザーが入力できるものを表示するわけではないため、安全なものとして利用しています。
また、head
の値を動的に生成するため、resolveValue
を使用しています。resolveValue
を使用すると、routeLoader$
で取得した値を head
関数の中で利用できます。
詳細については、Qwikの Dynamic head のドキュメントをご確認ください。
これで、投稿詳細についての設定も完了です。
http://localhost:5173/articles/article-3/
にアクセスして、以下のように投稿詳細が表示されれば成功です。
6. Cloudflare Pagesにデプロイする
続いて、Cloudflare Pagesへのデプロイを設定します。
6-1. Cloudflare Pages Adapterを設定する
Cloudflare Pages Adapter を利用しましょう。
以下のどちらかを実行します。
npm run qwik add cloudflare-pages
# or
yarn qwik add cloudflare-pages
実行すると、以下のように表示されます。
cloudflare-pages
のアップデートを適用していいか聞かれるので、Yes
を選択します。
┌ 🦋 Add Integration cloudflare-pages
│
◇ 👻 Ready? Add cloudflare-pages to your app?
│
│ 🐬 Modify
│ - package.json
│ - .gitignore
│ - README.md
│
│ 🌟 Create
│ - .node-version
│ - src/entry.cloudflare-pages.tsx
│ - public/_headers
│ - public/_redirects
│ - adapters/cloudflare-pages/vite.config.ts
│
│ 💾 Install yarn dependency:
│ - wrangler ^3.0.0
│
│ 📜 New yarn script:
│ - yarn build.server
│ - yarn deploy
│ - yarn serve
│
◇ Ready to apply the cloudflare-pages updates to your app?
│ Yes looks good, finish update!
│
◇ App updated
│
○ New scripts added ───╮
│ │
│ - yarn build.server │
│ - yarn deploy │
│ - yarn serve │
│ │
├─────────────╯
│
○ 🟣 Next Steps ──────────────────────────────╮
│ │
│ Now you can build and deploy to Cloudflare Pages with: │
│ │
│ - yarn run build: production build for Cloudflare │
│ - yarn run deploy: it will use the Cloudflare CLI to deploy your site │
│ │
├───────────────────────────────────────╯
│
└ 🦄 Success! Added cloudflare-pages to your app
✨ Done in 147.25s.
6-2. axiosにadapterを設定する
NewtのSDKでは axios を利用しており、axiosはデフォルトで XMLHttpRequest を利用します。しかし、Cloudflare PagesではXMLHttpRequestに対応しておらず、Fetch API を利用する必要があります。
Fetch APIを利用するために、Newtのクライアントに adapter
として @vespaiach/axios-fetch-adapter を設定します。
npm install @vespaiach/axios-fetch-adapter
# or
yarn add @vespaiach/axios-fetch-adapter
src/libs/newt.ts
は以下のようになります。
+import fetchAdapter from '@vespaiach/axios-fetch-adapter'
import { createClient } from 'newt-client-js'
export const generateClient = (spaceUid: string, token: string) => {
return createClient({
spaceUid,
token,
apiType: 'cdn',
+ adapter: fetchAdapter,
})
}
また、このままだと Cannot use import statement outside a module
のエラーが出るため、vite.config.ts
を以下のように修正しましょう。
詳細はViteの 外部SSR のドキュメントをご確認ください。
import { defineConfig } from 'vite'
import { qwikVite } from '@builder.io/qwik/optimizer'
import { qwikCity } from '@builder.io/qwik-city/vite'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig(() => {
return {
plugins: [qwikCity(), qwikVite(), tsconfigPaths()],
preview: {
headers: {
'Cache-Control': 'public, max-age=600',
},
},
+ ssr: {
+ noExternal: '@vespaiach/axios-fetch-adapter',
+ },
}
})
6-3. GitHubのリポジトリを作成し、Cloudflare Pagesと接続する
まず、GitHubのリポジトリを作成し、これまで作成したコードをプッシュします。
詳細はGitHubの リポジトリを作成する のドキュメントをご確認ください。
続いて、作成したリポジトリとCloudflare Pagesを接続します。
※ Cloudflare のアカウントを持っていない方は、登録をお願いします。
接続方法の詳細については、GitHubのリポジトリとCloudflare Pagesを接続して、ホスティングする のチュートリアルを参考にしてください。
「フレームワークプリセット」に「Qwik」を設定し、「環境変数」にご自身の環境変数を設定しましょう。
※ Node・npm・Yarnのバージョンを環境変数で指定しない場合、デフォルトのバージョン となります。NODE_VERSION
・NPM_VERSION
・YARN_VERSION
を適宜ご指定ください。
デプロイが成功し、サイトが表示されれば成功です!