世界一適当な技術ブログ

日々学んだ内容をとにかくにブログ形式でアウトプットします。(技術系中心)基本自分用備忘録です。

マネタイズ方法まとめ

マネタイズ方法まとめ

WEBサービスでマネタイズするパターンをそれぞれ記載します

1. 機能絞り込み

機能を絞り込み、人気機能を利用するには有料プランに以降を促す

例) - Pairs - openwork

■ メリット

  • ユーザーが気軽にサービス利用できる

■ デメリット

  • 魅力的な有料プランでなければ課金されない

2.フリーミアムモデル

最初無料でインストールできるが、特定のところまで行くと課金が必須になる(初月無料的な)

■ メリット

  • ユーザーが気軽にお試し利用できる

■ デメリット

  • 無料期間に魅力を感じなければ課金されない

3. 広告表示

広告を表示させて、その表示回数に合わせて広告料金をもらう

■ メリット

  • 導入が容易

■ デメリット

  • かなりのアクセス数がないと、稼げなく難易度が高い。
  • Googleアルゴリズムの影響を受けやすい

4.サブスクリションモデル

月額制で利用料金を支払うサブスクリションモデル

例) - netflex

■ メリット

■ デメリット

  • サブスクビジネスモデルの競合が多い(ライバルが多い)

5.手数料サービス

プラットフォームサービスでユーザーと企業がマッチングした時に発生する金額の一部を成果報酬として得る。

例) - Menta

■ メリット

  • toBであれば取引金額が高い

■ デメリット

  • ユーザー同士の揉め事があった時の対応がしんどい

Nuxt3でvalidationする方法

Nuxt3でvalidationする方法

インストール

npm i vee-validate @vee-validate/i18n @vee-validate/rules

vee-validate -> veeValidate

@vee-validate/i18n -> バリデーションエラーメッセージに日本語化

@vee-validate/rules -> バリデーションルールを利用する

pluginの設定

■ plugins/vee-validate.client.ts

import { localize, setLocale } from "@vee-validate/i18n";
// VeeValidateで用意されている英語語版入力チェックエラーメッセージを使う
import en from "@vee-validate/i18n/dist/locale/en.json";
// VeeValidateで用意されている日本語版入力チェックエラーメッセージを使う
import ja from "@vee-validate/i18n/dist/locale/ja.json";
import AllRules from "@vee-validate/rules";
import { defineRule, configure } from "vee-validate";
import { defineNuxtPlugin } from "#app";

export default defineNuxtPlugin((_nuxtApp) => {
  configure({
    generateMessage: localize({
      en,
      ja
    }),
  });

 
  configure({
    generateMessage: localize('ja', {
      names: {
        password: 'パスワード',
        email: 'Email'
      },
    }),
  });


  Object.keys(AllRules).forEach((rule) => {
    defineRule(rule, AllRules[rule]); // 全ルールを使えるようにする
  });

  setLocale("ja");
});

Fieldのバリデーション

<template>
  <div class="min-h-screen flex flex-col">
    <div
      class="container max-w-md mx-auto flex-1 flex flex-col items-center justify-center px-2"
    >
      <div class="bg-white px-6 py-8 text-black w-full">
        <h1 class="mb-8 text-3xl text-center">
          新規登録 🎉
        </h1>
        <input
          v-model="email"
          type="text"
          class="block border border-grey-light w-full p-3 rounded mb-4"
          name="email"
          placeholder="Email"
        >

        <input
          v-model="password"
          type="password"
          class="block border border-grey-light w-full p-3 rounded mb-4"
          name="password"
          placeholder="Password"
        >

        <input
          v-model="passwordConfirm"
          type="password"
          class="block border border-grey-light w-full p-3 rounded mb-4"
          name="passwordConfirm"
          placeholder="Confirm Password"
        >

        <button
          type="submit"
          :class="validInput ? 'bg-indigo-500 hover:bg-indigo-700' : 'bg-gray-500'"
          class="block border border-grey-light w-full p-3 rounded mb-4 text-white"
          :disabled="!validInput"
          @click="register"
        >
          登録
        </button>
      </div>
    </div>
  </div>
</template>

<script>
import { useField } from 'vee-validate';

export default {
  setup() {
    const { value: email, errorMessage: emailError } = useField(
      "email",
      "required|email",
    );

    const { value: password, errorMessage: passwordError } = useField(
      "password",
      "required|min:5",
    );

    const { value: passwordConfirm, errorMessage: passwordConfirmError } = useField('passwordConfirm', (passwordConfirm) => {
      if ( passwordConfirm === password.value) {
        return true;
      }
      return 'パスワードと一致してません';
    });


    return {
      email,
      emailError,
      password,
      passwordError,
      passwordConfirm,
      passwordConfirmError
    }
  },
}
</script>

