Next.js: Upload de imagem para a Cloudflare R2 Utilizando Presigned URL

Luciano Moraes Jr. - May 24 - - Dev Community

Olá, devs! Neste post, vamos aprender como fazer upload de uma imagem em uma aplicação Next.js com TypeScript para um bucket na Cloudflare utilizando presigned URLs. Vamos dividir o tutorial em etapas para facilitar o entendimento.

O que é uma Presigned URL?

Uma Presigned URL é uma URL temporária gerada para permitir que usuários façam upload ou download de arquivos diretamente em um serviço de armazenamento, como o Amazon S3 ou Cloudflare R2, sem a necessidade de expor as credenciais de acesso no cliente. Essa URL inclui credenciais temporárias e permissões específicas, como tempo de expiração e tipo de ação permitida (upload ou download).

Vantagens de Usar Presigned URLs:

  • Segurança: As credenciais não são expostas no frontend.
  • Controle: A URL pode ser configurada para expirar após um determinado tempo.
  • Simplicidade: Facilita o processo de upload/download, delegando ao cliente a responsabilidade de transferir os arquivos.

O que Vamos Precisar?

Este artigo necessita de conhecimento intermediário/avançado em Next.js

  1. Uma aplicação Next.js configurada com TypeScript.
  2. Uma conta na Cloudflare e acesso ao Cloudflare R2.

Passo 1: Configurando o Cloudflare R2

Primeiro, precisamos configurar o nosso bucket na Cloudflare R2.

  1. Acesse o painel da Cloudflare e navegue até a seção R2.
  2. Copie seu ID da conta.
  3. Crie um novo bucket e anote o nome do bucket, pois vamos precisar dele mais tarde.
  4. Crie um novo token de API, dê um nome ao token e permissões de gravação/leitura.
  5. Copie Access Key ID e Secret Access Key para Clientes S3 para interagir com o bucket utilizando a SDK da AWS.

Adicione política de CORS no seu bucket

Para que a aplicação consiga realizar o upload para dentro do bucket no R2, é necessário liberar as seguintes origens, métodos e headers:

[
  {
    "AllowedOrigins": [
      "http://localhost:3000"
    ],
    "AllowedMethods": [
      "GET",
      "PUT"
    ],
    "AllowedHeaders": [
      "Content-Type"
    ]
  }
]
Enter fullscreen mode Exit fullscreen mode

Passo 2: Criando a Aplicação Next.js

Vamos começar criando uma nova aplicação Next.js utilizando o comando npx create-next-app@latest:

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

