Hello Tech

AutoReserve 等を開発する株式会社ハローのテックブログです。スタートアップの最前線から本質的な価値を届けるための技術を紹介します。

ウェブとReact Nativeアプリのコード共通化による同時展開

javascripterです。ハローでは、プロダクトのローンチ前からAutoReserve の開発に関わっています。今回の記事では、AutoReserveでおこなっているコード共通化の取り組みについて紹介します。

背景

AutoReserveのネイティブアプリはReact Nativeで書かれており、またウェブ版は、Reactで書かれています。

ウェブ版では、React Native for Webという、React上でReact NativeのコンポネントのAPIを使えるようにするライブラリを使用しています。

React Native for Webを採用したことで、ハローでは現在、エンジニア1人でiOS、Android、ウェブの全てのプラットフォームに同時展開できるようになりました。 また、不具合修正やデザインの修正も、一箇所を修正するだけで同時にできるようになりました。それぞれのプラットフォームでの実装・デザインの乖離が起こりづらいこともメンテナンス性の向上に繋がっています。

今回の記事では、AutoReserveでどのようにアプリとウェブのコードの共通化をおこなっているか説明します。

何を共通化しているのか

以前の記事(なぜNext.jsをやめたのか?)でも軽く紹介していますが、アプリの機能実装を含む、下記のコードを共通化しています。

  • ボタン、チェックボックスなどのUIコンポネント
  • アプリケーション画面のコード
    • レストランの画面
    • 予約の表示画面
    • レストランの予約フォーム画面
  • eslint、prettier、TypeScriptの設定ファイル
  • TypeScriptの型
    • Protocol Buffersで自動生成したAPIの型
  • 定数
    • 色などのテーマの定義 - レスポンシブデザインのbreakpointの幅定義
    • 税率など定数
  • モジュール
    • usePrevious / useStable / useMediaQueryなどよく使うhooks
    • アプリ・ウェブで共通して使えるalert関数の実装

これらのコードを、@hello-ai/ar_sharedという名前でパッケージ化して社内で利用しています。

現在、フロントエンドはmonorepo化を進めており、一部アプリを除き、共通ライブラリの変更とアプリケーションコードの変更をまとめて行えるようになっています。

まだmonorepo化できていないリポジトリに関しては、

  • 共通コードに変更があった場合、GitHub Packagesにnpmパッケージを自動でpublishする
  • 各リポジトリの@hello-ai/ar_sharedの依存関係のバージョンを自動であげる

というGitHub Actionsのアクションを書き、対処しています。パッケージをまたぐ変更が行いやすくなるよう、いずれ全てmonorepoに集約される予定です。

再利用可能なUIライブラリの共通化

まず、再利用可能なUIコンポネント集・hooksなどのモジュールを用意しています。

  • フォーム関連のコンポネント
  • グリッドレイアウトのコンポネント
  • チェックボックス
  • テキスト入力のコンポネント

などです。UIライブラリはドキュメントとしてstorybookを用意しています。

AutoReserveのUIコンポネント集のstorybook

UIコンポネント集を作成する取り組みはよく見かけますが、AutoReserveではさらに一歩進んで、アプリケーションコード自体も共通化しています。

アプリケーションコードの共通化

アプリケーションコードを共通化するには、アプリとウェブのスクロールの仕組みの違いや、ナビゲーションの構造の違いなどを吸収する必要があり、工夫が必要です。この章では、 アプリケーション画面のコードを共通化する方法を紹介します。

先行の取り組みとして、solitoというNext.jsとReact Nativeでの実装の共通化を行うライブラリがあり、一部参考にしていますが、AutoReserveではReact Routerを使っているため、内部で新規ライブラリを実装しています。

共通化しているもの

現在、アプリ、ウェブの両方で共通化しているアプリケーションコードは下記です。

  • アプリケーションの機能画面全体
  • リンク、画面遷移のコード
AutoReserveウェブサイトのレストラン画面
AutoReserve Androidアプリのレストラン画面

ナビゲーションの共通化の仕組み

アプリ、ウェブで使用しているライブラリはそれぞれ異なり

を使用しています。

アプリとウェブの主な構造の違いとして、通常、アプリはhooksを使い現在のスタックに対して画面をpushするという操作を行うのに対して、ウェブはURLによるリンクで遷移するということが挙げられます。

アプリ:

import { useNavigation } from '@react-navigation/native'

function Component() {
  const navigation = useNavigation()
  const onPress = () => {
    navigation.navigate('Restaurant', { restaurantId: restaurantId })
  }
  return <Button onPress={onPress}>レストラン</Button>
}

ウェブ:

import { Link } from 'react-router-dom'

function Component() {
  return <Link href={'/restaurants/xxxx'}>レストラン</Link>
}

ウェブでは、SEOやアクセシビリティの観点から、onClickによる遷移ではなく、必ず<a>要素としてレンダリングされるようにしたい、という要件があります。

これらの遷移の仕組みの違いを吸収するため、AutoReserveでは下記のような仕組みを採用しています。

  • ウェブでは、通常のリンクとなるようurlを指定して遷移
  • アプリではhooksを使用するが、遷移先の指定はdeep linkで行う

共通UIライブラリ側:

import { Platform, Linking, Text } from 'react-native'

const prefix = 'autoreserve://autoreserve'

export function useLinkTo() { // アプリでdeep linkによる遷移を行うhooks
  if (Platform.OS === 'web') {
    return () => {
      throw new Error('useLinkTo is not implemented for web. You should only use it in native.')
    }
  } else {
    return (to) => Linking.openURL(prefix + to)
  }
}

// ウェブではtoでリンク先を指定、アプリではonPressが作動するコンポネント
export function LinkText({ to, onPress, ...rest }) {
  return <Text
    href={to} // webではhrefを指定しリンク化
    onPress={
      Platform.select({
        web: () => React_Router_navigate(to), // React Routerによる遷移
        default: onPress // アプリでは通常のonPressを指定
      })
    }
  />
}

アプリケーションコードでの使用例

function Component() { // 使用例:
  const linkTo = useLinkTo()

  return <LinkText
    to="/restaurants/slug" // toを指定するとウェブでのみリンクになる(アプリでは無視)
    onPress={() => { // onPressはアプリでのみ実行される
      linkTo('/restaurants/1234')
    }}>
    レストランへのリンク
  </LinkText>
}

まとめると、ウェブではリンク、アプリではonPressのイベントハンドラーを渡し、deep linkによる遷移をおこなっているという形になります。

アプリ側ではdeep linkのpathを渡すのではなくonPressを渡すよう実装にした理由は、アプリとウェブでURLのpathnameの構造が一致しておらず、またアプリでは現状deep linkで表現できない遷移がまだあり、JSコードを間に挟めるようescape hatchを用意したかったためです。

スクロール周りの挙動の違いの吸収

