Criar um site gerador de imagens com IA: template e projeto no GitHub

Um template funcional para um site gerador de imagens com IA: a stack Next.js, a estrutura do projeto no GitHub, a rota do servidor que chama a API de um modelo de imagem, a página de prompt, verificações de segurança, limites de requisições e deploy. Todos os arquivos estão incluídos, para você copiar e publicar.

Criar um site gerador de imagens com IA: template e projeto no GitHub
Cristian Da Conceicao
Fundador do Picasso IA

A maioria dos tutoriais sobre como criar um site gerador de imagens com IA termina com uma captura de tela e uma promessa vaga. Este termina com um projeto funcionando: uma caixa de prompt, uma rota do servidor que chama um modelo de imagem, um loop de polling, uma tela de resultado e um checklist de deploy. Todos os arquivos cabem nesta página, então você pode colá-los em um repositório novo, enviar para o GitHub e ter um gerador no ar na mesma tarde.

O template é pequeno de propósito. Ele tem quatro arquivos com lógica real, sem banco de dados, sem provedor de login e sem biblioteca de estado. Isso facilita a leitura de uma só vez e a expansão depois com contas, histórico ou vídeo. Se você consegue rodar npm install, consegue publicar.

💡 Resposta rápida: um site gerador de imagens é um formulário, uma rota do servidor que esconde seu token da API, um loop de polling e uma tag <img>. O resto é acabamento.

O que o template faz

O site final recebe um prompt de texto, envia para um modelo de imagem, espera o resultado e mostra a imagem com um link para download. O navegador nunca vê o seu token da API, porque cada chamada passa pela sua própria rota do servidor.

Este é o fluxo completo da requisição:

  1. O visitante digita um prompt e clica no botão.
  2. A página envia o prompt para /api/generate no seu servidor.
  3. Seu servidor valida o prompt e cria um job na API de imagens.
  4. A página consulta /api/generate/{id} a cada dois segundos para saber o status.
  5. Quando o status muda para succeeded, a página mostra a URL da imagem.

Esboço em quadro branco da arquitetura com navegador, servidor e modelo de imagem

Recursos em resumo

RecursoIncluídoOnde fica
Formulário de prompt com limite de caracteresSimcomponents/Generator.tsx
Rota do servidor que cria o jobSimapp/api/generate/route.ts
Rota de status para pollingSimapp/api/generate/[id]/route.ts
Estados de carregamento, erro e timeoutSimcomponents/Generator.tsx
Link de download e histórico localSimAdicionado na etapa da galeria
Contas, cobrança, galerias de usuáriosNãoAdicione após o primeiro lançamento

Para quem serve: desenvolvedores solo ganham um projeto de portfólio que de fato gera imagens. Agências ganham uma base que podem personalizar para um cliente em um dia. Equipes de produto ganham um protótipo para testar a demanda antes de investir em uma plataforma completa.

Stack e estrutura do projeto

Notebook em um café mostrando uma página simples de gerador de imagens com campo de prompt

Escolha a stack

O template usa Next.js com App Router, TypeScript e Tailwind CSS. Um só framework oferece a página e as rotas do servidor no mesmo repositório, e é por isso que ele combina com um primeiro projeto. Qualquer stack com servidor funciona do mesmo jeito: React com Express, SvelteKit, Nuxt ou Node puro.

Crie o projeto e envie para o GitHub em quatro comandos:

npx create-next-app@latest ai-image-generator --ts --app --tailwind --eslint
cd ai-image-generator
git init && git add . && git commit -m "Initial template"
gh repo create ai-image-generator --public --source=. --push

O backend é uma API de texto para imagem. A API da PicassoIA segue a convenção do Replicate: POST /v1/models/{owner}/{name}/predictions cria um job, e GET /v1/predictions/{id} retorna o status dele. No momento da escrita, a API oferece quatro modelos: picassoia/picassoia-image, picassoia/picassoia-image-editor-pro, picassoia/picassoia-video e picassoia/seedance-2.5-lite. Crie seu token na página da API da PicassoIA.

Árvore de pastas e variáveis

ai-image-generator/
├── app/
│   ├── api/
│   │   └── generate/
│   │       ├── route.ts          # creates the prediction
│   │       └── [id]/route.ts     # returns status and output
│   ├── layout.tsx
│   └── page.tsx                  # renders <Generator />
├── components/
│   └── Generator.tsx             # form, polling, result
├── .env.example
├── .gitignore
└── README.md

