AstroとHygraphで作るWebサイト 第1回 AstroとHygraphはどう連携するのか

CodeGridの実績に基づき、静的サイト構築にAstro、コンテンツ管理にHygraphというコンビネーションでの制作を解説します。まずは、Hygraphにコンテンツを登録し、Astroで基本的な1ページを生成し、2つのツールがどう連携するのかを掴みましょう。

発行

著者 宇野 陽太 フロントエンド・エンジニア
AstroとHygraphで作るWebサイト シリーズの記事一覧

はじめに

このシリーズでは、静的サイトジェネレーターであるAstroとヘッドレスCMSのHygraphを使って、簡単なニュースサイトをSSGで制作します。この制作を通して、AstroとHygraphの連携によって静的サイトを作る場合の、データ登録の方法、ディレクトリ構造や環境変数や、考えどころとなるポイントまで、実践的な部分にまで触れていきます。

作成するWebサイトは、CodeGridのWebアプリケーションで採用した技術・構成をもとに作っていますので、万全の正解ではないにせよ、一定の実績はある方法です。

実践的な内容となるので、本シリーズを読む前に、以下のシリーズにも目を通しておくと、よりスムーズに読み進められるかと思います。

AstroとHygraph

まずは、AstroとHygraphの特徴について軽く触れながら、サンプルサイトをどのように構成しているのか解説します。

Astroとは

AstroとはMPA(Multi Page Application)を作れるWebフレームワークのひとつです。ReactやVue、Svelteなどの各種UIライブラリで作成したコンポーネントを、.astroファイルから読み込めます。そのため、既存のコード資産を流用することができます。

また、HTMLを生成するために使用されるJavaScriptのコードはビルド時に除去され、クライアントで実行する必要のあるコードだけがランタイムで読み込まれます。さらに、コンポーネント単位でハイドレーションを行う、パーシャルハイドレーションという手法を採用しており、これは、ハイドレーションのタイミングまで任意に決めることができます。

これらの特徴により、Astroを使うとランタイムでのパフォーマンスが高いWebサイトを構築することができます。サイト構築に用いるデータソースには、今回使用するHygraphのようなヘッドレスCMSのほか、静的に用意したMarkdownファイルを使うこともできます。

補足:パーシャルハイドレーション

パーシャルハイドレーションについて詳しく知りたい方は、次の記事も参考にしてみてください。

Hygraphとは

HygraphとはヘッドレスCMSのひとつで、データの取得にGraphQLを採用している点が特徴です。そのため、柔軟かつ効率的にデータを取得できます。

昨今では、APIにGraphQLを採用しているヘッドレスCMSは増えています。有名なところですと、ContentfulStrapiSanityなどが、GraphQLで取得できるAPIを持っています。

CodeGridでは2019年からHygraphの利用を続けていますが、採用当時に比べると、GraphQL nativeであるという優位性は薄れてきているかなという印象です。

今回は、CodeGridで採用していることと、筆者が慣れ親しんでいるということから、Hygraphを採用しています。

サンプルサイトの構成

Astroでは、SSGモードとSSRモードを選んでビルドすることができます。今回のサンプルサイトは、静的なHTMLで構成されたサイトを想定しているので、SSGモードを採用します。

コンテンツ群はHygraphで管理し、登録したコンテンツをAstroコンポーネントで取得します。取得したコンテンツは、Astroによってテンプレートに展開され、HTMLをレンダリングするという流れになります。

実際にシンプルなデータでページの生成を試してみたいと思います。今ご覧になっているCodeGridのような、記事があり、その記事の著者が紐づいているという情報構造をもったサイトをニュースサイトとして仕立ててみたいと思います。

この解説を通して、次のようなサイト制作のステップを体験します。

  • Hygraphのプロジェクト作成
  • Hygraphにデータの雛形を作成
    • モデル作成
    • フィールド作成
  • Hygraphでのデータ登録:モデルを雛形に実際のデータを登録
  • Astroのプロジェクト作成
  • Hygraphのトークンを作成して保存
  • Astroの基本を知る
  • Astroコンポーネントを作成
  • AstroでレンダリングしてHTMLを生成

では、さっそくニュースサイトを作っていきましょう。

Step 1. Hygraphのプロジェクトを作成する

まずは、ニュースサイトのデータを登録するために、Hygraphのプロジェクトを作成しましょう。

https://app.hygraph.com/ にアクセスして、「Add a new project」ボタンをクリックします。すると、新規プロジェクトをつくるダイアログが表示されるので、ここに必要事項を入力します。

今回のデモ用プロジェクトでは、以下のようにしました。

  • Project name: 2023-astro-with-hygraph
  • Project description (optional): 2023-astro-with-hygraphのデモ用プロジェクト
  • Region: Japan (Tokyo)