また、アプリとウェブで挙動が大きく異なる部分として、スクロール周りがあります。

  • React Nativeのアプリでは<ScrollView>というコンポネントを使用すると任意の箇所を スクロール可能にできる
  • ウェブの場合、ページのスクロールは通常<body>要素によって行われる

例えば、ウェブでbodyの内側のdivoverflow-y: autoを当てメインのスクロール要素にすると、モバイルでURLバーが自動で隠れなくなるなどUX上の問題があるため、基本的には必ずbodyでスクロールを発生させる必要があります。

これらの問題に対処するため、アプリケーションコードの共通化を行う際は、スクロール要素そのものはコンポネントに含めず、内部のコンテンツのみをコンポネント化しています。

共通化部分:

function RestaurantContent(props: RestaurantContentProps) {
  return <View>
    {
      /* レストランページの中身 */
    }
  </View>
}

アプリ:

import { RestaurantContent } from '@hello-ai/ar_shared/...'

function RestaurantScreen() {

  return <View style={{ flex:1 }}>
    <Header /> // ヘッダは上部固定
    <ScrollView style={{ flex: 1 }}>
      <RestaurantContent .../> // レストラン画面の中身だけをスクロール可能に
  </ScrollView>
  </View>
}

ウェブ:

import { RestaurantContent } from '@hello-ai/ar_shared/...'

function RestaurantPage() {
  return <body>
    <Header style={{
      // body全体をスクロール可能にしているためposition: fixedでレイアウト
      position: 'fixed',
      top: 0,
      left:0,
      right: 0
    }}/>
    <View style={{
      marginTop: headerHeight
    }}>
      // レストラン画面の中身はbody内にそのまま広げ
      // bodyでスクロール可能に
      <RestaurantContent .../>
    </View>
  </body>
}

ページ/画面のコンポネント自体はアプリ/ウェブで別に実装し、そこからの共通コンポネント実装を呼び出すようにしています。この方法には

  • ウェブ/アプリで最適なUI構造となるよう構造を変えられる
  • アプリ/ウェブ側から共通ライブラリ側にpropsを渡せるので、プラットフォームの差異がある場合も共通ライブラリ内で分岐せずに済む

といったメリットがあります。

今後の課題

コード共通化の仕組みは途中で導入したため、まだ共通化ができていない箇所がいくつかあります。現時点での課題として

  • ウェブとアプリで、URLパスとdeep linkの構造が一致していないため、リンク先を複数記述する必要がある
  • データ取得のコードをアプリ・ウェブで共通化できていない

があります。

まとめ

React Native for Webを使用し、少人数でiOS, Android, ウェブの全プラットフォームに同時展開できるコード共通化基盤を構築しているという事例を紹介しました。

ハローでは、生産性を追求し、積極的に技術的な挑戦に取り組める本物のエンジニアを募集しています。

少しでも気になる方は気軽に javascripter にDMでお声がけください!

エンジニア採用情報 | Hello, Inc.

電話音声の自動機械判定モデルとサービス活用

はじめまして、Xです!私は普段アメリカのカーネギーメロン大学に在学していて、機械学習関連の研究をしています。ハローでは主に音声と関係する機械学習の開発をしています。

この記事では、AutoReserveの中で機械学習を使っている自動機械判定というコンポーネントについて紹介します。

自動機械判定とは

ご存知の方も多いと思いますが、電話をかけるときに相手が電話に出られないときは留守番につながることがよくあります。その際に自動音声が流れることがよくあります。AutoReserveのサービスでも、自動音声で予約する際によく店側の電話の自動音声につながることが多くあります。そのときに店側に予約を取ることが難しいため、再び時間をおいてから自動音声をかけ直す必要があります。そのため、電話をかけた際に店側が自動音声で出ているのか、店員が実際に出ているのかを知りたいことがたくさんあります。自動機械判定はこれを分類する機械学習のモデルを指します。

先行研究

意外かもしれませんが、音声合成をする研究はかなりメジャーな研究分野である一方で、自動で機械音声かどうかを判別する研究は多くありません。その原因は色々考えられますが、主な原因はこの判定タスクの定義自体がかなり曖昧で、具体的なシナリオによっては難易度が大きく変わるからだと思います。

たとえば、オーディオブックをアナウンサーが読んだ音声と機械が読んだ音声を区別するシナリオであれば、このタスクはかなり難しいと思います。その理由はそもそも音声合成は人間の声を忠実に再現することを目指しているため、よいモデルであればあるほど区別することが難しいです。一例として、発音の韻律を考慮した最近のこの研究では、人間の主観でも機械かどうかを統計的に有意に判定することが難しいです。一方で、私達は店側に電話をかけたときに留守番の自動音声かどうかを判定するシナリオについてですが、幸いこのタスクはかなり簡単な部類に入ります。

機械学習モデル

まずどんなタスクであってもある程度データを集める必要があります。このタスクは前述のようにあまり研究がなされていないもので、既存のデータはありません。そのために私達自身で、AutoReserveのサービスで集めた音声のうち、ランダムに数百個のサンプルを選んで自動かどうかを手でラベリングしました。

次に実際にどうモデリングできるかを考えてみましょう。このタスクは二値分類の問題で、入力された音声に対して、自動音声かどうかを判定します。ここ10年の音声技術分野ではさまざまな深層学習の手法が開発されていますが、どれもある程度の規模なデータセット(数百時間レベル)を想定しているが、われわれのデータセットは数時間程度しかありませんので、大半の深層学習の手法は使えません。最近流行りのself-supervised learningの深層学習の手法では、データセットは小規模で良いケースもありますが、モデルが大きいため、デプロイする際のコスト問題が発生します。

そこで、私達は深層学習ではなく、もっとシンプルな線形モデル(ロジスティック回帰)を使いました。古典的な線形モデルはシンプルでデータ量が比較的に必要としないかわりに、特徴量の選択が重要になってきます。私達は色々な特徴量をつくって実験してみましたが、そのうちから、重要かつ面白い特徴量を3つ取り上げて紹介します。

  • Callee Overlap Feature
  • Noise Feature
  • Duration Feature

Callee Overlap Feature

自動留守番の機械音声の場合、相手を無視して音声が流れます。一方で私たちも自動で予約音声を話しているため、電話の両方で自動音声が同時に流れます。この状況は実際に人が電話に出ている場合では、なかなか起きません、っていうのはこちらの音声を聞いている間はとくになにも話さないことが多いからです。そのため、通話時間のうち、どれだけ音声がかぶっているかを調べることで、相手が機械かどうかのヒントを与えてくれます。私達の実験では、この特徴量だけでもかなりの精度(75%)を達成することができました。

Noise Feature

音声生成に詳しい方なら知っているかもしれませんが、生成された音声は基本的にノイズが少なく、かなりきれいな声で話されています。これは音声のスペクトル図を見てもはっきりわかります。そのため留守番の機械音声ですと、ノイズがほとんど乗りません。一方で店側で人が出ている場合は、店側の環境音がかなり混じっていることが多く、ノイズが多く乗ります。そのため、店側の音声にノイズがどれだけ混じっているかを調べることで、機械音声かどうかがわかるケースも多くあります。

