mcpField
R
railway-storage

railway-storage

github.com/blink-newクラウド・DevOps50 回インストール
リポジトリ

railway-storage について

railway-storageは、AWS SDKでRailwayのS3互換ストレージを扱うためのリファレンススキルです。設定、アップロード、ファイル取得、よくあるエラーを解説し、ファイルを公開する場合のプロキシエンドポイントや署名付きURLも扱います。

主な機能

  1. 01Railway環境変数を使ったS3クライアント設定
  2. 02クライアントの遅延初期化の解説
  3. 03バケットオブジェクトのアップロードと取得
  4. 04プロキシエンドポイント経由での非公開ファイル配信
  5. 05ファイルアクセス用の署名付きURLの説明
  6. 06よくある設定エラーへの対応

活用シーン

  1. 01アプリでRailwayストレージバケットを設定
  2. 02APIルート経由で保存ファイルを配信
  3. 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" の設定は無視されます。

ファイルを公開するには:

  1. プロキシエンドポイント(推奨)- S3 から取得してクライアントに配信する API ルート
  2. 署名付き 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}

コメント