Skip to content

Como fazer upload de arquivos com Netlify Functions | Limites, Large Media e Soluções Alternativas

Categoria:Netlify · Configuração de Deploy
Este artigo está disponível atualmente apenas em japonês. As versões traduzidas serão publicadas sequencialmente.

Ao implementar upload de arquivo em Netlify Functions (função serverless baseada em AWS Lambda), o corpo da requisição tem um limite. Muitos desenvolvedores enfrentam problemas por desconhecer esse limite padrão de 6MB. Este artigo explica o limite de tamanho do corpo em Netlify Functions, configuração via <code>netlify.toml</code>, métodos de parse para <code>multipart/form-data</code> e alternativas para lidar com arquivos que excedem o limite.

Functions 6 MB request body 10s timeout Background Fn 6 MB request body 15min timeout Edge Functions 20 MB request body streaming OK
Figura: Limite de tamanho de corpo por função do Netlify

Limite de tamanho do corpo em Netlify Functions

Netlify Functions é baseada em AWS Lambda e possui um limite fixo de tamanho para o corpo da requisição.

Tipos de Function Limite de tamanho do corpo Possibilidade de alteração Observações
Netlify Functions (síncrono) 6 MB Não permitido Limitações do AWS Lambda
Netlify Functions(Background) 6 MB Não permitido O tempo de processamento é no máximo 15 minutos
Netlify Edge Functions Com limitações Não permitido Runtime baseado em Deno

Enviar requisições maiores que 6MB para Netlify Functions resulta em HTTP 413 ou timeout da função. Embora seja ligeiramente maior que Vercel (4.5MB), ainda não é possível receber diretamente arquivos grandes como imagens e vídeos.

Configuração de netlify.toml

Em <code>netlify.toml</code> você pode configurar o runtime, timeout, memória, redirecionamentos, headers e muito mais para Functions. O tamanho do corpo em si não pode ser alterado, mas é importante entender as configurações relacionadas.

# 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"

Implementação de análise de multipart/form-data

Em Netlify Functions, o corpo da requisição pode ser codificado em Base64 por padrão. Explicamos como fazer parse de dados multipart usando <code>busboy</code> ou <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 (Depreciado) e Alternativas

Netlify Large Media era um serviço baseado em Git LFS para gerenciar arquivos grandes, mas atualmente novas inscrições são restritas e é recomendado migrar para alternativas.

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

a configuração recomendada como alternativa é a seguinte.

// 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;
}

Arquivo de teste disponível para usar neste artigo (gratuito)

  • → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Imagem PNG de teste (1MB)</a> — Para verificação de operação de upload via <code>Netlify Functions</code>
  • → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Arquivo de teste PDF (1MB)</a> — Para teste de upload de arquivo dentro do limite de 6MB
  • → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Arquivo de teste ZIP (1MB)</a> — Para teste de upload direto no S3 via Presigned URL

Artigos relacionados

  • → <a href="/ja/blog/vercel-upload-config/" class="text-primary-600 dark:text-primary-400 hover:underline">Configuração e limites de upload de arquivos do Vercel | Vercel Blob, limitações de API e soluções alternativas</a>
  • → <a href="/ja/blog/render-upload-config/" class="text-primary-600 dark:text-primary-400 hover:underline">Como configurar upload de arquivos PHP/Node.js no Render | Disco, variáveis de ambiente e integração S3</a>
  • → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">Resumo dos limites de upload de arquivos do AWS S3 e CloudFront</a>