Duration Feature

通話時間の長さからもかなり多くのことがわかります。留守番電話のケースですと、いくつかの決まった留守音声のテンプレートがあって、そのテンプレートが流れ終わると自動的にその秒数で電話が切られるか、あるいはその後ずっとこちらの留守メッセージを聞き続けて、タイムアウトがくるまでにずっと通話するかのパターンが多いです。そのため留守番の通話時間は特徴的な秒数をとっていることが多くあります。この特徴的な秒数をとっているかどうかを調べること(たとえば混合ガウスモデル)で、機械音声かどうかがわかります。

これらの特徴量に加えて、いくつかほかの典型的な特徴量(MFCC特徴量やクラスタリング特徴量など)を組み込んで線形モデルをつくりました。機械学習のライブラリは最近はやりのpytorchではなく、scikit-learnを採用しています。音声の特徴量抽出はpython_speech_featuresというライブラリを使っています。callee overlapの有無はpy-webrtcvadというモデルを改造して判別しています。すべての実装はnumpyベースであるため、軽量に動かすことができます。

下がおおよそのメインクラスの実装になります。モデルを予め訓練してpickle形式に保存しておき、デプロイ時はそれをロードして使います。毎回新しいリクエストに対して判別を行うときに、まず上にあげた特徴量などを計算しておき、線形モデルに入れて判別します。

class AutomaticMachineDetection:

    def __init__(self):

        # sklearnのロジスティック回帰を使います
        self.model = LogisticRegression(C=100)

        # 音声から典型的なMFCC特徴量を抽出するモデルを準備します
        self.pm = create_pm_model("mfcc_hires")

        # クラスタリング特徴量
        self.cluster = BowCluster(100)

    def feature(self, audio):
        """
        音声から特徴量を抽出します。

        :param audio:
        :return:
        """

        # クラスタリング特徴量など
        cluster_feat = self.get_cluster_feature(audio)

        # 上のcallee overlapの特徴量など
        stat_feat = self.get_stats_feature(audio)

        feat = np.concatenate([cluster_feat, stat_feat])

        return feat

    def predict(self, audio_or_wav_path):

        # only use the first channel which is corresponding to the callee audio (The caller side is ignored for now)
        if isinstance(audio_or_wav_path, str) or isinstance(audio_or_wav_path, Path):
            audio = read_wav(audio_or_wav_path, channel=0)
        else:
            audio = audio_or_wav_path

        # 特徴量抽出
        feat = self.feature(audio)

        # 推論
        return self.model.predict([feat])[0]

さまざまなパラメーターや特徴量をチューニングして実験した結果、98%ぐらいの精度を達成することができました。

このタスクのように、機械学習モデルは必ずしも深層学習を使う必要がなく、簡単なモデルで物足りるケースも多々あります。実際に深層学習を使おうとすると、大量のデータを集める必要があるのに加えて、訓練やデプロイする際に多くの計算資源を必要とします。しかし、この簡単なモデルは個人のノートPCで数分程度で訓練することができて、簡単に開発することができます。ただ、シンプルなモデルの性能を最大限に引き出すために、特徴量を注意深く作る必要があります。

まとめ

今回のモデルは線形モデルであるため非常に軽量なインスタンスで動かすことができて、最終的にこのモデルをFlask に乗せて Google Cloud Functions のサーバーレス環境にデプロイしています。自動音声予約の数は増え続けていますが、ほぼメンテなしで安定してスケールしています。ハローの電話料金コストを削減するのに役立っています。

ハローではこのように音声に関する知見を深めて、フロントエンド、バックエンドの開発にとどまらず、これからも音声の機械学習分野で技術開発を続けていきます。

プログラミング未経験ビジネス職メンバーがSQL覚えたら最強になった件

こんにちは!

株式会社ハロー、マーケティングチームです。日頃は、サービス全体の設計やAutoReserveの問い合わせ数増加施策などグロース全般を担当しています。

2022年2月頃からハローでは、開発職以外の希望するメンバーがSQLを勉強することになりました。実際に学んだ過程や学んだ感想について今回はご紹介したいと思います。

知らない人のために...「SQL」とは?

データベース言語の1つで、データを検索・挿入する際に利用します。GoogleAnalyticsではアクセスデータを確認することができますが、SQLを活用することでサービスが保有しているデータを確認することができます。

例えば、

  • ユーザーの月次登録数
  • ユーザーのアカウントが保有している情報一覧
  • ユーザーのうち、日本人以外のユーザー一覧
  • レストランの都道府県ごとの登録数
  • 店舗向けに販売してるSaasの都道府県ごとの利用数

などをチェック可能です。

データベースにはたくさんのデータが保存されていますが、それを効率的に確認し、マーケティング活動や現在の状況を確認するために利用します。

SQLを勉強することになったきっかけ

ハローは、データを元に施策を考える文化自体はあったものの、ビジネス職メンバーが取得したいデータを自分で取得する方法はわからないためエンジニアに依頼し、Redashで作成してもらうという流れが一般的でした。

ただ、この運用フローだとエンジニアの業務量が増えてしまうことはもちろん、軽い気持ちで社内のデータをチェックしたい時にチェックができないという点もあり、社内でSQL勉強チャンネルを設置し希望者を中心に勉強することにしました。

勉強方法は?

ハローでは、エンジニアが自社のサービスが保有するデータをどの様に活用すべきかをまとめた「SQL道場」を準備し、それを順番に進めていくことである程度のSQLを書ける様になる環境を準備しました。

SQL道場を見ながら、実際にredashを触って少しずつ情報の取得方法を勉強していきます。最初は簡単なものが多くスラスラと進めていけますが、徐々に難しくなりエラーが出てきたり、そもそもSQLの書き方がわからなかったりとスムーズに行かないことも増えてきました。

実際に勉強してみて気づいたことですが、最初にSQLの仕組み自体を理解するため、本を読むこともおすすめです。

社内で実際のデータを元に勉強ができないという方には以下のサイトもおすすめです。回答はgoogle検索で調べるといろんな方が解説しているので独学でも進めることができます。 https://sqlzoo.net/wiki/SQL_Tutorial

大変だったこと

最初は思い通りのデータを取得することができず、エラーをたくさん出しました。数時間詰まることも・・・。その度に、エンジニアにフォローしてもらいながら学習を進めていきました。すぐに聞ける環境を会社全体で作ってくれたのはとても助かりました。

また、取得したデータが思ったデータとは違うことも最初のうちはよくあり、エラーなく表示できたとしてもレビューをお願いすることが大切だと学びました。(そのままその値を元に分析すると、間違った意思決定をするリスクもあるためです)

これからSQLを勉強しようと思っている人で、もしアドバイスをもらえる方が身近にいるというラッキーな方はぜひお願いすることを推奨します。

やってみた結果、どうなったか?

ハローのメンバーが実際に学んでみてどのような効果があったかについても、ご紹介したいと思います。

