Astro&microCMSでブログ作り 第2回

Astro

前回の続きの記事です👇

今回はお問い合わせフォームをastroで実装します。

お問い合わせフォームで送信された内容がmicroCMSに保存されるようにします。

ページの作成

src/components/Header.astroを編集します

      <HeaderLink href="/">Home</HeaderLink>
      <HeaderLink href="/blog">Blog</HeaderLink>
      <HeaderLink href="/contact">Contact</HeaderLink>

AboutのところをContactに置き換えてください。

(Aboutページはもう使わないので削除でも問題ないです)

src/pages/contact/index.astroを作成します。

---
import BaseHead from "../../components/BaseHead.astro";
import Footer from "../../components/Footer.astro";
import Header from "../../components/Header.astro";
---

<!doctype html>
<html lang="en">
  <head>
    <BaseHead title={"お問い合わせ"} description={"description"} />
  </head>
  <body>
    <Header />
    <main>
      <h1>お問い合わせ</h1>
      <form method="POST">
        <label>
          名前:
          <input type="text" name="name" required />
        </label>
        <label>
          Email:
          <input type="email" name="email" required />
        </label>
        <label>
          内容:
          <textarea name="message" required></textarea>
        </label>
        <button>送信</button>
      </form>
    </main>
    <Footer />
  </body>
</html>

フォームが現れればOKです!

フォーム部分をコンポーネントに切り出しておきます。

src/components/ContactForm.astro

      <form method="POST">
        <label>
          名前:
          <input type="text" name="name" required />
        </label>
        <label>
          Email:
          <input type="email" name="email" required />
        </label>
        <label>
          内容:
          <textarea name="message" required></textarea>
        </label>
        <button>送信</button>
      </form>

src/pages/contact/index.astro

---
import BaseHead from "../../components/BaseHead.astro";
import Footer from "../../components/Footer.astro";
import Header from "../../components/Header.astro";
import ContactForm from "../../components/ContactForm.astro";
---

<!doctype html>
<html lang="en">
  <head>
    <BaseHead title={"お問い合わせ"} description={"description"} />
  </head>
  <body>
    <Header />
    <main>
      <h1>お問い合わせ</h1>
      <ContactForm />
    </main>
    <Footer />
  </body>
</html>

分割して見やすくなりました。

このままではダサいので、スタイルをいい感じに設定します。

<!doctype html>
<html lang="en">
  <head>
    <BaseHead title={"お問い合わせ"} description={"description"} />
  </head>
  <body>
    <Header />
    <main>
      <h1>お問い合わせ</h1>
      <ContactForm />
    </main>
    <Footer />
    <style>
      h1 {
        font-size: 32px;
        text-align: center;
        margin-bottom: 24px;
      }
    </style>
  </body>
</html>

h1の部分のスタイルをbodyの最後に追加しました。

src/pages/contact/index.astro

