API de Pixabay
Receta
Usa la API de Pixabay para buscar y mostrar fotos de stock gratuitas y videos. Las imágenes de Pixabay están bajo su propia licencia, que permite uso comercial gratuito sin atribución (aunque se aprecia la atribución).
Paso 1: Obtener una clave API
- Crea una cuenta en pixabay.com.
- Visita pixabay.com/api/docs para encontrar tu clave API.
- Guarda la clave en
.env.local:
PIXABAY_API_KEY=your_api_key_herePaso 2: Crear un cliente API
// lib/pixabay.ts
interface PixabayImage {
id: number;
pageURL: string;
type: string;
tags: string;
previewURL: string;
previewWidth: number;
previewHeight: number;
webformatURL: string;
webformatWidth: number;
webformatHeight: number;
largeImageURL: string;
imageWidth: number;
imageHeight: number;
views: number;
downloads: number;
likes: number;
user: string;
userImageURL: string;
}
interface PixabaySearchResponse {
total: number;
totalHits: number;
hits: PixabayImage[];
}
type ImageType = "all" | "photo" | "illustration" | "vector";
type Orientation = "all" | "horizontal" | "vertical";
type Category =
| "backgrounds" | "fashion" | "nature" | "science" | "education"
| "feelings" | "health" | "people" | "religion" | "places"
| "animals" | "industry" | "computer" | "food" | "sports"
| "transportation" | "travel" | "buildings" | "business" | "music";
interface SearchParams {
query: string;
page?: number;
perPage?: number;
imageType?: ImageType;
orientation?: Orientation;
category?: Category;
}
const PIXABAY_BASE = "https://pixabay.com/api/";
export async function searchImages({
query,
page = 1,
perPage = 20,
imageType = "photo",
orientation = "all",
category,
}: SearchParams): Promise<PixabaySearchResponse> {
const params = new URLSearchParams({
key: process.env.PIXABAY_API_KEY!,
q: query,
page: String(page),
per_page: String(perPage),
image_type: imageType,
orientation,
safesearch: "true",
});
if (category) params.set("category", category);
const res = await fetch(`${PIXABAY_BASE}?${params}`, {
next: { revalidate: 3600 },
});
if (!res.ok) {
throw new Error(`Pixabay API error: ${res.status}`);
}
return res.json();
}Ejemplo Funcional
Un navegador de fotos de stock con filtrado por categoría:
// app/stock-photos/page.tsx
import { searchImages, type Category } from "@/lib/pixabay";
import { CategoryFilter } from "./category-filter";
import { PhotoGrid } from "./photo-grid";
const categories: Category[] = [
"nature", "people", "animals", "food", "travel",
"buildings", "business", "sports", "science", "fashion",
];
interface PageProps {
searchParams: Promise<{ q?: string; category?: string; page?: string }>;
}
export default async function StockPhotosPage({ searchParams }: PageProps) {
const params = await searchParams;
const query = params.q || "landscape";
const category = params.category as Category | undefined;
const page = Number(params.page) || 1;
const results = await searchImages({ query, page, perPage: 18, category });
return (
<main className="mx-auto max-w-6xl px-4 py-8">
<h1 className="mb-2 text-3xl font-bold">Stock Photos</h1>
<p className="mb-6 text-gray-500">
{results.totalHits.toLocaleString()} free photos found
</p>
<CategoryFilter categories={categories} activeCategory={category} query={query} />
<PhotoGrid images={results.hits} />
<div className="mt-8 flex justify-center gap-4">
{page > 1 && (
<a
href={`/stock-photos?q=${query}${category ? `&category=${category}` : ""}&page=${page - 1}`}
className="rounded-lg bg-gray-200 px-4 py-2 hover:bg-gray-300"
>
Previous
</a>
)}
{page * 18 < results.totalHits && (
<a
href={`/stock-photos?q=${query}${category ? `&category=${category}` : ""}&page=${page + 1}`}
className="rounded-lg bg-gray-200 px-4 py-2 hover:bg-gray-300"
>
Next
</a>
)}
</div>
</main>
);
}// app/stock-photos/category-filter.tsx
"use client";
import { useRouter } from "next/navigation";
interface CategoryFilterProps {
categories: string[];
activeCategory?: string;
query: string;
}
export function CategoryFilter({ categories, activeCategory, query }: CategoryFilterProps) {
const router = useRouter();
function selectCategory(category: string | null) {
const params = new URLSearchParams({ q: query });
if (category) params.set("category", category);
router.push(`/stock-photos?${params}`);
}
return (
<div className="mb-6 flex flex-wrap gap-2">
<button
onClick={() => selectCategory(null)}
className={`rounded-full px-4 py-1.5 text-sm font-medium transition-colors ${
!activeCategory
? "bg-blue-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
All
</button>
{categories.map((cat) => (
<button
key={cat}
onClick={() => selectCategory(cat)}
className={`rounded-full px-4 py-1.5 text-sm font-medium capitalize transition-colors ${
activeCategory === cat
? "bg-blue-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
{cat}
</button>
))}
</div>
);
}// app/stock-photos/photo-grid.tsx
import Image from "next/image";
interface PixabayImage {
id: number;
webformatURL: string;
largeImageURL: string;
tags: string;
user: string;
likes: number;
webformatWidth: number;
webformatHeight: number;
}
export function PhotoGrid({ images }: { images: PixabayImage[] }) {
return (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{images.map((image) => (
<figure key={image.id} className="group relative overflow-hidden rounded-xl bg-gray-100">
<div className="relative aspect-[4/3]">
<Image
src={image.webformatURL}
alt={image.tags}
fill
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
className="object-cover"
/>
</div>
<div className="absolute bottom-0 left-0 right-0 flex items-center justify-between bg-gradient-to-t from-black/50 to-transparent p-3 text-sm text-white opacity-0 transition-opacity group-hover:opacity-100">
<span>by {image.user}</span>
<span>{image.likes} likes</span>
</div>
</figure>
))}
</div>
);
}Análisis Profundo
Cómo Funciona
- La API de Pixabay es una API REST que devuelve JSON. La autenticación es mediante un parámetro de consulta
key(no un encabezado). - El endpoint de búsqueda de imágenes (
/api/) acepta parámetros incluyendoq(término de búsqueda),image_type,orientation,category,min_width,min_height,colors,per_page(3-200) ypage. - La respuesta incluye
totalHits(limitado a 500 para usuarios no incluidos en la lista blanca),total(total verdadero) ehits(array de objetos de imagen). - Cada resultado incluye múltiples URLs de imagen:
previewURL(150px),webformatURL(640px),largeImageURL(1280px) yfullHDURL/imageURL(disponibles con acceso API completo). - El endpoint de búsqueda de videos está en
/api/videos/con parámetros similares y devuelve objetos de video con múltiples URLs de resolución. - El límite de velocidad es 100 solicitudes por minuto. Excederlo devuelve un código de estado 429.
Variaciones
Búsqueda de videos:
interface PixabayVideo {
id: number;
pageURL: string;
tags: string;
videos: {
large: { url: string; width: number; height: number };
medium: { url: string; width: number; height: number };
small: { url: string; width: number; height: number };
tiny: { url: string; width: number; height: number };
};
user: string;
}
interface PixabayVideoResponse {
total: number;
totalHits: number;
hits: PixabayVideo[];
}
export async function searchVideos(query: string, page = 1): Promise<PixabayVideoResponse> {
const params = new URLSearchParams({
key: process.env.PIXABAY_API_KEY!,
q: query,
page: String(page),
per_page: "12",
});
const res = await fetch(`https://pixabay.com/api/videos/?${params}`, {
next: { revalidate: 3600 },
});
return res.json();
}Notas de TypeScript
- La API de Pixabay no proporciona un SDK oficial de TypeScript. Define tus propios tipos basándote en la documentación de la API.
- Usa tipos de unión para parámetros restringidos como
image_type,orientationycategory. - Exporta los tipos junto con las funciones de API para que los componentes que los consuman puedan hacer referencia a ellos.
Trampas Comunes
- La clave API se pasa como un parámetro de consulta, no un encabezado. Nunca uses esto en código del lado del cliente, ya que la clave será visible en las solicitudes de red. Siempre llama desde Server Components o rutas API.
totalHitsse limita a 500 para usuarios de API estándar. Para acceder a más resultados, solicita acceso API completo.- Las imágenes
webformatURLtienen una marca de agua de Pixabay en algunos casos para solicitudes no autenticadas. Asegúrate de que tu clave API sea válida. - El parámetro
per_pageacepta valores entre 3 y 200. Los valores fuera de este rango devuelven un error. - Pixabay requiere que el hotlinking a imágenes desde su CDN sea temporal. Para uso en producción, descarga y sirve imágenes desde tu propia infraestructura.
- Agrega
cdn.pixabay.comypixabay.comaremotePatternsennext.config.tspara quenext/imagefuncione.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
| API de Pixabay | Uso comercial gratuito, sin atribución requerida, también videos | Calidad promedio más baja, límite de 500 resultados |
| API de Unsplash | Fotos de mayor calidad, biblioteca más grande | Atribución requerida |
| API de Pexels | Buena calidad, límites generosos, sin atribución requerida | Biblioteca más pequeña |
| API de Flickr | Biblioteca masiva, opciones Creative Commons | Licencia compleja por foto |
Preguntas Frecuentes
¿Cómo difiere la autenticación de la API de Pixabay de Unsplash y Pexels?
- Pixabay pasa la clave API como un parámetro de consulta
key, no un encabezado. - Esto significa que la clave es visible en la URL, así que nunca la uses en código del lado del cliente.
- Siempre llama desde Server Components o rutas API.
¿Qué tamaños de imagen devuelve la API de Pixabay en cada resultado?
previewURL-- 150px de ancho.webformatURL-- 640px de ancho.largeImageURL-- 1280px de ancho.fullHDURLeimageURLestán disponibles solo con acceso API completo.
¿Se requiere atribución para las imágenes de Pixabay?
- No, la atribución no es legalmente requerida bajo la licencia de Pixabay.
- Las imágenes son gratuitas para uso comercial sin atribución.
- Sin embargo, se aprecia la atribución.
¿Cuál es el límite de velocidad para la API de Pixabay?
- 100 solicitudes por minuto.
- Excederlo devuelve un código de estado 429.
Trampa Común: ¿Cuál es el límite de totalHits y cómo afecta la paginación?
totalHitsse limita a 500 para usuarios de API estándar (no incluidos en la lista blanca).totalmuestra el total verdadero, pero no puedes paginar más allá de 500 resultados.- Solicita acceso API completo para eliminar este límite.
¿Qué valores de per_page acepta la API de Pixabay?
- Entre 3 y 200 inclusive.
- Los valores fuera de este rango devuelven un error.
¿Cómo buscas videos en lugar de imágenes con Pixabay?
const res = await fetch(`https://pixabay.com/api/videos/?${params}`, {
next: { revalidate: 3600 },
});Usa el endpoint /api/videos/ con parámetros similares a la búsqueda de imágenes.
¿Qué tipos de TypeScript deberías definir para la API de Pixabay?
- Define tus propios tipos ya que Pixabay no tiene un SDK oficial de TypeScript.
- Usa tipos de unión para parámetros restringidos como
ImageType,OrientationyCategory. - Exporta tipos junto con funciones de API para componentes consumidores.
Trampa Común: ¿Puedes hacer hotlinking directamente a imágenes de Pixabay CDN en producción?
- Pixabay requiere que el hotlinking a su CDN sea solo temporal.
- Para uso en producción, descarga y sirve imágenes desde tu propia infraestructura.
¿Qué cambios en next.config.ts se necesitan para imágenes de Pixabay con next/image?
images: {
remotePatterns: [
{ protocol: "https", hostname: "cdn.pixabay.com" },
{ protocol: "https", hostname: "pixabay.com" },
],
}¿Cómo actualiza el componente de filtro de categoría la URL en el ejemplo?
- Usa
useRouter().push()para navegar con parámetros de consulta actualizados. - La función
selectCategoryconstruye un objetoURLSearchParamscon la consulta y categoría opcional. - El Server Component de página lee
searchParamsy obtiene resultados filtrados.