意思決定速度が向上、時間も作れる様になった

今までエンジニアがデータ分析のためにSQLを書いていましたが、メンバーが取得したいデータを自分で確認することができる様になったことでエンジニアが稼働しなくても、数値を元にアクションを決定したり議論することが可能になりました。

また、エンジニアが時間を取られることも少なくなったため開発により集中することができるようになりました。プロダクトに集中してくれる時間が増える方が会社としても早く成長することができるためすごくいい取り組みでした。

「このデータが気になったのでRedashで出してみました!」という発言が社内で増えたのもいい変化だと思います。

<実際にあった事例>

ハローでは、店舗向けにオートリザーブオーダーというモバイルオーダーシステムを提供しています。現在は、店舗が新規導入する際にメニューの登録を代行していますが、その業務の中で商品リストが正しく登録されたか2重チェックするフローが存在します。

今までだと、目視でダブルチェックをするだけで1−2時間かかっていました。今ではSQLを活用し、すでに登録されたメニューをデータベースから取得することで、ダブルチェックが50%以下の時間で終わるようになっています。

分析脳が身に付く

取得するデータは、数字であることが多いため

この施策は、◯◯の結果からわかる様に非効率である ユーザーには△△の傾向があるので、次はサービスをA案で改善した方がいい

という分析を元に議論できる様になりました。

今までは感覚で議論する場面もありましたが、メンバーが少しでもSQLを書ける様になったことでチーム全体として数字を根拠としてやりとりできる様になったこともいい変化でした。マーケティングチームとしても、セールスやその他関係者に、データを元に会話をしてもらうことは「無意味な施策を打たない」「データを大事にする人が報われる文化ができる」という副次効果があるため、今後が楽しみです。

まとめ

マーケティングチームはもちろんのこと、セールスやバックオフィスに関わるメンバーもこの取り組みに参加し、簡単なSQLなら書ける様になりました。

セールスメンバーは、売り上げや問い合わせ数、サービスが導入されている店舗の一覧を自分で出したり、分析したりすることが可能になり、エンジニアはより開発に集中するいいサイクルができています。

ちなみにハローでは各職種が同じ様な勉強をできる環境を準備しており、「figma」や「SEO」についても部活があります。これからさらにメンバー一同で成長を続けていきたいと思います。

現在ハローでは新しい仲間を募集中です!少しでも、ハローに興味を持った方はぜひお気軽に採用のフォームからご応募ください!

Reactでロジックをhooksにまとめないという選択肢

javascripterです。ハローでは、プロダクトのローンチ前からAutoReserve の開発に関わっています。

突然ですが、Reactを使用する際、コンポネントのロジックや状態が増えてきたとき、みなさんはどうされてるでしょうか。

関数コンポネントでは、一般にcustom hooksとしてまとめて切り出すことが多く行われていると思います。

今回の記事では、useState/useRef + custom hooksという単位で切り出すのではなく、 クロージャを使いロジックや状態をコンポネントの外に持たせるようにリファクタリングすることで、コードの見通しが良くなる、という事例を紹介します。

JavaScriptにおけるクロージャとは、関数が外側のスコープの変数などへの参照を保持できる機能のことです。ここではクロージャとして実装しましたが、同等のことはclassを使っても実装できます。

AutoReserveでの複雑なロジックの例

概要

状態管理やロジックが複雑になる例として、非同期処理があります。

ハローでは、レストラン向けにAutoReserve For RestaurantsというReact Native製のレストラン管理システムを提供しています。アプリには予約台帳の機能があります。

予約台帳とは、レストランが予約を管理するための記録システムで、

  • 来店の日時
  • 来店人数
  • 客の名前
  • 予約する席

などの情報を記録できるようになっています。 ある日の予約状況を一覧して見れるよう、チャート表示のUIを実装しています。

予約台帳チャート表示画面

レストランの営業時間は曜日によって異なる場合があり、また祝日の場合など変則的な営業時間の日もあります。よって、クライアント側で特定の日の営業時間を知りたい場合、日付を指定してバックエンド側から動的にとってくる必要があります。

予約が入るのは基本的に営業開始時間以降なため、デフォルトで営業開始時刻までスクロールさせた方が親切です。ここで、画面をスクロールさせるときの実装を考えてみます。

基本的な実行順序

基本的なスクロールの実行順序はこのようになります。ここに、別の日付に移動した時のキャンセル処理などが入ります。

  1. チャート画面を開く
  2. 開いている日付の営業時間を取得し、取得後にスクロールするコールバックを設定
  3. 営業時間の取得が終わったら、コールバックを実行しスクロールを行う

具体的な実装方法

このようなチャートのコンポネントを用意したとします。

// チャート
const Chart = React.forwardRef(({
  dateString,
  onLoadStart,
  onLoadEnd
}, ref) => {
  // ここでは、コンポネント内でswrを使いAPIからデータを取得することを想定
  // https://swr.vercel.app/
  const { data } = useSWR(`/reservations/by_date?date=${dateString}`)

  // 初回マウント、日付が変わったタイミングでonLoadStart()を呼ぶ
  // データのロードが終わったらonLoadEnd()を呼ぶ

  if (!data) return <Loading />

  return <View ref={ref}>
    { /* ... */ }
  </View>
})

このような場合、ざっくり下記のように書き始めることが多いのではないでしょうか。

function Screen() {
  const ref = useRef()

  // dateのstateの定義やヘッダ実装は省略
  const [loadingState, setLoadingState] = useState('loading')

  const requestScroll = () => {
    // 営業日を非同期で取ってきた後、開始時間にスクロールする
    getBusinessTimesByDate(date).then((data) => {
      // この時点でloadingStateがまだloadingの場合はスクロールできないので
      // キューに入れるなどする必要があるが一旦無視
      ref.current?.scrollTo({
        x: getScrollX(data)
      })
    })
  }
  const onLoadStart = () => {}
  const onLoadEnd = () => {}

  return (
    <Chart
      ref={ref}
      date={date}
      onLoadStart={onLoadStart}
      onLoadEnd={onLoadEnd}
    />
  )
}

ここから、

  • loadingStateはレンダリングのタイミングと関係ないためrefを使う
  • requestScrollの実行は日付が変わったら前の分はキャンセルしなければいけないのでcancel処理を追加
  • コンポネントのアンマウント後、スクロール処理はキャンセル
  • ロジックが複雑になってきたため、custom hooksに切り出す
  • useEffect内で使う関数があるため、stableにするためuseCallbackで囲う
  • Chartのロードが完了するまでスクロールを待機させる

のようなことを考え、実装を進めていくと