「Add project」ボタンをクリックすると、プロジェクトができあがります。次に、モデルとコンテンツを作成しましょう。

Step 2-1. モデルを作成する

今回のニュースサイトでは、「ニュース」と、それを書いた「著者」の2種類のデータを扱います。

それぞれのデータは、次のような要素で構成されるものとします。

  • ニュース
    • slug
    • タイトル
    • 本文
    • 著者
  • 著者
    • slug
    • 名前

ここで「ニュース」のデータがもつ「著者」は、「著者」データと紐づくように定義してみます。

それでは、モデルから作っていきましょう。モデルとは、Hygraphにおけるコンテンツの雛形のようなデータです。モデルをもとに、各コンテンツを作っていきます。

補足:モデルとフィールドの登録について

モデルとフィールドを登録するUIについて詳しく知りたい方は、次の記事をご覧ください。

サイドバーから「Schema」の画面を開き、Models > Addから、「News」モデルと「Author」モデルを作成してみましょう。

ここでは、次のように入力しました。

Newsモデル

  • Display name: News
  • API ID: News
  • Plural API ID: NewsCollection

news modelの登録内容

スクリーンショット

Authorモデル

  • Display name:Author
  • API ID:Author
  • Plural API ID:Authors

author modelの登録内容

スクリーンショット

モデルを作成できたので、各モデルにそれぞれのフィールドを設定していきます。

Step 2-2. モデルにフィールドを設定する

フィールドとは各モデルの構成要素のことを指します。たとえばニュースモデルなら、タイトル・本文などがそれにあたります。

各モデルの画面を開き、右側の「Add Fields」から、それぞれのフィールドにマッチするタイプを選んで、フィールドを追加します。

ここでは、それぞれ次のように入力しました。以下のメモされている部分以外はデフォルトで大丈夫です。

Newsモデル

  • slug
    • Slugフィールドで作成
      • Display name:slug
      • API ID:slug
      • 「VALIDATIONS」タブの「Make field required」にチェック
  • title
    • Single line textフィールドで作成
      • Display name:title
      • API ID:title
      • 「SETTINGS」タブのField optionsの「Use as title field」にチェック
      • 「VALIDATIONS」タブの「Make field required」にチェック
  • body
    • Multi line textフィールドで作成
      • Display name:body
      • API ID:body
      • 「VALIDATIONS」タブの「Make field required」にチェック
  • author
    • Referenceフィールドで作成
      • Model to reference:「Author」を選ぶ
      • Reference Directions:「One-way reference」
      • Display name:author
      • API ID:author

News modelのフィールド登録内容

スクリーンショット

注:ReferenceフィールドのField visibility

ReferenceフィールドのField visibilityは特に本文では言及していませんが、単にUI上での表示/非表示を決定するものです。デフォルトは「Read/Write」で、UI上で読み取りと書き込みができるという意味です。APIのアクセス権限とは関係ありません。

Authorモデル

  • slug
    • Slugフィールドで作成
      • Display name:slug
      • API ID:slug
      • 「VALIDATIONS」タブの「Make field required」にチェック
  • name
    • Single line textフィールドで作成
      • Display name:name
      • API ID:name
      • 「SETTINGS」タブの「Field options」の「Use as title field」にチェック
      • 「VALIDATIONS」タブの「Make field required」にチェック

Author modelのフィールド登録内容

スクリーンショット

モデルとそのフィールドの作成が終わり、コンテンツを作成する準備ができました。

Step 3. コンテンツの登録

Hygraphにコンテンツを作成するには、以下のようにいくつかの方法があります。

  • ダッシュボードからひとつずつ登録
  • API Playgroundでひとつずつmutationを実行
  • スクリプトを書いてまとめてmutationを実行

初回はひとまず、ダッシュボードから登録しましょう。「Content」の画面を開いてコンテンツを登録します。

補足:コンテンツの登録

コンテンツを登録するUIの詳細については、以下の記事をご覧ください。

以下が今回登録するコンテンツです。NewsモデルがAuthorモデルを参照しているという関係上、先にAuthorモデルのコンテンツから登録するようにしてください。右上にある「+ Add entry」ボタンでコンテンツを追加する空のフィールドが表示されます。

データ登録

# Author
slug: "alice",
name: "alice"

# News
slug: "news-1",
title: "地元の祭り開催決定!",
body: "この度、地元の祭りが今年も開催されることが発表されました。さまざまなイベントが行われる予定です。みなさんぜひお越しください。",
author": "alice"

