話題の記事

NetlifyキラーのVercelでウェブサイトをホストしたら簡単すぎて笑顔になった

2020.12.19

最近話題のVercelを試してみました。競合のNetlifyと同様に、ビルドとホスティング他をまとめてやってくれます。Netlifyと比べて1人で開発をするならほぼフル機能が使えますし、無料プランのままでも100回/日までデプロイできるのが利点です。

Vercel screenshot

前提

Next.jsと親和性の高いVercelですが、今回アプリはGatsby + Contentfulで構築しています。

詳しくは過去に書いた記事がありますので、下記の「1. Contentfulの準備」「2. Gatsbyアプリの立ち上げ」を参考にしてください。

CircleCI × Contentful × S3で作るJamstackなブログ環境。

また、Githubリポジトリを作成し、masterにソースコードをプッシュしておきます。

Vercelにアプリをデプロイする

https://vercel.comにアクセスし、「Start Deploying」から進むのですが、次のページでアプリをビルドするGitリポジトリを聞かれます。話が早いですね。

Vercel screenshot

その後進んでいくと、デプロイ直前に環境変数を入力できます。

Vercel screenshot

今回はアプリをビルドする際にContentfulから記事を取得しますので、ContentfulのAPIキーを環境変数として登録します。ここは適宜自分の環境に合わせて追加して下さい。

「Deploy」をクリックすると、Vercelがビルドを開始してくれます。

Vercel screenshot

無事ビルドとデプロイが成功するとVercelから祝われます。やったね。

Vercel screenshot

これでVercelが gatsby-blog-pn9ehiwtd.vercel.appのようなパブリックドメインでアプリを立ち上げてくれます。

以上、めちゃくちゃ簡単にアプリがローンチできました。

次回以降リポジトリのソースコードが更新された際も自動で同じプロセスを実行してくれます。

コンタクトフォームをつけてみる

デプロイだけだと笑ってしまうほど簡単すぎるので、ついでにコンタクトフォームを作ってみます

今回はフォームのバックエンドを提供してくれるFormspreeというサービスを使ってみます。

Google Sheets, MailChimp, Slackなどのサードパーティと連携でき、スパム対策もしてくれます。50件までの問い合わせなら無料で利用できます。

ステップ1: Formspreeに登録する

下記のページからFormspreeに登録します。

https://formspree.io/create/zeit

ZEITはVercelの前のサービス名ですので気にしなくてOK。

Formspree screenshot

登録が完了するとエンドポイントが発行されます。後ほど使うのでURLをコピーしておきます。

Formspree screenshot

ステップ2: アプリ側でフォームの構築

HTTPクライアントであるaxiosをインストールします。

$ npm install axios

フォームコンポーネントを作成します。

src/components/contactForm.js

import React, { useState } from 'react'
import axios from 'axios'

export default () => {
  const [status, setStatus] = useState({
    submitted: false,
    submitting: false,
    info: { error: false, msg: null },
  })
  const [inputs, setInputs] = useState({
    email: '',
    message: '',
  })
  const handleServerResponse = (ok, msg) => {
    if (ok) {
      setStatus({
        submitted: true,
        submitting: false,
        info: { error: false, msg: msg },
      })
      setInputs({
        email: '',
        message: '',
      })
    } else {
      setStatus({
        info: { error: true, msg: msg },
      })
    }
  }
  const handleOnChange = (e) => {
    e.persist()
    setInputs((prev) => ({
      ...prev,
      [e.target.id]: e.target.value,
    }))
    setStatus({
      submitted: false,
      submitting: false,
      info: { error: false, msg: null },
    })
  }
  const handleOnSubmit = (e) => {
    e.preventDefault()
    setStatus((prevStatus) => ({ ...prevStatus, submitting: true }))
    axios({
      method: 'POST',
      url: 'https://formspree.io/f/XXXXXXXX',
      data: inputs,
    })
      .then((response) => {
        handleServerResponse(
          true,
          'Thank you, your message has been submitted.'
        )
      })
      .catch((error) => {
        handleServerResponse(false, error.response.data.error)
      })
  }
  return (
    <main 
      style={{
        margin: `0 auto`,
        width: 500,
        padding: `1.45rem 1.0875rem`,
      }}
    >
      <h1>Sample Form</h1>
      <form onSubmit={handleOnSubmit}>
        <div style={{margin: `20px auto`}}>
          <label htmlFor="email" style={{width: 100, display: `inline-block`}}>Email</label>
          <input
            id="email"
            type="email"
            name="_replyto"
            onChange={handleOnChange}
            required
            value={inputs.email}
          />
        </div>
        <div style={{margin: `20px auto`,}}>
          <label htmlFor="message" style={{width: 100, display: `inline-block`}}>Message</label>
          <textarea
            id="message"
            name="message"
            onChange={handleOnChange}
            required
            value={inputs.message}
          />
        </div>
        <div>
        <button type="submit" disabled={status.submitting}>
          {!status.submitting
            ? !status.submitted
              ? 'Submit'
              : 'Submitted'
            : 'Submitting...'}
        </button>
        </div>
      </form>
      {status.info.error && (
        <div className="error">Error: {status.info.msg}</div>
      )}
      {!status.info.error && status.info.msg && <p>{status.info.msg}</p>}
    </main>
  )
}

上記で注意する点として、

axios({
  method: 'POST',
  url: 'https://formspree.io/f/XXXXXXXX',
  data: inputs,
})

urlに先ほどFormspreeで生成されたエンドポイントURLを入力します。

ちなみにFormspreeの便利な機能として、emailフォームにname="_replyto"属性を付与しておくと、問い合わせがあった際に管理者に届くメールに対し、"Reply to"として、入力されたメールアドレスを自動でセットしてくれます。問い合わせをしてくれたユーザーへの返信がちょっと簡単になる気の利いた機能です。

次に、フォームを呼び出すページを作成します。

src/pages/form.js

import React from 'react'
import ContactForm from "../components/contactForm";

export default function App() {
  return (
    <div>
      <ContactForm />
    </div>
  )
}

ステップ3: Vercelにデプロイする

Vercelにデプロイします。といっても、Githubのmasterにpushするだけです。

htps://[アプリURL]/form にアクセスすると、フォームが出現します。

Formspree screenshot

送信すると、管理者のメールアドレスに通知が届き、FormspreeのダッシュボードからSubmissionsが確認できます。

Formspree screenshot

以上、静的サイト上にコンタクトフォームが非常に楽に実装できました。

さいごに

Jamstackのウェブサイトをこれ以上ないほど簡単にホストしてみました。

先日、CloudflareがJamstackサイトをホストするサービスを準備中というニュースもありましたし、今後こういったサービスが増えていくんでしょうかね。

参考URL

https://vercel.com/guides/deploying-gatsby-with-vercel

https://formspree.io

https://vercel.com/guides/deploying-react-forms-using-formspree-with-vercel