Vista de cima de uma mesa com wireframe, notebook e celular mostrando uma galeria de fotos

Duas variáveis de ambiente controlam tudo:

# .env.example
PICASSOIA_API_TOKEN=pia_sk_replace_me
PICASSOIA_MODEL=picassoia/picassoia-image

Copie o arquivo para .env.local e cole ali seu token real. create-next-app ignora todos os arquivos .env*, então adicione a linha !.env.example em .gitignore se quiser o arquivo de exemplo no repositório. Nunca coloque o token em uma variável que comece com NEXT_PUBLIC_, porque o Next.js envia essas variáveis para o navegador.

Escreva a rota do servidor

Mãos de um desenvolvedor digitando código em um notebook diante de um monitor

Crie a predição

// app/api/generate/route.ts
import { NextResponse } from "next/server";

const API = "https://api.picassoia.com/v1";
const MODEL = process.env.PICASSOIA_MODEL ?? "picassoia/picassoia-image";

export async function POST(req: Request) {
  const { prompt } = await req.json();
  if (typeof prompt !== "string" || prompt.length < 3 || prompt.length > 4000) {
    return NextResponse.json({ error: "Invalid prompt" }, { status: 400 });
  }

  const res = await fetch(`${API}/models/${MODEL}/predictions`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PICASSOIA_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ input: { prompt } }),
  });

  if (!res.ok) {
    return NextResponse.json({ error: "Upstream error" }, { status: 502 });
  }
  const prediction = await res.json();
  return NextResponse.json({ id: prediction.id });
}

O corpo segue a convenção do Replicate, um objeto input que contém o prompt. Cada página de modelo lista campos extras, como a proporção, que você pode adicionar ao lado de prompt. A verificação de 4.000 caracteres corresponde ao limite de prompt documentado, então o usuário recebe um erro claro antes de a requisição sair do seu servidor.

Faça polling até terminar

A geração é assíncrona. A primeira chamada retorna um id, e você pede o status até que ele diga succeeded ou failed.

// app/api/generate/[id]/route.ts
import { NextResponse } from "next/server";

export async function GET(
  _req: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  if (!/^[A-Za-z0-9_-]+$/.test(id)) {
    return NextResponse.json({ error: "Bad id" }, { status: 400 });
  }

  const res = await fetch(`https://api.picassoia.com/v1/predictions/${id}`, {
    headers: { Authorization: `Bearer ${process.env.PICASSOIA_API_TOKEN}` },
    cache: "no-store",
  });
  const p = await res.json();
  const output = Array.isArray(p.output) ? p.output[0] : p.output;
  return NextResponse.json({
    status: p.status,
    output: output ?? null,
    error: p.error ?? null,
  });
}

A verificação de id é importante. Sem ela, um visitante poderia passar ../ segmentos no caminho e fazer seu servidor chamar outro endpoint com o seu token anexado.

Trate erros e limites

Três tipos de falha aparecem no tráfego real:

  1. Entrada inválida: retorne 400 antes de gastar uma requisição.
  2. Erros do upstream ou filas ocupadas: retorne 502 ou 429 e deixe a página mostrar uma mensagem para tentar de novo.
  3. Jobs lentos: pare o polling após cerca de dois minutos e avise o usuário.

A API permite 5 predições simultâneas por conta, compartilhadas entre todos os tokens. Se seu site receber dez visitantes ao mesmo tempo, cinco esperam. Adicione uma pequena fila ou desabilite o botão enquanto um job roda.

SintomaCausa provávelSolução
401 da APIToken errado ou ausenteVerifique .env.local e reinicie npm run dev
429 ou esperas longasTodas as 5 vagas estão ocupadasColoque as requisições em fila ou mostre uma mensagem de espera
O status nunca mudaPolling com o id erradoRegistre o id retornado pela primeira chamada
Funciona localmente, falha onlineVariáveis não definidas na hospedagemAdicione as duas variáveis nas configurações da hospedagem

Monte a página de prompt

Designer desenhando um card de layout para uma galeria de imagens em um tablet

Formulário e estado

// components/Generator.tsx
"use client";
import { useState } from "react";