コンテンツを登録できたら、作成したコンテンツを公開(Publish)します。「Save & Publish」ボタンをクリックするか、コンテンツ一覧画面から個別にチェックを入れて、「Publish」ボタンをクリックしてください。

公開状態へと移行したコンテンツになんらかの変更を加えても、その変更は即座に公開されません。変更を公開するには、再度同じようにコンテンツの公開作業を行う必要があります。

補足:公開状態

Hygraphの公開状態について詳しく知りたい方は、以下の記事をご覧ください。

ここまでで、ニュースサイトを作るために必要な、最低限のデータが準備できました。

Step 4. Astroプロジェクトを作成する

ニュースサイトのコードを書いていくために、まずはAstroのプロジェクトを作成しましょう。

Astroプロジェクトを作成するには、次のコマンドを実行します。

Astroプロジェクトの作成

$ npm create astro

コマンドを実行すると、対話形式でセットアップを行うことができます。今回は以下の選択肢でプロジェクトを作成します。

プロジェクトのセットアップ

 dir   Where should we create your new project?
        .

tmpl   How would you like to start your new project?
        Empty
    ✔  Template copied

deps   Install dependencies?
        Yes
    ✔  Dependencies installed

  ts   Do you plan to write TypeScript?
        No
    ◼  No worries! TypeScript is supported in Astro by default,
        but you are free to continue writing JavaScript instead.

 git   Initialize a new git repository?
        Yes
    ✔  Git initialized

ここで作成したプロジェクトをもとに、ニュースサイトを作成していきます。

Step 5. Permanent Auth Tokenの取得

Astroプロジェクトを作成したところで、AstroがHygraph上のコンテンツを利用するための作業をしておきましょう。AstroからHygraphのデータへアクセスするためには、モデルへアクセスする何らかの権限をもったトークン(Permanent Auth Token)が必要になります。

トークンを作成するには、Hygraphのダッシュボードから、「Project settings」>「API Access」を開き、「Permanent Auth Tokens」の見出しに移動します。

「Add token」ボタンを押すとダイアログが開くので、ここでトークンの設定を行います。「Token name」にわかりやすい名前をつけたら、「Default stage for content delivery」に「Published」を選び、「Add & configure permissions」ボタンをクリックします。

すると、トークンの設定画面に遷移します。遷移したら「Add permission」ボタンをクリックします。各モデルに対して個別にどのような権限をもつかが設定できます。

ここでは、NewsモデルとAuthorモデルからデータを取得してニュース画面を生成したいので、この2つのモデルに対して権限設定を行います。

まずは、Authorモデル用の権限を設定します。「Model」のセレクトメニューから「Author」を選びます。閲覧権限を持ったトークンがほしいので、「Rules」のチェックボックスは「Read」を選びます。その他の項目はデフォルトのままで良いでしょう。

設定し終えたら「Add」ボタンをクリックします。これで、「query demo」トークンが、Authorモデルの閲覧権限をもつようになりました。

同様に、Newsモデルの権限も設定しましょう。「Add permission」からダイアログを開き、「Model」に「News」を選びます。「Rules」はAuthorモデルと同じ「Read」でかまいません。

これで、コンテンツを取得するためのトークンを作ることができました。

環境変数としてトークンを保存

作成したトークンは、環境変数として.envファイルに記述しておきます。

補足:環境変数

トークンなどの秘匿情報はセキュリティ上、リポジトリにコミットする可能性があるファイルには記述せず、環境変数として保存します。環境変数に関して詳しくは、次の記事などを参照してください。

プロジェクトルートに.envファイルを置いておけば、Astroが裏で使っているViteによって自動的に読み込まれ、import.meta.envからアクセスできるようになります。

補足:import.meta.env

import.meta.envについて詳しく知りたい方は、次の記事をご覧ください。

HYGRAPH_QUERY_PATという名前の環境変数でHygraphのトークンを定義しておけば、JavaScriptで、import.meta.env.HYGRAPH_QUERY_PATから値を取得できます。

.env

HYGRAPH_QUERY_PAT=XXXXXXXXXXXXXXX(トークン)

Step 6. Astroの基本を知る

サイトを作り始める前に、Astroの基本的なところを簡単に解説します。

Astroでは、ファイルベースルーティングを採用しており、src/pagesディレクトリに置いた各ファイルのパスがエンドポイントとなります。たとえば、src/pages/example.astroというファイルを作ったとします。これをexample.comにホスティングしたとすると、example.astroをもとに生成された画面に、https://example.com/example/というパスで、ブラウザからアクセスできるようになります。

.astroという拡張子は、Astroコンポーネントをつくる際に使われる拡張子です。

