NextJS con AWS S3


En este post aprenderemos a conectar nuestras aplicaciones NextJs con los buckets de AWS S3

oscar Escrito por oscar 07 November 2025 1 0

En este post aprenderemos a conectar nuestras aplicaciones NextJs con los buckets de AWS S3

Antes de iniciar recomiendo leer

Escenario con el que trabajaremos

En los archivos de 

Estructura recomendada

project/
│
├── app/
│   ├── api/
│   │   └── b/[...path]/route.ts   ← proxy hacia S3
│   └── page.tsx                   ← ejemplo de uso
│
├── lib/
│   └── s3.ts                      ← helper AWS S3
│
├── .env.local
├── next.config.mjs
└── package.json

Crear el proyecto Next.js

Comienza creando tu aplicación Next.js con soporte para TypeScript:

npx create-next-app@latest next-prisma --typescript

Esto generará la estructura básica de Next.js con TypeScript.

cd next-prisma

Next.js con AWS S3

Instalar el SDK de AWS

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

Configurar variables de entorno .env.local

AWS_ACCESS_KEY_ID=TU_ACCESS_KEY
AWS_SECRET_ACCESS_KEY=TU_SECRET
AWS_REGION=us-east-1
AWS_S3_BUCKET=tu-bucket

Crear un helper para S3

Creamos en el directorio el siguiente archivo lib/s3.js en donde colocamos:

import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3Client = new S3Client({
  region: process.env.AWS_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
});

export async function getPresignedUrl(key: string) {
  const command = new GetObjectCommand({
    Bucket: process.env.AWS_S3_BUCKET,
    Key: key,
  });

  const url = await getSignedUrl(s3Client, command, { expiresIn: 3600 }); // 1 hora
  return url;
}

Crea la ruta que genera el acceso a las imágenes

Procedemos a crear archivo route con la siguiente estructura /app/b/[...path]/route.ts donde colocamos 

import { NextResponse } from "next/server";
import { getPresignedUrl } from "@/lib/s3";

export async function GET(
  req: Request,
  context: { params: Promise<{ path: string[] }> }
) {
  try {
    const { path } = await context.params; // 👈 desestructura el params correctamente
    const fullPath = path.join("/"); // oscardevops/images/2025/08/imagen.webp

    const bucket = process.env.AWS_BUCKET_NAME!;
    const key = fullPath.replace(`${bucket}/`, ""); // images/2025/08/imagen.webp

    // Generar URL firmada
    const signedUrl = await getPresignedUrl(key);

    // Descargar la imagen desde S3 usando fetch
    const response = await fetch(signedUrl);
    if (!response.ok) {
      return NextResponse.json(
        { error: "No se pudo descargar la imagen desde S3" },
        { status: 404 }
      );
    }

    // Convertir a bytes y devolver la respuesta
    const arrayBuffer = await response.arrayBuffer();
    const contentType = response.headers.get("content-type") || "image/jpeg";

    return new NextResponse(arrayBuffer, {
      headers: {
        "Content-Type": contentType,
        "Cache-Control": "public, max-age=3600",
      },
    });
  } catch (error) {
    console.error("Error al obtener la imagen desde S3:", error);
    return NextResponse.json(
      { error: "Error interno al obtener la imagen" },
      { status: 500 }
    );
  }
}

 Nota: En versiones recientes de Next.js, el handler debe declarar params como una promesa y usar await para obtener los valores.

Antes (Next.js 14 o menor) Ahora (Next.js 15 o superior)
params llegaba como objeto directo { path: string[] } params llega como Promise<{ path: string[] }>
Acceso directo: params.path Necesitas hacer const { path } = await context.params

¿Qué hace este flujo?

  1. Cuando tu navegador pide: http://localhost:3000/b/oscardevops/images/2025/08/17551018070831755101807083circuitolm35raspberrypipico.webp
  2. Next.js llama a /app/b/[...path]/route.ts
  3. El backend genera una URL firmada temporal
  4. Descarga la imagen desde S3
  5. La devuelve directamente (con los headers correctos)
  6. El usuario nunca ve la URL real de S3

next.config.mjs

Para que Next.js permita optimizar imágenes que provienen del backend:

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "http",
        hostname: "localhost",
        port: "3000",
        pathname: "/b/**",
      },
    ],
  },
};

export default nextConfig;

✅ Resultado


Comentario

Debe aceptar antes de enviar