跳到内容

使用 Netlify Functions 上传文件的方法|限制・Large Media・规避策略

分类:Netlify·部署配置
本文目前仅提供日文版本。我们正在进行翻译工作。

在使用 Netlify Functions(基于 AWS Lambda 的无服务器函数)实现文件上传时,请求正文有大小限制。许多开发者不知道默认 6MB 的限制而陷入困境。本文说明 Netlify Functions 的请求正文大小限制、<code>netlify.toml</code> 中的配置、<code>multipart/form-data</code> 的解析方法,以及处理超过限制文件的替代方案。

Functions 6 MB request body 10s timeout Background Fn 6 MB request body 15min timeout Edge Functions 20 MB request body streaming OK
图:Netlify 各函数类型的请求体大小限制

Netlify Functions 请求正文大小限制

Netlify Functions 基于 AWS Lambda,请求正文大小有固定的上限。

Function 的种类 正文大小限制 是否可更改 备注
Netlify Functions(同步) 6 MB 不可能 AWS Lambda 的限制
Netlify Functions(Background) 6 MB 不可能 处理时间最长为 15 分钟
Netlify Edge Functions 有限制 不可能 基于 Deno 的运行时

向 Netlify Functions 发送超过 6MB 的请求会导致 HTTP 413 或函数超时。虽然比 Vercel(4.5MB)稍大,但仍然无法直接接收图像或视频等大文件。

netlify.toml 中的配置

在 <code>netlify.toml</code> 中,你可以配置 Functions 的运行时、超时时间、内存、重定向、标头等。body 大小本身无法更改,但理解相关设置很重要。

# netlify.toml

[build]
  command = "npm run build"
  publish = ".next"
  functions = "netlify/functions"  # Functions のディレクトリ

# Node.js バンドラーの設定
[functions]
  node_bundler = "esbuild"

# 特定の Function の設定
[functions."upload"]
  included_files = ["uploads/**"]

# リダイレクト設定(Next.js など SPA との組み合わせ)
[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200

# ヘッダーの設定(CORS など)
[[headers]]
  for = "/.netlify/functions/*"
  [headers.values]
    Access-Control-Allow-Origin = "*"
    Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, OPTIONS"
    Access-Control-Allow-Headers = "Content-Type, Authorization"

# 環境変数(本番用。機密情報は Netlify UI で設定すること)
[context.production.environment]
  NODE_ENV = "production"

[context.deploy-preview.environment]
  NODE_ENV = "development"

multipart/form-data 的解析实现

在 Netlify Functions 中,请求正文默认可能被 Base64 编码。我们说明如何使用 <code>busboy</code> 或 <code>formidable</code> 解析多部分数据。

// netlify/functions/upload.js(CommonJS)
const busboy = require('busboy');

exports.handler = async (event) => {
  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' };
  }

  return new Promise((resolve, reject) => {
    const contentType = event.headers['content-type'] || event.headers['Content-Type'];

    const bb = busboy({ headers: { 'content-type': contentType } });
    const files = [];
    const fields = {};

    bb.on('file', (name, file, info) => {
      const { filename, encoding, mimeType } = info;
      const chunks = [];

      file.on('data', (data) => chunks.push(data));
      file.on('end', () => {
        const buffer = Buffer.concat(chunks);

        // ファイルサイズの検証(6MB 上限の手前で確認)
        const MAX_SIZE = 5 * 1024 * 1024; // 5MB(余裕を持たせる)
        if (buffer.length > MAX_SIZE) {
          resolve({
            statusCode: 413,
            body: JSON.stringify({ error: 'ファイルが大きすぎます(上限5MB)。' }),
          });
          return;
        }

        files.push({ name, filename, mimeType, buffer, size: buffer.length });
      });
    });

    bb.on('field', (name, value) => {
      fields[name] = value;
    });

    bb.on('finish', () => {
      if (files.length === 0) {
        resolve({
          statusCode: 400,
          body: JSON.stringify({ error: 'ファイルが見つかりません。' }),
        });
        return;
      }

      const file = files[0];
      // ここでファイルを S3 などに保存する処理を行う

      resolve({
        statusCode: 200,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          success: true,
          filename: file.filename,
          size: file.size,
          mimeType: file.mimeType,
          fields,
        }),
      });
    });

    bb.on('error', (err) => {
      resolve({
        statusCode: 500,
        body: JSON.stringify({ error: err.message }),
      });
    });

    // Netlify Functions ではボディが Base64 エンコードされる場合がある
    const body = event.isBase64Encoded
      ? Buffer.from(event.body, 'base64')
      : event.body;

    bb.end(body);
  });
};
// netlify/functions/upload-s3.js(S3 に転送する例)
const busboy = require('busboy');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');

