2021-02-19 追記: 仕様変更後の挙動が予想と異る箇所あったので「新しい仕様」「消極的な対応」の章を大幅に書き直しました。
microCMS のメディアファイルの仕様変更に対応するために URL を間接的に扱う方法の実験。
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
に「画像フィールド」は指定できない(既存のコンテンツでは従来通りに動作します)
メディア上に以下のようなファイルが存在している場合。
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 のコンテンツ一覧画面でも追加の操作なしに入れ替わった状態になる
このとき 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 が自動的に取得される。
- リッチエディタで画像を挿入する
- GET-API 経由なので新しい URL が自動的に取得される。
- 👍 よって問題は発生しない。(2021-02-19 追記: 運用によっては画像のリンク切れがおきやすいです)
- GET-API 経由なので新しい URL が自動的に取得される。
- ロゴやアイコンとして画像の URL を扱っている
- メディア管理画面から画像の URL をコピーして
site.config.ts
のような設定ファイルに書き込んでいる。 - 同名ファイルを再アップロードした場合に手動で URL をコピーしなおす必要がある。
- ❌ よって問題が発生する。
- メディア管理画面から画像の 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 をメディアに紐付くように作成します。
APIが作成できたら利用したいメディアファイルを登録していきます。
これで以下のようにメディアファイルの 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つがあります。
- 静的サイトビルド時に、画像 API で加工された状態のファイルをデプロイ先等にダウンロードしておく。ファイル参照時にはダウンロードされたファイルを参照する
- メディアファイルに対してリバースプロキシ的に動作する 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 のフォワードに近い感覚で使っています)。
- 事前準備: 画像 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 を直接扱う場合と比較してもとくに違いはなさそうでした。
しかしながら、ページリロード時の表示などではやはり「ワンテンポ遅れている感じがする」「(フォワードバージョンでは)受信しながら描画している様子が見える」といったように遅さが気になってしまいました。また、当初は「単純にリダイレクトするヘッダを返せばよいかな」くらいに考えていたのですが、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