NewtとAstroを利用してブログを作成する

このチュートリアルでは、Newtと Astro を利用して、ブログを作成する手順を紹介します。
具体的には、Newtで管理しているコンテンツの一覧ページと詳細ページを作る手順を紹介します。

記事内で使用している主なソフトウェアのバージョン

  • Astro(astro): 2.3.0
  • newt-client-js(newt-client-js): 3.2.4

概要

Astroでプロジェクトを作成し、Newtのコンテンツ情報を取得できるようにします。
コンテンツの一覧ページ(パス: /)と詳細ページ(パス: /articles/:slug 。slugがarticle-1の場合は /articles/article-1)を作成し、ローカル環境で表示を行うまでを説明します。
また、ここではビルド時にHTMLを生成する、静的生成(Static Generation)の方法を紹介します。

1. Astroのセットアップ

はじめに、Astroのセットアップを行います。create-astro を利用することで、簡単にAstroのプロジェクトを作成できます。
以下のどれかのコマンドを入力します。

npm create astro@latest
# or
pnpm create astro@latest
# or
yarn create astro

コマンドを入力すると、以下の質問を聞かれるので、お好きな設定を選びましょう。

  • どこにプロジェクトを作成するか(ここでは ./astro-blog と入力)
  • プロジェクトをどのように始めるか(ここでは Include sample files を選択)
  • dependenciesをインストールするか(ここでは Yes を選択)
  • TypeScriptを利用するか(ここでは Yes を選択)
  • TypeScriptの設定をどうするか(ここでは Strict を選択)
  • gitリポジトリを初期化するか?(ここでは Yes を選択)

yarnを利用した場合、以下のように表示されます。

$  yarn create astro                                                                                                                                                            14:15:31
yarn create v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-astro@3.1.3" with binaries:
      - create-astro

╭─────╮  Houston:
│ ◠ ◡ ◠  Let's create a new project!
╰─────╯

 astro   v2.3.0 Launch sequence initiated.
   dir   Where should we create your new project?
         ./astro-blog
  tmpl   How would you like to start your new project?
         Include sample files
      ✔  Template copied
  deps   Install dependencies?
         Yes
      ✔  Dependencies installed
    ts   Do you plan to write TypeScript?
         Yes
   use   How strict should TypeScript be?
         Strict
      ✔  TypeScript customized
   git   Initialize a new git repository?
         Yes
      ✔  Git initialized
  next   Liftoff confirmed. Explore your project!

         Enter your project directory using cd ./astro2.3.0-blog
         Run yarn dev to start the dev server. CTRL+C to stop.
         Add frameworks like react or tailwind using astro add.

         Stuck? Join us at https://astro.build/chat

╭─────╮  Houston:
│ ◠ ◡ ◠  Good luck out there, astronaut! 🚀
╰─────╯
✨  Done in 69.45s.

作成したプロジェクトに移動して、開発サーバーを立ち上げます。

$ cd astro-blog
$ yarn dev

http://localhost:3000/ にアクセスして、以下のような画面が表示されることを確認します。
astro-blog1.png

2. Newtのセットアップ

次にNewtにコンテンツとAPIトークンを用意し、コンテンツの取得を行うための準備を行います。

2-1. Appを追加する

「Appを追加」をクリックして「テンプレートから追加」を選択します。

Appを追加する

表示されるテンプレートの中から「Blog」を選択して、「このテンプレートを追加」をクリックします。
Appテンプレート

テンプレートが追加されると、「投稿データ」「カテゴリデータ」「著者データ」が追加されます。

quick-start03.jpg

2-2. スペースUID・App UID・モデルUIDを確認する

スペースUIDは「スペース設定」から確認できます。

quick-start0402.jpgquick-start0503.jpg

上記の例だと、スペース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を作成します。

quick-start06.jpg

名前と取得対象を決めて「作成」を押します。

quick-start07.jpg

ここで作成したトークンの値は3-1で環境変数として定義します。

3. リクエストの準備

Newtの SDK を利用することで、NewtのAPIをより簡単に利用できます。
ここではSDKを利用して、NewtのAPIクライアントを作成します。

3-1. 環境変数の設定

