FreshでSupabase上の画像を取得する
作成日
更新日
前提
- FreshでSupabase上の記事を取得する
管理者API Key を使わずにやりたかったけど、anonキーのやり方がむずい...
import { supabase } from "./supabase.ts";
const bucketName = "";
export async function getSignedUrl(fileName: string): Promise<string | null> {
const safeFileName = slugifyFileName(fileName);
const { data, error } = await supabase.storage.from(bucketName)
.createSignedUrl(safeFileName, 60 * 60);
if (error) {
console.error(
`Error generating signed URL for ${safeFileName}:`,
(error as Error).message,
);
return null;
}
return data.signedUrl;
}
// ファイル名を slugify して ASCII のみの形式に変換
function slugifyFileName(fileName: string): string {
return fileName
.normalize("NFKD") // Unicode 正規化(濁点・半濁点を分離)
.replace(/[\u0300-\u036f]/g, "") // ダイアクリティカルマーク除去
.replace(/[^\w.-]/g, "_") // 許可されていない文字を `_` に変換
.replace(/_{2,}/g, "_") // 連続する `_` を1つに
.toLowerCase();
}
export async function getPostWithImages(slug: string) {
// 記事データを取得
const { data: post, error } = await supabase
.from("articles")
.select("title, content, created_at, cover_image")
.eq("slug", slug)
.eq("private", false)
.single();
if (error || !post) {
console.error("Error fetching post:", error?.message);
return null;
}
console.log("🐈image:", post.cover_image);
// `cover_image` の署名付きURLを取得
const coverImageUrl = post.cover_image
? await getSignedUrl(post.cover_image)
: null;
return { ...post, cover_image: coverImageUrl };
}
これでカバー画像が取得できることは確認した。
api を使う
Freshでapiフォルダとutilsフォルダの使い分けより、画像はapiで取得したらいいんじゃないか説。
routes/api/getImage/[fileName].ts
に配置
routes/api/getImage/[fileName].ts
import { Handlers } from "$fresh/server.ts";
import { supabase } from "../../../utils/supabase.ts";
export const handler: Handlers = {
async GET(_req, ctx) {
const { fileName } = ctx.params;
// デコードしてからさらにASCIIにする
const encodeFileName = decodeURIComponent(fileName);
const safeFileName = slugifyFileName(encodeFileName);
const bucketName = Deno.env.get("SUPABASE_IMAGE_BUCKET") ?? "";
const { data, error } = await supabase.storage.from(bucketName)
.createSignedUrl(safeFileName, 60 * 60);
if (error || !data) {
console.error(
`Error generating signed URL for ${safeFileName}:`,
(error as Error).message,
);
return new Response("Error generating signed URL", { status: 500 });
}
return new Response(JSON.stringify({ url: data.signedUrl }), {
headers: {
"Content-Type": "application/json"
},
});
},
};
// ファイル名を slugify して ASCII のみの形式に変換
function slugifyFileName(fileName: string): string {
return fileName
.normalize("NFKD") // Unicode 正規化(濁点・半濁点を分離)
.replace(/[\u0300-\u036f]/g, "") // ダイアクリティカルマーク除去
.replace(/[^\w.-]/g, "_") // 許可されていない文字を `_` に変換
.replace(/_{2,}/g, "_") // 連続する `_` を1つに
.toLowerCase();
}
slugifyFileName
をする理由は GitHub Acitonsを使ってSupabase Storage に画像をアップロードするの時に日本語が使えずASCIIに変換してるため。
考えていた方法
routes/images/[fileName].tsx
から取得するようにしようとしていたが、そっちの方が重いっぽく、api
を利用した形にはできないみたいなのでやめた。
signed urlが公開されないようにする
urlが公開されてしまうので、signed urlを公開しない形に修正する。
signedURL自体を使わなくても単に download
でよかった。
import { Handlers } from "$fresh/server.ts";
import { supabase } from "../../../utils/supabase.ts";
export const handler: Handlers = {
async GET(_req, ctx) {
const { fileName } = ctx.params;
// デコードしてからさらにASCIIにする
const encodeFileName = decodeURIComponent(fileName);
const safeFileName = slugifyFileName(encodeFileName);
const bucketName = Deno.env.get("SUPABASE_IMAGE_BUCKET") ?? "";
const { data, error } = await supabase.storage.from(bucketName).download(safeFileName);
if (error || !data) {
console.error(`Error fetching image ${safeFileName}:`, error);
return new Response("Error fetching image", { status: 500 });
}
// 画像データをそのままレスポンスとして返す
return new Response(data, {
headers: {
"Content-Type": data.type, // 画像のMIMEタイプを維持
"Cache-Control": "public, max-age=3600", // クライアント側キャッシュを1時間有効
},
});
},
};
// ファイル名を slugify して ASCII のみの形式に変換
function slugifyFileName(fileName: string): string {
return fileName
.normalize("NFKD") // Unicode 正規化(濁点・半濁点を分離)
.replace(/[\u0300-\u036f]/g, "") // ダイアクリティカルマーク除去
.replace(/[^\w.-]/g, "_") // 許可されていない文字を `_` に変換
.replace(/_{2,}/g, "_") // 連続する `_` を1つに
.toLowerCase();
}
islandで表示する
api
を使うので islandで表示する。
routes/islands/CoverImage.tsx
import { useState, useEffect } from "preact/hooks";
export default function CoverImage({ imageName }: { imageName: string }) {
const [imageSrc, setImageSrc] = useState<string | null>(null);
useEffect(() => {
async function fetchImageUrl() {
try {
const res = await fetch(`/api/getImage/${imageName}`);
if (!res.ok) throw new Error(`HTTP error! Status: ${res.status}`);
setImageSrc(res.url);
} catch (error) {
console.error("Failed to fetch image:", error);
}
}
fetchImageUrl();
}, [imageName]);
return imageSrc ? <img src={imageSrc} alt="Cover Image" /> : <p>Loading...</p>;
}
オケ

公開日
更新日