Subida de archivos con Next.js 13 y Cloudinary

En el ecosistema moderno de desarrollo web, manejar adecuadamente la subida de archivos es esencial para cualquier aplicación interactiva. Con la llegada de Next.js 13, uno de los Frameworks basados en React y Javascript más populares y versatiles del sector, gestionar archivos se ha vuelto aún más eficiente gracias a las mejoras de los route handlers. Cloudinary, un líder en la gestión de medios en la nube, se integra perfectamente con Next.js, permitiendo una subida de archivos optimizada y sin problemas. En este tutorial, abordaremos cómo aprovechar al máximo estas tecnologías, utilizando formData para la subida de archivos, y estableciendo una conexión fluida entre Next.js y Cloudinary desde la misma aplicación (Frontend y Backend).

Requerimientos

Para poder hacer este tutorial es necesario conocer:

Creación de proyecto Nextjs

Primero crearemos un proyecto de Nextjs:

npx create-next-app nextjs-cloudinary
cd nextjs-cloudinary
npm run dev

Código Cliente

Luego añadiremos algo de Código Cliente, de React para poder seleccionar un archivo y enviarlo a una Ruta de Backend.

En el archivo src/page.jsx añade lo siguiente:

"use client";
import { useState } from "react";

function HomePage() {
  const [file, setFile] = useState(null);
  const [imageUrl, setImageUrl] = useState(null);

  return (
    <div>
      <form
        onSubmit={async (e) => {
          e.preventDefault();

          const formData = new FormData();
          formData.append("image", file);

          const response = await fetch("/api/upload", {
            method: "POST",
            body: formData,
          });
          const data = await response.json();
          console.log(data);
          setImageUrl(data.url)
        }}
      >
        <input
          type="file"
          onChange={(e) => {
            setFile(e.target.files[0]);
          }}
        />
        <button>Enviar</button>
      </form>
      {
        imageUrl && (
          <img src={imageUrl} alt="" />
        )
      }
    </div>
  );
}

export default HomePage;

Este código representa un componente React llamado HomePage que permite al usuario subir una imagen a un servidor y luego mostrarla. Las partes importantes a tener en cuenta en este codigo son las siguientes:

  1. "use client";: Esto definiendo codigo del lado cliente en Nextjs 13, esto es util para poder usar hooks de react, como useState, useEffect, y poder manejar eventos como onChange
  2. const [file, setFile] = useState(null);: Usamos el hook useState para crear una variable de estado llamada file y una función setFile para actualizarla. Inicialmente, el valor de file es null.
  3. const [imageUrl, setImageUrl] = useState(null);: De manera similar, creamos otra variable de estado imageUrl y su función actualizadora setImageUrl.
  4. Luego creamos un componente con un formulario (<form>) que, al enviarlo, realiza la siguiente secuencia de acciones: i. Evita el comportamiento predeterminado del formulario con e.preventDefault();. ii. Crea un objeto FormData y añade el archivo seleccionado bajo la clave "image". iii. Envía este FormData al endpoint "/api/upload" mediante una petición HTTP POST. iv. Cuando recibe una respuesta, la convierte a formato JSON. v. Extrae la URL de la imagen de la respuesta y establece el estado imageUrl con esa URL. b. Hay un input de tipo "file" (<input type="file">). Cuando un usuario selecciona un archivo, el estado file se actualiza con ese archivo. c. A continuación, hay un botón (<button>) que, al hacer clic, envía el formulario. d. Después del formulario, hay una condición que verifica si imageUrl tiene algún valor. Si es así, muestra la imagen usando la etiqueta <img>. El atributo src de <img> se establece en el valor de imageUrl.

Y Al final, exporto el componente HomePage para que pueda ser utilizado en otras partes de la aplicación.

En resumen, este componente permite a los usuarios seleccionar un archivo de imagen, enviarlo a un servidor y luego mostrar la imagen en la página.

Route Handler para subir archivo

La ruta de Backend luciria así. Esta se encarga de guardar el archivo en la carpeta Public.

import { nextresponse } from "next/server";
import { writefile } from "fs/promises";
import { v2 as cloudinary } from "cloudinary";
import path from "path";

cloudinary.config({
  cloud_name: "",
  api_key: "",
  api_secret: "",
});

export async function post(request) {
  const data = await request.formdata();
  const image = data.get("image");

  if (!image) {
    return nextresponse.json("no se ha subido ninguna imagen", { status: 400 });
  }

  const bytes = await image.arraybuffer();
  const buffer = buffer.from(bytes);

  const filepath = path.join(process.cwd(), "public", image.name);
  await writefile(filepath, buffer)
  const response = await cloudinary.uploader.upload(filepath);
  console.log(response.secure_url)

  return nextresponse.json({
    message: "imagen subida",
    url: response.secure_url,
  });
}

Intalación de Cloudinary

Luego instalaremos la biblioteca de npm adecuada para Cloudinary, el cual es el mismo paquete que se usa en Nodejs.

npm i cloudinary

Luego añadiremos la siguiente ruta para subir el archivo

Subiendo Buffer

Para subir un buffer en Nexjs podemos usar el siguiente código:

  cloudinary.uploader
    .upload_stream({ 
      resource_type: "image",
      // public_id: "olympic_flag",
      format: "png",
  }, function (error, result) {
      console.log(result);
      return NextResponse.json({ success: true });
    })
    .end(buffer);

Este codigo lo que hace es aceptar el Buffer y enviarlo a los servidores de Cloudinary como un Stream. Sin embargo este formato espera usar los callbacks, así que es mejor cambiarlo por su formato async/await a traves del objeto Promise

Conclusión

En fin la combinación de tecnologías modernas como React, junto con la potencia y versatilidad de Next.js, da muchas mas ventajas a los desarrolladores. La nueva incorporación de route handlers en Next.js 13 facilita la forma en la que gestionamos las rutas y los datos que llegan a ella, facilitando la subida de archivos y permitiendo procesarlos en el backend. Ademas que al integrarlo con plataformas robustas como Cloudinary nos permite no solo subir archivos, sino también optimizarlos y entregarlos de manera eficiente.

En resumen, al combinar estas tecnologías, no solo mejoramos la experiencia de desarrollo, y la escalabilidad de nuestras aplicaciones, sino que también ofrecemos a los usuarios finales una experiencia más fluida y simple.