Astroの環境変数は、プロジェクトディレクトリの .env ファイルから読み込めます。詳細はAstroの 環境変数 のドキュメントをご確認ください。

ここでは、.env ファイルを作成し、2-2で確認したスペースUID、2-3で作成したトークンの値を定義します。以下を、実際の値で置き換えて定義してください。

NEWT_SPACE_UID=sample-for-docs
NEWT_CDN_API_TOKEN=xxxxxxxxxxxxxxx

上記のように定義しておくと、import.meta.env.NEWT_SPACE_UIDimport.meta.env.NEWT_CDN_API_TOKEN として利用できるようになります。

また、src/env.d.tsImportMetaEnv を以下のように設定することで、環境変数の自動補完が効くようになります。

/// <reference types="astro/client" />

+ interface ImportMetaEnv {
+   readonly NEWT_SPACE_UID: string
+   readonly NEWT_CDN_API_TOKEN: string
+ }

3-2. newt-client-jsのインストール

次に newt-client-js をインストールします。

npm install newt-client-js
# or
yarn add newt-client-js

3-3. APIクライアントの作成

CDN APIを利用するためのクライアントを作成します。
spaceUidtoken のところには3-1で設定した環境変数を入力します。
ここではCDN APIを利用するので、apiType には cdn を指定しましょう。

// src/lib/newt.ts

import { createClient } from 'newt-client-js'

export const newtClient = createClient({
  spaceUid: import.meta.env.NEWT_SPACE_UID,
  token: import.meta.env.NEWT_CDN_API_TOKEN,
  apiType: 'cdn',
})

これで、NewtにAPIリクエストを送るための準備ができました。

4. 一覧ページの作成

4-1. 言語の設定とディスクリプションの設定をする

src/layouts/Layout.astro を修正します。
言語の設定では、lang 属性を修正します。ここでは日本語 ja を指定します。
また、引数として description を渡すと、メタディスクリプションとして設定されるようにします。

---
export interface Props {
  title: string
+ description: string
}

- const { title } = Astro.props
+ const { title, description } = Astro.props
---

<!DOCTYPE html>
- <html lang="en">
+ <html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="generator" content={Astro.generator} />
    <title>{title}</title>
+   <meta name="description" content={description} />
  </head>
  <body>
    <slot />
  </body>
</html>

4-2. 投稿の型を定義する

はじめに、投稿の型 Article を定義しておきます。
このチュートリアルでは、titleslugbody のみを使うため、以下のように定義しておきます。

// src/lib/newt.ts

import { createClient } from 'newt-client-js'

+ export interface Article {
+   title: string
+   slug: string
+   body: string
+ }

export const newtClient = createClient({
  spaceUid: import.meta.env.NEWT_SPACE_UID,
  token: import.meta.env.NEWT_CDN_API_TOKEN,
  apiType: 'cdn',
})

4-3. 投稿一覧の取得メソッドを作成する

Astroはファイルベースルーティングを採用しており、src/pages ディレクトリの配下にファイルを作成すると、自動的にルートとして利用できるようになります。
例えば、以下のようにルーティングされます。

  • src/pages/blog/index.astro/blog
  • src/pages/blog/first-post.astro/blog/first-post

※ ルーティングの詳細については、Astroの 静的ルーティング のドキュメントをご確認ください。

ここではトップページ(パス: /)で投稿一覧を表示したいので、src/pages/index.astro のファイルを修正します。

投稿一覧を取得するために、SDKが提供している getContents メソッドを利用します。getContentsはNewtのコンテンツ一覧を取得するためのメソッドです。getContentsのパラメータに Article の型を渡すことで、返却される items の型として Article[] が指定されます。
また、selectパラメータを利用して、取得するフィールドを titleslugbody のみに制限します。

// src/pages/index.astro

---
import Layout from '../layouts/Layout.astro'
import { newtClient } from '../lib/newt'
import type { Article } from '../lib/newt'

const { items: articles } = await newtClient.getContents<Article>({
  appUid: 'blog',
  modelUid: 'article',
  query: {
    select: ['title', 'slug', 'body'],
  },
})
---

<Layout title="Newt・Astroブログ" description="NewtとAstroを利用したブログです">
  <main></main>
</Layout>

4-4. 投稿一覧を表示する