function useChartResponder(ref) {
  const loadingStateRef = useRef('loading')
  const cancelRef = useRef(null)
  const scrollPayloadRef = useRef(null)

  const requestScroll = useCallback(() => {
    let canceled = false

    // 前回のスクロールが実行中ならキャンセル
    cancelRef.current?.()
    cancelRef.current = () => {
      canceled = true
    }

    getBusinessTimesByDate(date).then((data) => {
      if (canceled) return
      if (loadingStateRef.current === 'loading') {
        // ロード中であればスクロールを待機させる
        scrollPayloadRef.current = { x: getScrollX(data) }
      } else {
        ref.current?.scrollTo({
          x: getScrollX(data)
        })
      }
    })
  }, [ref])

  const onLoadStart = useCallback(() => {
    loadingStateRef.current = 'loading'
    reset()
  }, [loadingStateRef, reset])

  const onLoadEnd = useCallback(() => {
    loadingStateRef.current = 'loaded'
    if (scrollPayloadRef.current) {
      ref.current?.scrollTo({
        x: getScrollX(data)
      })
      scrollPayloadRef.current = null
    }
  }, [ref])

  // unmount時に呼ぶ
  const reset = useCallback(() => {
    cancelRef.current?.()
    cancelRef.current = null
    scrollPayloadRef.current = null
  }, [cancelRef])

  return useMemo(() => {
    return {
      requestScroll,
      onLoadStart,
      onLoadEnd,
    }
  }, [onLoadEnd, onLoadStart, requestScroll])
}

のように切り出すことになるかもしれません。 しかし、このように実装していくと、複数のrefが存在し、それぞれをcurrentで参照しないといけなかったり、useCallbackが増えたり、だんだんと見通しが悪くなっていきます。

状態をhooksの外に出す

そこで、hooksに状態を持たせるのではなく、クロージャを利用し普通の関数に処理をまとめてみると、こうなります。

function createChartResponder(ref) {
  let loadingState = 'loading'
  let cancel = null
  let scrollPayload = null

  const requestScroll = () => {
    let canceled = false
    cancel?.()
    cancel = () => {
      canceled = true
    }
    getBusinessTimesByDate(date).then((data) => {
      if (canceled) return
      if (loadingState === 'loading') {
        scrollPayload = {
          x: getScrollX(data)
        }
      } else {
        ref.current?.scrollTo({
          x: getScrollX(data)
        })
      }
    })
  }

  const onLoadStart = () => {
    loadingState = 'loading'
    reset()
  }

  const onLoadEnd = () => {
    loadingState = 'loaded'
    if (scrollPayload) {
      ref.current?.scrollTo({
        x: getScrollX(data)
      })
      scrollPayload = null
    }
  }

  const reset = () => {
    cancel?.()
    cancel = null
    scrollPayload = null
  }

  return {
    requestScroll,
    onLoadStart,
    onLoadEnd,
    reset
  }
}

この関数を使用する際は

function Screen() {
  const [chartResponder] = useState(() => createChartResponder())

  useEffect(() => {
    return () => {
      chartResponder.reset()
    }
  }, [chartResponder])

  // ...
}

のように使います。こうすることで、

  • createChartResponder内ではrefを使わず普通の変数を自由に使ってコードを書ける
  • useCallbackも使用する必要がない
  • ロジック自体をReactの外に出せるため、テストが容易になる

といったメリットがあります。コード自体も短くなり、メンテナンス性も向上します。

まとめ

Reactを使用していると、useStateやuseRef、useReducer、useEffectなどを使用しcustom hooksを活用してコードを書く場面が多いと思います。

もちろん、custom hooksで簡単に解決できるものも多くありますが、記事で取り上げたようにプレーンな関数・クロージャにロジックをまとめ、hooksを使ってReactとの繋ぎ込みを行うことも検討すると良いと思います。

ハローでは、単純明快なコードを追求する本物のプログラマを募集しています。

少しでも気になる方は気軽に javascripter にDMでお声がけください!

エンジニア採用情報 | Hello, Inc.

脱SSRのための Dynamic Rendering 運用

uiu です。

前回の記事 『なぜ Next.js をやめたのか?』 では、Reactベースのウェブフロントエンドで、サーバーサイドレンダリング(SSR)をしない選択をすることで、アーキテクチャをシンプルに保っているという話を紹介しました。
アーキテクチャをシンプルに保つ工夫のおかげで、ウェブとアプリのコード共通化が簡単になり、1人のエンジニアが最小限の労力で複数プラットフォームに変更を入れることが可能になっています。

Dynamic Rendering はスケールしないのでは?メンテが大変なんでは? という質問があったので、今回は実際どのように Dynamic Rendering を運用しているかについて紹介したいと思います。

結論を簡単に言うと、キャッシュが効くためサーバーコストは高くなく、またサーバーレス環境で動いてるため運用のメンテナンスコストも低いです。
サーバーコストは当然 SSR での運用と比較すると高いものの、全体のコストと比較すると微々たるものです。そのため、開発生産性を優先して Dynamic Rendering する選択をしています。

Dynamic Rendering とは

Dynamic Rendering は、検索エンジン最適化(SEO)を目的に、クローラーのリクエストのときのみ Headless Chrome を使い、JSを解釈することで、レンダリング済みの HTML をレスポンスとして返すテクニックです。
検索エンジンのクローラーかどうかは、サーバー側で User Agent を見て分岐します。
一般のユーザーからのリクエストに対しては、通常通りレンダリングせずにレスポンスを返す、というのがポイントです。

怪しい裏技的なテクニックではなく、Google の公式でも詳細に説明されている方法です: Dynamic Rendering | Google Search Central  |  Documentation  |  Google Developers

技術要件

あらゆる問題に対して最適な解法がないのと同じように、技術要件なしに技術選択を語ることはできません。

弊社サービスでの技術要件は以下のようなものでした:

  • 検索エンジン最適化と各種SNSでのOGP展開等が目的
  • ページ数は高々100万ページ程度
  • サービス性質的に、ページの更新性はそれほど高くない (1日1回更新できれば十分)

YAGNI

当然、将来的に、インデックスするページ数を多くしたい、ページの更新性を高くしたい、などといった要求仕様の変更が起こる可能性はあります。
しかし、それは不確実な未来の低い可能性です。現時点では将来的な要件は重視しない判断をしています。

現時点で、精度高く必要だと言える要件に集中することで、投資のコストとリターンを判断し、最適な技術を選択しています。
リソースの限られたスタートアップでは、過剰な投資を避け、無駄を省き、他のプレイヤーが時間を浪費している間に速く動くことが大事だと考えています。

一言でいえば、Keep It Simple もしくは YAGNI になります。

クライアントサイドレンダリングを選択しなかった理由

最近のクローラーは JavaScript を解釈するから、クライアントサイドレンダリングでいいのでは? という指摘もあるとは思います。

以下の理由で Dynamic Rendering をしています: - デバッグがしやすい: クローラーによる JavaScript 解釈の挙動が予測しづらいため、自社サーバー側で静的HTMLにしておくほうが動作検証がしやすい - Google以外の検索エンジンへの対応 - Twitter, Facebook, Slack など各種SNSでのOGP展開をしたい

