Next.js: Upload de imagem para a Cloudflare R2 Utilizando Presigned URL
Tags: nextjs, r2, upload, typescript
Published: 2024-05-24 03:53:50

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:

```json
[
  {
    "AllowedOrigins": [
      "http://localhost:3000"
    ],
    "AllowedMethods": [
      "GET",
      "PUT"
    ],
    "AllowedHeaders": [
      "Content-Type"
    ]
  }
]
```

## 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`:

```bash
npx create-next-app@latest
```

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.

4. **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.

5. **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:

```bash
cd upload-r2
code .
```

## 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:

```env
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
```

Inicie a aplicação:

```bash
npm run dev
```

## 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:

```bash
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
```
## Passo 5: Criando a Função para Gerar Presigned URL

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

```typescript
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 })
}
```

## Passo 6: Utilizando shadcn/ui

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

```bash
npx shadcn-ui@latest init
```

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:

```bash
 npx shadcn-ui@latest add label
 npx shadcn-ui@latest add input
 npx shadcn-ui@latest add button
 npx shadcn-ui@latest add sonner
```

## 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`:

```typescript
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>
  )
}
```

## 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:

```typescript
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>
  );
}
```

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