RemixでCloudflare R2にアップロードしてみる

2023.10.02

はじめに

Cloudflare R2はAWS S3互換オブジェクトストレージです

このブログはR2について詳しい詳細は割愛します。エグレス料金が無料なのが魅力的です。

Cloudflare R2にRemixからデプロイする

R2を有効化します。

クレジットカード登録すれば有効化できます。無料枠もあります。

wrangler.tomlを作成する

local実行時のリソースや構成管理のためにwrangler.tomlを作っておきます。

$ bunx wrangler generate

Cloudflare R2のバケットをつくる

$ bunx wrangler r2 bucket create [BUCKET_NAME]

wrangler.tomlにR2をリソースを追加

wrangler.toml

name = "[PROJECT_NAME]"
compatibility_date = "2023-09-22"

[[r2_buckets]]
binding = 'R2'
bucket_name = '[BUCKET_NAME]'

RemixからR2バケットにアクセスする

最近のReactのフレームワークは単にSPAとして動くわけではなく、サーバー機能を持っていることが多くなっています。 シンプルにbuildにして、CDNとかで配信して終わりじゃなくなっています。

今回のRemixの用途も同じで、サーバーの機能を思っています。普通はサーバー部分はNodeやGOやPHPやRustなどで書くことが多いですが、Remixはサーバー部分もReactで書きます。なのでサーバーとクライアントで動く境界がわかりにくく、Nodeにしかない機能はクライアントではつかえず、ブラウザにしかない機能はサーバーではつかえません。どっちで動くコードなのか意識する必要があります。

今回のR2 Bucketも同じサーバーで取得できる機能となっています。

R2にアップロードしてみます。

app/routes/upload.tsx

import {
  type ActionFunctionArgs,
  json,
  unstable_createMemoryUploadHandler,
  unstable_parseMultipartFormData,
} from "@remix-run/cloudflare";

type AppEnv = {
  R2: R2Bucket;
};

export async function action({request, context}: ActionFunctionArgs) {
    const env = context.env as AppEnv
    const uploadHandler = unstable_createMemoryUploadHandler({
        maxPartSize: 1024 * 1024 * 10,
    });
    const form = await unstable_parseMultipartFormData(request, uploadHandler);
    const file = form.get("file") as Blob;
    const response = await env.R2.put("file", await file.arrayBuffer(), {
        httpMetadata: {
            contentType: file.type,
        }
    })
    return json({object: response})
}


export default function Index() {
    return (
      <div>
        <div>
          <form method={"POST"} encType="multipart/form-data">
            <input type="file" name={"file"} />
            <button type={"submit"}>送信</button>
          </form>
        </div>
        <div>
          <img src="/images/file" alt="" />
        </div>
      </div>
    );
}

R2の画像を表示

R2のKeyをパスパラメータで取得するようにします。

app/routes/images.$key.tsx

import type { LoaderFunction } from "@remix-run/cloudflare";
import { json } from "@remix-run/cloudflare";

type AppEnv = {
  R2: R2Bucket;
};

export const loader: LoaderFunction = async ({ params, context }) => {
  const key = params.key;
  if (key == null) {
    return json({ message: "Object not found" }, { status: 404 });
  }

  const { R2 } = context.env as AppEnv;
  const object = await R2.get(key);
  if (object == null) {
    return json({ message: "Object not found" }, { status: 404 });
  }
  const headers: HeadersInit = new Headers();
  object.writeHttpMetadata(headers);
  headers.set("etag", object.etag);
  return new Response(object.body, { headers });
};

Cloudflare Pagesの設定からR2を使えるように設定する

Cloudflare PagesのSetttingsにfunctionsの設定に環境設定ができる項目から、R2をbindingします。

デプロイする

$ bun run build
$ bun run pages:deploy

まとめ

Cloudflare R2にファイルをアップロードできるようになりました。R2のセットアップ済みのオブジェクトがもらえるので簡単に使えて便利です。 wrangler.tomlをかけばローカル時でも動いて、こんな簡単で良いのか不安になりますが、いい感じです。