Strapi v4をHerokuとCloudinaryを使って無料で動かす
※Herokuについての知識が多少ある前提で進めます。Herokuについて知りたい場合は他の記事を参照してください。
Strapiとは?

Strapiは簡単に言うと、オープンソースのセルフホスト型ヘッドレスCMSです。
CMSにしては珍しく、自分でビルドしてホストするタイプのものとなっています。
最近メジャーバージョンがv4となり、UIやビルドシステムが一新されてより使いやすくなったので紹介しようと思います。
機能&料金

データベースやサーバーを自分で用意する必要がある代わりに、基本的な機能は全て無料となっています。リレーションや画像の配信も無料プランでできますし、ユーザーの権限設定も細かく設定できます。GraphQLにも対応していたり(らしい?[1])、リッチテキストをマークダウンのまま返してくれたりとContentfulやmicroCMSにない機能もあります。
特に有難いのがリッチテキストなどで日本語が自然入力にできるところです。(Contentfulの日本語入力はかなりバグっていて使いづらいのです)
自分でホストするので独自ドメインが使えるという点も嬉しい。
無料で使う方法
Strapiを使うにはまずそれを動かすサーバーを用意する必要があります。選択肢としては、AWS EC2やAzure、GAE、DigitalOceanがありますが、Herokuを使うことで完全無料[2]で使うことができます。
公式ドキュメントにHerokuでの導入記事があります。Herokuは専用のPostgresSQLを使うことができるので、データベースは気にしなくて良い[2:1]のですが、画像などのファイルだけはHerokuのephemeralという機能によって一定時間で綺麗に抹消されてしまうので、画像は別の場所に保存する必要があります。それに使うのがCloudinaryというサービスです。
Cloudinary
Cloudinaryは画像の配信サーバーで、画像の変換に使うこともできますが今回は単に画像を保存するためだけに使います。
料金

一ヶ月で2万5千回の画像変換(今回は使わないので気にしなくていい)、25GB分のストレージ、25GB分の通信帯域がフリープランの範囲です。個人で使う分なら余裕で余ると思います。
アカウント作成とキーの保存
アカウントを作っていない人はサクッと作ってしまいましょう。
登録ができたらダッシュボードに行き、Cloud Name、API Key、API Secretが後で必要になるので保存しておきます。

※2022/1/8時点でのダッシュボードの画面
Strapiプロジェクトの作成
基本的には公式の説明と同じです。
前提
- Herokuアカウント(フリープラン以上)
- Heroku CLI
- Node.js
- Git
Strapiプロジェクトの作成
お好きなディレクトリで以下を実行
npx create-strapi-app@latest my-project --quickstart
my-projectは自分の好きな名前に変えてもいいです。--quickstartオプションをつけると、プロジェクト作成後にすぐにStrapiの管理画面を開くことができます。不要ならつけなくてもいいです。
Herokuにpush
my-projectディレクトリが作成されるので、
git init
git add .
git commit -m "first commit"
でコミットし、お好きな方法でHerokuにプッシュします。
まだHerokuのappを作成していない場合は、
heroku login
を実行してHerokuにログインし、
heroku create custom-project-name
でcustom-project-nameという名前のappを作成できます。このcustom-project-nameは固有の名前でないといけません。
Heroku PostgresSQLを有効化
GUI、CUIどちらから追加しても構いません。
今回はCUIから追加します。
既に作成したappに追加する場合は、
heroku git:remote -a your-heroku-app-name
を実行することでyour-heroku-app-nameを選択することができます。
次に、
heroku addons:create heroku-postgresql:hobby-dev
を実行することでappにHeroku PostgresSQLが追加されます。
必要ライブラリのインストール
今回はnpmを使います。yarnの人は適宜置き換えてください。
HerokuのPostgresSQLを使うために必要なライブラリをインストールします。
npm install pg pg-connection-string --save
データベースを利用するためのセットアップ
以下を実行してHerokuのNODE_ENVをproductionにセットします。(理由は後述)
heroku config:set NODE_ENV=production
Herokuで実行する際にHeroku PostgresSQLを使用するように設定をします。
my-projectプロジェクト内にconfig/env/production/database.jsというファイルを作成し、以下の内容を記述します。丸々コピペでいいです。
const parse = require('pg-connection-string').parse;
const config = parse(process.env.DATABASE_URL);
module.exports = ({ env }) => ({
  connection: {
    client: 'postgres',
    connection: {
      host: config.host,
      port: config.port,
      database: config.database,
      user: config.user,
      password: config.password,
      ssl: {
        rejectUnauthorized: false
      },
    },
    debug: false,
  },
});
次に、Heroku PostgresSQLのURLを読み込むための設定をします。
以下を実行してSQLのURLをHerokuの環境変数にセットします。丸々コピペして実行してください。
heroku config:set MY_HEROKU_URL=$(heroku info -s | grep web_url | cut -d= -f2)
config/env/production/server.jsというファイルを作成し、以下の内容を記述します。こちらもそのままコピペでいいです。
module.exports = ({ env }) => ({
  url: env('MY_HEROKU_URL'),
  app: {
    keys: env.array('APP_KEYS'),
  },
});
環境変数`NODE_ENV`に`production`をセットした理由
Strapiではセキュリティ上の理由から、公開リンクではproductionモードにすることが推奨されています。productionモードではContent-Type Builderで新たなContent-Typeを作成することができません。Content-Typeは記事やユーザーなどの構造体のことで、これを編集したり作成したい場合はローカル環境で編集し再度デプロイする必要があります。記事を書いたりするときにはそのまま作成できるので記事を書くたびにデプロイする必要はありません。
config/env/productionディレクトリ内のファイルは、NODE_ENVがproductionの時にのみ実行されます。
2022/3/21 変更点
  app: {
    keys: env.array('APP_KEYS'),
  },