<style>
  form {
    background: #fff;
    padding: 2em;
    border-radius: 10px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    max-width: 400px;
    width: 100%;
    margin: auto;
    animation: fadeIn 1s ease-in-out;
  }

  @keyframes fadeIn {
    from {
      opacity: 0;
      transform: translateY(-20px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  label {
    display: block;
    margin-bottom: 1em;
    font-weight: bold;
    color: #333;
  }

  input[type="text"],
  input[type="email"],
  textarea {
    width: calc(100% - 20px);
    padding: 10px;
    margin-top: 5px;
    border: 1px solid #ccc;
    border-radius: 5px;
    font-size: 1em;
  }

  input[type="text"]:focus,
  input[type="email"]:focus,
  textarea:focus {
    border-color: #007bff;
    outline: none;
    box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
  }

  textarea {
    height: 100px;
    resize: vertical;
  }

  button {
    display: inline-block;
    width: 100%;
    padding: 10px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    font-size: 1em;
    cursor: pointer;
    transition: background-color 0.3s ease;
  }

  button:hover {
    background-color: #0056b3;
  }
</style>

このようになればOKです!

microCMSの設定

お問い合わせを保存するAPIを作っておきます。

「自分で決める」

基本情報を埋めます。エンドポイントはcontactにしました。

リスト形式を選択。

スキーマを、フォームに合わせて定義します。

全部必須項目としておきます。

これで作成し、準備はOKです。

CMSにフォームの内容を送信する

まずフォームを使用してCMSに送信するには、サーバーサイドレンダリング(SSR)が必要です。

astro.config.mjsに、outputの設定を追記します。

export default defineConfig({
  site: "https://example.com",
  integrations: [mdx(), sitemap()],
  output: "hybrid",
});

hybridにすることで、静的生成するページとSSRするページを設定できるようにします。

詳しくはこちら👇

オンデマンドレンダリングアダプター

デフォルトでは静的生成になります。

SSRするには、pages配下のファイルに追記します。

export const prerender = false;

これで、contactページがSSRモードになります。

次に、フォームで送信した内容をサーバーサイドで受け取ります。

---
if (Astro.request.method === "POST") {
  try {
    const data = await Astro.request.formData();
    const name = data.get("name");
    const email = data.get("email");
    const message = data.get("message");
    console.log(name, email, message);
  } catch (error) {
    if (error instanceof Error) {
      console.error(error.message);
    }
  }
}
---

これで適当に入力して送信してみましょう!

送信した内容が、ターミナルに出力されるはずです。

受け取った内容をmicroCMSに保存してきます!

libs/client.tsに追記します。

export type Contact = {
    name: string;
    email: string;
    message: string;
};

export const postContact = async (data: Contact) => {
    return await client.create({ endpoint: "contact", content: data });
}

エンドポイント「contact」にPOSTする関数を作成しました。

あとはこれを呼び出せばOKです。

import { postContact } from "../../libs/client";

if (Astro.request.method === "POST") {
  try {
    const data = await Astro.request.formData();
    const name = data.get("name")?.toString();
    const email = data.get("email")?.toString();
    const message = data.get("message")?.toString();
    if (!name || !email || !message) {
      throw new Error("名前、Email、内容は必須です");
    }
    const response = await postContact({ name, email, message });
    console.log(response);
  } catch (error) {
    if (error instanceof Error) {
      console.error(error.message);
    }
  }
}

toString()で、string型に変換しています。

それだけだとundefindの可能性もあるので、その場合はエラーを投げるようにしました。

(実際はemailの形式や⚪︎文字以上、などの条件をつけます。今回は簡易的なバリデーションにしています。)

その後、先ほど作成したpostContactを呼び出しています。

この状態で送信してみましょう!

fetch API response status: 400 message is POST is forbidden.

のようなメッセージが出る場合、CMSの管理画面からAPIキーにPOSTの権限を付与してあげてください。

ターミナルのログに、投稿のidが返ってくればOKです!

管理画面側で、お問い合わせ内容が反映されています!

二重送信対策

このままでもフォームとしては機能しますが、送信後にリロードすると同じ内容が再度POSTされてしまいます。

if (Astro.request.method === "POST") {
  try {
    const data = await Astro.request.formData();
    const name = data.get("name")?.toString();
    const email = data.get("email")?.toString();
    const message = data.get("message")?.toString();
    if (!name || !email || !message) {
      throw new Error("名前、Email、内容は必須です");
    }
    await postContact({ name, email, message });
    return Astro.redirect("/contact");
  } catch (error) {
    if (error instanceof Error) {
      console.error(error.message);
    }
  }
}

Astro.redirectで、成功したらcontactにリダイレクトするようにしました。

API Reference

これで再度POSTが送信されることはなくなるため、リロード対策ができました。

これでお問い合わせフォームは完成となります!

今回はCMSに投稿しましたが、その前後でメールを送信する処理を入れるとさらに実用性が上がりそうですね!

次回はこのブログをデプロイしてみます。