export default function Generator() {
  const [prompt, setPrompt] = useState("");
  const [image, setImage] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function generate() {
    setBusy(true);
    setError(null);
    setImage(null);
    try {
      const start = await fetch("/api/generate", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ prompt }),
      });
      if (!start.ok) throw new Error("Could not start the job");
      const { id } = await start.json();

      for (let i = 0; i < 60; i++) {
        await new Promise((r) => setTimeout(r, 2000));
        const poll = await (await fetch(`/api/generate/${id}`)).json();
        if (poll.status === "succeeded") return setImage(poll.output);
        if (poll.status === "failed" || poll.status === "canceled") {
          throw new Error(poll.error ?? "Generation failed");
        }
      }
      throw new Error("Timed out, try again");
    } catch (e) {
      setError((e as Error).message);
    } finally {
      setBusy(false);
    }
  }

  return (
    <main className="mx-auto max-w-3xl p-6">
      <textarea
        value={prompt}
        onChange={(e) => setPrompt(e.target.value)}
        maxLength={4000}
        placeholder="A lighthouse at dawn, 35mm film, soft fog"
        className="w-full rounded border p-3"
      />
      <button
        onClick={generate}
        disabled={busy || prompt.length < 3}
        className="mt-3 rounded bg-black px-4 py-2 text-white disabled:opacity-50"
      >
        {busy ? "Generating..." : "Generate"}
      </button>
      {error && <p className="mt-3 text-red-600">{error}</p>}
      {image && <img src={image} alt={prompt} className="mt-6 w-full rounded" />}
    </main>
  );
}

Importe-o em app/page.tsx e renderize <Generator />. Rode npm run dev, abra localhost:3000, digite um prompt e pronto: você tem um gerador funcionando.

Galeria e downloads

Adicione dois toques quando o básico funcionar. Envolva a imagem em um link com o atributo download para que o usuário possa salvar o arquivo. Depois, guarde cada resultado em localStorage, um array de objetos { prompt, url }, e renderize tudo abaixo do formulário em forma de grade. Isso dá histórico sem banco de dados.

💡 Use o prompt como texto alternativo. Isso ajuda leitores de tela e dá à sua galeria um texto útil para os mecanismos de busca.

Como usar o Imagen 4 na PicassoIA

Antes de fixar um estilo no seu site, teste onde iterar custa pouco. O site da PicassoIA tem um playground para cada modelo, e os controles seguem o mesmo padrão: um campo de prompt, algumas configurações e um botão de gerar.

Fotografias impressas dispostas em grade ao lado de um notebook com uma página de galeria

Rode um prompt de teste

  1. Abra a página do Imagen 4.
  2. Cole um prompt estruturado: assunto, cenário, luz, lente e textura.
  3. Escolha a proporção 16:9, se o modelo oferecer.
  4. Clique em gerar e confira o resultado em tamanho real.
  5. Mude um detalhe por vez e gere de novo.

Um prompt que funciona bem para saídas fotográficas:

Uma xícara de cerâmica com café preto sobre uma mesa de carvalho desgastada, luz de janela vinda da esquerda, lente de 50mm em f/2, profundidade de campo rasa, veio de madeira e vapor visíveis, cores do Kodak Portra 400.

Cada parte desse prompt tem uma função:

ParteExemploPor que ajuda
AssuntoUma xícara de cerâmica com café pretoNomeia a única coisa de que a imagem trata
CenárioUma mesa de carvalho desgastadaDá ao fundo um material e um clima
LuzLuz de janela vinda da esquerdaDefine sombras e direção
Lente50mm em f/2Controla a profundidade de campo e a perspectiva
TexturaVeio de madeira, vaporPuxa o resultado para uma fotografia real

Compare três modelos

