Crear un sitio web generador de imágenes con IA: plantilla y proyecto de GitHub

Una plantilla funcional para un sitio generador de imágenes con IA: el stack de Next.js, la estructura del proyecto en GitHub, la ruta de servidor que llama a la API de un modelo de imagen, la página de prompt, las comprobaciones de seguridad, los límites de peticiones y el despliegue. Cada archivo aparece completo, para que lo copies y lo publiques.

Crear un sitio web generador de imágenes con IA: plantilla y proyecto de GitHub
Cristian Da Conceicao
Fundador de Picasso IA

La mayoría de tutoriales sobre cómo crear un sitio web generador de imágenes con IA terminan con una captura de pantalla y una promesa vaga. Este termina con un proyecto funcional: un cuadro de prompt, una ruta de servidor que llama a un modelo de imagen, un bucle de consultas, una vista de resultados y una lista de comprobación para desplegarlo. Todos los archivos caben en esta página, así que puedes pegarlos en un repositorio nuevo, subirlo a GitHub y tener un generador en vivo la misma tarde.

La plantilla es pequeña a propósito. Tiene cuatro archivos con lógica real, sin base de datos, sin proveedor de inicio de sesión y sin biblioteca de estado. Eso facilita leerla de una sentada y ampliarla después con cuentas, historial o video. Si puedes ejecutar npm install, puedes publicarlo.

💡 Respuesta rápida: un sitio generador de imágenes con IA es un formulario, una ruta de servidor que oculta tu token de API, un bucle de consultas y una etiqueta <img>. Todo lo demás es pulido.

Qué hace la plantilla

El sitio terminado recibe un prompt de texto, lo envía a un modelo de imagen, espera el resultado y muestra la imagen con un enlace de descarga. El navegador nunca ve tu token de API, porque cada llamada pasa por tu propia ruta de servidor.

Este es el flujo completo de la petición:

  1. El visitante escribe un prompt y pulsa el botón.
  2. La página envía el prompt a /api/generate en tu servidor.
  3. Tu servidor lo valida y crea un trabajo en la API de imágenes.
  4. La página pide a /api/generate/{id} el estado cada dos segundos.
  5. Cuando el estado cambia a succeeded, la página muestra la URL de la imagen.

Boceto en pizarra de la arquitectura del navegador, el servidor y el modelo de imagen

Características de un vistazo

CaracterísticaIncluidoDónde está
Formulario de prompt con límite de caracteresSícomponents/Generator.tsx
Ruta de servidor que crea el trabajoSíapp/api/generate/route.ts
Ruta de estado para las consultasSíapp/api/generate/[id]/route.ts
Estados de carga, error y tiempo agotadoSícomponents/Generator.tsx
Enlace de descarga e historial localSíAñadido en el paso de la galería
Cuentas, facturación, galerías de usuarioNoAñadir después del primer lanzamiento

Para quién es: los desarrolladores independientes obtienen una pieza para su portafolio que genera imágenes de verdad. Las agencias obtienen una base que pueden personalizar para un cliente en un día. Los equipos de producto obtienen un prototipo para probar la demanda antes de invertir en una plataforma completa.

Stack y estructura del proyecto

Equipo portátil en una cafetería mostrando una página sencilla de generador de imágenes con un campo de prompt

Elige el stack tecnológico

La plantilla usa Next.js con App Router, TypeScript y Tailwind CSS. Un solo framework te da la página y las rutas de servidor en el mismo repositorio, por eso es ideal para un primer proyecto. Cualquier stack con servidor funciona igual: React con Express, SvelteKit, Nuxt o Node puro.

Crea el proyecto y súbelo a GitHub en cuatro 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

El backend es una API de texto a imagen. La API de PicassoIA sigue la convención de Replicate: POST /v1/models/{owner}/{name}/predictions crea un trabajo y GET /v1/predictions/{id} devuelve su estado. Al escribir esto, la API sirve cuatro modelos: picassoia/picassoia-image, picassoia/picassoia-image-editor-pro, picassoia/picassoia-video y picassoia/seedance-2.5-lite. Crea tu token en la página de la API de PicassoIA.

Árbol de carpetas y variables

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 cenital de un escritorio con un boceto de interfaz, un equipo portátil y un teléfono mostrando una galería de fotos

Dos variables de entorno controlan todo:

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

Copia el archivo a .env.local y pega ahí tu token real. create-next-app ignora todos los archivos .env*, así que añade la línea !.env.example a .gitignore si quieres que el archivo de ejemplo esté en el repositorio. Nunca pongas el token en una variable que empiece por NEXT_PUBLIC_, porque Next.js envía esas variables al navegador.