参考リンク

Nuxt3でvee-validate4(Composable API)を使う | デバッグライフ

Vue3でフォームバリデーションのVee-Validate4の基礎を理解 | アールエフェクト

Nuxt3とfirebase9で認証機能設定する方法

Nuxt3とfirebase9で認証機能設定する方法

各バージョン

Nuxt: 3.0.0-rc.11
Firebase: 9.13.0

インストール

npm install firebasepackage.jsonfirebaseが追加されればOK

Firebaseの初期設定

■ plugins/firebase.client.ts

import { initializeApp } from 'firebase/app'
import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  const firebaseConfig = {
    apiKey: config.FIREBASE_API_KEY,
    authDomain: config.FIREBASE_AUTH_DOMAIN,
    projectId: config.FIREBASE_PROJECT_ID,
    storage_bucket: config.FIREBASE_STORAGE_BUCKET,
    messaging_sender_id: config.FIREBASE_MESSAGING_SENDER_ID,
    api_id: config.FIREBASE_API_ID,
    measurement_id: config.FIREBASE_MEASUREMENT_ID,
  }
  initializeApp(firebaseConfig)
})

(Authenticationのみで利用するので、初期化時の引数はapiKeyとauthDomainとprojectIdの3つを最低限指定しておけば問題なく動く。)

  • plugins ディレクトリについて
  • pluginsディレクトリ配下のjsファイルは自動的に読まれるので、nuxt.config.jsへの設定は不要。
  • defineNuxtPluginというplugin読み込み用の関数が用意されているので、これを使う。
  • プラグイン内でランタイムの設定を使用したい場合は、defineNuxtPlugin 関数内でuseRuntimeConfig() を使用する

Nuxt3はdotenvが内包されているので.envでお手軽に環境変数を設定する

■ .env

FIREBASE_API_KEY=****************
FIREBASE_AUTH_DOMAIN=****************
FIREBASE_PROJECT_ID=****************
FIREBASE_STORAGE_BUCKET=****************
FIREBASE_MESSAGING_SENDER_ID=****************
FIREBASE_API_ID=****************
FIREBASE_MEASUREMENT_ID=G-****************

nuxt3には、実行時に設定値をロードするための仕組みとしてruntimeConfigというものがある。コンポーネント内からはuseRuntimeConfigや$configで値にアクセスすることができる。

■ nuxt.config.ts

  runtimeConfig: {
    public: {
      FIREBASE_API_KEY: process.env.FIREBASE_API_KEY || '',
      FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN || '',
      FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID || '',
      FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET || '',
      FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID || '',
      FIREBASE_API_ID: process.env.FIREBASE_API_ID || '',
      FIREBASE_MEASUREMENT_ID: process.env.FIREBASE_MEASUREMENT_ID || '',
    },
  },

例)config.FIREBASE_API_KEYと記載すれば.envで設定されたFIREBASE_API_KEYの値が取得できる

各認証メソッド

認証の処理はcomposableにまとめる。

Composables directoryではこちらのドキュメントの通り、各Componentでimportを書かなくても呼び出しができるものになります。

■ composables/useAuth.ts

import {
  getAuth,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut as firebaseSignOut,
  onAuthStateChanged,
  GoogleAuthProvider,
  signInWithPopup,
  TwitterAuthProvider
} from 'firebase/auth'