次に、投稿一覧を表示します。src/pages/index.astro は以下のようになります。

// src/pages/index.astro

---
import Layout from '../layouts/Layout.astro'
import { newtClient } from '../lib/newt'
import type { Article } from '../lib/newt'

const { items: articles } = await newtClient.getContents<Article>({
  appUid: 'blog',
  modelUid: 'article',
  query: {
    select: ['title', 'slug', 'body'],
  },
})
---

<Layout title="Newt・Astroブログ" description="NewtとAstroを利用したブログです">
  <main>
    <ul>
      {
        articles.map((article) => {
          return (
            <li>
              <a href={`/articles/${article.slug}`}>{article.title}</a>
            </li>
          )
        })
      }
    </ul>
  </main>
</Layout>

ここでは詳細ページへのリンクとして、HTML標準の <a> 要素を利用しています。
詳細については、Astroの ページ間のリンク をご確認ください。

http://localhost:3000/ にアクセスして、以下のように投稿一覧が表示されれば成功です。

astro-blog2.png

5. 詳細ページの作成

5-1. 動的ルーティングを設定する

Astroで 動的ルーティング を設定するためには、以下の2つが必要です。

  1. src/pages/blog/[slug].astrosrc/pages/[username]/settings.astro のように、角括弧を使って動的なパラメータを識別すること
  2. getStaticPaths() 関数をエクスポートして、Astroでプリレンダリングされるパスを正確に指定すること

まず、ここでは /articles/:slug/articles/article-1 など)のパスで投稿の詳細を表示したいので、src/pages/articles/[slug].astro のファイルを作成します。

次に getStaticPaths() 関数を作成します。この関数の params キーによって、どのパスがプリレンダリングされるか決まります。
ここでは全投稿のスラッグを定義するため、以下のように params を指定します。
投稿一覧を取得する部分は4-3と同じです。

// src/pages/articles/[slug].astro

---
import Layout from '../../layouts/Layout.astro'
import { newtClient } from '../../lib/newt'
import type { Article } from '../../lib/newt'

export const getStaticPaths = async () => {
  const { items: articles } = await newtClient.getContents<Article>({
    appUid: 'blog',
    modelUid: 'article',
    query: {
      select: ['title', 'slug', 'body'],
    },
  })
  return articles.map((article) => ({
    params: { slug: article.slug },
  }))
}
---

<Layout title="投稿詳細ページ" description="投稿詳細ページです">
  <main></main>
</Layout>

5-2. 投稿詳細を表示する

次に、投稿詳細を表示します。

ここでは getStaticPathsprops を利用して、投稿データを渡しています。
src/pages/articles/[slug].astro は以下のようになります。

// src/pages/articles/[slug].astro

---
import Layout from '../../layouts/Layout.astro'
import { newtClient } from '../../lib/newt'
import type { Article } from '../../lib/newt'

export const getStaticPaths = async () => {
  const { items: articles } = await newtClient.getContents<Article>({
    appUid: 'blog',
    modelUid: 'article',
    query: {
      select: ['title', 'slug', 'body'],
    },
  })
  return articles.map((article) => ({
    params: { slug: article.slug },
    props: { article },
  }))
}

const { article } = Astro.props
---

<Layout title={article.title} description="投稿詳細ページです">
  <main>
    <h2>{article.title}</h2>
    <article set:html={article.body} />
  </main>
</Layout>

※ bodyの表示で利用されている set:html はXSSの危険性があるため、利用には注意が必要です。ここでは、Newtで管理している投稿情報を表示するものであり、不特定多数のユーザーが入力できるものを表示するわけではないため、安全なものとして利用しています。

これで、投稿詳細についての設定も完了です。
http://localhost:3000/articles/article-3 にアクセスして、以下のように投稿詳細が表示されれば成功です。

astro-blog3.png

次のステップ

このチュートリアルを行うことで、Astroのプロジェクトを作成し、開発環境でコンテンツの取得・表示を行う方法を学習しました。
更に深く学習したい方は、以下のチュートリアルもおすすめです。

ホスティングを行いたい方

問い合わせフォームを作成したい方

その他にも様々なチュートリアルを用意しているので、ぜひ チュートリアル のページもご確認ください。

Newt Made in Newt