Escribe la ruta de servidor

Manos de un desarrollador escribiendo código en un equipo portátil delante de un monitor

Crea la predicción

// 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 });
}

El cuerpo sigue la convención de Replicate: un objeto input que contiene el prompt. Cada página de modelo enumera campos extra, como la relación de aspecto, que puedes añadir junto a prompt. La comprobación de 4.000 caracteres coincide con el límite documentado del prompt, así que los usuarios reciben un error claro antes de que la petición salga de tu servidor.

Consulta hasta que termine

La generación es asíncrona. La primera llamada devuelve un id, y pides el estado hasta que diga succeeded o 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,
  });
}

La comprobación de id es importante. Sin ella, un visitante podría pasar ../ segmentos y hacer que tu servidor llame a otro endpoint con tu token adjunto.

Gestiona errores y límites

Tres tipos de fallo aparecen en el tráfico real:

  1. Entrada incorrecta: devuelve 400 antes de gastar una petición.
  2. Errores de la API o colas saturadas: devuelve 502 o 429 y deja que la página muestre un mensaje para reintentar.
  3. Trabajos lentos: deja de consultar tras unos dos minutos y avisa al usuario.

La API permite 5 predicciones simultáneas por cuenta, compartidas entre todos los tokens. Si tu sitio recibe diez visitantes a la vez, cinco esperan. Añade una cola pequeña o desactiva el botón mientras un trabajo se ejecuta.

SíntomaCausa probableSolución
401 de la APIToken incorrecto o ausenteRevisa .env.local y reinicia npm run dev
429 o esperas largasLas 5 ranuras están ocupadasPon las peticiones en cola o muestra un mensaje de espera
El estado nunca cambiaConsultas el id equivocadoRegistra el id que devuelve la primera llamada
Funciona en local, falla en líneaVariables no definidas en el hostAñade ambas variables en la configuración del host

Construye la página de prompt

Diseñador haciendo un boceto del diseño de tarjetas para una galería de imágenes en una tableta

Formulario y 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>
  );
}

Impórtalo en app/page.tsx y renderiza <Generator />. Ejecuta npm run dev, abre localhost:3000, escribe un prompt y ya tienes un generador funcional.

Galería y descargas

Añade dos detalles cuando lo básico funcione. Envuelve la imagen en un enlace con el atributo download para que los usuarios puedan guardar el archivo. Luego guarda cada resultado en localStorage, un array de objetos { prompt, url }, y muéstralo debajo del formulario como una cuadrícula. Así tienes historial sin base de datos.

💡 Usa el prompt como texto alternativo. Ayuda a los lectores de pantalla y da a tu galería texto útil para los buscadores.

Cómo usar Imagen 4 en PicassoIA

Antes de fijar un estilo en tu sitio, pruébalo donde iterar sale barato. El sitio de PicassoIA tiene un área de pruebas para cada modelo, y los controles siguen el mismo patrón: un campo de prompt, algunos ajustes y un botón de generar.

Fotografías impresas dispuestas en cuadrícula junto a un equipo portátil con una página de galería

Ejecuta un prompt de prueba

  1. Abre la página de Imagen 4.
  2. Pega un prompt estructurado: sujeto, escenario, luz, lente y textura.
  3. Elige una relación de aspecto 16:9 si el modelo la ofrece.
  4. Pulsa generar y revisa el resultado a tamaño completo.
  5. Cambia un detalle cada vez y vuelve a generar.

Un prompt que funciona bien para resultados fotográficos:

Una taza de café negro de cerámica sobre una mesa de roble desgastada, luz de ventana desde la izquierda, lente de 50 mm a f/2, poca profundidad de campo, veta de la madera y vapor visibles, colores de Kodak Portra 400.

Cada parte de ese prompt cumple una función:

ParteEjemploPor qué ayuda
SujetoUna taza de café negro de cerámicaNombra lo único de lo que trata la imagen
EscenarioUna mesa de roble desgastadaDa al fondo un material y un ambiente
LuzLuz de ventana desde la izquierdaDefine sombras y dirección
Lente50 mm a f/2Controla la profundidad de campo y la perspectiva
TexturaVeta de la madera, vaporAcerca el resultado a una fotografía real

Compara tres modelos