Passe o mesmo prompt por vários modelos antes de decidir o que alimenta o site. No momento da escrita, a API oferece apenas os quatro modelos picassoia/*, então os outros desta tabela servem para escolher um estilo no playground.

ModeloPonto fortePágina
Imagen 4Detalhe fotográfico naturalAbrir
FLUX 2 ProBoa fidelidade ao promptAbrir
Seedream 4.5Cor rica e retratosAbrir
GPT Image 2Texto dentro das imagensAbrir
FLUX SchnellRascunhos rápidosAbrir
PicassoIA ImageO modelo padrão do templateAbrir

Mantenha o prompt vencedor como texto inicial no atributo placeholder, e transforme seus três melhores prompts em chips de exemplo clicáveis abaixo da área de texto. Visitantes que veem um bom exemplo escrevem prompts melhores, e prompts melhores significam menos gerações desperdiçadas.

Adicione LLMs e vídeo depois

Gere código com um LLM

Você pode construir o template inteiro acima com um assistente de programação. Claude Sonnet 5 e GPT 5.6 Sol estão listados para tarefas de programação, e Gemini 3.5 Flash serve para edições rápidas. Cole a árvore de pastas deste artigo e peça um arquivo de cada vez, depois leia cada linha antes de fazer commit.

Dois desenvolvedores programando em par em um aplicativo web com uma grade de imagens geradas

Reescreva prompts curtos

A maioria dos visitantes digita cinco palavras. Um modelo de linguagem pode expandi-las antes da chamada de imagem:

  1. Adicione uma etapa de rewrite em route.ts que envie o texto do usuário para um modelo como o Kimi K2.6.
  2. Instrua o modelo a retornar um prompt com assunto, luz, lente e textura.
  3. Envie esse prompt para o modelo de imagem e mostre as duas versões ao usuário.

Anime os resultados

As mesmas duas rotas cuidam do vídeo. Troque PICASSOIA_MODEL por um modelo de vídeo, como picassoia/seedance-2.5-lite, e a saída passa a ser um link MP4. Renderize com uma tag <video controls> e aumente o intervalo de polling, porque vídeo demora mais que imagem. Para comparar opções antes, as páginas do Seedance 2.5 Lite e do Wan 3 mostram o que cada modelo produz.

Publique sem surpresas

Racks de servidores em um corredor silencioso de data center

Modere os prompts

Um gerador público é abusado em poucos dias. Passe cada prompt por um modelo de moderação antes de criar a predição. O Llama Guard 4 12B foi feito para isso: ele classifica um prompt como seguro ou inseguro, e você bloqueia a requisição quando ele diz que é inseguro. Adicione também um aviso visível com os termos sob o formulário.

Limites de requisições e filas

RiscoSolução
Um visitante inunda a rotaLimite as requisições por IP, por exemplo 10 por hora
Mais visitantes do que vagas simultâneasDesabilite o botão enquanto um job roda e mostre uma mensagem de espera
Token vazado no navegadorMantenha as chamadas no servidor e nunca use NEXT_PUBLIC_ para segredos
Custo descontroladoLimite as gerações diárias e envie um alerta ao atingir 80%

Um contador em memória funciona em um único servidor. Em hospedagem serverless, cada instância de função tem sua própria memória, então use um armazenamento compartilhado para o limitador.

Faça o deploy e monitore

O deploy leva cinco etapas:

  1. Envie o repositório para o GitHub.
  2. Importe-o na plataforma de hospedagem de sua escolha.
  3. Adicione PICASSOIA_API_TOKEN e PICASSOIA_MODEL como variáveis de ambiente.
  4. Defina uma duração máxima de função longa o bastante para a sua rota de polling.
  5. Abra a URL ao vivo e gere três imagens de teste.

Depois do lançamento, registre o status de cada job (nunca o token) e confira a taxa de falhas toda semana. Um aumento repentino geralmente indica uma mudança de modelo ou um limite atingido. Escreva também um README.md curto, com as duas variáveis, o comando de execução e uma captura de tela, porque o README é a primeira coisa que as pessoas veem em um projeto no GitHub.

Construa o seu na Picasso IA

Agora você tem as peças: uma stack, uma árvore de pastas, duas rotas do servidor, uma página de prompt e um checklist de segurança. A forma mais rápida de fazer o site parecer pronto é escolher o visual antes de escrever mais código.

Pequena equipe comemorando o lançamento do seu site gerador de imagens

Abra a Picasso IA, rode seu primeiro prompt pelo Imagen 4 ou pelo FLUX 2 Pro e salve os três resultados de que mais gostar. Esses prompts viram seus chips de exemplo, sua página inicial e seus primeiros casos de teste. Depois, crie seu token da API na página da API da PicassoIA, cole o código deste artigo em um repositório novo e clique em gerar. A primeira imagem no seu próprio domínio é o momento em que o projeto ganha vida, então faça desse prompt um bom prompt.

Compartilhe este artigo

Escolha seu idioma