Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?
@sekai_holotube

飽き性の僕が初めてサービスをリリースするために必要だったこと。それはFirebaseHostingとGithubActionsで完全自動化すること。

本記事では以下のことについて紹介します。

  • ホロライブはいいぞ
  • YoutubeAPIを使ったランキングデータ作成
  • Vue.jsを使ったランキングサイト作成
  • FirebaseHostingを使ったホスティング方法
  • GithubActionsでランキング作成からデプロイまでを自動化

初めての投稿で拙い部分もあるかと思いますが、よければ最後までお付き合いしていただけると嬉しいです。

個人開発でリリースするために大切だと思うこと

まず結論から述べておきます(そしてあくまでも個人の感想です)。
それは、どんな手段を取ろうとも1秒でも早くリリースすること、そしてなるべく放置できることです。

ちなみにこの記事での「リリース」の定義として、FirebaseHosting環境にWebサイトをデプロイすることとさせてください。Firebaseに上げてしまえば公開されてるも同然なので。

これまで、色々なアプリを作ろうとしてアイデアを色々考えてやめて挫折。途中まで作ってやめて挫折。
挫折と挫折を繰り返しながら生きてきました。

極端な飽き性なのか、それともやる気がないだけなのか。
僕はどっちもだと思います。

そんな僕が今回、なんと初めてサービスを公開することができました。
それがホロライブ切り抜き動画ランキングサイト「ホロチューブ」です。
781dcec4ca0cdbc03f30771c7803859e.png

なので僕がサービスをリリースをするためには何が必要なのかを考えてみました。

まず最優先事項として、

  • デザインは後回し。とにかくリリース。
  • サイト構成は最小限(まずTOPの1ページのみ)。とにかくリリース。
  • 技術スタックは使い慣れたもの。とにかくリリース。
  • ホスティングサービスを使ってサーバー管理はしない。とにかくリリース。
  • コストはなるべくかけない。ドメイン以外は無料で運営。そしてとにかくリリース。

そしてできればやりたいこととして、

  • サイト更新も自動化。そうすれば最悪飽きても問題なし。とにかくリリース。

以上のことだけを意識してサービス開発に臨みました。

はじめに

はじめまして。普段はWeb系のサーバーサイドエンジニアをしています。
いつもQiitaは見るばかりで1度も記事を書いたことはなかったのですが、今回作ったサービスを作った勢いで記事も書いてみよう、せっかくだからアドベントカレンダーに参加してみようと思い、この記事を作成しました。

何もかも勢いで進めてしまいました。

前提として、環境構築やバージョン情報についての話は詳細には書きません。
あくまでも、1つのサービスをリリースすることについて重点を置きます。

まずはホロライブの話をしよう

昨日は「#CRホロライブ」や「#ペコらいぶ」でTwitterのトレンドに上がっていましたね。
今、僕はホロライブ3期生の兎田ぺこらちゃんの配信を見ながらこの記事を書いています。
ホロライブとは、主にYoutubeでVtuber活動をするグループおよびその事務所のことです。
運営はよくTwitterで炎上しているカバー株式会社です。

僕はホロライブを箱推ししています。
でも、敢えていうなら👯ぺこみこ🌸が好きです。ぺこみこてぇてぇ…。

本当はホロライブメンバーを全員紹介したいのですが、あくまでの技術記事なのでこれくらいにしておきます。

そしては僕は今ホロライブの切り抜き動画を作ることに熱中しています。

そうだ、ホロライブの切り抜きランキングサイトを作ろう

いつものことながら唐突にアイデアだけは浮かびます。
でも今回はいつもと違うところがあります。それは何か。

そう、僕も切り抜き動画を作ってYoutubeに動画投稿をしているのです。

以下は僕が作って投稿した動画です。
今度は○○○を全ロスするさくらみこ【ホロライブ切り抜き】
202021127_miko_02.jpg

そもそも切り抜きとは何か。
上記の動画の概要欄に元動画のリンクを貼っていますが、その動画をもとに一部を切り抜いて短くまとめた動画が切り抜き動画です。
(著作権的には、ホロライブのガイドラインに従っていれば一応問題ないことになっています)

話を戻して、切り抜き動画を作っている僕ですが、いまいち再生が伸びず、日々試行錯誤を繰り返す日々です。
Youtubeでバズるためには、トップおすすめ欄や動画のおすすめ欄に多く出してもらう必要があります。
とはいえ、最初はなかなか再生されず、SNSを活用したり、キーワードを意識したりetc…
その中の施策の1つとして、Webサイトを作って、流入場所を増やしてみるのはどうか、と唐突に思いました。

僕も1人のエンジニアとして「いっちょやってみるか」と、ここまではいつもの勢いで、今回の「ホロチューブ」を作ることを考えました。