export const useAuth = () => {
  const token = useState<string>('token', () => null)

  // メールアドレス新規登録関数
  async function signUp(email:string, password:string){
    return await new Promise((resolve)=>{
      // getAuth()でAuthを取得
      const auth = getAuth()
      // メールアドレスとパスワードでアカウントを作成する
      createUserWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        console.log(userCredential)
        // サインアップできたらログインする
        resolve("success")
      })
      .catch((error) => {
        console.log(error)
        const errorMessage = error.message;
        resolve(errorMessage)
      })
    })
  }

  // メールアドレスとパスワードでログインする関数
  async function signIn(email: string, password: string) {
    return await new Promise<void>((resolve, reject) => {
      const auth = getAuth()
      // メールアドレスとパスワードでログインする
      return signInWithEmailAndPassword(auth, email, password)
        .then((userCredential) => {
          userCredential.user
            .getIdToken()
              .then((idToken) => {
                token.value = idToken
                resolve()
              })
            .catch(reject)
        })
        .catch(reject)
    })
  }

  // ログアウトする関数
  async function signOut() {
    return await new Promise<void>((resolve, reject) => {
      const auth = getAuth()
      // ログアウトする
      firebaseSignOut(auth)
        .then(() => {
          token.value = null
          resolve()
        })
        .catch((error) => {
          reject(error)
        })
    })
  }

  // ログイン状態確認関数
  async function checkAuthState() {
    return await new Promise<void>((resolve, reject) => {
      // client only
      if (process.server) return resolve()
      const auth = getAuth()
      onAuthStateChanged(
        auth,
        (user) => {
          if (user) {
            user
              .getIdToken()
              .then((idtoken) => {
                token.value = idtoken
                resolve()
              })
              .catch(reject)
          } else {
            token.value = null
            resolve()
          }
        },
        (error) => {
          reject(error)
        }
      )
    })
  }

  // google認証関数
  async function loginWithGoogle() {
    const auth = getAuth();
    const provider = new GoogleAuthProvider();
    signInWithPopup(auth, provider)
      .then((result) => {
        const credential = GoogleAuthProvider.credentialFromResult(result);
        const token = credential?.accessToken;
        const user = result.user;
        console.log({ credential, token, user });
    })
    .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        const email = error.email;
        const credential = GoogleAuthProvider.credentialFromError(error);
        console.log({ errorCode, errorMessage, email, credential });
    });
  };

  // twitter認証関数
  async function loginWithTwitter() {
    const auth = getAuth();
    const provider = new TwitterAuthProvider();
    signInWithPopup(auth, provider)
      .then((result) => {
        const credential = TwitterAuthProvider.credentialFromResult(result);
        const token = credential?.accessToken;
        const user = result.user;
        console.log({ credential, token, user });
    })
    .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        const email = error.email;
        const credential = TwitterAuthProvider.credentialFromError(error);
        console.log({ errorCode, errorMessage, email, credential });
    });
  };



  return {
    signUp,
    signIn,
    signOut,
    token,
    checkAuthState,
    loginWithGoogle,
    loginWithTwitter
  }
}

ログイン状態の永続性

■ middleware/auth.ts

export default defineNuxtRouteMiddleware(async () => {
  if (!process.server) {
    const { checkAuthState, token } = useAuth()
    await checkAuthState()
    if (!token.value) {
      return navigateTo('/login', { replace: true })
    }
  }
})

Page コンポーネントや Layout コンポーネントにて、使用する middleware を指定すると、コンポーネントが作成 (setup) される前に(ページ遷移に先立ち) middleware 内の指定のファイルが実行されます。

<script setup>
definePageMeta({
  middleware: ['auth']
})
</script>

参考

Nuxt3 & Firebase9でFirebase Authentication決定版

Nuxt3のruntimeConfigで環境変数を設定する | デバッグライフ

【Vue.js】Nuxt3でComposables directoryを使ってグローバルstateを管理する

Nuxt 3 の Route Middleware で簡単な認証フローを構築する

Nuxt3にtailwindを導入する

Nuxt3にtailwindを導入する

導入手順

インストール

npm install -D @nuxtjs/tailwindcss tailwindcss@latest postcss@latest autoprefixer@latest

@nuxtjs/tailwindcss → Nuxt3でTailwindCSSを利用するためのモジュール tailwindcss@latest → Tailwindの最新版 postcss@latest → postcss(CSSツールを作るためのフレームワーク)の最新版 autoprefixer@latest → autoprefixerの最新版

nuxt のビルド設定

ファイル名:nuxt.config.ts

export default defineNuxtConfig({
  buildModules: ['@nuxtjs/tailwindcss'],
  css: ["@/assets/css/tailwind.css"],
})

buildModules: ['@nuxtjs/tailwindcss'] → @nuxtjs/tailwindcssモジュールをビルド時に利用する css: ["@/assets/css/tailwind.css"] → @/assets/css/tailwind.cssCSS設定の読み込み

Tailwind のセットアップ

npx tailwindcss initでtailwind.config.jsを作成する

ファイル名:tailwind.config.js

