railway-storage について
railway-storageは、AWS SDKでRailwayのS3互換ストレージを扱うためのリファレンススキルです。設定、アップロード、ファイル取得、よくあるエラーを解説し、ファイルを公開する場合のプロキシエンドポイントや署名付きURLも扱います。
主な機能
- 01Railway環境変数を使ったS3クライアント設定
- 02クライアントの遅延初期化の解説
- 03バケットオブジェクトのアップロードと取得
- 04プロキシエンドポイント経由での非公開ファイル配信
- 05ファイルアクセス用の署名付きURLの説明
- 06よくある設定エラーへの対応
活用シーン
- 01アプリでRailwayストレージバケットを設定
- 02APIルート経由で保存ファイルを配信
- 03アップロードやエンドポイント設定の問題を調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ blink-new/ claude --skill railway-storageインストール後、Claude Code で/railway-storageを入力して読み込まれたか確認します。
詳細
Railway S3 互換ストレージ
Railway は、標準の AWS SDK で動作する S3 互換ストレージバケットを提供しています。ただし、AWS S3 とは重要な違いがあります。
重要:プライベートバケットのみ
Railway のバケットはデフォルトでプライベートであり、パブリックバケットには対応していません。 ACL: "public-read" の設定は無視されます。
ファイルを公開するには:
- プロキシエンドポイント(推奨)- S3 から取得してクライアントに配信する API ルート
- 署名付き URL - 直接アクセスできる、有効期限付きの署名 URL を生成します
環境変数
ストレージバケットをサービスにリンクすると、Railway が次の環境変数を自動で設定します。必ず以下の正確な名前を使用してください。
AWS_ENDPOINT_URL=https://storage.railway.app
AWS_DEFAULT_REGION=auto
AWS_S3_BUCKET_NAME=your-bucket-name
AWS_ACCESS_KEY_ID=tid_xxx
AWS_SECRET_ACCESS_KEY=tsec_xxx
重要: Railway はデフォルトで AWS_* をプレフィックスとする名前を使用します。S3_* のプレフィックスは Railway が設定する環境変数と一致しないため、使用しないでください。
S3 クライアントの設定
Docker のビルド中は環境変数を利用できないため、ビルド時のエラーを避けるには遅延初期化を使用します。
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
let s3Client: S3Client | null = null;
function getS3Client(): S3Client {
if (!s3Client) {
s3Client = new S3Client({
endpoint: process.env.AWS_ENDPOINT_URL,
region: process.env.AWS_DEFAULT_REGION ?? "auto",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
forcePathStyle: true, // Required for Railway
});
}
return s3Client;
}
主なポイント:
forcePathStyle: trueは必須です- モジュールレベルで
process.envにアクセスしないでください - リージョンは通常
"auto"です
アップロードの実装
export async function uploadToS3(key: string, body: Buffer, contentType: string): Promise<string> {
const client = getS3Client();
const bucket = process.env.S3_BUCKET_NAME!;
await client.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: body,
ContentType: contentType,
// Note: ACL is ignored - Railway buckets are always private
}));
// Return proxy URL (not direct S3 URL)
return `/uploads/${key}`;
}
プロキシエンドポイントのパターン
S3 からファイルを配信する API ルートを作成します。
// src/app/api/uploads/[...path]/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ path: string[] }> }
) {
const { path } = await params;
const key = path.join("/");
const result = await getS3Object(key);
if (!result) {
return NextResponse.json({ error: "File not found" }, { status: 404 });
}
return new NextResponse(result.body, {
headers: {
"Content-Type": result.contentType,
"Content-Length": result.contentLength.toString(),
"Cache-Control": "public, max-age=31536000, immutable",
},
});
}
S3 から読み取るヘルパー:
export async function getS3Object(key: string) {
const client = getS3Client();
const response = await client.send(
new GetObjectCommand({ Bucket: process.env.AWS_S3_BUCKET_NAME!, Key: key })
);
if (!response.Body) return null;
return {
body: response.Body.transformToWebStream(),
contentType: response.ContentType || "application/octet-stream",
contentLength: response.ContentLength || 0,
};
}
Next.js のリライトルール
// next.config.ts
const nextConfig: NextConfig = {
async rewrites() {
return [{ source: "/uploads/:path*", destination: "/api/uploads/:path*" }];
},
};
依存関係
bun add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
よくある問題
| 問題 | 原因 | 解決策 |
|---|---|---|
| ファイルのアップロードは成功するが、403 が返される | Railway が ACL を無視する | プロキシエンドポイントを使用する |
| 環境変数が見つからず、ビルドに失敗する | S3 クライアントがモジュールレベルにある | 遅延初期化を使用する |
| 「Invalid endpoint」エラーが発生する | forcePathStyle が設定されていない | forcePathStyle: true を追加する |
| アップロード後も画像が更新されない | ブラウザーまたは React Query のキャッシュ | invalidateQueries() を追加する |
URL 形式
データベースには S3 の直接 URL ではなく、プロキシ URL を保存してください。
- 正しい:
/uploads/{teamId}/avatar/{filename} - 誤り:
https://storage.railway.app/bucket/{key}
コメント
Markdown 対応