ここからは技術的な話

前置きが長くなってしまいました。
ここからホロチューブを作るにあたって、工夫したことや躓いたことなどを書けるといいかなと思います。

最小限の構成を考えよう

今回は何としてもリリースまでもっていきたいと考えた僕。
まずは要件を設定します。

最低限の要件

  • サンプルでもいいからWebページが1枚
  • デプロイできる環境

たったこれだけです。

これから分かる通り、なんでもいいからとりあえずリリースしてみようの精神で取り組みました。

インフラ構成1

infra_01.png

まずは超最小限構成でリリースまで持っていきたいと思います。

Webページ作成

これは使い慣れたVue.jsを使って作成しました。

さっそくvue-cliを使ってプロジェクトをポチッと作ります。

$ vue create holotube

僕はVueの2系を選択しました。使い慣れているので。

プロジェクトが完成した、プロジェクト内に移動していつものコマンドを叩きます。

$cd holotube
$npm run serve

vue_init_ss.png

はい、いつもの画面ができました。

今回、一番最初にリリースしたのはこのページでした。

Firebaseプロジェクトを設定

さあ、FirebaseHostingにリリースしよう。

あらかじめFirebase CLIを導入しておきましょう。

$npm install -g firebase-tools

そして今回作ったプロジェクトをFirebaseプロジェクトに設定します。

$firebase init hosting

後述しますが、ここで設定をしっかり行えばGithubActionsのworkflowも自動で作成・設定も行ってくれます。

$firebase init hosting

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  作業ディレクトリ/holotube


=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

? Please select an option: (Use arrow keys)
❯ Use an existing project
  Create a new project
  Add Firebase to an existing Google Cloud Platform project
  Don't set up a default project

まずは新しいFirebase上のプロジェクトを作成しましょう。
「Create a new project」を選択します。

? Please select an option: Create a new project
i  If you want to create a project in a Google Cloud organization or folder, please use "firebase projects:create" instead, and return to this command when you've created the project.
? Please specify a unique project id (warning: cannot be modified afterward) [6-30 characters]:
 ()

プロジェクト名を決めましょう。ここでは必ずユニークなものでないといけません。
ここでは一旦「holotube」としておきます。

? What would you like to call your project? (defaults to your project ID) ()

ここはなんでもいいので僕はスキップします。

✔ Creating Google Cloud Platform project
✔ Adding Firebase resources to Google Cloud Platform project

🎉🎉🎉 Your Firebase project is ready! 🎉🎉🎉

Project information:
   - Project ID: holotube
   - Project Name: holotube

Firebase console is available at
https://console.firebase.google.com/project/holotube/overview
i  Using project holotube (holotube)

しばらく待つと、Firebase上にプロジェクトが作成されています。

次にHostingのセットアップをします。
今回vue-cliを使っていますので、ビルドされるディレクトリはdistになります。
なのでdistディレクトリを入力しましょう。

=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory?

次にサイトの構成を聞かれます。

? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) 

今回はSPAではないので、Noを選択します。

最後にGithubを使って自動デプロイを行うか聞かれます。
これが今回の要の部分になります。

? Set up automatic builds and deploys with GitHub? (y/N) 

こちらを設定しておけば、GithubとFirebaseHostingの連携をよしなに行ってくれます。

一応ここで設定だけ済ませておきましょう。すぐに終わります。

まずGithubの認証を行うために認証画面に飛ばされます。

認証が完了すれば、コンソールに戻り、完了していることを確認します。

✔  Success! Logged into GitHub as ユーザー名

次にリポジトリを聞かれるので、user/repositoryの形で渡してあげましょう

? For which GitHub repository would you like to set up a GitHub workflow? (format: user/repository)

これで、Github側にFirebaseのsecret情報が自動で設定されます。
このsecret情報を使ってGithubActionsからFirebaseにデプロイを行えるようになります。

次にプルリクがメインブランチにマージされた時にデプロイを行うか聞かれます。ここではYesにしておきます。

? Set up automatic deployment to your site's live channel when a PR is merged? (Y/n)

最後に、メインブランチを選択します。ここで選択したブランチがリリースされるブランチになり、マージをトリガーとしたわworkflowファイルが作成されます。

? What is the name of the GitHub branch associated with your site's live channel?

以上で大体の設定が完了しました。

GithubActionsの連携は後ほど記述しますので、さっそくリリースしましょう。

リリース

まずは以下のデプロイコマンドを叩きます。

$firebase deploy --only hosting

すると、Firebaseのデフォルトのindex.htmlと404.htmlが作成されます。

デプロイされたURLにアクセスすると↓の感じになります。

スクリーンショット 2020-12-14 11.19.47.png

これで準備OKです!