Ejecuta el mismo prompt en varios modelos antes de decidir qué impulsará el sitio. La API sirve solo los cuatro modelos picassoia/* al escribir esto, así que los demás de esta tabla sirven para elegir un estilo en el área de pruebas.

ModeloFortalezaPágina
Imagen 4Detalle fotográfico naturalAbrir
FLUX 2 ProBuen seguimiento del promptAbrir
Seedream 4.5Color rico y retratosAbrir
GPT Image 2Texto dentro de las imágenesAbrir
FLUX SchnellBorradores rápidosAbrir
PicassoIA ImageEl modelo por defecto de la plantillaAbrir

Guarda el prompt ganador como texto inicial en el atributo placeholder, y convierte tus tres mejores prompts en chips de ejemplo pulsables debajo del área de texto. Los visitantes que ven un buen ejemplo escriben mejores prompts, y mejores prompts significan menos generaciones desperdiciadas.

Añade LLM y video más adelante

Redacta código con un LLM

Puedes construir toda la plantilla anterior con un asistente de programación. Claude Sonnet 5 y GPT 5.6 Sol aparecen ambos como opciones para tareas de programación, y Gemini 3.5 Flash sirve para ediciones rápidas. Pega el árbol de carpetas de este artículo y pide un archivo cada vez; luego lee cada línea antes de hacer commit.

Dos desarrolladores programando en pareja una aplicación web con una cuadrícula de imágenes generadas

Reescribe prompts cortos

La mayoría de visitantes escriben cinco palabras. Un modelo de lenguaje puede ampliarlas antes de la llamada de imagen:

  1. Añade un paso de rewrite en route.ts que envíe el texto del usuario a un modelo como Kimi K2.6.
  2. Indícale que devuelva un prompt con sujeto, luz, lente y textura.
  3. Envía ese prompt al modelo de imagen y muestra al usuario ambas versiones.

Anima los resultados

Las mismas dos rutas sirven para video. Cambia PICASSOIA_MODEL por un modelo de video, como picassoia/seedance-2.5-lite, y la salida pasa a ser un enlace MP4. Muéstralo con una etiqueta <video controls> y sube el intervalo de consulta, porque el video tarda más que las imágenes. Para comparar opciones primero, las páginas de Seedance 2.5 Lite y Wan 3 muestran lo que produce cada modelo.

Publica sin sorpresas

Racks de servidores en un pasillo tranquilo de un centro de datos

Modera los prompts

Un generador público sufre abusos en cuestión de días. Pasa cada prompt por un modelo de moderación antes de crear la predicción. Llama Guard 4 12B está hecho para esa tarea: clasifica un prompt como seguro o no seguro, y bloqueas la petición cuando dice que no es seguro. Añade también un aviso visible de condiciones bajo el formulario.

Límites de peticiones y colas

RiesgoSolución
Un visitante satura la rutaLimita las peticiones por IP, por ejemplo 10 por hora
Más visitantes que ranuras simultáneasDesactiva el botón mientras se ejecuta un trabajo y muestra un mensaje de espera
El token se filtra en el navegadorMantén las llamadas en el servidor y nunca uses NEXT_PUBLIC_ para secretos
Costo descontroladoLimita las generaciones diarias y avisa al llegar al 80 %

Un contador en memoria funciona en un solo servidor. En alojamiento sin servidor, cada instancia de función tiene su propia memoria, así que usa un almacén compartido para el limitador.

Despliega y monitoriza

Desplegar lleva cinco pasos:

  1. Sube el repositorio a GitHub.
  2. Impórtalo en la plataforma de alojamiento que prefieras.
  3. Añade PICASSOIA_API_TOKEN y PICASSOIA_MODEL como variables de entorno.
  4. Define una duración máxima de función suficientemente larga para tu ruta de consultas.
  5. Abre la URL en vivo y genera tres imágenes de prueba.

Tras el lanzamiento, registra el estado de cada trabajo (nunca el token) y revisa la tasa de fallos cada semana. Un aumento repentino suele significar un cambio de modelo o un límite que alcanzaste. Escribe también un README.md breve, con las dos variables, el comando de ejecución y una captura de pantalla, porque el README es lo primero que ve la gente en un proyecto de GitHub.

Construye el tuyo en Picasso IA

Ya tienes las piezas: un stack, un árbol de carpetas, dos rutas de servidor, una página de prompt y una lista de comprobación de seguridad. La forma más rápida de que el sitio parezca terminado es elegir su aspecto antes de escribir más código.

Pequeño equipo celebrando el lanzamiento de su sitio generador de imágenes

Abre Picasso IA, ejecuta tu primer prompt con Imagen 4 o FLUX 2 Pro, y guarda los tres resultados que más te gusten. Esos prompts serán tus chips de ejemplo, tu página de inicio y tus primeros casos de prueba. Luego crea tu token de API en la página de la API de PicassoIA, pega el código de este artículo en un repositorio nuevo y pulsa generar. La primera imagen en tu propio dominio es el momento en que el proyecto se vuelve real, así que haz que ese prompt sea bueno.

Compartir este artículo

Elige tu idioma