Skip to content

Instantly share code, notes, and snippets.

@hankei6km
Last active February 19, 2021 07:33
Show Gist options
  • Save hankei6km/9091f1d735ada9bc96f26180ac291e00 to your computer and use it in GitHub Desktop.
Save hankei6km/9091f1d735ada9bc96f26180ac291e00 to your computer and use it in GitHub Desktop.
microCMS のメディアファイルの URL を間接的に扱う方法の実験

microCMS のメディアファイルの URL を間接的に扱う方法の実験

2021-02-19 追記: 仕様変更後の挙動が予想と異る箇所あったので「新しい仕様」「消極的な対応」の章を大幅に書き直しました。

microCMS のメディアファイルの仕様変更に対応するために URL を間接的に扱う方法の実験。

sample screen shot

背景

microCMS からメディアのファイル管理の仕様が変更されるというメールが配信されてきました。それによると大筋では「(自分の理解が正しければ)microCMS のメディアにアップロードした画像等の URL を直接利用していると問題が発生するようになることもある」とのこと。そのようなわけで、自分の利用している(これから利用したいの)範囲でどの程度影響があるか?あるならば、どのように対応しておいた方がよいのかを実験してみることにしました。

なお、Hobby プランで利用しているので「画像ファイルにのみ着目しての実験」となります。その他の種類のファイルには事情が違うかもしれません。

新しい仕様

メールの内容からだと細い挙動がわからなかったので、実験する前にその辺を予測により補完しておきます(よって、この後の記述は憶測に憶測を重ねたものです)。

(2021-02-19 追記: 予想と異る箇所があったので、この章は全面的に書き直しました)

簡単に確認した限りでは以下のような挙動となりました。

同名ファイルを(上書きせずに)複数アップロード

メディア上に以下のようなファイルが存在している場合。

https://images.micrcms-.. /xxxxxx.xxxxx/2020-12-file01.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2020-12-file02.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2021-01-file03.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file04.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file05.jpg

ファイル 2021-02-file04.jpg を(アップロード済みファイルを上書きしないように)アップロードすると

https://images.micrcms-.. /xxxxxx.xxxxx/2020-12-file01.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2020-12-file02.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2021-01-file03.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file04.jpg <- そのまま残る
https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file05.jpg
https://images.micrcms-.. /xxxxxx.yyyyy/2021-02-file04.jpg <- これが増える

メディア管理画面でも以下のように2つの項目が表示されるようになる。

同名ファイルが複数存在

このとき GET API 等の挙動は以下のようになる。

  • GET-API のスキーマに配置していた「画像フィールド」「リッチエディタ」で2021-02-file04.jpg を選択していた場合、レスポンスには https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file04.jpg がそのまま利用される。
  • API の画面で新しいコンテンツを追加して、https://images.micrcms-.. /xxxxxx.yyyyy/2021-02-file04.jpg を選択することもできる(同名ファイルだが、あくまでも別項目という扱い)。
  • 直接 URL を参照していた場合も、https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file04.jpg はそのまま利用できる。また、同名ファイルの方の https://images.micrcms-.. /xxxxxx.yyyyy/2021-02-file04.jpg も同時に利用できる。
  • GET-API のクエリーで filters に「画像フィールド」は指定できない(既存のコンテンツでは従来通りに動作します)

同名ファイルそれぞれをAPIから選択

同名ファイルを(上書きしながら)再アップロード

メディア上に以下のようなファイルが存在している場合。

https://images.micrcms-.. /xxxxxx.xxxxx/2020-12-file01.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2020-12-file02.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2021-01-file03.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file04.jpg <- 同名ファイル
https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file05.jpg
https://images.micrcms-.. /xxxxxx.yyyyy/2021-02-file04.jpg <- 同名ファイル

ファイル yyyyy/2021-02-file04.jpg を(上書きするように)再アップロードすると

https://images.micrcms-.. /xxxxxx.xxxxx/2020-12-file01.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2020-12-file02.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2021-01-file03.jpg
https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file04.jpg <- そのまま残る
https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file05.jpg
https://images.micrcms-.. /xxxxxx.zzzzz/2021-02-file04.jpg <- URL が変更されて入れ替わる