const s3 = new S3Client({ region: process.env.AWS_REGION });

exports.handler = async (event) => {
  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' };
  }

  return new Promise((resolve) => {
    const bb = busboy({
      headers: { 'content-type': event.headers['content-type'] },
      limits: { fileSize: 5 * 1024 * 1024 }, // 5MB で切り捨て
    });

    bb.on('file', async (fieldName, stream, { filename, mimeType }) => {
      const chunks = [];
      let truncated = false;

      stream.on('data', (chunk) => chunks.push(chunk));
      stream.on('limit', () => { truncated = true; });

      stream.on('end', async () => {
        if (truncated) {
          return resolve({
            statusCode: 413,
            body: JSON.stringify({ error: 'ファイルが5MBを超えています。' }),
          });
        }

        const buffer = Buffer.concat(chunks);
        const key = `uploads/${Date.now()}-${filename}`;

        try {
          await s3.send(new PutObjectCommand({
            Bucket: process.env.AWS_S3_BUCKET,
            Key: key,
            Body: buffer,
            ContentType: mimeType,
          }));

          resolve({
            statusCode: 200,
            body: JSON.stringify({
              success: true,
              key,
              url: `https://${process.env.AWS_S3_BUCKET}.s3.amazonaws.com/${key}`,
            }),
          });
        } catch (err) {
          resolve({ statusCode: 500, body: JSON.stringify({ error: err.message }) });
        }
      });
    });

    const body = event.isBase64Encoded
      ? Buffer.from(event.body, 'base64')
      : event.body;

    bb.end(body);
  });
};

Netlify Large Media(已弃用)和替代方案

Netlify Large Media 是基于 Git LFS 的大文件管理服务,但目前新用户注册已受限,建议迁移到替代方案。

# Netlify Large Media(非推奨・新規利用不可)
# .lfsconfig
[lfs]
    url = https://large-media.netlify.com/<repo-id>

推荐的替代方案配置如下。

// Presigned URL 経由で S3 に直接アップロードする Netlify Function
// netlify/functions/get-upload-url.js
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');

const s3 = new S3Client({ region: process.env.AWS_REGION });

exports.handler = async (event) => {
  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' };
  }

  const { filename, contentType, size } = JSON.parse(event.body);

  // サーバー側でのバリデーション
  const MAX_SIZE = 100 * 1024 * 1024; // 100MB
  if (size > MAX_SIZE) {
    return { statusCode: 413, body: JSON.stringify({ error: 'ファイルが大きすぎます。' }) };
  }

  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
  if (!allowedTypes.includes(contentType)) {
    return { statusCode: 415, body: JSON.stringify({ error: '許可されていない形式です。' }) };
  }

  const key = `uploads/${Date.now()}-${filename}`;

  const command = new PutObjectCommand({
    Bucket: process.env.AWS_S3_BUCKET,
    Key: key,
    ContentType: contentType,
    ContentLength: size,
  });

  // 署名付き URL を生成(このリクエストはファイル本体を含まないため制限外)
  const presignedUrl = await getSignedUrl(s3, command, { expiresIn: 900 }); // 15分

  return {
    statusCode: 200,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ presignedUrl, key }),
  };
};
// クライアント側:Netlify Function から Presigned URL を取得して S3 に直接アップロード
async function uploadFile(file) {
  // 1. Netlify Function から Presigned URL を取得
  const res = await fetch('/.netlify/functions/get-upload-url', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      filename: file.name,
      contentType: file.type,
      size: file.size,
    }),
  });

  const { presignedUrl, key } = await res.json();

  // 2. S3 に直接 PUT(Netlify Function を経由しないため容量制限なし)
  const uploadRes = await fetch(presignedUrl, {
    method: 'PUT',
    headers: { 'Content-Type': file.type },
    body: file,
  });

  if (!uploadRes.ok) throw new Error('アップロードに失敗しました');

  return key;
}

本文中可用的测试文件(免费)

  • → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PNG 测试图像(1MB)</a> — 用于验证通过 Netlify Functions 的上传行为
  • → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PDF 测试文件(1MB)</a> — 用于测试 6MB 限制内的文件上传
  • → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">ZIP 测试文件 (1MB)</a> — 用于通过 Presigned URL 进行 S3 直接上传测试

相关文章

  • → <a href="/ja/blog/vercel-upload-config/" class="text-primary-600 dark:text-primary-400 hover:underline">Vercel 文件上传配置和限制 | Vercel Blob・API 限制・解决方案</a>
  • → <a href="/ja/blog/render-upload-config/" class="text-primary-600 dark:text-primary-400 hover:underline">在 Render 上配置 PHP/Node.js 文件上传的方法 | Disk・环境变量・S3 集成</a>
  • → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">AWS S3・CloudFront 文件上传限制总结</a>