使用 Netlify Functions 上传文件的方法|限制・Large Media・规避策略
分类:Netlify·部署配置
本文目前仅提供日文版本。我们正在进行翻译工作。
在使用 Netlify Functions(基于 AWS Lambda 的无服务器函数)实现文件上传时,请求正文有大小限制。许多开发者不知道默认 6MB 的限制而陷入困境。本文说明 Netlify Functions 的请求正文大小限制、<code>netlify.toml</code> 中的配置、<code>multipart/form-data</code> 的解析方法,以及处理超过限制文件的替代方案。
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>