Vueプロジェクトをビルドして公開してみましょう。

$npm run build
$firebase deploy --only hosting

どうでしょうか。ローカルで動かしていたページが、ホスティング先のURLからも見えるようになったと思います。

はい、これであなたもリリース童貞を卒業できました。

ここまで来れば、コマンド1つでサービスをリリースすることができるようになりました。

しかし、さすがにこれだけでは酷すぎるので、サービスをある程度形にしたいと思います。

再度要件を定義しよう

では、改めて作りたいサービスに必要な要件を考えます。今回もミニマムを意識します。

  • 動画へのリンクを10個表示する。
  • 動画は24時間以内に投稿されたもので、再生数が多いものから取得する。
  • サムネイル、タイトル、アップロード時間を表示する。
  • 動画のタイトルはリンクになっており、クリックした際はYoutubeの動画ページに遷移させる。

以上のようなものを考えました。
これに実現するために必要な構成をまず考えます。
と、その前に1つ考慮しておかなければいけないことがあります。

YoutubeAPIの利用制限について

ご存知の方がほとんどだと思いますが、YoutubeのAPIには1日の利用制限が定められており、乱用できるものではありません。

そのため、サイトにアクセスするたび、APIを叩いて最新のデータを取得するなんてことはできません。

ある程度時間をおいてデータを更新かつ、ビルドする際にはあらかじめ取得しておいたデータを利用する形にしておく必要がありました。

インフラ構成2

infra02.png

① 4時間毎にGithubActionsを実行するようにします。
② GithubActionsからNodeスクリプトを実行します。
③ NodeスクリプトからYoutubeAPIを実行し、ランキングデータを取得します。
④ 取得したランキングデータをjsonファイルとして書き出します。
⑤ Github Actionsから「npm run build」を実行し、ランキングページをビルドします。
⑥ ビルドが完了したのち、Github ActionsからFirebase deployを実行します。

以上の動きになるようにGithubのworkflowファイルを作成しつつ、それぞれのスクリプトも作成します。

ランキングデータを作成しよう

YoutubeAPIをNodeから叩きます。

※ちなみDay.jsとaxiosというライブラリを使用しています。

index.js
async function fetchYoutubeData () {
    const utc = dayjs.utc().subtract(24, 'hours').format();
    const params = {
        'key': '${APIKEY}',
        'type': 'video',
        'part': 'snippet',
        'q': 'ホロライブ 切り抜き -にじさんじ',
        'order': 'viewCount',
        'publishedAfter': utc,
        'maxResults': '10'
    };
    const res = await axios.get('https://www.googleapis.com/youtube/v3/search', { params: params });
    return res.data.items;
}

YoutubeAPIの仕様上、設定できる時間がUTCとなっているため、全てUTCに変換して計算しています。
①JSTをUTCに変換
②UTCから24時間を減算
の処理を挟んでいます。

下記の部分でキーワードを設定しています。
ホロライブの切り抜きだけを取得したいので、にじさんじ(ホロライブとは別のプロダクション)が入らないように除外設定を行います。

index.js
'q': 'ホロライブ 切り抜き -にじさんじ'

取得したデータは以下のコードでjsonファイルとしてpostsといディレクトリに吐き出しています。

index.js
fetchYoutubeData().then( res => {
    const now = dayjs().format('YYYY/MM/DD HH:00:00');
    const fileName = 'ranking.json';
    const data = {
        last_update: now,
        items: res
    };
    const json = JSON.stringify(data, null, ' ')
    fs.writeFileSync('./src/assets/' + fileName, json);
});

フロントページを作ろう

続いてフロントページの作成を行います。
CSSフレームワークとしてVuetifyを使用します。

まずはコンテンツ用のコンポーネントを作ります。

card.vue
<template>
  <v-card
    class="mx-auto"
    max-width="480"
  >
   <template v-if="$vuetify.breakpoint.xs">
    <v-img
        class="white--text align-end"
        max-height="195"
        :src="card.thumbnails.high.url"
      >
      </v-img>
   </template>
   <template v-else>
     <v-img
        class="white--text align-end"
        max-height="270"
        :src="card.thumbnails.high.url"
      >
      </v-img>
   </template>
   <v-card-subtitle class="pb-0">{{ date(card.publishedAt) }}</v-card-subtitle>
   <a :href="'https://www.youtube.com/watch?v=' + id" target="_blank" style="text-decoration: none; ">
      <v-card-title>{{ card.title }}</v-card-title>
   </a>
  </v-card>
</template>

<script>
  export default {
    name: 'Card',
    props: {
      id: String,
      card: Object
    },
    methods: {
//日付をUTCから見やすいように変換
      date(text) {
        const str = text;
        const pos1 = str.indexOf("T");
        const date = str.substring(0, pos1);
        const time = str.substring(pos1+1).replace('Z','');
        return date + ' ' + time;
      }
    },
  }