API のコンテンツ一覧画面でも追加の操作なしに入れ替わった状態になる

APIで選択されていた画像も入れ替わる

このとき GET API 等の挙動は以下のようになる。

  • GET-API のレスポンスには https://images.micrcms-.. /xxxxxx.yyyyy/2021-02-file04.jpg の かわりに https://images.micrcms-.. /xxxxxx.zzzzz/2021-02-file04.jpg が含まれるようになる。
  • 直接 URL を参照していた場合は、https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file04.jpg は更新されないまま利用され続ける、新しい https://images.micrcms-.. /xxxxxx.xxxxx/2021-02-file04.jpg も同時に利用できる。ただし https://images.micrcms-.. /xxxxxx.yyyyy/2021-02-file04.jpg は利用できなくなる(404 が返ってくるようになる)。
  • 今回の仕様変更と直接の関係はないですが、ファイルのみを再アップロードした状態では Webhook は起動されるようにはなっていないため、GET API を使っていたとしても再度ビルド等を実施しないと古い URL を参照した状態になる可能性がある。

問題が発生しそうな箇所

自分が利用している(利用しようとしている)ケース別に問題が発生するか考えてみました。

静的サイトのコンテンツ作成時

  • ogp 用等に画像フィールドで画像を選択している
    • GET-API 経由なので新しい URL が自動的に取得される。
      • 👍 よって問題は発生しない。
  • リッチエディタで画像を挿入する
    • GET-API 経由なので新しい URL が自動的に取得される。
      • 👍 よって問題は発生しない。(2021-02-19 追記: 運用によっては画像のリンク切れがおきやすいです)
  • ロゴやアイコンとして画像の URL を扱っている
    • メディア管理画面から画像の URL をコピーして site.config.ts のような設定ファイルに書き込んでいる。
    • 同名ファイルを再アップロードした場合に手動で URL をコピーしなおす必要がある。
      • ❌ よって問題が発生する。
  • モックのデータとして画像の URL を扱っている
    • メディア管理画面から画像の URL をコピーして(aspid-mockの)モックデータとして扱っている
    • 同名ファイルを再アップロードした場合に手動で URL をコピーしなおす必要がある。
      • ❌ よって問題が発生する。
  • markdown で画像の URL を利用している
    • メディア管理画面から画像の URL をコピーしながら markdown を記述している。
    • 同名ファイルを再アップロードした場合に手動で URL をコピーしなおす必要がある。
      • ❌ よって問題が発生する。

静的サイトのコンテンツが参照されるとき

  • ソーシャルサービス側が og;image 等を参照した場合
    • 生の URL がカード情報として参照される
    • (ソーシャルサービスの実装にもよるが)ソーシャルカードはページの URL をキーにキャッシュされていると思われる(よって画像の URL が変更されても新旧のカードが出来上がる可能性は低い)
    • og:image の URL はキャッシュ更新時等に自動更新されると思われる
      • 👍 問題は発生しないと予想する(かえってキャッシュの更新が促進される?)
        • 問題が発生するしたらカードのキャッシュが更新されないということなので、同じ URL で画像が更新されても問題は出ると思われるので、別途対応を考える必要がある。
  • RSSフィードなどで画像 URL を利用する場合
    • 基本的には og:image と同様になると思われる。
      • 👍 こちらも問題は発生しないと予想。
  • クリッピングサービスが画像を含むページを切り出した場合
    • サービスの実装によるのでなんとも言えない。

その他

画面プレビュー等の静的サイト以外の使い方もあるのですが、プレビューは使っていたりいなかったりで対応も個々の事例別になりそうなので今回は割愛します。

対応

自分の場合では上記のような状況なので「設定ファイルで URL を直接記述している」「モックデータの中に URL を記述している」「markdown を記述するときに URL を直接利用している」の3つが問題になりそうですが、これらについては、モック以外の2つは「サイトビルド時に URL が解決できればよい = GET-API を利用する」の対応方法が適用できそうです。

しかし、

  • 「モックデータは GET-API を使わずにレスポンスデータを用意する」という使い方をしているので、モックデータを用意するために実際に GET-API を実行するのは現実的ではない(と思う)
  • また mardown についても「markdown を VSCode や Vim などで(プレビューを確認しながら)記述した後に、 microCMS 側へコピーしている」ような場合には、単純に GET-API を使うだけでは解決は難しそう(API-KEY の扱いの問題が出そう)