module.exports = {
  // tailwindを適用したいファイル群を指定
  content: [
    "./app.vue",
    './components/**/*.{vue,js}',
    './layouts/**/*.vue',
    './pages/**/*.vue',
    './plugins/**/*.{js,ts}',
    './nuxt.config.{js,ts}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

./assets/css/tailwind.cssを作成

ファイル名:./assets/css/tailwind.css

@tailwind base;
@tailwind components;
@tailwind utilities;

上記の3つは【Tailwind CSS】標準で読み込まれるCSSについて 【Tailwind CSS】標準で読み込まれるCSSについて | プログラミングマガジン

参考

Nuxt 3 + Tailwind CSS 3 Starter: How to add Tailwind CSS v3 to your Nuxt 3 application - YouTube

Nuxt3 に Tailwind CSS をインストールしてみた

Nuxt3にEslintとprettierを導入する

Nuxt3にEslintとprettierを導入する

ESLintとPrettierはどちらもソースコードの品質を高めるツールになります。

ESLint:静的解析ツール。バグの可能性がある書き方を指摘する。

Prettier:コードフォーマッター。インデント、改行などを自動整形してくれる。

ESLintとPrettierの違いについて - 世界一適当な技術ブログ

1. Eslintの導入

1.1 必要パッケージのインストール

npm install --save-dev eslit eslint-plugin-vue @vue/eslint-config-typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
  • eslit -> ESLint本体
  • eslint-plugin-vue -> vueファイルを静的検証
  • @vue/eslint-config-typescript -> TSをESLintで検証するために必要なライブラリ
  • @typescript-eslint/parser -> TSをESLintで検証するために必要なライブラリ
  • @typescript-eslint/eslint-plugin -> TSをESLintで検証するために必要なライブラリ

1.2 .eslintrc.ymlの作成

.eslintrc.ymlにEslintの設定情報を記載していきます。

env:
  # windowやalertも含まれるたくさんのブラウザ用のグローバル変数を一気にglobals設定に登録してしまい、ESLintにチェックしなくてよいと伝える
  browser: true
  # ECMAScript2021を適用
  es2021: true
extends:
  # eslint-plugin-vueが推奨するルールを適用/https://eslint.vuejs.org/user-guide/#installation
  - 'plugin:vue/vue3-recommended'
  - '@vue/typescript/recommended'
  # ESLintが推奨するチェック項目でまとめてチェックする
  - 'eslint:recommended'
parserOptions:
  # parserOptionsのecmaVersionは、使用するECMAScriptのバージョンを指定します。envのes2021は指定しているため、それに合わせる
  ecmaVersion: 12
plugins:
  - vue
  - '@typescript-eslint'
rules: {}

1.3 package.jsonのscriptsにeslintの実行コマンドを指定

  "scripts": {
    ...
    "lint-fix": "eslint --fix --ext .ts,.js,.vue .",
  },

--fix は勝手に直しちゃうオプションなのでなしがいい場面もあるかも
--ext で範疇とする拡張子を選ぶ.
最後の . は現在のディレクトリ内を探して、という意味。 --ignore-path で .gitignore に書いてあるものは無視します。

2. Prettierの導入

2.1 必要パッケージのインストール

npm install --save-dev prettier @vue/eslint-config-prettier
  • prettier -> prettier本体
  • @vue/eslint-config-prettier -> ESLintのフォーマットのルールを無効にします。そのため、Prettierが整形した箇所に対してエラーを出さなくなります。

2.2 .prettierrc.ymlの作成

Prettierの設定ファイル.prettierrc.ymlを作成します

# 80文字で折り返す
printWidth: 80
# 半角スペースの数を2にする
tabWidth: 2
# ダブルクオテーションの代わりにシングルクオテーションを利用する
singleQuote: true
# 末尾のセミコロンを無しにする
semi: false

2.3 package.jsonのscriptsにprettierrcの実行コマンドを指定

  "scripts": {
    ...
    "format-fix": "prettier -w .ts,.js,.vue ."
  },

--w で上書き保存します

参考サイト

Vite + Vue3にESLintとPrettierを導入する

個人開発ドキュメントまとめ

イデア

  • 自分の問題にフォーカスする
  • 自分と同じ悩みを持つ人は他にもたくさんいる
  • 本ではなく自分の経験から考える
  • 問題からまずは考える、解決はその後
  • ニッチから始めよう
  • イデアは人に話しても問題ない。アイデアが同じでも人によってアウトプットが変わるので
  • 自分が毎日使うようなサービスが良い

開発

  • 最初はMVPでOK
  • 完璧を目指さない
  • バグは出しても良いが、報告できるようなUIにすべき。そうしないとユーザーがイライラする
  • アプリがグロースしてきたら人を雇う。一人では時間的制限がありすぎる
  • 技術力が高くなくても良い、分からなければその都度調べていくスタンスで十分
  • 小さく初めて、小さく試して、ダメなら作り直す。成功するまで繰り返す
  • コストダウンは大事
  • コネクションは大事ではない、大事なのはスキルとお金
  • あなたの悩みはググればほぼ答えがある。大事なのは検索力
  • 利用する技術は慣れていて一番早く開発できる技術が一番良い
  • 最初はwebで、人気出たらnativeが良い。
  • リリース前にLPは作った方が良い
  • 問い合わせ機能はGoogleFormで作成
  • タスク自動化は Zapierが良い
  • 課金はStripeが良い
  • 外部APIは依存リスクが高い(クローズする可能性があるので)

リリース

  • 3ヶ月以内にリリースしないとモチベきつい
  • バグはできる限り回収した後にリリースしよう
  • アナリティクスはいれるべき
  • フィードバック機能はいれるべき
  • 顧客ターゲットがいそうな場所でプレスリリース出す
  • リリース情報は複数箇所に挙げて、どんな反応があるか比べる
  • 友達に拡散を依頼しない、良いプロダクトは依頼しなくても拡散される
  • 嘘のlikesなどをしない
  • fakeは短期ではworkするが、長期ではworkしない

グロース

  • グロースハックなどの小手先に頼らない
  • 自然なグロースが一番良い、フォロワーを金で買うと長期で見ると破綻する
  • バージョンアップするたびに、ストーリをプレスリリースする
  • SNSシェア機能は必須
  • 公開API作るのもあり

マネタイズ

  • マネタイズすることに罪悪感を抱く必要はない
  • マネタイズ方法は早めに考えておくべき
  • [マネタイズ方法1] 機能を絞り込み、人気機能を利用するには有料プランに以降を促す
  • [マネタイズ方法2] ほぼ全ての機能はフリーで利用できるか、一部の機能のみを有料化する
  • [マネタイズ方法3] 広告型収入でマネタイズする
  • [マネタイズ方法4] 月額制で利用料金を支払うサブスクリションモデル
  • [マネタイズ方法5] プラットフォームサービスでユーザーと企業がマッチングした時に発生する金額の一部を成果報酬として得る。
  • 課金システムはStripe利用が良い
  • 返金要求された時は、フォームで理由を聞いた上で返金するのがベター(クレームは顧客ヒアリングのチャンス)

自動化

  • 人を雇わなくても自動化できそうな箇所はロボットで自動化する
  • 単純作業や繰り返し処理は自動化できないか検討する
  • 不労所得に夢を見ない
  • テストやデプロイの自動化は必須

エクジット戦略

  • 売却オファーができるように連絡先をオープンにしておく
  • オファーに浮つくのはNG
  • 90%程度のオファーはシリアルでないので、そういうのは無視でOK
  • ブローカー使って売却先探すのもあり
  • 現金売却、株式売却のどちら選ぶ
  • 売却するときは税金もかかる
  • 売却してFIREしたとしても、何もしないとメンタルがやられる
  • 相手からオファーがあって売る方が勿論ベター、自分から売ると買い手の言い値になりやすい

参考

MAKE: The Indie Maker Handbook

JavaScriptでディープコピーとシャローコピーについて

JavaScript(Vue.js)で実装している時に参照先のデータがうまく参照できないケースが発生しました。

そこで、データを参照する為ようのコピーを作成したのですが、その時にディープコピーとシャローコピーについて理解が出来ておらず詰まったのでそれぞれの違いを記載します。

結論

ディープコピー:オブジェクトの中のオブジェクトまで丸ごとコピーする
シャローコピー:オブジェクトの中の中のオブジェクトは参照コピーする

ディープコピーはコピー元とコピー先の実体は別なので、どちらかのオブジェクトを変更したも、もう片方のオブジェクトに影響を与えない

シャローコピーはコピー元とコピー先の実態は同じなので、どちらかのオブジェクトを変更したら、もう片方のオブジェクトも変更される

違いについてはこちらの動画がわかりやすい:ディープコピーとシャローコピーの違い - YouTube

ディープコピー図解

画像参照: 【JavaScript】ディープコピーとシャローコピーの違い

ディープコピー実装方法

単純にシリアライズ → デシリアライズという形でも、ディープコピーできるようです。

例)

let lists = [{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]
let copyLists = JSON.parse(JSON.stringify(lists))

copyLists[0].key1 = "hoge"
console.log('lists')
console.log(lists)

console.log('copyLists')
console.log(copyLists)

実行結果

lists
0: {key1: "value1", key2: "value2"}
1: {key1: "value3", key2: undefined}

copyLists
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3"}

シャローコピー図解

画像参照: 【JavaScript】ディープコピーとシャローコピーの違い