サービスの要件次第では、クライアントサイドレンダリングを選択することは可能だと思います。
ただし、問題が発生した際に速やかに対策したい場合を考えると、動作を再現しやすい環境にしておくのは悪い投資ではないです。

運用

Google がOSSで公開している Dynamic Rendering サーバー実装である rendertron を利用しています。
GCP の Cloud Run 上で運用しており、サーバーレス環境でオートスケールを活用して動かしています。メンテナンスに関して困ったことはあまりなく、2年以上ほぼノーメンテで動いています。

当初は Google App Engine で rendertron を運用していましたが、サーバーコストを抑えるため Cloud Run に移行しました。 *1

設定

基本的な設定に関しては、Dockerfile を用意して、キャッシュ設定やタイムアウト設定を追加した程度で、比較的簡単にデプロイできました。

Vercel の Rewrite 機能を使って、User Agent で分岐してクローラーからのリクエストを振り分ける挙動を実現しています。
他にも nginx 等リバースプロキシを使う方法や Cloudflare Workers を使う方法などが考えられると思います。

具体的な設定については Vercel を導入した際の記事で説明しています:

tech.hello.ai

レイテンシー

平均や95%パーセンタイルでのレイテンシーの数値は高いですが、レイテンシーが原因で悪影響は今のところ出ていません。

運用して長いですが、検索トラフィックは堅調に伸びています。検索エンジンは事業的にも重要な流入経路の1つであるため、レイテンシーにより悪影響があるかは入念に検証しましたが、今のところ大きな問題は出ていなさそうです。

rendertron では Cloud Datastore ベースのキャッシュが動いており、同一ページへのリクエストに対してはレスポンスを高速に返すことができます。運用しているサービスの技術要件では、更新性がそれほど高くないため、キャッシュを活用しやすいです。
ユーザー体験についてもOGP展開等ではキャッシュヒット率が高く、大きな問題はないと思っています。

インフラコスト

クラウドでのサーバーコストの観点でいうと、全体のインフラコストの比率から言っても、割合は少なく許容範囲です。
キャッシュが活用できるため、トラフィックに比例してサーバーコストが増加するわけではなく、将来的にもサーバーコストは一定範囲に収まると予想しています。

開発人件費に比べると微々たるものであり、開発生産性を犠牲にしてまでサーバーコストをケチる判断をする理由にはなりません。

他社事例

導入時には知りませんでしたが、より大規模で更新性の高いサービスでも Dynamic Rendering の導入事例があるようです。

inside.pixiv.blog

まとめ

Dynamic Rendering はスケールしないのでは? という懸念は、チーム内で当初導入を検討していたときにもありました。
入念に調査をし小さく技術検証を行うことで、この懸念は大きな問題でないことが検証できたため、Dynamic Rendering を導入する判断をしました。

フロント側コードでサーバー環境での動作を考える必要がなくなり、フロントエンドの開発生産性を高く保つことに貢献しています。
当然将来的には状況に合わせて、変わる可能性はありますが、現時点ではこれがより最適な技術選択であると思っています。

ハローでは、単に流行りを追うのではなく自分で考え技術選択ができるエンジニアを募集しています。

少しでも気になる方は気軽に uiu までDMでお声がけください!

hello.ai

*1:当時、同等スペックのインスタンスが App Engine よりも安かった

なぜNext.jsをやめたのか?

javascripterです。ハローでは、プロダクトのローンチ前からAutoReserve の開発に関わっています。

この記事では、AutoReserveウェブ版が、Next.jsを一度採用したがやめ、その後create-react-app + react-routerの構成に移行した経緯を書きます。

ウェブ版開発の背景

AutoReserve はAIが電話予約を代行してくれる飲食店向け予約グルメアプリで、現在はiOS / Android / ウェブにサービスを展開しています。 元々はReact Native製のネイティブアプリのみ展開していましたが、ユーザ獲得の面でウェブ版が必要となったため、 追加でウェブ版を実装し、現在の3プラットフォームでの展開に至ります。

最初の技術選定

ウェブ版の最初のバージョンでは、フレームワークとしてNext.jsを採用しました。Reactで書け、SEOのための最適化やサーバーサイドレンダリングが行えるためです。

理由をいくつか挙げると、

  • AutoReserveは全国のレストラン情報を掲載するメディアサイトなため、SEOを考慮する必要がある
  • 先行してローンチしたReact Native製のアプリと技術スタックをなるべく揃えたい

といったものがあります。

また当時、AutoReserveアプリで採用していた技術として、

  • React Native
  • redux / redux-saga

があげられます。よって、ウェブ版でも同様にredux, redux-sagaを使用することにしました。

React Native for Webの採用

ウェブ版を開発するに当たって、初期はVercelの styled-jsxをCSS in JSとして利用し、プレーンなReactアプリとして実装していました。

しかし、下記のような課題が生じていました。

  • アプリ版とウェブ版で二重にコンポネントを実装しメンテナンスしていくのが非効率的
  • styled-jsxではCSSセレクタを自由に使えるが、CSSセレクタには詳細度などの概念があり、最終的にどのスタイルが適用されるのかぱっと見でわからず複雑

これらの問題を解決するため、styled-jsxを使用するのはやめ、React Native for Webという、React NativeのAPIやコンポネントをウェブ向けに実装したライブラリを使用し、React Nativeのコードをそのまま移植する方針を取りました。

React Native / React Native for Webでコードを書く場合はこのようになります。

function Component() {
  return (
    <View
      style={{
        flex: 1,
        backgroundColor: 'red',
      }}
    >
      <Text>Hello</Text>
    </View>
  )
}

React Nativeのスタイリングの仕組みの特徴としては

  • Viewのスタイルに元々display: flexが当たっている
  • メディアクエリや::before::afterなどには対応していない
    • 動的なスタイルの変更はJSによって行う
  • Atomic CSSを採用しているため、CSSのカスケーディングが存在しない(ウェブ)

ということが挙げられます。

発生した課題

AutoReserveのアプリ、ウェブ版の大きな違いとして

  • アプリ版はスマホ向けレイアウトオンリー
  • ウェブ版はスマホ、PC向けのレイアウトの両方が必要

ということがあります。

React Native for Web自体はメディアクエリに対応していないため、レスポンシブに実装する必要がある場合、divをコンテナとして囲って、そこにスタイルを当てる必要がありました。

また、オートリザーブでは、単純なレスポンシブデザインではなく、PCとモバイル向けで、完全にわかれたコンポネントを出し分けたい場面が多くありました。

具体的には、

  • PCの場合は予約のフォームをインラインで出したい
  • モバイルの場合は予約ボタンのみを表示し、タップしたらハーフモーダルを出したい

といった場面です。こういった場合、@artsy/fresnelなどのライブラリを使い、PC・モバイル両方のコンポネントをレンダリングしメディアクエリで片方のコンポネントを隠す必要がありました。

