Vercel の Virtual Event Starter Kitを少し見てみる

VercelがOSSとして公開している**Virtual Event Starter Kit**というのがあります。これは[Next\.js Conf 2020](https://nextjs.org/2020/conf)で実際に使われた物のようです。

https://github.com/vercel/virtual-event-starter-kit

これを少し見てみた時のメモです。

## 環境
- ChromeOS Crostini (Linux) Debian 10.10
- Docker version 20.10.7

## ローカルでDockerを使って開発環境の起動
Virtual Event Starter KitはNext.js製なので当然Node.jsが無いと動きませんが、私はそんな物はインストールしていないのでDockerで起動するようにします。Virtual Event Starter Kitの[package.json](https://github.com/vercel/virtual-event-starter-kit/blob/bc1f934ef45d20a2e6838725f3391b25f8efaa9c/package.json#L10)でNode.js 12.xに縛られているので注意します。

```docker-compose.yml
version: '3'
services:
  app:
    image: node:12-alpine
    volumes:
      - .:/app
    working_dir: /app
    command: yarn dev
    ports:
      - 3000:3000
```

```sh
$ docker-compose run --rm yarn install
$ docker-compose up
```

これで http://localhost:3000/ へアクセスすると画面が映ります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/3a18eceb-f2ca-6cb9-3adf-995fb3978b49.png" width=640 />

## HeadlessCMS
`$ cp .env.local.example .env.local`することで、HeadlessCMSとしてデモ用の**DatoCMS**が割り当てられます。ここで得られるデータはあくまでデモ用であり変更はできないようですが、どんなデータをどう使うのかは確認できます。

DatoCMSの他にもContentfulやStrapiなどいくつかのHeadlessCMSに標準で対応していて、[lib/cms-providers](https://github.com/vercel/virtual-event-starter-kit/tree/bc1f934ef45d20a2e6838725f3391b25f8efaa9c/lib/cms-providers)にクライアントコードが書いてあります。ここを増やせば何にでも対応できそうな気がします。CMSの切り替えは[lib/cms-api.ts](https://github.com/vercel/virtual-event-starter-kit/blob/bc1f934ef45d20a2e6838725f3391b25f8efaa9c/lib/cms-api.ts)で環境変数に応じて行っています。

実際にどんなデータが使われているかはソースコード内にあるGraphQLクエリを読めばだいたい分かります。[lib/cms-providers/dato.ts](https://github.com/vercel/virtual-event-starter-kit/blob/bc1f934ef45d20a2e6838725f3391b25f8efaa9c/lib/cms-providers/dato.ts#L72-L94)を見ると、たとえば`getAllStages`で各セッション情報として下記のデータを取得している事が分かります。

```ts
export async function getAllStages(): Promise<Stage[]> {
  const data = await fetchCmsAPI(`
    {
      allStages(first: 100, orderBy: order_ASC) {
        name
        slug
        stream
        discord
        schedule {
          title
          start
          end
          speaker {
            name
            slug
            image {
              url(imgixParams: {fm: jpg, fit: crop, w: 120, h: 120})
            }
          }
        }
      }
    }
  `);

  return data.allStages;
}
```

以下の情報を取得しているのが分かります。

- name
- slug
- stream
- discord
- schedule
  - title
  - start
  - end
  - speaker
    - name
    - slug
    - image
      - url

ここで取ってきているデータを実際に覗いてみると下記のようになっていました。

```js
{
  name: 'Stage A',
  slug: 'a',
  stream: 'https://www.youtube.com/embed/1-NzQ9ObsfM',
  discord: 'https://discord.com',
  schedule: [
    {
      title: 'Keynote',
      start: '2020-10-27T09:00:00-07:00',
      end: '2020-10-27T09:30:00-07:00',
      speaker: [
        { name: 'Christa Collyn', slug: 'christa', image: [Object] },
        { name: 'Blagun Dobromil', slug: 'blagun', image: [Object] },
        { name: 'Harshad İrem', slug: 'harshad', image: [Object] }
      ]
    },
(略)
```

この辺の情報をもとに、`/schedule`のスケジュール一覧ページを生成しているようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/1a885ee6-ddf4-9f5d-1af5-4c566cad1f4c.png)

## カスタマイズ
[lib/constants.ts](https://github.com/vercel/virtual-event-starter-kit/blob/bc1f934ef45d20a2e6838725f3391b25f8efaa9c/lib/constants.ts)にいくつか定数が定義してあり、ここを書き換えることでタイトルなどを独自のカンファレンスにカスタマイズできるようになっています。

試しにいくつか適当に変更してみます。

```diff:lib/constants.ts
-export const BRAND_NAME = 'ACME';
+export const BRAND_NAME = 's2terminal';
```

```diff:lib/constants.ts
-export const DATE = 'October 27, 2020';
+export const DATE = '2038年1月19日';
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/4bfb522b-d180-4959-f7f4-2ebd5cfb807d.png" width=640 />

なんだかトップページの文言が中途半端に切り替わりました。定数を変更するだけでどんなカンファレンスも対応できるというほど柔軟な仕様ではないようです。たとえばトップページならば[components/hero.tsx](https://github.com/vercel/virtual-event-starter-kit/blob/bc1f934ef45d20a2e6838725f3391b25f8efaa9c/components/hero.tsx)を変更する必要があります。

```diff:components/hero.tsx
       <h1 className={cn(styleUtils.appear, styleUtils['appear-third'], styles.hero)}>
-        The first {BRAND_NAME}
-        <br className={styleUtils['show-on-desktop']} /> global user conference
+        第1回 {BRAND_NAME}
+        <br className={styleUtils['show-on-desktop']} /> カンファレンス
       </h1>
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/b38388b3-89ba-17f1-047a-08074447ae4b.png" width=640 />

## Static Site Generation（SSG） と Incremental Static Regeneration（ISR）
このVirtual Event Starter Kitは`getStaticProps`のみ使っており`getServerSideProps`は使われていないので、**Static Site Generation（SSG）**で生成しているようです。

```sh
$ git grep -o get[a-zA-Z]*Props | awk -F: '{ print $2 }' | sort | uniq -c
      8 getStaticProps
```

さらに[pages/schedule.tsx](https://github.com/vercel/virtual-event-starter-kit/blob/bc1f934ef45d20a2e6838725f3391b25f8efaa9c/pages/schedule.tsx)を見てみると`revalidate: 60`が設定されており、[Incremental Static Regeneration（ISR）](https://nextjs.org/docs/basic-features/data-fetching#incremental-static-regeneration)を使っているようです。

```ts
export const getStaticProps: GetStaticProps<Props> = async () => {
  const allStages = await getAllStages();

  return {
    props: {
      allStages
    },
    revalidate: 60
  };
};
```

実際にHeadlessCMS部分の更新がどれくらいの頻度で行われるのを想定したのかは分かりませんが、ある程度頻繁にデータが変更されてもデータの最新化とパフォーマンスとを両立できるようになっています。

## 所感
実際にカンファレンスのWebサイトを作りたいとなった時は、このリポジトリをそのまま使うというよりはForkするなり参考にするなりして自前で組んでいくための物だと感じました。Vercel謹製なだけあってNext.jsでサイトを作るなら色々と参考になる所が多そうな気がします。

## 参考
- [Virtual Event Starter Kit – Vercel](https://vercel.com/virtual-event-starter-kit)
- [vercel/virtual\-event\-starter\-kit の実装を読んでみた \- odan blog](https://blog.odan.dev/articles/2020/12/read-virtual-event-starter-kit)