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. createClientのfetchオプションを設定する
- 6-3. GitHubのリポジトリを作成し、Cloudflare Pagesと接続する
このチュートリアルでは、Newtと Qwik City を利用して、ブログを作成する手順を紹介します。
具体的には、Newtで管理しているコンテンツの一覧ページと詳細ページを作る手順を紹介した後、Cloudflare Pages にデプロイして、サーバーサイドレンダリング(SSR)でページを表示します。
記事内で使用している主なソフトウェアのバージョン
- Qwik(
@builder.io/qwik
): 1.3.5 - Qwik City(
@builder.io/qwik-city
): 1.3.5 - newt-client-js(
newt-client-js
): 3.3.0
概要
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.3.5)
│
◇ 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 (Qwik City + Qwik)
│
◇ 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
$ npm install
$ npm 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で作成したトークンの値を定義します。以下を、実際の値で置き換えて定義してください。
1NEWT_SPACE_UID=sample-for-docs
2NEWT_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
を指定します。
1import { createClient } from 'newt-client-js'
2
3export const generateClient = (spaceUid: string, token: string) => {
4 return createClient({
5 spaceUid,
6 token,
7 apiType: 'cdn',
8 })
9}
これで、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
のみを使うため、以下のように定義しておきます。
1export interface Article {
2 _id: string
3 title: string
4 slug: string
5 body: string
6}
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')
とすると、環境変数を取得できます。
1import { routeLoader$ } from '@builder.io/qwik-city'
2import { generateClient } from '../lib/newt'
3
4export const useArticles = routeLoader$(async ({ env }) => {
5 const spaceUid = env.get('NEWT_SPACE_UID') || ''
6 const token = env.get('NEWT_CDN_API_TOKEN') || ''
7 const client = generateClient(spaceUid, token)
8})
続いて、投稿一覧を取得するために、SDKが提供している getContents メソッドを利用します。getContentsはNewtのコンテンツ一覧を取得するためのメソッドです。getContentsのパラメータに Article
の型を渡すことで、返却される items
の型として Article[]
が指定されます。
また、selectパラメータを利用して、取得するフィールドを _id
・title
・slug
・body
のみに制限します。
1import { routeLoader$ } from '@builder.io/qwik-city'
2import { generateClient } from '../lib/newt'
3import type { Article } from '~/types/article'
4
5export const useArticles = routeLoader$(async ({ env }) => {
6 const spaceUid = env.get('NEWT_SPACE_UID') || ''
7 const token = env.get('NEWT_CDN_API_TOKEN') || ''
8 const client = generateClient(spaceUid, token)
9
10 const { items: articles } = await client.getContents<Article>({
11 appUid: 'blog',
12 modelUid: 'article',
13 query: {
14 select: ['_id', 'title', 'slug', 'body'],
15 },
16 })
17
18 return articles
19})
4-4. 投稿一覧を表示する
次に、投稿一覧を表示します。src/routes/index.tsx
は以下のようになります。
useArticles()
で取得した articles
は Readonly<Signal<Article[]>>
という型になっているので、articles.value
のようにすると投稿一覧の情報を取得できます。
1import { component$ } from '@builder.io/qwik'
2import { routeLoader$ } from '@builder.io/qwik-city'
3import { generateClient } from '../lib/newt'
4import type { DocumentHead } from '@builder.io/qwik-city'
5import type { Article } from '~/types/article'
6
7export const useArticles = routeLoader$(async ({ env }) => {
8 const spaceUid = env.get('NEWT_SPACE_UID') || ''
9 const token = env.get('NEWT_CDN_API_TOKEN') || ''
10 const client = generateClient(spaceUid, token)
11
12 const { items: articles } = await client.getContents<Article>({
13 appUid: 'blog',
14 modelUid: 'article',
15 query: {
16 select: ['_id', 'title', 'slug', 'body'],
17 },
18 })
19
20 return articles
21})
22
23export default component$(() => {
24 const articles = useArticles()
25 return (
26 <main>
27 <ul>
28 {articles.value.map((article) => {
29 return (
30 <li key={article._id}>
31 <a href={`articles/${article.slug}`}>{article.title}</a>
32 </li>
33 )
34 })}
35 </ul>
36 </main>
37 )
38})
39
40export const head: DocumentHead = {
41 title: 'Newt・Qwik Cityブログ',
42 meta: [
43 {
44 name: 'description',
45 content: 'NewtとQwik Cityを利用したブログです',
46 },
47 ],
48}
ここでは、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のステータスを返すようにしています。
1import { routeLoader$ } from '@builder.io/qwik-city'
2import { generateClient } from '~/lib/newt'
3import type { Article } from '~/types/article'
4
5export const useArticle = routeLoader$(async ({ env, params, fail }) => {
6 const spaceUid = env.get('NEWT_SPACE_UID') || ''
7 const token = env.get('NEWT_CDN_API_TOKEN') || ''
8 const client = generateClient(spaceUid, token)
9
10 const article = await client.getFirstContent<Article>({
11 appUid: 'blog',
12 modelUid: 'article',
13 query: {
14 slug: params.slug,
15 select: ['_id', 'title', 'slug', 'body'],
16 },
17 })
18 if (!article) {
19 return fail(404, {
20 errorMessage: 'Not found',
21 })
22 }
23
24 return article
25})
5-2. 投稿詳細を表示する
次に、投稿詳細を表示します。
src/routes/articles/[slug]/index.tsx
は以下のようになります。
1import { component$ } from '@builder.io/qwik'
2import { routeLoader$ } from '@builder.io/qwik-city'
3import { generateClient } from '~/lib/newt'
4import type { DocumentHead } from '@builder.io/qwik-city'
5import type { Article } from '~/types/article'
6
7export const useArticle = routeLoader$(async ({ env, params, fail }) => {
8 const spaceUid = env.get('NEWT_SPACE_UID') || ''
9 const token = env.get('NEWT_CDN_API_TOKEN') || ''
10 const client = generateClient(spaceUid, token)
11
12 const article = await client.getFirstContent<Article>({
13 appUid: 'blog',
14 modelUid: 'article',
15 query: {
16 slug: params.slug,
17 select: ['_id', 'title', 'slug', 'body'],
18 },
19 })
20 if (!article) {
21 return fail(404, {
22 errorMessage: 'Not found',
23 })
24 }
25
26 return article
27})
28
29export default component$(() => {
30 const article = useArticle()
31 if (article.value.errorMessage) {
32 return <h1>{article.value.errorMessage}</h1>
33 }
34
35 return (
36 <>
37 <h1>{article.value.title}</h1>
38 <div dangerouslySetInnerHTML={article.value.body} />
39 </>
40 )
41})
42
43export const head: DocumentHead = ({ resolveValue }) => {
44 const article = resolveValue(useArticle)
45 return {
46 title: article?.title,
47 meta: [
48 {
49 name: 'description',
50 content: '投稿詳細ページです',
51 },
52 ],
53 }
54}
※ 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
を選択します。
$ npm run qwik add cloudflare-pages
┌ 🦋 Add Integration cloudflare-pages
│
◇ 👻 Ready? Add cloudflare-pages to your app?
│
│ 🐬 Modify
│ - package.json
│ - README.md
│ - .gitignore
│
│ 🌟 Create
│ - .node-version
│ - public/_headers
│ - public/_redirects
│ - src/entry.cloudflare-pages.tsx
│ - adapters/cloudflare-pages/vite.config.ts
│
│ 💾 Install npm dependency:
│ - wrangler ^3.0.0
│
│ 📜 New npm script:
│ - npm run build.server
│ - npm run deploy
│ - npm run serve
│
◇ Ready to apply the cloudflare-pages updates to your app?
│ Yes looks good, finish update!
│
◇ App updated
│
◇ New scripts added ──────╮
│ │
│ - npm run build.server │
│ - npm run deploy │
│ - npm run serve │
│ │
├────────────────╯
│
◇ 🟣 Next Steps ───────────────────────────╮
│ │
│ Now you can build and deploy to Cloudflare Pages with: │
│ │
│ - npm run build: production build for Cloudflare │
│ - npm run deploy: it will use the Cloudflare CLI to deploy your site │
│ │
├────────────────────────────────────╯
│
└ 🦄 Success! Added cloudflare-pages to your app
6-2. createClientのfetchオプションを設定する
NewtのSDKでは axios を利用しており、axiosはデフォルトで XMLHttpRequest を利用します。しかし、Cloudflare PagesではXMLHttpRequestに対応しておらず、Fetch API を利用する必要があります。
Fetch APIを利用するために、Newtのクライアントに fetch
として globalThis.fetch
を設定します。
src/lib/newt.ts
は以下のようになります。
import { createClient } from 'newt-client-js'
export const generateClient = (spaceUid: string, token: string) => {
return createClient({
spaceUid,
token,
apiType: 'cdn',
fetch: globalThis.fetch
})
}
6-3. GitHubのリポジトリを作成し、Cloudflare Pagesと接続する
まず、GitHubのリポジトリを作成し、これまで作成したコードをプッシュします。
詳細はGitHubの リポジトリを作成する のドキュメントをご確認ください。
続いて、作成したリポジトリとCloudflare Pagesを接続します。
※ Cloudflare のアカウントを持っていない方は、登録をお願いします。
接続方法の詳細については、GitHubのリポジトリとCloudflare Pagesを接続して、ホスティングする のチュートリアルを参考にしてください。
「フレームワークプリセット」に「Qwik」を設定し、「環境変数」にご自身の環境変数を設定しましょう。
※ Node・Yarnなどは、バージョンを環境変数で指定しない場合、デフォルトのバージョン となります。必要に応じて NODE_VERSION
・YARN_VERSION
などを適宜ご指定ください。
デプロイが成功し、サイトが表示されれば成功です!