Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner// lib/s3.ts
import { S3Client } from "@aws-sdk/client-s3";
export const s3Client = new S3Client({
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});// app/api/upload/route.ts
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { s3Client } from "@/lib/s3";
export async function POST(req: Request) {
const { filename, contentType } = await req.json();
const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET_NAME!,
Key: `uploads/${Date.now()}-${filename}`,
ContentType: contentType,
});
const url = await getSignedUrl(s3Client, command, { expiresIn: 600 });
return Response.json({ url });
}Quando usar isso: Você precisa fazer upload, download ou gerenciar arquivos no Amazon S3 a partir de um aplicativo Next.js, usando URLs pré-assinadas para uploads seguros do lado do cliente.
// app/components/FileUploader.tsx
"use client";
import { useState, useRef } from "react";
export default function FileUploader() {
const [uploading, setUploading] = useState(false);
const [uploadedUrl, setUploadedUrl] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
async function handleUpload() {
const file = fileInputRef.current?.files?.[0];
if (!file) return;
setUploading(true);
try {
// Etapa 1: Obter URL pré-assinada da nossa API
const res = await fetch("/api/upload", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
const { url } = await res.json();
// Etapa 2: Fazer upload diretamente para o S3 usando a URL pré-assinada
await fetch(url, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file,
});
// Extrair a URL permanente (sem parâmetros de consulta)
const permanentUrl = url.split("?")[0];
setUploadedUrl(permanentUrl);
} catch (error) {
console.error("Upload falhou:", error);
} finally {
setUploading(false);
}
}
return (
<div className="p-6 max-w-md mx-auto space-y-4">
<div>
<input
ref={fileInputRef}
type="file"
accept="image/*,.pdf,.doc,.docx"
className="block w-full text-sm"
/>
</div>
<button
onClick={handleUpload}
disabled={uploading}
className="bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50"
>
{uploading ? "Enviando..." : "Enviar"}
</button>
{uploadedUrl && (
<p className="text-sm text-green-600">
Enviado: <a href={uploadedUrl} className="underline">Ver arquivo</a>
</p>
)}
</div>
);
}// app/actions/s3-actions.ts
"use server";
import {
ListObjectsV2Command,
GetObjectCommand,
DeleteObjectCommand,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { s3Client } from "@/lib/s3";
const BUCKET = process.env.S3_BUCKET_NAME!;
export async function listFiles(prefix: string = "uploads/") {
const command = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: prefix,
MaxKeys: 50,
});
const response = await s3Client.send(command);
return (
response.Contents?.map((item) => ({
key: item.Key!,
size: item.Size!,
lastModified: item.LastModified!.toISOString(),
})) ?? []
);
}
export async function getDownloadUrl(key: string) {
const command = new GetObjectCommand({
Bucket: BUCKET,
Key: key,
});
return getSignedUrl(s3Client, command, { expiresIn: 3600 });
}
export async function deleteFile(key: string) {
const command = new DeleteObjectCommand({
Bucket: BUCKET,
Key: key,
});
await s3Client.send(command);
}O que isso demonstra:
@aws-sdk/client-s3)envie()-o através do cliente@aws-sdk/s3-request-presigner gera URLs assinadas com tempo limitado para qualquer comando S3/ como delimitador, mas o S3 é armazenamento plano - pastas são apenas prefixos de chaveUpload de arquivo com Server Action (para arquivos menores):
"use server";
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { s3Client } from "@/lib/s3";
export async function uploadFile(formData: FormData) {
const file = formData.get("file") as File;
const buffer = Buffer.from(await file.arrayBuffer());
const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET_NAME!,
Key: `uploads/${Date.now()}-${file.name}`,
Body: buffer,
ContentType: file.type,
});
await s3Client.send(command);
}Download e streaming de um arquivo:
// app/api/download/[key]/route.ts
import { GetObjectCommand } from "@aws-sdk/client-s3";
import { s3Client } from "@/lib/s3";
export async function GET(
req: Request,
{ params }: { params: { key: string } }
) {
const command = new GetObjectCommand({
Bucket: process.env.S3_BUCKET_NAME!,
Key: decodeURIComponent(params.key),
});
const response = await s3Client.send(command);
const stream = response.Body as ReadableStream;
return new Response(stream, {
headers: {
"Content-Type": response.ContentType ?? "application/octet-stream",
"Content-Length": String(response.ContentLength),
},
});
}Copiar objetos entre buckets:
import { CopyObjectCommand } from "@aws-sdk/client-s3";
const command = new CopyObjectCommand({
Bucket: "destination-bucket",
CopySource: "source-bucket/path/to/file.pdf",
Key: "new-path/file.pdf",
});
await s3Client.send(command);PutObjectCommandInput, GetObjectCommandInput, etc.GetObjectCommandOutput.Body é tipado como StreamingBlobPayloadOutputTypes - converta para ReadableStream em ambientes serverlessS3ClientConfig para configuração personalizada do clienteimport type {
PutObjectCommandInput,
ListObjectsV2CommandOutput,
} from "@aws-sdk/client-s3";
const params: PutObjectCommandInput = {
Bucket: "my-bucket",
Key: "file.txt",
Body: "Hello, World!",
};Erros de CORS no upload do lado do cliente - O navegador bloqueia requisições PUT para o S3. Correção: Configure o CORS no bucket S3 para permitir PUT do seu domínio. Adicione AllowedOrigins, AllowedMethods: ["PUT"] e AllowedHeaders: ["Content-Type"].
URL pré-assinada expirada - URLs expiram após os segundos configurados em expiresIn. Correção: Gere URLs pouco antes de usar. Use o padrão de 600 segundos (10 minutos) para uploads; não gere URLs com muita antecedência.
Falha em uploads de arquivos grandes - Arquivos acima de 5GB não podem usar PUT único. Correção: Use upload multipart com a classe Upload do @aws-sdk/lib-storage para arquivos acima de 100MB.
ContentType ausente - Arquivos enviados sem ContentType recebem application/octet-stream. Correção: Sempre passe ContentType no PutObjectCommand e nos cabeçalhos fetch do lado do cliente.
Limite de tamanho do corpo do Next.js - Uploads de Server Action são limitados ao limite de tamanho do corpo do Next.js (padrão de 1MB). Correção: Use uploads com URL pré-assinada para arquivos maiores ou aumente experimental.serverActions.bodySizeLimit em next.config.js.
Exposição de credenciais - Nunca importe @aws-sdk/client-s3 em componentes do cliente. Correção: Todo o uso do AWS SDK deve estar em rotas de API, Server Components ou Server Actions.
| Biblioteca | Melhor Para | Contraponto |
|---|---|---|
| @aws-sdk/client-s3 | Acesso completo à API S3 | Requer conta AWS e configuração de CORS |
| Vercel Blob | Armazenamento simples de arquivos no Vercel | Apenas Vercel, menos controle |
| Uploadthing | Uploads de arquivos com hooks React | Abstração sobre S3, menos flexibilidade |
| Cloudflare R2 | Compatível com S3, sem taxas de saída | SDK separado ou modo de compatibilidade S3 |
| Supabase Storage | Integrado com Supabase | Vinculado ao ecossistema Supabase |
getSignedUrl e a retornafetch(url, { method: "PUT", body: file }) para fazer upload diretamente para o S3PUT do seu domínioAllowedHeaders: ["Content-Type"] e seu domínio em AllowedOriginsPutObjectCommand único é limitado a 5GB@aws-sdk/lib-storage e sua classe Upload para uploads multipartesexperimental.serverActions.bodySizeLimit em next.config.jsconst command = new GetObjectCommand({ Bucket: BUCKET, Key: key });
const response = await s3Client.send(command);
return new Response(response.Body as ReadableStream, {
headers: {
"Content-Type": response.ContentType ?? "application/octet-stream",
"Content-Length": String(response.ContentLength),
},
});ContentType no PutObjectCommand, o S3 usará application/octet-stream por padrãoContentType: file.type tanto no comando quanto nos cabeçalhos fetch do lado do clienteimport type { PutObjectCommandInput } from "@aws-sdk/client-s3";
const params: PutObjectCommandInput = {
Bucket: "my-bucket",
Key: "file.txt",
Body: "Hello, World!",
ContentType: "text/plain",
};Cada comando tem um tipo *CommandInput e *CommandOutput correspondente.
Body é tipado como StreamingBlobPayloadOutputTypes, não ReadableStreamresponse.Body as ReadableStreamundefined antes de usar o corpoconst command = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: "uploads/images/",
MaxKeys: 50,
});
const response = await s3Client.send(command);
const files = response.Contents ?? [];S3 é armazenamento plano - "pastas" são apenas prefixos de chave separados por /.
"use server";
import { DeleteObjectCommand } from "@aws-sdk/client-s3";
import { s3Client } from "@/lib/s3";
export async function deleteFile(key: string) {
await s3Client.send(
new DeleteObjectCommand({ Bucket: BUCKET, Key: key })
);
}Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