よって、「ビルド時での対応」と「ビルド時以外での対応方法」の2つを考えてみることにします。

ビルド時での対応

今回の microCMS の仕様変更で問題になるのは「(自分の理解が正しければ)生の URL を直接利用する」ことなのですが、逆にいえば microCMS の GET-API を利用して間接的に URL を利用すれば問題がないことになります。よって、以下のような API をメディアに紐付くように作成します。

input api information

select api type

define api scheme

APIが作成できたら利用したいメディアファイルを登録していきます。

add items

これで以下のようにメディアファイルの URL 一覧を安全に取得できるようになります。

$ curl 'https://test-media-file-indirect.microcms.io/api/v1/media?limit=10000' --glob -s -
H "X-API-KEY: ????????????????????????????????????" | jq
{
  "contents": [
    {
      "id": "gijuadiw2",
      "createdAt": "2021-01-28T05:19:05.659Z",
      "updatedAt": "2021-01-28T05:21:30.719Z",
      "publishedAt": "2021-01-28T05:19:05.659Z",
      "revisedAt": "2021-01-28T05:19:05.659Z",
      "media": {
        "url": "https://images.microcms-assets.io/protected/ap-northeast-1:9063452c-019d-4ffe-a96f-1a4524853eda/service/test-media-file-indirect/media/2020-11-03-yoko1.jpg",
        "height": 768,
        "width": 1536
      }
    },

    << snip >>

  ],
  "totalCount": 23,
  "offset": 0,
  "limit": 10000
}

同名ファイルをどのように扱うのかという問題と、処理が少し重くなりそうですが、ビルド時に(Next.js であれば getStaticProps から)全件受信してフィルタリングすれば URL を参照できるようになると思われます。

ビルド時以外での対応

ビルド時であれば GET-API を使うのはあまり難しくはないのですが、それ以外の状況では GET-API は(API-KEY の隠蔽などのために)間接的に利用する工夫が必要になりそうです。とりあえず思いつく方法としては以下の2つがあります。

  1. 静的サイトビルド時に、画像 API で加工された状態のファイルをデプロイ先等にダウンロードしておく。ファイル参照時にはダウンロードされたファイルを参照する
  2. メディアファイルに対してリバースプロキシ的に動作する API (Serverless Function) を作成する。ファイル参照時にはその API の URL を利用する。

画像表示時のレスポンスなどを考慮すると、1番目の方法が良いとは思うのですが、転送量とビルド時間の削減やダウンロード先の確保等を考えるとちょっと試すというにはボリュームがあるかなという気がしたので、今回は2番めの方法で試してみることにしました。

2番目の方法については、前述の GET-API を利用することで「ファイル名からメディアの URL を安定して取得できる」ようにはなっているので、この情報を利用し https://????????.vercel.app/api/iamge/filename.jpg のようにアクセスしたら microCMS のメディアファイルを返すような API(Serverless funciton)は作成できそうです。

実装

今回は Next.js + Vercel で、以下のようなビルド時に URL を取得する静的サイト + リダイレクトとフォワードを行う API を作成してみました(フォワードという単語の使い方が適切ではない気もしますが、ここでは SSH のフォワードに近い感覚で使っています)。

sample screen shot

  • 事前準備: 画像 URL が必要になった時点で GET API を利用してもよいのですが、今回は JSON ファイルとして一覧をダウンロードするスクリプトを作成し、ビルド時に実行されるようにしておきます。
# URL 一覧をダウンロード
curl "https://test-media-file-indirect.microcms.io/api/v1/media?limit=10000" -s -H "X-API-KEY:${GET_API_KEY}" > lib/\$items.json
  • URL の取得: 各ページの getStaticProps でファイル名をキーに JSON ファイルから画像 URL を取得。
export const getStaticProps: GetStaticProps = async () => {
  const items: Media[] = await getMediaItems([
    '2020-11-03-yoko1.jpg',
    '2020-11-03-tate2.jpg',
    '2020-11-03-yoko2.jpg',
    '2010-11-03-tate1.jpg'
  ]);
  return { props: { items } };
};
  • リダイレクト: API はファイル名を受け取ったら、JSON ファイルから URL を確定し HTTP リダイレクトさせるレスポンスをクライアントへ返す。