</script>

これで動画リンクのカードができました。

続いてこれをリスト表示するようのコンポーネントを作成します。

list.vue
<template>
  <v-container v-if="ranking !== null">
    <v-row dense>
      <v-col v-for="(card, i) in ranking.items" :key="i" cols='12'>
        <Card :id="card.id.videoId" :card="card.snippet" />
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import Card from './Card';
import data from '../assets/ranking.json';

  export default {
    name: 'CardList',
    components: {
      Card
    },
    data: () => ({
      ranking: data
    }),
  }
</script>

ここで出力されたjsonを読み込んでいます。

最後にこれをApp.vueに読み込ませて完了です。

・PC版
スクリーンショット 2020-12-14 13.39.42.png

・SP版
781dcec4ca0cdbc03f30771c7803859e.png

※細かいデザインについての記述は省略しています。

更新を自動化しよう

ここまで来れば、あとはデータの取得を自動化、ビルド&デプロイを自動化すれば目的達成です。

まずはGithubActionsをスケジュール実行できるようにします。

ベースとして、Firebaseプロジェクトを設定した際に自動作成されたymlファイルを使います。

base.yml
name: Deploy to Firebase Hosting on merge
'on':
  push:
    branches:
      - master
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_HOLOTUBE }}'
          channelId: live
          projectId: holotube
        env:
          FIREBASE_CLI_PREVIEWS: hostingchannels

※上記ファイルの動作確認は済んでいるものとします。
ファイル名を「deploy.yml」でコピーして、スケジュール実行に変更します。

deploy.yml
name: Schedule Deploy #ワークフロー名を変更

on:
  schedule:
    - cron: '0 3-23/4 * * *' #スケジュールを4時間ごとに動くように設定

次にNode.jsを実行できるようにします。

deploy.yml
name: Schedule Deploy

on:
  schedule:
    - cron: '0 3-23/4 * * *'

jobs:
  fetch:
    name: fetch and deploy # ジョブ名を変更
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [14.8.0] # Nodeのバージョンを指定
    steps:
    - uses: actions/checkout@v2
    ### ここから
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@main
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm install
    - run: npm run build
    ### ここまで
    - uses: FirebaseExtended/action-hosting-deploy@v0
      with:
        repoToken: '${{ secrets.GITHUB_TOKEN }}'
        firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_HOLOTUBE }}'
        channelId: live
        projectId: holotube
      env:
          FIREBASE_CLI_PREVIEWS: hostingchannels

これをGithubのmainブランチにあげれば、スケジュール実行と自動デプロイができます!

完成!でもやれることはまだいっぱい…

ようやく、見せられる最低限には持っていけたかと個人的には思っています。
ここまで来れば、4時間毎データを勝手に更新してくれるランキングサイトの雛形が完成です。

あとは追加したい機能やデザインの修正を他のブランチで行いつつ、動いているサービスを運営することができます。

今後のTODOと課題

ここまでは1人でやりきりましたが、ここからは友人を加えて2名体勢でのんびり運営していこうと考えています。

それにあたって以下のことを今後やっていきたいと考えています。

  • Nuxt.jsに置き換えて静的サイトにする。→ 対応中
  • せっかく毎日作っているランキングデータはひたすら上書かれているので、これが蓄積するようにする。 → Firebase Storageに保存していく。(対応済み)
  • 過去のランキングデータも見れるようにする。→ 未対応

また課題としてパフォーマンス面でCSSがボトルネックになっているのでこれを修正したいと考えています。

スクリーンショット 2020-12-14 14.07.59.png

上記はLighthouseでの計測結果です。ひえ〜。

最後に

ここまで長々と読んでくださりありがとうございます。
今回は自分が初めてリリースできたサービスに関して、どうすればリリースできるか試行錯誤した結果を紹介させていただきました。

まだまだ技術的にも運営者としても至らぬ部分はありますが、精進して参りますので暖かく見守っていただけると幸いです。

この記事が少しでも誰かのためになれば嬉しいです。

止まらないホロライブ

先日、ホロライブで4人目のチャンネル登録者数100万突破者が出ました(もうすぐ5人目も出そう)。
今Vtuberで一番勢いがあるプロダクションといって間違いないです。

12月21日・22日にはオンライン配信限定のセカンドライブが予定されています。
https://beyondthestage.hololive.tv/

こちらは有料のみとなっておりますが、普段の配信はYoutubeで行われており、ライブもアーカイブ(一部を除く)もいつでも無料で視ることができます。

この機会にぜひ、ホロライブの沼にはまってみませんか?

参考

後ほどまとめます。

4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What is going on with this article?