Ao executar o comando acima, o CLI do Next.js fará as seguintes perguntas, e você deve respondê-las conforme as sugestões:

  1. What is your project named? (upload-r2)

    • Responda com o nome desejado para a sua aplicação. Por exemplo, upload-r2.
  2. Would you like to use TypeScript?

    • Resposta: Yes
    • Explicação: Escolha "yes" para habilitar o suporte ao TypeScript na sua aplicação.
  3. Would you like to use ESLint with this project?

    • Resposta: Yes
    • Explicação: ESLint é uma ferramenta útil para manter a qualidade do código, ajudando a identificar e corrigir problemas de estilo e erros de programação.
  4. Would you like to use Tailwind CSS with this project?

    • Resposta: Yes
    • Explicação: Para este tutorial, estamos focando em usar a biblioteca shadcn/ui para estilizar os componentes, então Tailwind CSS é um requisito.
  5. Would you like to use src/ directory with this project?

    • Resposta: Yes
    • Explicação: Usar um diretório src/ ajuda a organizar melhor os arquivos de código-fonte, separando-os de outros arquivos de configuração e metadados.
  6. Would you like to use App Router (recommended)?

    • Resposta: No
    • Explicação: Para manter as coisas simples, usaremos a estrutura de diretórios padrão. O diretório app/ pode não ser necessário para a maioria dos projetos neste estágio.
  7. Would you like to customize the default import alias (@/*)?

    • Resposta: No
    • Explicação: O alias de importação @/ é uma convenção comum que facilita os caminhos de importação relativos, tornando o código mais legível e fácil de manter.

Após a instalação das dependências do Next.js, acesse a aplicação e abra no VSCode:

cd upload-r2
code .
Enter fullscreen mode Exit fullscreen mode

Passo 3: Configurando Variáveis de Ambiente

No arquivo .env.local da sua aplicação Next.js, adicione as seguintes variáveis de ambiente com as informações do Cloudflare R2:

CLOUDFLARE_ACCOUNT_ID=your-account-id
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_BUCKET_NAME=your-bucket-name
Enter fullscreen mode Exit fullscreen mode

Inicie a aplicação:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Passo 4: Instalando Dependências Necessárias

Vamos precisar do SDK da AWS para interagir com o Cloudflare R2, pois ele é compatível com o S3. Instale com o seguinte comando:

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
Enter fullscreen mode Exit fullscreen mode

Passo 5: Criando a Função para Gerar Presigned URL

Crie um arquivo em pages/api/presigned-url.ts para gerar a presigned URL:

import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { NextApiRequest, NextApiResponse } from 'next';

const accountId = process.env.CLOUDFLARE_ACCOUNT_ID

const client = new S3Client({
  endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
  region: 'auto',
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  }
})

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { key } = req.body; // key pode ser o nome da imagem

  if (!key) {
    return res.status(400).json({ error: 'File key is required.' })
  }

  const signedUrl = await getSignedUrl(client, new PutObjectCommand({
    Bucket: process.env.AWS_BUCKET_NAME,
    Key: key
  }), {
    expiresIn: 60 // URL válida por 1 minuto
  })

  res.status(200).json({ signedUrl })
}
Enter fullscreen mode Exit fullscreen mode

Passo 6: Utilizando shadcn/ui

Vamos começar adicionando o shadcn/ui na aplicação utilizando o comando:

npx shadcn-ui@latest init
Enter fullscreen mode Exit fullscreen mode

Ao executar o comando acima, o CLI do shadcn/ui fará as seguintes perguntas, e você deve respondê-las conforme as sugestões:

  1. Which style would you like to use?

    • Escolha um estilo que deseja utilizar. Por exemplo, New York.
  2. Which color would you like to use as base color?

    • Escolha uma base de cores. Por exemplo, Slate.
  3. Would you like to use CSS variables for colors?

    • Resposta: Yes
    • Explicação: Variáveis CSS para cores é útil para o reuso.

Feito isso, estamos prontos para adicionar os componentes. Vamos adicionar os componentes de Label, Input, Button e Sonner para toast:

 npx shadcn-ui@latest add label
 npx shadcn-ui@latest add input
 npx shadcn-ui@latest add button
 npx shadcn-ui@latest add sonner
Enter fullscreen mode Exit fullscreen mode

Passo 7: Criando o Formulário de Upload com shadcn/ui

Agora, crie um componente para fazer o upload da imagem utilizando a presigned URL. Crie um arquivo components/UploadForm.tsx:

import { ChangeEvent, FormEvent, useState } from "react";
import { toast } from "sonner";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Label } from "./ui/label";

export function UploadForm() {
  const [avatar, setAvatar] = useState<File | null>(null);

  function handleSelectAvatar(event: ChangeEvent<HTMLInputElement>) {
    const selectedFile = event.target.files?.[0] || null;

    setAvatar(selectedFile);
  }

  async function handleSubmit(event: FormEvent) {
    event.preventDefault();

    try {
      if (!avatar) return;

      const uniqueFileName = `${crypto.randomUUID()}-${avatar.name}`;

      const response = await fetch('/api/presigned-url', {
        method: 'POST',
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ key: uniqueFileName })
      })

      const { signedUrl } = await response.json()

      await fetch(signedUrl, {
        method: 'PUT',
        headers: {
          "Content-Type": avatar.type
        },
        body: avatar
      })

      toast.success('Upload realizado com sucesso!')
    } catch {
      toast.error('Ocorreu um erro ao realizar o upload!')
    }
  }

  return (
    <form className="flex flex-col gap-4" onSubmit={handleSubmit}>
      <div>
        <Label htmlFor="avatar">Avatar</Label>
        <Input id="avatar" type="file" onChange={handleSelectAvatar} />
      </div>
      <Button>Upload</Button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

Passo 8: Integrando o Componente no Projeto

Finalmente, vamos integrar o componente UploadForm em uma página do Next.js. Abra pages/index.tsx e adicione o componente:

import { UploadForm } from "@/components/UploadForm";
import { Toaster } from "@/components/ui/sonner";

export default function Home() {
  return (
    <div className="flex h-screen items-center justify-center">
      <div>
        <UploadForm />
        <Toaster />
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Conclusão

Pronto! Agora você tem uma aplicação Next.js com TypeScript capaz de fazer upload de imagens para um bucket na Cloudflare utilizando presigned URLs, e com uma interface estilizada usando shadcn/ui. Essa abordagem é ótima para garantir segurança e controle sobre os uploads.

Espero que este tutorial tenha sido útil para você. Se tiver alguma dúvida, deixe um comentário abaixo. Até a próxima!


Gostou deste post? Siga-me para mais conteúdos sobre desenvolvimento web e tecnologias! 🚀

. .
Terabox Video Player