// 一覧から URL を探してリダイレクトする
const m = await mediaUrl(req?.query);
if (m) {
  res.redirect(307, m);
} else {
  res.status(404);
}
  • フォワード: API はファイル名を受け取ったら、JSON ファイルから URL を確定後にデータをフェッチし、クライアントへパイプさせる。
// 一覧から URL を探してデータをフォワードさせる
const m = await mediaUrl(req?.query);
if (m) {
  const img = await fetch(m);
      // ... 中略
  res.writeHead(img.status, {
    'Content-Type': header['Content-Type'],
    'Content-Length': header['Content-Length'],
    // age: header['age'],
    'cache-control': header['cache-control']
  });
  // 定義と作成されるインスタンスの整合性がとれないのでとりあえず any
  // 定義では pipeTo だが実際のインスタンスは pipe.
  await (img as any).body.pipe(res);
}

動作サンプル: Home | microCMS のメディアファイルを間接的に使う実験 リポジトリ: GitHub - hankei6km/test-media-file-indirect: Created with CodeSandbox

使用感

ビルド時での対応

普通に GET-API を使っているだけなのでコードを書く上ではとくに良い悪いはないのですが、API を1つ消費してしまう(Hobby プランだと作成できるAPI は 10まで)」という点はあまり良くはないので、通常処理のAPI に含めることができるならばその方が良いのかなといったところです。

ビルド時以外での対応

lighthouse から警告があるかと思っていたのですが、生の URL を直接扱う場合と比較してもとくに違いはなさそうでした。

lighthouse screen shot 1

lighthouse screen shot 2

しかしながら、ページリロード時の表示などではやはり「ワンテンポ遅れている感じがする」「(フォワードバージョンでは)受信しながら描画している様子が見える」といったように遅さが気になってしまいました。また、当初は「単純にリダイレクトするヘッダを返せばよいかな」くらいに考えていたのですが、CORS 対応なども行うことになったりと、そこそこ手間がかかることもわかり(セキュリティ的な観点からも)まじめに使うには慎重に実装した方がよさそうです。

よって、とくに理由がなければビルド時に GET-API で対応するようにしておいて、どうしても GET-API を直接利用するのが難しいときには、前述の「どこかにダウンロードして静的に扱う」なども含めて GET-API も間接的に利用する方法を考えるといった感じになるかと。

消極的な対応

仕様変更されてからリッチエディタ等から画像を利用してみましたが、再アップロードによる画像ファイルの上書きは注意して使わないと(GET API を経由したとしても)画像のリンク切れをおこしやすいということがわかりました。

一方で同名ファイルを複数アップロードできる仕様は、それぞれの画像ファイル(URL)が完全に独立しているので、これまで「画像を変更したらファイル名に枝番付けてアップロード」していたような場合は「ファイル名をそのままでアップロードしても同様の効果を得られる」ので、ファイル名変更の手間を削減できます。また、リッチエディタ等で利用する場合にも、「サイトの表示上は古い画像ファイルのまま」「記事編集は新しい画像で行う」といったこを両立しやすいという利点もあります。

よって、再アップロードによる上書きがどうしても必要ということでなければ、個人的には画像 URL を直接扱っていたとしても、(従来のように)「画像を変更したら(上書きしないように)アップロード」「コンテンツ内の画像 URL はその都度書き換える」といった対応でも問題ないかなと思っています。

おわりに

最後の方では「消極的な対応でもいいんじゃない?」と少しトーンダウンしてしまいましたが、メディアファイルの URL を間接的に扱う方法は、今回の仕様変更対への応以外にも使いどころはあると予想しているので、この辺の実際の挙動が確認できたのはよかったかなと思っています。

また、メディアファイルの URL を取得する API についても、ファイルのライセンスなどのメタデータ的なものをあわせて追記できるようにすれば(登録が面倒ですが)活用の方法が広がるかもしれません。


License: CC0 1.0 http://creativecommons.org/publicdomain/zero/1.0/deed.ja

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment