DEV Community

Luciano Moraes Jr.
Luciano Moraes Jr.

Posted on

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

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! 🚀

Top comments (0)