新卒未経験エンジニアが自社ホームページを作ってみた

## 自己紹介

こんにちは、コズム開発部の山田です。

今年から新卒エンジニアとしてコズムに入社し、普段は自社プロダクトなどのフロントエンド開発に携わっています。今回は入社して最初に携わったプロジェクトである、弊社ホームページ(コーポレートサイト)の制作についてレポートしたいと思います。

## プロジェクトの概要

私がコズムに入社したころに、元々あった弊社HPを大幅に刷新するというプロジェクトが走りだしていました。そこで実装を担うフロントエンドエンジニアとして白羽の矢が立ったのが、当時ペーペーエンジニアであった私でありました。

実装としては既存のソースコードを踏襲することなく新規に作ることになり、コーディングするのは私一人、適宜先輩エンジニアにレビューをしていただくという流れで進行することになりました。

# どういう技術つこたん？

基本的な実装はTypeScriptやReact/Next.jsといった技術を用いて行いました。

特筆すべき点は、まずニュース記事やブログ記事のページにmicroCMSという国産のヘッドレスCMSを導入したことです。これによって、ニュースの更新を行う際にもソースコードを改変する必要がなく、非エンジニアの広報部の方でもブラウザから簡単に記事の追加を行うことができるようになっています。

また、ページを開いた際のオープニングアニメーションも凝ったポイントの一つです。

## microCMSどーやって実装したん？

microCMSを使用する際には、

1. microCMSのAPIをコールし、記事の内容を取得 

2.フロントエンドで取得した情報を整形して表示

という手順を踏むことになります。

取得したデータをフロントで表示する際には、div要素にdangerouslySetInnerHtmlプロパティで記事内容を渡してあげる形になります。APIによって取得したデータ見てみると、記事内容はこのようなhtml要素で返されていることがわかります。

```json
 {
 
 "main":
 "<p>株式会社コズムは、2022年11月に創業し、急速な成長を遂げているスタートアップ企業です。</p>
 <p>今回の記事では、コズムのxx担当として活躍しているxxさんにインタビューを行いました。</p>
 <p>&lt;プロフィール&gt;</p>...",
 
 }
```

このような形のため、このhtml内の個々の要素を自在にスタイリングすることは難しくなってきます。(対応策ご存じの方はコメント等でご指摘いただけると嬉しいです！)。そのため、今回の実装ではレイアウトや装飾には拘らず、表示を崩さないように実装することを最優先にしました。

```jsx
   <div
       dangerouslySetInnerHTML={{ __html: Item.main }}
       className='businessdetail__desc'
    />
```

まずこのようなdiv要素を用意し、APIで取得したhtml要素を内包させます。そして、このdivにclassNameを付与します(JSXの場合)。

```css
.businessdetail__desc img {
  width: 80svw;
  max-width: 980px;
  height: auto;
}
```

