Webフロントエンド何も分からんがGatsby.jsを使ってポートフォリオを作り直してみる


# はじめに
こんにちは。 odmishienです。この記事は[DeNA 21 新卒 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/dena-21-shinsotsu)の12日目の記事です。実は[4日目も担当していました](https://qiita.com/odmishien/items/c0fa9c30cb16a3edc90a)。

4日目に書いた記事はどちらかというと技術的な話というより勉強法や精神論的なポエムだったので、今回は技術ネタを持ってきました。

私はアルバイトの業務では主にバックエンドを担当していて、特にPerl, Python, Go などを書くことが多いです。Webフロントエンドに関しては知らないことばかり...という感じで、Vue.jsを趣味のプロダクトで使ってみたことがあり、かろうじてコンポーネントを理解している…というようなレベルです。

でも....カッコよくて便利なポートフォリオが欲しい.....!!!!

そんな気持ちが、[ポートフォリオ](https://www.odmishien.fun)になりました❤︎

今回はWebフロントエンドをバリバリに書いたことのない私がGatsby.jsを使ってポートフォリオサイトを作るにあたってやってみたことをまとめてみようかと思います。

# これまでのポートフォリオの問題点

### なんとも言えないパフォーマンス
以前作っていたポートフォリオはVue.jsで作成してGitHubPagesでホストしていました。Lighthouse Report Viewerのスコアがこんな感じ。

![スクリーンショット 2020-12-03 23.53.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205979/9c77a9d6-8e0d-fee2-42d3-485477a9fff3.png)


そこまで重たいコンテンツを置いているわけではないのですが、スコアが微妙。

### コンテンツをHTMLにベタ書きして更新しなければならない

ポートフォリオなので作ったものを掲載したりしていたのですが、これを手動でやっていました。具体的には以下のようなコンポーネントを作成してそこに `props` を渡したものを毎回書いていました。

```vue
<template>
  <div class="card">
    <img class="card-img-top" width="100%" height="50%":src="src" />
    <div class="card-body">
      <h4 class="card-title">{{ title }}</h4>
      <p class="card-text">{{ text }}</p>
      <div class="row my-2">
        <a
          v-if="appLink"
          class="btn btn-primary col-4 offset-2"
          target="_blank"
          :href="appLink"
        >Check App</a>
        <a v-if="githubLink" class="col-4" target="_blank" :href="githubLink">
          <LogoGithubIcon w="40px" h="40px"></LogoGithubIcon>
        </a>
      </div>
      <div class="tags">
        <p v-for="tag in tags" class="card-text text-muted">#{{ tag }}</p>
      </div>
    </div>
  </div>
</template>
<script>
import LogoGithubIcon from "vue-ionicons/dist/logo-github.vue";
export default {
  name: "card",
  components: {
    LogoGithubIcon
  },
  props: {
    imgPath: {
      type: String,
      required: true
    },
    title: {
      type: String,
      required: true
    },
    text: {
      type: String,
      required: true
    },
    appLink: {
      type: String,
      default: null
    },
    githubLink: {
      type: String,
      default: null
    },
    tags: {
      type: Array,
      required: true
    }
  },
  data() {
    return {
      src: require(`../assets/${this.imgPath}`)
    };
  }
};
</script>
```

新しいものを作成するたびにポートフォリオの方も修正して、buildして、pushして....みたいなことをしていて、結構面倒でした。

### 手動でビルドしないといけない

GitHubPagesを使っていたので、決まったブランチの決まったディレクトリにpushさえすればデプロイはできたのですが、手元でwebpackを使ってbuildしてあげる必要がありました。よくコマンドを忘れてhistoryから探したり、「あれ？ビルドしたっけ？」となることがあったりして、少し不便でした。

# そもそもGatsby.jsとは何か

[公式サイト](https://www.gatsbyjs.com/)

>Gatsby is a React-based open source framework for creating websites and apps. Build anything you can imagine with over 2000 plugins and performance, scalability, and security built-in by default.

- 早いらしい
- プラグインで色々簡単に拡張できるらしい
- コマンドポチポチしたらサイトができてるらしい

とにかく導入が簡単！コマンドを打つだけで複雑な設定は隠蔽してくれます！！というインターネットの声をよく目にしました。こういうなんでもやってくれる系のフレームワークはお節介が過ぎると敬遠してしまう場面もありますが、Webフロントエンドに精通していない私にとっては手取り足取りして欲しい！ということでGatsbyを導入してみることにしました。

プロジェクトの作成方法など、Gatsbyのコマンドに関する話はここでは割愛させていただきます。(インターネットにたくさん良記事が転がっていた記憶があります)

# 構成

- フロントエンド
  - TypeScript+React(Gatsby.js)
- ホスティング
  - GitHub Pages
- デプロイ
  - GitHub Actions

ある程度長くメンテナンスするだろうなということとお勉強のためにTypeScriptを選びました。Reactを使うのはGatsby.jsの場合避けられません。

ホスティングはこれまでも使っていて使い慣れているGitHub Pagesを採用しました。また、デプロイも親和性の高いGitHub Actionsで行うことにしました。

# GitHubのリポジトリ情報にGraphQL経由でアクセスする

[ldd/gatsby-source-github-api](https://github.com/ldd/gatsby-source-github-api) を使います。

READMEの通りに設定していきます。まずは `gatsby-config.js` にプラグインを追加します。

```javascript
{
  resolve: "gatsby-source-github-api",
  options: {
    token: process.env.GITHUB_API_TOKEN,
    graphQLQuery: `
    query ($nFirst: Int, $q: String!) {
      search(query: $q, type: REPOSITORY, first: $nFirst) {
        edges {
          node {
            ... on Repository {
              id
              name
              description
              url
            }
          }
        }
      }
    }`,
    variables: {
      q: "topic:portfolio user:odmishien",
      nFirst: 10,
    },
  },
}
```

今回は `portfolio` というtopicが指定された 私のリポジトリを10件取ってくる設定になっています。これで人様に見せてもいいと思うリポジトリには `portfolio` というtopicを付けておくだけでOKです。

そしてindexページのtsxファイルの中でGraphQLでデータにアクセスします。

```typescript
import { IndexQuery } from "../../types/graphql-types"

interface IndexProps {
  data: IndexQuery
}

const IndexPage: React.FC<IndexProps> = ({ data }) => {
  const repos = data.githubData?.data?.search?.edges
   ... // 省略
}
export const query = graphql`
  query Index {
    githubData {
      data {
        search {
          edges {
            node {
              id
              name
              description
              url
            }
          }
        }
      }
    }
  }
`
```

ひとまずこんな感じでリポジトリとそのdescriptionを表示させることに成功。

![スクリーンショット 2020-12-03 23.59.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205979/2fd935a2-2b6f-8fcc-6af5-32956b69aeee.png)

GraphQLのクエリの構築に結構苦戦しましたが、[GitHub GraphQL API Explorer](https://docs.github.com/en/free-pro-team@latest/graphql/overview/explorer) で型の補完を効かせながら試行錯誤しました。

また同じようなことを自分の技術ブログ(はてなブログ)のRSSフィードから生成するみたいなことを[mottox2/gatsby-source-rss-feed](https://github.com/mottox2/gatsby-source-rss-feed)を使って実現しています。


# GraphQLのスキーマをTypeScriptの型に落とし込む

GraphQLを使ってAPIなどのレスポンスを使えるのは便利なのですが、その構造を型に落とし込むのを手動でやっていくのはちょっと骨が折れます。先ほど触れなかったのですが 

```
import { IndexQuery } from "../../types/graphql-types"`
```

としている部分があったことにお気付きでしょうか。これは私が定義したのではなく、 [gatsby-plugin-graphql-codegen](https://www.gatsbyjs.com/plugins/gatsby-plugin-graphql-codegen/) を使って、型定義ファイルを自動で生成しています。

またも、`gatsby-config.js`の中にプラグインを書いていきます。

```javascript
{
      resolve: "gatsby-plugin-graphql-codegen",
      options: {
        fileName: `types/graphql-types.d.ts`,
      },
}
```

これで `types/graphql-types.d.ts` というファイルに `gatsby build` されるたびに発行しているqueryに応じて、型定義が追加されていきます。

例えば私のポートフォリオの場合、`index.ts`の末尾に以下のような`Index`という名前のクエリを発行しています。

```typescript
export const query = graphql`
  query Index {
    ...省略
    }
`
```

ビルドしてみると、この名前に `Query` というsuffixが付いた `IndexQuery` という型が定義されているのが確認できます。便利！

# GitHub Actionsを使って決まった時間に自動デプロイ

GitHubやRSSから欲しい情報を取ってくることはできますが、コンテンツの更新はビルドし直さないとできません。そこでmasterにpushがあった時と毎日夜0:00にビルドしてGitHub PagesにデプロイするというActionを仕込みます。これは[kentaromさんの記事がとても参考になりました](https://blog.kentarom.com/how-to-build-the-portfolio-for-lazy-person/)。

```yaml
name: Deploy

on:
  push:
    branches:
      - master
  schedule:
    - cron: "0 0 * * *"

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1

      - name: setup node 14.x
        uses: actions/setup-node@v1
        with:
          node-version: "14.x"

      - name: install
        run: npm install

      - name: build
        run: npm run build
        env:
          GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          cname: your.domain.com # 独自ドメインを利用したい場合 
```

`secrets.GITHUB_TOKEN` の方はActionsのworkflow内で使える自動で生成されたトークンなのでsecretsに登録する必要がありません[^1]。便利！

[peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) は `github_token`を指定しておくだけで、デフォルトで [`gh-pages`ブランチ](https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-set-another-github-pages-branch-publish_branch)に[`public`ディレクトリ](https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-source-directory-publish_dir)の内容をデプロイしてくれます。カスタマイズしたかったらこれらの値をyamlに書いてあげてください。

また、独自ドメインを利用したい場合は `cname` フィールドに利用したいドメインを指定しておきます。これをしないとデプロイの度に`CNAME`ファイルが作られないので、毎回デフォルトのURL(`https://username.github.io`)が指定されてしまいます。

# まとめ

![スクリーンショット 2020-12-02 16.52.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205979/ce877d18-8d48-3173-fff3-66e8cec5b976.png)


無事読み込みも速くなって、オールグリーン！ビルドやデプロイのことはGitHub Actionsが全て担ってくれるので、私がやることはなくなりました。サイコー！Webフロントエンドのことをあまりよく分かっていない私でもある程度のセッティングができたので便利ですね、Gatsby!!

改善点としては

- `npm install` を毎回やってしまっているので、キャッシュする
- GraphQLについてかなりフィーリングで触っているので勉強して無駄のないクエリを書く
- 掲載するコンテンツを増やす

などでしょうか。随時手を入れていこうと思います。

最後までお読みいただきありがとうございました。

# 参考になった記事

- [Reactの最強フレームワークGatsby.jsの良さを伝えたい！！](https://qiita.com/hppRC/items/00739eaf9ae7fc95c1ca)


- [怠惰なエンジニアのためのポートフォリオサイト構築術](https://blog.kentarom.com/how-to-build-the-portfolio-for-lazy-person/)


- [Gatsby.js を完全TypeScript化する](https://qiita.com/Takepepe/items/144209f860fbe4d5e9bb)

# 宣伝

この記事を読んで「面白かった」「学びがあった」と思っていただけた方、よろしければ Twitter や facebook、はてなブックマークにてコメントをお願いします。
また [DeNA 公式 Twitter アカウント @DeNAxTech](https://twitter.com/DeNAxTech) では、 Blog記事だけでなく色々な勉強会での登壇資料も発信してます。ぜひフォローしてみて下さい。

<a href="https://twitter.com/intent/follow?screen_name=DeNAxTech">Follow @DeNAxTech</a>


[^1]: https://docs.github.com/ja/free-pro-team@latest/actions/reference/authentication-in-a-workflow#github_token%E3%82%B7%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)