その他、対応が必要だった点を挙げると、

  • redux-sagaでロード完了状態を待ってSSRした結果を返す必要がある
  • ログインが必要なページはキャッシュを行わないようにする
  • reduxのstateをサーバーサイドとクライアントサイドで共有する
  • cookieに認証情報を同期する

などが挙げられます。一つ一つの問題は解決可能でしたが、全体として見ると複雑度が高まり開発効率が低下する原因となっていました。

SSRをしないという選択

なぜNext.jsでうまくいかなかったか

  • SSRを前提としていない設計のアプリのコードをウェブに移植した

というのが移植の上で発生した問題の主な要因かなと思います。

SPAへの移行

そこで、思い切って元々のアプリの設計に寄せるため、サーバーサイドレンダリングを行わず、SPA(Single Page Application)として書き換えることにしました。

SSRについては、Dynamic Renderingを行ってみることにしました。

Dynamic Renderingとは、JavaScriptを解釈しないクローラのために、クローラがページにアクセスした際に裏でヘッドレスのブラウザを立ち上げJavaScript実行後のHTMLを返す、という技術です。AutoReserveのウェブサイトではRendertronを使用しています。

オートリザーブでは、クローラからのアクセスがあった場合、モバイル版のサイトをDynamic Renderingし返すように実装しています。

Dynamic Rendering の具体的な運用については以下で詳しく説明しています: tech.hello.ai

サーバーサイドレンダリングを行わずSPAになったため、技術スタックは下記のようになりました。

  • react-router
  • react-native-web
  • redux/redux-saga(現在はreduxではなくswrを使用)

どうなったか

実行環境がブラウザのみになったため、Node.jsで実行した際の動作環境の差異を考慮する必要がなくなり、開発スピードが上がり不具合の発生頻度も減りました。

レスポンシブ対応についても、JSの分岐によりレスポンシブ対応を行えるようになり、コンポネントを切り替えたり、プロパティの値を分岐したりといった柔軟な対応が行えるようになりました。 具体的には、このようなコードで書けるようになりました。

function Component() {
  const { width, sm, md } = useResponsive()

  if (width < sm) {
    return <SPComponent />
  }

  return <PCComponent style={{ paddingHorizontal: width < md ? 16 : 24 }} />
}

現在に至るまで、サイトのトラフィックの増加は順調で、SEO上の問題は生じていません。

また、サーバーサイドレンダリングを廃止し、React Native for Webを使用するようになったため、ウェブとアプリのコード差異がほとんどなくなりました。 結果的に、ウェブのコードの大部分をアプリからのコピペで実装できるようになり、現在ではar_sharedという共通パッケージを作成し、ウェブとアプリでコードの共通化をおこなっています。

現在、アプリとウェブでコード共有できている部分は

  • ボタン、チェックボックスなどのUIコンポネント
  • 色の定義などの定数
  • Reactのhooksや日付・数値のフォーマット、文字列操作などのユーティリティ関数
  • TypeScriptの型
  • 予約の表示画面、予約送信画面のコンポネントなど、アプリケーション画面のコード

です。ウェブとアプリでコードを共通化する取り組みについては、今後このブログで別記事で詳しく取り上げる予定です。 アプリとウェブの画面遷移やルーティングの仕組みの違いなどプラットフォームの差異を吸収するため設計上工夫が必要な面もあり、そのあたりについても解説していく予定です。

まとめ

サーバーサイドレンダリングをしない、という選択によって開発を単純化し、少人数で多プラットフォームに同時展開できるようになったという事例を紹介しました。

社内ではNext.jsを使用しているプロジェクトもあり、プロジェクトごとに最適な技術選択を行い生産性を追求しています。

ハローでは、開発スピードを追求する本物のエンジニアを募集しています。

少しでも気になる方は気軽に javascripter にDMでお声がけください!

エンジニア採用情報 | Hello, Inc.

スタートアップがWebマーケティング戦略を考える前にやるべきこと

はじめまして!

株式会社ハロー、マーケティングチームです。日頃は、サービス全体の設計やAutoReserveの問い合わせ数増加施策などグロース全般を担当しています。 4月は新社会人や転職してこれからマーケターとしてやっていくという方がいると思います、そういう方に向けて「Webマーケティングって何をすればいいの?」という質問に自分なりに回答をしてみたいと思います。合わせて各トピックにおいて参考となりそうな本についても紹介します。

Webマーケティング戦略を考える前に大事にしたいこと

f:id:hello-tech:20220406122401p:plain

いきなり残酷な話ですが世の中のほとんどのサービスは、思った通りには伸びません。

これを読んでいる方も 「こういうサービスを作ったら絶対売れる」 「○○の悩みを解決したら確実に成功する」 と思ったことはないでしょうか?

私も同じように、幾度となく同じようなことを考えたことがあります。例えば、「寝坊する人が多いから、絶対寝坊しないためにモーニングコールのサービスを作ったら売れるはずだ」とか....いろんな妄想をして人生を過ごしてきました。

Webマーケティングってなんかいい響きだし、うまくできる人ならなんでも伸ばせるんだと思っている人がいるならそれは間違いです!

「Webマーケティング戦略を考える前に大事にしたいこと」というのは、「そもそも市場があるところで勝負して初めてWebマーケティングが生きてくる」ということです。確かに、市場にない製品を生み出してうまくいく、iPhoneのようなものもあると思います。ただ、市場がないところから商品を理解し、売ってもらうためには多額の資金を用意してマーケットを自ら作りに行って初めてその商品が売れるかわかるため、成功確率も基本的には低いとされています。

なにより、みなさんが日頃触れている製品やサービスも革新的なものというより、基本的にはすでに知っているものがリプレイス(代替すること)されているだけのものが多いと思います。それくらい市場にないものに理解を得ることは難しいということです。今手元に持っているもの、見渡しているもの、使っているものもほとんどが何かに代替されていると思います。

何をまずチェックすべきか

結論、Webマーケティングを行うためにはこのように ・なにをリプレイスするものなのか ・どれくらい市場が存在するものなのか を見極めて初めて戦略を考えることができるようになります。闇雲にいろいろ試しても意味はありません。これから本格的にWebマーケティングを行おうと思っている方はぜひ要素分解からするようにしてみてください。

リサーチにおすすめの書籍は四季報

f:id:hello-tech:20220406121652p:plain

いろいろな企業がどういう商品を代替して、どういう市場で戦っているかをたくさん見ることで事業リサーチ能力が鍛えられます。ぜひ読んだことがない方は四季報を読んで、企業の事例をたくさん読みあさってみてください。

※四季報とは ー 1936年に創刊。上場企業を中心にしたデータを網羅している会社専門誌。四季報記者たちの専門的取材があるため、あくまで中立な立場で業界分析を行っている書籍です。

事業領域と競合分析が終わったら、次はWebでどうやってお客様がくるか考えてみよう

先ほど、「リサーチが大事である」とお伝えしました。

では、実際にWebマーケティングを行う上でこのリサーチがどう役立ってくるのかについてお話しします。

例えば、今回オンラインで「コップ」を売ることにしましょう!(今回オフラインについては無視して記載します)