Astroコンポーネントは、スクリプト部分(コンポーネントスクリプト)とテンプレート部分(コンポーネントテンプレート)に分かれています。スクリプト部分はファイル先頭のコードフェンス(---)の中にJavaScriptを記述します。このJavaScriptは、テンプレートのレンダリング時にのみ使われ、ランタイムのコードには出力されません。

Astroコンポーネントの構造

---
// スクリプト部分(コンポーネントスクリプト)
---
<!-- テンプレート部分(コンポーネントテンプレート) -->

スクリプト部分で変数を定義すると、その変数をテンプレート部分で使うことができます。

テンプレート部分では、JSXのように変数を展開することができます。

定義した変数を使う

---
const greeting = "こんにちは!"
---
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
  </head>
  <body>
    <p>{greeting}</p>
  </body>
</html>

これを踏まえて、Hygraphに登録したデータから、ニュース一覧をとってきて描画してみます。

役割としては大まかにいうと、スクリプト部分でHygraph上のデータを取得し、テンプレート部分でそのデータを渡し、HTMLを生成できるようにするといった具合です。

Step 7. ニュース画面を作成する

src/pages/news/に、news-1.astroというファイルを作りましょう。このAstroコンポーネントで、ニュース画面を描画します。

先ほど述べたように、Astroコンポーネントのスクリプト部分で定義した変数は、同じコンポーネントのテンプレート部分で参照することが可能です。

スクリプト部分では、fetch()関数を使ってHTTPリクエストを行うことができます。

すなわち、スクリプト部分でHTTPリクエストして得たデータを、変数に格納しておけば、そのデータをテンプレートで使うことができるというわけです。

次のコードは、Astroコンポーネントでニュースをひとつだけとってくるコードです。

ニュースのデータを取得する

Hygraphのデータの取得(スクリプト部分)

---
const res = await fetch(
  "https://api-ap-northeast-1.hygraph.com/v2/clf9ej9ti0tev01upfsb73kfm/master",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${
        import.meta.env.HYGRAPH_QUERY_PAT
      }`,
    },
    body: JSON.stringify({
      query: `
        query {
          news (stage: PUBLISHED, where: {slug: "news-1"}){
            slug
            title
            body
            publishedAt
            author {
              name
            }
          }
        }
      `,
    }),
  }
);

if (!res.ok)
  throw new Error(`Fetch failed: ${await res.text()}`);

const {
  data: { news },
} = await res.json();
---

Hygraphのエンドポイントに対して、GraphQLクエリをPOSTリクエストします。エンドボイントはSettings > ACCESS > API Access > Endpointsにある「Content API」のものです。

Astroコンポーネントのスクリプト部分では、importすることなくfetch()関数を使用することができます。また、スクリプト部分のトップレベルに限り、async関数なしでawaitを使用することができます。

GraphQLクエリには、Newsモデルのコンテンツを取得するnewsフィールドを使用します。公開したコンテンツだけが取得対象となるように、stageにはPUBLISHEDを指定しておきましょう。

取得してきたデータは、news変数に格納しています。テンプレート部分からnews変数を参照することで、ニュース一覧を描画することができます。

テンプレートを作成する

では、ニュースを描画するテンプレートも作っていきましょう。

ニュースページ(テンプレート部分)

---
// 中略

const {
  data: { news },
} = await res.json();
---

<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{news.title} | Sample news site</title>
  </head>
  <body>
    <h1>Sample news site</h1>
    <h2>{news.title}</h2>
    <p>
      {
        Intl.DateTimeFormat("ja").format(
          new Date(news.publishedAt)
        )
      } 公開
    </p>
    <p>{news.body}</p>
    <p>ライター: {news.author.name}</p>
  </body>
</html>

テンプレート部分で参照する変数は、JSX同様JavaScriptの変数として扱われます。そのため、ニュースのデータが入ったnewsオブジェクトから、各プロパティを展開できます。

ISO 8601の文字列が入っているpublishedAtは、Intl.DateTimeFormatを使ってフォーマットしています。この例では、yyyy/MM/dd 公開のように出力されます。

補足:publishedAt

publishedAtはシステムフィールドと呼ばれ、Hygraphによって自動的に付与されるフィールドです。詳しくはこちらの記事を参照してください。

これで、ニュースを描画することができました。

npm run devで開発サーバーを立ち上げ、http://localhost:3000/news/news-1/にアクセスすると、この画面を確認することができます。

ここまでのまとめ

今回はHygraphにデータを登録し、そのデータを用いて、Astroで単一ページを静的に生成する一連の方法を解説しました。

次回以降はより実務に沿った形で、Astroファイルをコンポーネントを分割します。その際、どのコンポーネントでデータを取得すべきか、データ取得の方法を考察します。