を追記しました。
Heroku側にも App_KEYS をセットする必要があります。
ここはよくわからなかったのですが、 .env ファイルで生成されるものをそのまま移したら動きました。
動作確認
以上が完了したら一度Herokuにデプロイして動作を確認しましょう。
https://[your-herokuapp-name].herokuapp.comにアクセスするとStrapiと対面できると思います。

管理画面のURLはhttps://[your-herokuapp-name].herokuapp.com/adminです。一番最初だけアカウントの登録画面となりますが、2回目以降はログイン画面が出てきます。Wordpressみたいな感じ。
ログインすると下の画像みたいな画面になります。

今回の記事はStrapiの使い方解説ではないので詳しい使い方は解説しません。UIがわかりやすいので他のCMSを使ったことがある人ならすぐに使えるかもしません。わからなかったら公式ドキュメントを参照してください。
Cloudinaryと連携する
Cloudinaryと連携するために以下の作業を行います。
ライブラリのインストール
@strapi/provider-upload-cloudinaryをインストールします。Strapi v3についての記事では別のライブラリを使っていることがありますが、v4では@strapi/provider-upload-cloudinaryでないとビルドに失敗します。
npm install @strapi/provider-upload-cloudinary
環境変数のセット
最初にメモしたCloudinaryのAPIキーなどをHerokuの環境変数にセットします。
CLOUDINARY_NAMEにCloud Nameを、CLOUDINARY_KEYにAPI Keyを、CLOUDINARY_SECRETにAPI Secretをセットします。
Cloudinaryライブラリの利用
config/env/production/plugins.jsを作成し、以下のように記述します。
画像をアップロード時に自動でCloudinaryにアップロードされるようになります。
module.exports = ({ env }) => ({
  upload: {
    config: {
      provider: "cloudinary",
      providerOptions: {
        cloud_name: env("CLOUDINARY_NAME"),
        api_key: env("CLOUDINARY_KEY"),
        api_secret: env("CLOUDINARY_SECRET"),
      },
      actionOptions: {
        upload: {},
        delete: {},
      },
    },
  },
});
config/env/production/middlewares.jsを作成し、以下のように記述します。
Strapi上で画像ファイルのサムネイルを表示できるようになります。
module.exports = [
  "strapi::errors",
  {
    name: "strapi::security",
    config: {
      contentSecurityPolicy: {
        useDefaults: true,
        directives: {
          "connect-src": ["'self'", "https:"],
          "img-src": ["'self'", "data:", "blob:", "res.cloudinary.com"],
          "media-src": ["'self'", "data:", "blob:", "res.cloudinary.com"],
          upgradeInsecureRequests: null,
        },
      },
    },
  },
  "strapi::cors",
  "strapi::poweredBy",
  "strapi::logger",
  "strapi::query",
  "strapi::body",
  "strapi::session",
  "strapi::favicon",
  "strapi::public",
];
2022/3/21変更点
    "strapi::session",
を追加
参考
動作確認
以上が完了したらHerokuにデプロイし、StrapiのMedia LibraryからちゃんとCloudinaryにアップロードされるかチェックしましょう。

Upload Assetsをクリックし、ワニの画像をアップロードします。

このGIFのようにアップロードに少し時間がかかっていたらおそらく成功です。CloudinaryのMedia Libraryを確認してみましょう。

sampleの画像は初めから入っているサンプル画像なので気にしないでください
ワニの画像がちゃんと追加されています!しかし、追加したのは1枚だけなのになぜか2枚ありますね。
これは、画像のサイズによって小さいサイズのバージョンや、サムネ用サイズの画像も自動で生成してくれるためです。
画像を削除するときはStrapiからDeleteするだけでCloudinary側の画像もちゃんと削除されます。
以上で、HerokuとCloudinaryを使って無料でStrapiをホストすることができました!
最後に
StrapiはContentfulなんかと比べてデベロッパーに優しい感じになっていて好きです。
Nuxt3はnuxt/contentが動かない(2022/1/8現在)ので、代わりにStrapiを使ってみようと思います。いい感じに行けたら記事にする予定です。
誤字や間違っている点などがあったら教えてください。



Discussion