概要
AstroとCloudflare R2(S3互換ストレージ)を組み合わせてファイルを配布できる仕組みを作ったので、備忘録がてら記事にまとめました。
署名付きURLでセキュアに一時的なダウンロードリンクを発行できるのと、データベースが不要なのでサクッと作れるのが良い点かと思っています。
ただし、大規模なものではなく、ちょっとしたデータ配布をするとき用の構成です。
用途の具体例としては、イベント限定配布や期間限定公開の素材など「数種類だけのデータを手軽に配布したい」ケースを想定しています。
全体の流れ
- Cloudflare R2にファイルをアップロード
- 環境変数の設定
- AstroのAPI Routeで署名付きURLを発行
- フロントエンドからAPI Routeを叩き、R2のファイルをダウンロード
Cloudflare R2にファイルをアップロード
Cloudflare R2のバケットを作成し、管理画面やターミナルからファイルをアップロードします。
管理画面からアップロードする場合、作成したバケットのページに行ってアップロード
ボタンからアップロードするだけです。
詳細は公式ドキュメントをご覧ください。
ターミナルからアップロードする場合、R2はAWS S3互換APIがあるので、AWS CLIを利用できます。
aws s3 cp ./sample.zip s3://your-bucket/your-folder/sample.zip --endpoint-url=https://<your-account-id>.r2.cloudflarestorage.com
こちらも詳細は公式ドキュメントをご覧ください。
AstroのAPI Routeで署名付きURLを発行
AstroのAPI Route(src/pages/api/download/[id].ts
といったファイル)で、R2上のファイルに対して「一時的に有効なダウンロードURL」を発行し、リダイレクトします。
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import type { APIRoute } from "astro";
export const prerender = false;
export const GET: APIRoute = async ({ params, redirect, locals }) => {
const s3Client = new S3Client({
region: "auto",
endpoint: locals.runtime.env.AWS_ENDPOINT,
credentials: {
accessKeyId: locals.runtime.env.AWS_ACCESS_KEY_ID,
secretAccessKey: locals.runtime.env.AWS_SECRET_ACCESS_KEY,
},
});
const command = new GetObjectCommand({
Bucket: "your-bucket",
Key: `your-folder/${params.id}.zip`,
});
const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 86400 });
return redirect(signedUrl);
};
署名付きURLで、直リンクを晒さず有効期限付きで安全に配布できます。
また先ほども記載したとおりS3互換なので、AWS SDKがそのまま使えます。
環境変数の設定
.dev.vars
に環境変数を設定します。
AWS_ENDPOINT=https://<your-account-id>.r2.cloudflarestorage.com
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
それぞれの値の取得の方法は公式ドキュメントを確認してください。
フロントエンドからAPI Routeを叩き、R2のファイルをダウンロード
Astroのページ内で、API Routeへのリンクをダウンロードボタンとして設置します。
---
import { getCollection } from "astro:content";
import type { APIRoute } from "astro";
export async function getStaticPaths() {
const items = await getCollection("exampleCollection");
return items.map((item) => ({
params: { id: item.id },
props: { item },
}));
}
const { item } = Astro.props;
---
<a
download
href={`/api/download/${item.id}`}
>
ダウンロード
</a>
まとめ
- Astro × Cloudflare R2で、サーバーレスでセキュアにファイルを配布できる仕組みが作れる
- データベース不要、運用コストも最小限
- 署名付きURLで安全に配布できるので、素材配布や限定ファイルの配布にも最適