そして親要素であるdiv要素に内包される要素に対して、このように親要素を起点にしてスタイリングしていきます。上記の手順を追って実装されたページの一例が、[こちら](https://cosmcorp.jp/pickup-detail/ajic30dhcm)です。

スタイリングの制約がある点を鑑みれば、比較的まとまった見やすいレイアウトになっていると思います。

## オープニングアニメーションどーやったの？

オープニングアニメーションは、弊社ロゴの軌跡をなぞるような形のものを実装しています。ライブラリはframer motionを使用しており、使いたいロゴのsvgファイルがあれば比較的簡単に実装することができます。

```tsx
 <motion.svg viewBox='任意のサイズ' xmlns='http://www.w3.org/2000/svg'>
        <motion.path
          initial={{ pathLength: 0 }}
          animate={{ pathLength: 1 }}
          transition={{
            ease: 'easeInOut',
            duration: 3.4,
          }}
          stroke='#fff'
          strokeWidth={2}
          fill='none'
          d='任意のsvg '
        />
  </motion.svg>
```

実装としては、このようにmotion.svg要素でmotion.path要素をラップしてあげます。motion.svgにはviewBoxパラメータに任意の表示サイズを入力します。

motion.path要素にはinitialで初期状態、animateでアニメーション起動後の状態を定義し、transitionでアニメーション中の挙動を定義することができます。strokeは軌跡の線を指し、色や太さを指定することができます。

もっとも重要なのはdパラメータで、ここに使用したいsvgのpath情報を渡します。path情報はコードエディタ等でsvgファイルを開くと取得することができます。このpath情報に応じて、図形の軌跡が描かれることになります。

このアニメーションはサイトのトップページを開いた際に発動するように実装していますが、毎度毎度アニメーションを見せられたらたまったものではないので(？)、session storageに足跡を記録することによって、セッション中の最初にトップページに訪れた時にのみアニメーションが発動するような制御を実装しています。

## こだわりのお問い合わせフォーム

お問い合わせフォームも、力を入れて作ったポイントのひとつです。

<aside>
💡 特徴

1. zodによるフォームのバリデーション
2. お問い合わせ完了時に、自動で確認メール送信&自社slackに通知
</aside>

### 1. zodによるフォームのバリデーション

今回の実装ではお問い合わせフォームの入力項目の検証にzodを使用しています。

実装例

```tsx

import { z } from 'zod'

const phoneNumber: z.ZodString = z
  .string({ required_error: '入力が必須の項目です' })
  .min(10, { message: '入力が必須の項目です' })
  .max(15, { message: '15文字以内で入力してください' })
  .regex(/^[0-9-]+$/, {
    message: '半角数字および-（半角ハイフン）で入力してください',
  })
```

ここでは電話番号のデータ検証の定義を見てみましょう。まず.string()メソッドで入力がなかった場合のエラーとして、「入力が必須の項目です」というエラーメッセージを追加しています。次に、min()およびmax()メソッドで最低/最高文字数を設定し、それぞれの条件が満たされなかった場合のエラーメッセージを追加しています。

さらに、regex()メソッドを使用することで、許容する文字列の条件を正規表現によって指定することができます。ここでは電話番号の入力をするので、数字の0から9とハイフンを許容するように設定しています。

### 2. お問い合わせ完了時に、自動で確認メール送信&自社slackに通知

お問い合わせ完了時に、お問い合わせ元に確認のメールを自動送信し、同時にお問い合わせがあった旨を社内のslackに通知するような実装を行なっています。

slackへの通知は、slackのAPIを利用します(slack側での設定も必要ですが、ここでは割愛させていただきます)。

```tsx
NEXT_PUBLIC_SLACK_BOT_OAUTH_TOKEN=任意のトークン
```

トークンやAPIキーをソースコード中にハードコーディングしてしまうのはセキュリティ上の問題がありますので、環境変数に設定していきます。

slackのダッシュボードにてトークンを取得し、”NEXT_PUBLIC_SLACK_BOT_OAUTH_TOKEN”という名前をつけて環境変数に設定します(ここでは.envファイルに記述)。このように設定した環境変数は、process.env.NEXT_PUBLIC_xxx…の形で参照することが可能です。

:::note warn
6/12追記
NEXT_PUBLICを環境変数に設定すると、クライアントサイドのバンドルJSにこの値が含まれ、公開されてしまう恐れがあるようです。今回のケースではサーバーサイドでAPIコールを行なっていることもあり、NEXT_PUBLICとせずに環境変数を設定するのがベターかと思われます。
ご指摘ありがとうございました🙇
:::


```tsx
import { WebClient } from '@slack/web-api'

export async function sendNotificationSlack(formData: ContactSchemaType) {
  // OAuth トークン
  const token = process.env.NEXT_PUBLIC_SLACK_BOT_OAUTH_TOKEN
  // #チャンネル名 of @ユーザー名
  const channel = '任意のチャンネル名'
  // メッセージ
  const text = '任意のメッセージ'
  
  const client = new WebClient(token)
  
  const response = await client.chat.postMessage({ channel, text })

}
```

次に実際にslackに通知する処理を書いていきます。

まず環境変数に設定したトークンを利用して、クライアントを宣言します。そして、チャンネル名、投稿したいメッセージをchat.postMessage()メソッドに渡すことで、そのメッセージを指定したチャンネルに投稿することができます。

自動メール送信に関しては、SendGirdのAPIを利用しています。

```tsx
'use server'

export async function sendAutoEmail(formData: ContactSchemaType) {
  const headers = new Headers([
    ['Content-Type', 'application/json'],
    ['Authorization', 'Bearer ' + process.env.NEXT_PUBLIC_SENDGRID_API_KEY],
  ])
  const requestBody = {
    personalizations: [
      {
        to: [
          {
            email: formData.mailAddress,
          },
        ],
      },
    ],
    subject: 'お問い合わせを受け付けました。',
    from: {
      email: 'no-reply@sample.jp',
    },
    content: [
      {
        type: 'text/html',
        value:
          '送信する内容'
      },
    ],
  }
  try {
    const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
      method: 'POST',
      headers: headers,
      body: JSON.stringify(requestBody),
    })
    if (response.ok) {
      return {
        success: true,
        message: 'お問い合わせを受け付けました。',
      }
    } else {
      return {
        success: false,
        message: 'お問い合わせの送信に失敗しました。',
      }
    }
  } catch () {
    return {
      success: false,
      message: 'お問い合わせの送信に失敗しました。',
    }
  }
}

```

ここではまずヘッダーを宣言しています。送信するコンテンツの形式はjsonになるのでその旨を記載します。次にAPIの認証のために、AuthorizationヘッダーにSendGridダッシュボードで取得できるトークンを挿入します。こちらの先ほどと同様に、環境変数に設定した値を参照しています。

次に、メールで送信する本文を宣言します。personalizationにフォームデータで収集した問い合わせ元のメールアドレスを設定し、そのアドレスにこのメールが送信されるようにします。subjectとcontentでそれぞれタイトルと本文を設定し、fromの部分には送信元のメールアドレスを設定します。(送信元のメールアドレスを設定する際に、SendGrid側でドメインの認証を行う必要があります。)

また、このAPIをクライアント側からそのままコールしようとすると、CORSのエラーが出るため、”use server”を宣言することによってサーバー側でAPIの処理を行う必要があります。

# ということで…

初めて一人で実装を行なったHP制作は、実装上では壁にぶつかることの連続でした。しかし今回の記事でご紹介した点のみならず、さまざまな工夫を散りばめたHPが作れたと思っています。ぜひ一度、リニューアルした弊社HPを覗いてみてください！

- https://cosmcorp.jp/