まずダメな事例は、すぐに「SEOをやろう」「広告をやろう」「インスタで売ろう」と戦略を決めずに手を動かすことです。もし仮にうまく行っても戦略をすっ飛ばしてしまうと再現性がなく、また失敗した時も失敗から学ぶことができません。

コップに接点を持つタイミングを考えよう

コップにユーザーが興味を示したり、購入したりする接点はオンラインにどれくらいありそうでしょうか?Amazonや楽天など多数のショッピングサイトから購入されることは誰でもわかるところですが、例えばamazonで今調べると、

f:id:hello-tech:20220406121738p:plain

60000件以上のコップが存在します。つまり最初にお伝えした市場というのは間違いなく合いそうというのはわかります。ただ、競合が多すぎてリプレイスすることの難易度が高そうです。ただ購入者の母数も大きそうなので、始めるタイミングというのはいつになるかわからないですが、いつかは必ず参入しないといけないということだけはわかります。この時にに初めて数値データを元に、戦略の1つが決まるわけです。

ただ、いきなり60000種類ある市場に作ったコップを出してもだれも知らないブランドなのできっと購入しません。では、それ以外にコップに接点を持つタイミングや抜け道を考えてみましょう。

例えば、

  • お祝いの時に贈る
  • 一人暮らしを始める時に買う
  • 同棲をする時に買う

など様々なケースが考えられます。

さらに、その接点はどこから起こり得るか考えてみます。

  • お祝いの時に贈る → お祝い専門サイトで買うかも・・・
  • 一人暮らしを始める時に買う → 「引越し 買うべきもの」と引越しについてGoogleで調べているユーザーが接点を持った上でamazonで購入しているかも・・・
  • 結婚をする時に買う → 「結婚」についてGoogleで調べている時に買うかも・・・

と、接点がなんとなく考えられると思います。思いつかない時は対象ユーザーになりそうな方にヒアリングを行うこともおすすめです。

接点を深堀しよう

上記の接点をもう少し調べてみます。

お祝いの時に贈る f:id:hello-tech:20220406121945p:plain 引用:https://giftmall.co.jp/search/?q=%E3%82%B3%E3%83%83%E3%83%97

ギフト専門サイトでコップと検索すると、2000件ほどのアイテムが確認できました。amazonよりは少ないので、少しチャンスがあるかもしれません。

一人暮らしを始める時に買う f:id:hello-tech:20220406122006p:plain 引用:https://hikkoshizamurai.jp/price/household-goods/

引っ越しのときに買うべきものにはコップがほとんどのサイトで紹介されていました。たとえば、このメディアに連絡をして紹介をしてもらえばもしかすると売れるかもしれません。

結婚をする時に買う f:id:hello-tech:20220406122025p:plain 結婚 ペアグラス でamazonで検索しました。そうすると一気に9000件まで減っていることがわかります。例えば、他社が取り扱っていない材質や色で作ればもしかすると売れるかもしれないという可能性がわかります。

ざっくりで終えない、さらにここから無料でできるリサーチを徹底的に行う

Webマーケティングの醍醐味は「数値で会話できる」ということです。例えばamazonならamazon広告を利用すれば1人あたりいくらでお客様を自分の商品ページに誘導できるか、検索されている数はどれくらいか把握可能です。

またGoogleも同じように検索ボリュームがわかります。さきほど説明した媒体と連携して商品を掲載するか相談できる場があれば対象のURLのアクセス数を事前に聞けるかもしれません。

無料でできることを徹底的に行えばWebマーケティングはある程度の数値を算出でき、また数値の効果がいい・費用対効果がいいものを調べることができます。

「リサーチをする→数値で議論できるデータを集める→数値を元に施策を行うか行わないかを判断する」、このサイクルを効率よく回せるように株式会社ハローでは日々社内のチームメンバーでも数値をエビデンスに議論することを徹底しています。

Webマーケティングでよく使われるチャネル(集客経路)について

f:id:hello-tech:20220406122508p:plain

さて、リサーチについてこれ以上書くと本題と脱線しそうなためコップの例を元に接点の理解についてはある程度学んでいただけたと思います。あとは接点がGoogleでの検索なのか、はたまたインフルエンサーが紹介しているから売れるのかなどを調べ、それを元に効果のありそうな順番でWebマーケティングの戦略を構築してみてください。

Webマーケティングでは言葉の通り、オフラインではなくWeb上で行うマーケティング手法のことです。最近では以下のものをよく使うため箇条書きにしてまとめました。(それぞれの広告について、学べる本についてはリンクでご紹介しました)

・SEO

・SNS運用 ・リスティング広告 ・ディスプレイ広告(本はリスティング広告の本で説明がある場合が多いです)

・アフィリエイト広告

・メルマガ

・媒体掲載 ・SNS広告(本については広告というよりマーケティング全般となります、詳しい運用手法はインターネットにたくさん落ちています)

・インフルエンサーマーケティング

インパクト(施策効果)があるWebマーケティングの発想について

Webマーケティングはインパクトのある施策をたくさん成功させればさせるほどサービスが伸びます。

インパクトのある施策を考える上で大事になるのは、「競合をとにかく分析する」ということです。競合は今まで説明してきた接点をきっとあなたと同じかそれ以上考えて成功させています。つまり、相手がどの媒体でどうやってお客様を集めているかがなんとなくわかれば同じような施策を打つことで「相手が失敗してきた広告費を同じように使うことなく、成果を出すことができる確率が上がる」と言えます。

わかりやすくいうと、例えば競合が伸びていると聞くけど全くWeb広告を見ない、サイトにも掲載されていないという場面で考えられるのは自分に接点のない媒体でのユーザー獲得になります。そうすると、「メルマガ広告」や「紹介営業(オフライン)」などが考えられます。顧客層を抱えていそうなメルマガに連絡を取り、競合が出稿しているか確認することが見つけられるかもしれません。

Web広告は打っていそうだが、どういう広告を使っているかわからないときにはツールを使うのも1つの手です。Facebook広告、他社の広告をチェックできるツールが存在します。競合名で調べるといつから配信しているかも調べることができるかもしれません。

Webマーケティングでは競合の状況を見て、いいものは真似をするというのも大事な1つの戦略です。インパクトがいまいち出ていない時には気にかけるようにしてください。

まとめ:Webマーケティングは全ての人にチャンスがある

このブログ1記事では語ることができませんでしたが、ハローでは長期的に効果が出る・費用対効果の高い施策を中心に取り組んでいます。

ぜひこれからWebマーケティングを極めていきたい・Webがよくわからないが効率的な戦略を打ち出していきたいという方は参考にしていただけると幸いです。

また、「こういう議論のできるチームで働いてみたい!」と思ったそこのアナタ、ハローでは常にメンバーを募集しています!

ぜひこれをみていただいているのも何かのご縁ですので少しでも気になった方は気軽にご応募ください!

採用情報 | Hello, Inc.